From 28bccfdcb25a046cfdef56b7038079c025dbb449 Mon Sep 17 00:00:00 2001 From: Chris Goller Date: Tue, 22 Nov 2016 11:27:27 -0600 Subject: [PATCH 01/15] Add ylabels and yranges to layouts --- bolt/internal/internal.go | 6 + bolt/internal/internal.pb.go | 76 +++---- bolt/internal/internal.proto | 2 + bolt/internal/internal_test.go | 40 ++++ chronograf.go | 16 +- docs/LAYOUT.md | 2 + server/layout.go | 13 +- server/swagger.json | 361 +++++++++++++++++++++++---------- 8 files changed, 368 insertions(+), 148 deletions(-) diff --git a/bolt/internal/internal.go b/bolt/internal/internal.go index b148e0a4d..35a3b13ea 100644 --- a/bolt/internal/internal.go +++ b/bolt/internal/internal.go @@ -115,12 +115,15 @@ func MarshalLayout(l chronograf.Layout) ([]byte, error) { Wheres: q.Wheres, } } + cells[i] = &Cell{ X: c.X, Y: c.Y, W: c.W, H: c.H, I: c.I, + Yranges: c.YRanges, + Ylabels: c.YLabels, Name: c.Name, Queries: queries, } @@ -157,12 +160,15 @@ func UnmarshalLayout(data []byte, l *chronograf.Layout) error { Wheres: q.Wheres, } } + cells[i] = chronograf.Cell{ X: c.X, Y: c.Y, W: c.W, H: c.H, I: c.I, + YRanges: c.Yranges, + YLabels: c.Ylabels, Name: c.Name, Queries: queries, } diff --git a/bolt/internal/internal.pb.go b/bolt/internal/internal.pb.go index 177a86c5f..ceedff464 100644 --- a/bolt/internal/internal.pb.go +++ b/bolt/internal/internal.pb.go @@ -108,6 +108,8 @@ type Cell struct { Queries []*Query `protobuf:"bytes,5,rep,name=queries" json:"queries,omitempty"` I string `protobuf:"bytes,6,opt,name=i,proto3" json:"i,omitempty"` Name string `protobuf:"bytes,7,opt,name=name,proto3" json:"name,omitempty"` + Yranges []int64 `protobuf:"varint,8,rep,name=yranges" json:"yranges,omitempty"` + Ylabels []string `protobuf:"bytes,9,rep,name=ylabels" json:"ylabels,omitempty"` } func (m *Cell) Reset() { *m = Cell{} } @@ -171,40 +173,42 @@ func init() { func init() { proto.RegisterFile("internal.proto", fileDescriptorInternal) } var fileDescriptorInternal = []byte{ - // 555 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x8c, 0x94, 0x4f, 0x8e, 0xd3, 0x4a, - 0x10, 0xc6, 0xd5, 0xb1, 0x9d, 0xc4, 0x35, 0x4f, 0x79, 0xa8, 0x35, 0x42, 0x16, 0x62, 0x11, 0x59, - 0x2c, 0x82, 0x84, 0x66, 0x01, 0x27, 0x48, 0xe2, 0x11, 0x0a, 0x0c, 0x43, 0xe8, 0x4c, 0xc4, 0x8a, - 0x45, 0x93, 0x54, 0x88, 0x25, 0xc7, 0x36, 0xed, 0x36, 0x89, 0xcf, 0x00, 0x67, 0xe0, 0x12, 0x5c, - 0x80, 0xa3, 0xa1, 0x6a, 0xb7, 0x1d, 0x4b, 0xfc, 0xd1, 0xec, 0xea, 0xab, 0xaa, 0xae, 0xfc, 0xfa, - 0xab, 0x8e, 0x61, 0x14, 0xa7, 0x1a, 0x55, 0x2a, 0x93, 0xab, 0x5c, 0x65, 0x3a, 0xe3, 0xc3, 0x46, - 0x87, 0x3f, 0x18, 0x5c, 0x5c, 0x9f, 0xf2, 0x24, 0x53, 0x52, 0xc7, 0x59, 0xca, 0x47, 0xd0, 0x5b, - 0x44, 0x01, 0x1b, 0xb3, 0x89, 0x23, 0x7a, 0x8b, 0x88, 0x73, 0x70, 0x6f, 0xe5, 0x01, 0x83, 0xde, - 0x98, 0x4d, 0x7c, 0x61, 0x62, 0xfe, 0x10, 0xfa, 0xeb, 0x02, 0xd5, 0x22, 0x0a, 0x1c, 0xd3, 0x67, - 0x15, 0xf5, 0x46, 0x52, 0xcb, 0xc0, 0xad, 0x7b, 0x29, 0xe6, 0x8f, 0xc1, 0x9f, 0x2b, 0x94, 0x1a, - 0xb7, 0x53, 0x1d, 0x78, 0xa6, 0xfd, 0x9c, 0xa0, 0xea, 0x3a, 0xdf, 0xda, 0x6a, 0xbf, 0xae, 0xb6, - 0x09, 0x1e, 0xc0, 0x20, 0xc2, 0x9d, 0x2c, 0x13, 0x1d, 0x0c, 0xc6, 0x6c, 0x32, 0x14, 0x8d, 0x0c, - 0x7f, 0x32, 0xe8, 0xaf, 0xb2, 0x52, 0x6d, 0xf0, 0x5e, 0xc0, 0x1c, 0xdc, 0xbb, 0x2a, 0x47, 0x83, - 0xeb, 0x0b, 0x13, 0xf3, 0x47, 0x30, 0x24, 0xec, 0x94, 0x7a, 0x6b, 0xe0, 0x56, 0x53, 0x6d, 0x29, - 0x8b, 0xe2, 0x98, 0xa9, 0xad, 0x61, 0xf6, 0x45, 0xab, 0xf9, 0x03, 0x70, 0xd6, 0xe2, 0xc6, 0xc0, - 0xfa, 0x82, 0xc2, 0xbf, 0x63, 0xd2, 0x9c, 0x3b, 0x4c, 0xf0, 0x93, 0x92, 0xbb, 0x60, 0x58, 0xcf, - 0x69, 0x74, 0xf8, 0x8d, 0xae, 0x80, 0xea, 0x0b, 0xaa, 0x7b, 0x5d, 0xa1, 0x8b, 0xeb, 0xfc, 0x03, - 0xd7, 0xfd, 0x33, 0xae, 0x77, 0xc6, 0xbd, 0x04, 0x6f, 0xa5, 0x36, 0x8b, 0xc8, 0xfa, 0x5d, 0x8b, - 0xf0, 0x3b, 0x83, 0xfe, 0x8d, 0xac, 0xb2, 0x52, 0x77, 0x70, 0x7c, 0x83, 0x33, 0x86, 0x8b, 0x69, - 0x9e, 0x27, 0xf1, 0xc6, 0xbc, 0x10, 0x4b, 0xd5, 0x4d, 0x51, 0xc7, 0x1b, 0x94, 0x45, 0xa9, 0xf0, - 0x80, 0xa9, 0xb6, 0x7c, 0xdd, 0x14, 0x7f, 0x02, 0xde, 0x1c, 0x93, 0xa4, 0x08, 0xdc, 0xb1, 0x33, - 0xb9, 0x78, 0x3e, 0xba, 0x6a, 0x1f, 0x24, 0xa5, 0x45, 0x5d, 0xa4, 0x8b, 0x4c, 0x4b, 0x9d, 0xed, - 0x92, 0xec, 0x68, 0x88, 0x87, 0xa2, 0xd5, 0xe1, 0x57, 0x06, 0x2e, 0x75, 0xf1, 0xff, 0x80, 0x9d, - 0x0c, 0x9d, 0x27, 0xd8, 0x89, 0x54, 0x65, 0x90, 0x3c, 0xc1, 0x2a, 0x52, 0x47, 0xf3, 0xf3, 0x9e, - 0x60, 0x47, 0x52, 0x7b, 0x63, 0x88, 0x27, 0xd8, 0x9e, 0x3f, 0x85, 0xc1, 0xe7, 0x12, 0x55, 0x8c, - 0x45, 0xe0, 0x19, 0x88, 0xff, 0xcf, 0x10, 0xef, 0x4a, 0x54, 0x95, 0x68, 0xea, 0x74, 0x30, 0xb6, - 0x1b, 0x66, 0x31, 0xad, 0xc3, 0xd8, 0x3e, 0xa8, 0xd7, 0x41, 0x71, 0x58, 0x82, 0x67, 0xce, 0xd0, - 0xf2, 0xe7, 0xd9, 0xe1, 0x20, 0xd3, 0xad, 0x75, 0xac, 0x91, 0x64, 0x63, 0x34, 0xb3, 0x6e, 0xf5, - 0xa2, 0x19, 0x69, 0xb1, 0xb4, 0xde, 0xf4, 0xc4, 0x92, 0x2e, 0xfb, 0x52, 0x65, 0x65, 0x3e, 0xab, - 0x6a, 0x57, 0x7c, 0xd1, 0x6a, 0xfa, 0x87, 0xbd, 0xdf, 0xa3, 0xb2, 0xa8, 0xbe, 0xb0, 0x2a, 0xfc, - 0x00, 0xfe, 0x34, 0x41, 0xa5, 0x45, 0x99, 0xe0, 0x6f, 0x7b, 0xe2, 0xe0, 0xbe, 0x5a, 0xbd, 0xbd, - 0x6d, 0x9e, 0x0d, 0xc5, 0xe7, 0x65, 0x3b, 0x9d, 0x65, 0xd3, 0xf8, 0xd7, 0x32, 0x97, 0x8b, 0xc8, - 0xb8, 0xe3, 0x08, 0xab, 0xc2, 0x67, 0xe0, 0xd2, 0xa3, 0xea, 0x4c, 0x76, 0xcd, 0xe4, 0x4b, 0xf0, - 0xae, 0x0f, 0x32, 0x4e, 0xec, 0xe8, 0x5a, 0x7c, 0xec, 0x9b, 0x6f, 0xc9, 0x8b, 0x5f, 0x01, 0x00, - 0x00, 0xff, 0xff, 0x92, 0xf3, 0x5a, 0x42, 0x5d, 0x04, 0x00, 0x00, + // 583 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x8c, 0x94, 0x5f, 0x6e, 0xd3, 0x4e, + 0x10, 0xc7, 0xb5, 0xb1, 0x9d, 0xc4, 0xd3, 0x9f, 0xfa, 0x43, 0xab, 0x0a, 0xad, 0x10, 0x0f, 0x96, + 0xc5, 0x83, 0x91, 0x50, 0x1f, 0xe0, 0x04, 0x69, 0x5d, 0xa1, 0x40, 0x29, 0x65, 0xdb, 0x8a, 0x27, + 0x1e, 0xb6, 0xcd, 0xb4, 0xb5, 0xb4, 0xb1, 0xcd, 0xda, 0x26, 0xf1, 0x1d, 0x38, 0x03, 0x97, 0xe0, + 0x00, 0x70, 0x34, 0x34, 0xeb, 0xb5, 0x63, 0x89, 0x3f, 0xea, 0xdb, 0x7c, 0x67, 0x26, 0xb3, 0x9f, + 0xfd, 0xee, 0xc4, 0xb0, 0x9f, 0xe5, 0x35, 0x9a, 0x5c, 0xe9, 0xc3, 0xd2, 0x14, 0x75, 0xc1, 0xe7, + 0xbd, 0x8e, 0xbf, 0x33, 0xd8, 0x3b, 0xd9, 0x96, 0xba, 0x30, 0xaa, 0xce, 0x8a, 0x9c, 0xef, 0xc3, + 0x64, 0x99, 0x0a, 0x16, 0xb1, 0xc4, 0x93, 0x93, 0x65, 0xca, 0x39, 0xf8, 0x67, 0x6a, 0x8d, 0x62, + 0x12, 0xb1, 0x24, 0x94, 0x36, 0xe6, 0x8f, 0x61, 0x7a, 0x55, 0xa1, 0x59, 0xa6, 0xc2, 0xb3, 0x7d, + 0x4e, 0x51, 0x6f, 0xaa, 0x6a, 0x25, 0xfc, 0xae, 0x97, 0x62, 0xfe, 0x14, 0xc2, 0x63, 0x83, 0xaa, + 0xc6, 0xd5, 0xa2, 0x16, 0x81, 0x6d, 0xdf, 0x25, 0xa8, 0x7a, 0x55, 0xae, 0x5c, 0x75, 0xda, 0x55, + 0x87, 0x04, 0x17, 0x30, 0x4b, 0xf1, 0x56, 0x35, 0xba, 0x16, 0xb3, 0x88, 0x25, 0x73, 0xd9, 0xcb, + 0xf8, 0x27, 0x83, 0xe9, 0x45, 0xd1, 0x98, 0x1b, 0x7c, 0x10, 0x30, 0x07, 0xff, 0xb2, 0x2d, 0xd1, + 0xe2, 0x86, 0xd2, 0xc6, 0xfc, 0x09, 0xcc, 0x09, 0x3b, 0xa7, 0xde, 0x0e, 0x78, 0xd0, 0x54, 0x3b, + 0x57, 0x55, 0xb5, 0x29, 0xcc, 0xca, 0x32, 0x87, 0x72, 0xd0, 0xfc, 0x11, 0x78, 0x57, 0xf2, 0xd4, + 0xc2, 0x86, 0x92, 0xc2, 0xbf, 0x63, 0xd2, 0x9c, 0x4b, 0xd4, 0x78, 0x67, 0xd4, 0xad, 0x98, 0x77, + 0x73, 0x7a, 0x1d, 0x7f, 0xa5, 0x2b, 0xa0, 0xf9, 0x82, 0xe6, 0x41, 0x57, 0x18, 0xe3, 0x7a, 0xff, + 0xc0, 0xf5, 0xff, 0x8c, 0x1b, 0xec, 0x70, 0x0f, 0x20, 0xb8, 0x30, 0x37, 0xcb, 0xd4, 0xf9, 0xdd, + 0x89, 0xf8, 0x1b, 0x83, 0xe9, 0xa9, 0x6a, 0x8b, 0xa6, 0x1e, 0xe1, 0x84, 0x16, 0x27, 0x82, 0xbd, + 0x45, 0x59, 0xea, 0xec, 0xc6, 0x6e, 0x88, 0xa3, 0x1a, 0xa7, 0xa8, 0xe3, 0x1d, 0xaa, 0xaa, 0x31, + 0xb8, 0xc6, 0xbc, 0x76, 0x7c, 0xe3, 0x14, 0x7f, 0x06, 0xc1, 0x31, 0x6a, 0x5d, 0x09, 0x3f, 0xf2, + 0x92, 0xbd, 0x97, 0xfb, 0x87, 0xc3, 0x42, 0x52, 0x5a, 0x76, 0x45, 0xba, 0xc8, 0xa2, 0xa9, 0x8b, + 0x5b, 0x5d, 0x6c, 0x2c, 0xf1, 0x5c, 0x0e, 0x3a, 0xfe, 0xc1, 0xc0, 0xa7, 0x2e, 0xfe, 0x1f, 0xb0, + 0xad, 0xa5, 0x0b, 0x24, 0xdb, 0x92, 0x6a, 0x2d, 0x52, 0x20, 0x59, 0x4b, 0x6a, 0x63, 0x8f, 0x0f, + 0x24, 0xdb, 0x90, 0xba, 0xb7, 0x86, 0x04, 0x92, 0xdd, 0xf3, 0xe7, 0x30, 0xfb, 0xdc, 0xa0, 0xc9, + 0xb0, 0x12, 0x81, 0x85, 0xf8, 0x7f, 0x07, 0xf1, 0xa1, 0x41, 0xd3, 0xca, 0xbe, 0x4e, 0x3f, 0xcc, + 0xdc, 0x0b, 0xb3, 0x8c, 0x9e, 0xc3, 0xda, 0x3e, 0xeb, 0x9e, 0xc3, 0x5a, 0x2e, 0x60, 0xd6, 0x1a, + 0x95, 0xdf, 0x61, 0x25, 0xe6, 0x91, 0x97, 0x78, 0xb2, 0x97, 0xb6, 0xa2, 0xd5, 0x35, 0xea, 0x4a, + 0x84, 0x91, 0x97, 0x84, 0xb2, 0x97, 0x71, 0x03, 0x81, 0x3d, 0x87, 0x5a, 0x8e, 0x8b, 0xf5, 0x5a, + 0xe5, 0x2b, 0xe7, 0x72, 0x2f, 0xc9, 0xfa, 0xf4, 0xc8, 0x39, 0x3c, 0x49, 0x8f, 0x48, 0xcb, 0x73, + 0xe7, 0xe7, 0x44, 0x9e, 0x93, 0x41, 0xaf, 0x4d, 0xd1, 0x94, 0x47, 0x6d, 0xe7, 0x64, 0x28, 0x07, + 0x4d, 0xff, 0xca, 0x8f, 0xf7, 0x68, 0xdc, 0xf5, 0x42, 0xe9, 0x54, 0xfc, 0x09, 0xc2, 0x85, 0x46, + 0x53, 0xcb, 0x46, 0xe3, 0x6f, 0x6f, 0xcb, 0xc1, 0x7f, 0x73, 0xf1, 0xfe, 0xac, 0x5f, 0x35, 0x8a, + 0x77, 0x0b, 0xe2, 0x8d, 0x16, 0x84, 0xc6, 0xbf, 0x55, 0xa5, 0x5a, 0xa6, 0xd6, 0x51, 0x4f, 0x3a, + 0x15, 0xbf, 0x00, 0x9f, 0x16, 0x71, 0x34, 0xd9, 0xb7, 0x93, 0x0f, 0x20, 0x38, 0x59, 0xab, 0x4c, + 0xbb, 0xd1, 0x9d, 0xb8, 0x9e, 0xda, 0xef, 0xcf, 0xab, 0x5f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x47, + 0x4e, 0x32, 0xc9, 0x91, 0x04, 0x00, 0x00, } diff --git a/bolt/internal/internal.proto b/bolt/internal/internal.proto index 179d82c04..389c9621a 100644 --- a/bolt/internal/internal.proto +++ b/bolt/internal/internal.proto @@ -47,6 +47,8 @@ message Cell { repeated Query queries = 5; // Time-series data queries for Cell. string i = 6; // Unique identifier for the cell string name = 7; // User-facing name for this cell + repeated int64 yranges = 8; // Limits of the y-axes + repeated string ylabels = 9; // Labels of the y-axes } message Query { diff --git a/bolt/internal/internal_test.go b/bolt/internal/internal_test.go index ebf1bbafe..b23700c1d 100644 --- a/bolt/internal/internal_test.go +++ b/bolt/internal/internal_test.go @@ -71,3 +71,43 @@ func TestMarshalServer(t *testing.T) { t.Fatalf("source protobuf copy error: got %#v, expected %#v", vv, v) } } + +func TestMarshalLayout(t *testing.T) { + layout := chronograf.Layout{ + ID: "id", + Measurement: "measurement", + Application: "app", + Cells: []chronograf.Cell{ + { + X: 1, + Y: 1, + W: 4, + H: 4, + I: "anotherid", + YRanges: []int64{1, 2}, + YLabels: []string{"y1", "y2"}, + Name: "cell1", + Queries: []chronograf.Query{ + { + Command: "select mean(usage_user) as usage_user from cpu", + Wheres: []string{ + `"host"="myhost"`, + }, + GroupBys: []string{ + `"cpu"`, + }, + }, + }, + }, + }, + } + + var vv chronograf.Layout + if buf, err := internal.MarshalLayout(layout); err != nil { + t.Fatal(err) + } else if err := internal.UnmarshalLayout(buf, &vv); err != nil { + t.Fatal(err) + } else if !reflect.DeepEqual(layout, vv) { + t.Fatalf("source protobuf copy error: got %#v, expected %#v", vv, layout) + } +} diff --git a/chronograf.go b/chronograf.go index a07b70244..2fc8cde5f 100644 --- a/chronograf.go +++ b/chronograf.go @@ -245,13 +245,15 @@ type ExplorationStore interface { // Cell is a rectangle and multiple time series queries to visualize. type Cell struct { - X int32 `json:"x"` - Y int32 `json:"y"` - W int32 `json:"w"` - H int32 `json:"h"` - I string `json:"i"` - Name string `json:"name"` - Queries []Query `json:"queries"` + X int32 `json:"x"` + Y int32 `json:"y"` + W int32 `json:"w"` + H int32 `json:"h"` + I string `json:"i"` + YRanges []int64 `json:"yranges"` // YRanges are the initial limits of the y-axes + YLabels []string `json:"ylabels"` // YLabels are the labels of the y-axes (possible to have more than one) + Name string `json:"name"` + Queries []Query `json:"queries"` } // Layout is a collection of Cells for visualization diff --git a/docs/LAYOUT.md b/docs/LAYOUT.md index f6edebfd7..1b35ead03 100644 --- a/docs/LAYOUT.md +++ b/docs/LAYOUT.md @@ -49,6 +49,8 @@ The meaning of the fields are as follows: * cell - An array of graphs * x - Not currently used * y - Not currently used +* yranges - Optional key specifying the default y-axes ranges (must come in pairs) +* ylabels - Optional key specifying the user-facing names of the y-axes. Indices in ylabels correspond with yrange pairs * w - The width of the graph, for the initial release set to 4 to make everything line up properly. * h - The height of the graph, for the initial release set to 4 to make everything line up properly * i - A unique ID for the graph diff --git a/server/layout.go b/server/layout.go index e600c288e..02ccb6861 100644 --- a/server/layout.go +++ b/server/layout.go @@ -18,7 +18,14 @@ func newLayoutResponse(layout chronograf.Layout) layoutResponse { httpAPILayouts := "/chronograf/v1/layouts" href := fmt.Sprintf("%s/%s", httpAPILayouts, layout.ID) rel := "self" - + for i, c := range layout.Cells { + if c.YLabels == nil { + layout.Cells[i].YLabels = make([]string, 0) + } + if c.YRanges == nil { + layout.Cells[i].YRanges = make([]int64, 0) + } + } return layoutResponse{ Layout: layout, Link: link{ @@ -172,6 +179,10 @@ func ValidLayoutRequest(l chronograf.Layout) error { if c.W == 0 || c.H == 0 { return fmt.Errorf("w, and h required") } + // Y-Range must come in pairs (I'm leaving the option open for multiple y-axes) + if len(c.YRanges)%2 != 0 { + return fmt.Errorf("Incorrect length of yrange: %d", len(c.YRanges)) + } for _, q := range c.Queries { if q.Command == "" { return fmt.Errorf("query required") diff --git a/server/swagger.json b/server/swagger.json index b20e5bc93..bdc467b6b 100644 --- a/server/swagger.json +++ b/server/swagger.json @@ -18,6 +18,7 @@ "paths": { "/": { "get": { + "tags": ["routes"], "summary": "Lists all the endpoints", "description": "List of the endpoints.", "responses": { @@ -38,6 +39,7 @@ }, "/sources": { "get": { + "tags": ["sources"], "summary": "Configured data sources", "description": "These data sources store time series data.", "responses": { @@ -56,6 +58,7 @@ } }, "post": { + "tags": ["sources"], "summary": "Create new data source", "parameters": [ { @@ -92,6 +95,7 @@ }, "/sources/{id}": { "get": { + "tags": ["sources"], "parameters": [ { "name": "id", @@ -125,6 +129,7 @@ } }, "patch": { + "tags": ["sources"], "summary": "Update data source configuration", "parameters": [ { @@ -166,6 +171,7 @@ } }, "delete": { + "tags": ["sources"], "parameters": [ { "name": "id", @@ -197,6 +203,7 @@ }, "/sources/{id}/proxy": { "post": { + "tags": ["sources", "proxy"], "description": "Query the backend time series data source and return the response according to `format`", "parameters": [ { @@ -252,6 +259,7 @@ }, "/users": { "get": { + "tags": ["users"], "summary": "List of all users on this data source", "responses": { "200": { @@ -269,6 +277,7 @@ } }, "post": { + "tags": ["users"], "summary": "Create new user for this data source", "parameters": [ { @@ -305,6 +314,7 @@ }, "/users/{user_id}": { "get": { + "tags": ["users"], "parameters": [ { "name": "user_id", @@ -338,6 +348,7 @@ } }, "patch": { + "tags": ["users"], "summary": "Update user configuration", "parameters": [ { @@ -379,6 +390,7 @@ } }, "delete": { + "tags": ["users"], "parameters": [ { "name": "user_id", @@ -410,6 +422,8 @@ }, "/users/{user_id}/explorations": { "get": { + "tags": ["users", "explorations"], + "summary": "Returns all explorations for specified user", "parameters": [ { "name": "user_id", @@ -441,6 +455,7 @@ } }, "post": { + "tags": ["users", "explorations"], "summary": "Create new named exploration for this user", "parameters": [ { @@ -490,6 +505,7 @@ }, "/users/{user_id}/explorations/{exploration_id}": { "get": { + "tags": ["users", "explorations"], "parameters": [ { "name": "user_id", @@ -530,6 +546,7 @@ } }, "patch": { + "tags": ["users", "explorations"], "summary": "Update exploration configuration", "parameters": [ { @@ -578,6 +595,7 @@ } }, "delete": { + "tags": ["users", "explorations"], "parameters": [ { "name": "user_id", @@ -616,6 +634,7 @@ }, "/sources/{id}/kapacitors": { "get": { + "tags": ["sources", "kapacitors"], "parameters": [ { "name": "id", @@ -642,6 +661,7 @@ } }, "post": { + "tags": ["sources", "kapacitors"], "summary": "Create new kapacitor backend", "parameters": [ { @@ -685,6 +705,7 @@ }, "/sources/{id}/kapacitors/{kapa_id}": { "get": { + "tags": ["sources", "kapacitors"], "parameters": [ { "name": "id", @@ -725,6 +746,7 @@ } }, "patch": { + "tags": ["sources", "kapacitors"], "summary": "Update kapacitor configuration", "parameters": [ { @@ -773,6 +795,7 @@ } }, "delete": { + "tags": ["sources", "kapacitors"], "parameters": [ { "name": "id", @@ -809,30 +832,28 @@ } } }, - "/sources/{id}/kapacitors/{kapa_id}/tasks": { + "/sources/{id}/kapacitors/{kapa_id}/rules": { "get": { - "description": "Get all defined alert tasks.", - "parameters": [ - { - "name": "id", - "in": "path", - "type": "string", - "description": "ID of the source", - "required": true - }, - { - "name": "kapa_id", - "in": "path", - "type": "string", - "description": "ID of the kapacitor backend.", - "required": true - } - ], + "tags": ["sources", "kapacitors", "rules"], + "description": "Get all defined alert rules.", + "parameters": [{ + "name": "id", + "in": "path", + "type": "string", + "description": "ID of the source", + "required": true + }, { + "name": "kapa_id", + "in": "path", + "type": "string", + "description": "ID of the kapacitor backend.", + "required": true + }], "responses": { "200": { - "description": "All alert tasks for this specific kapacitor are returned", + "description": "All alert rules for this specific kapacitor are returned", "schema": { - "$ref": "#/definitions/Tasks" + "$ref": "#/definitions/Rules" } }, "404": { @@ -850,54 +871,57 @@ } }, "post": { - "description": "Create kapacitor alert task", - "parameters": [ - { - "name": "id", - "in": "path", - "type": "string", - "description": "ID of the source", - "required": true + "tags": ["sources", "kapacitors", "rules"], + "description": "Create kapacitor alert rule", + "parameters": [{ + "name": "id", + "in": "path", + "type": "string", + "description": "ID of the source", + "required": true + }, { + "name": "kapa_id", + "in": "path", + "type": "string", + "description": "ID of the kapacitor backend.", + "required": true + }, { + "name": "rule", + "in": "body", + "description": "Rule to generate alert rule", + "schema": { + "$ref": "#/definitions/Rule" }, - { - "name": "kapa_id", - "in": "path", - "type": "string", - "description": "ID of the kapacitor backend.", - "required": true - }, - { - "name": "task", - "in": "body", - "description": "Rule to generate alert task", - "schema": { - "$ref": "#/definitions/Task" - }, - "required": true - } - ], + "required": true + }], "responses": { "201": { - "description": "Successfully created new kapacitor alert task", + "description": "Successfully created new kapacitor alert rule", "headers": { "Location": { "type": "string", "format": "url", - "description": "Location of the newly created kapacitor task resource." + "description": "Location of the newly created kapacitor rule resource." } }, "schema": { - "$ref": "#/definitions/Task" + "$ref": "#/definitions/Rule" } }, "404": { - "description": "Kapacitor ID does not exist.", + "description": "Source ID or Kapacitor ID does not exist.", + "schema": { + "$ref": "#/definitions/Error" + } + }, + "422": { + "description": "Source ID , Kapacitor ID or alert are unprocessable", "schema": { "$ref": "#/definitions/Error" } }, "default": { - "description": "Internal server error", + "description": "Internal server error; generally a problem creating alert in kapacitor", "schema": { "$ref": "#/definitions/Error" } @@ -905,42 +929,39 @@ } } }, - "/sources/{id}/kapacitors/{kapa_id}/tasks/{task_id}": { + "/sources/{id}/kapacitors/{kapa_id}/rules/{rule_id}": { "get": { - "parameters": [ - { - "name": "id", - "in": "path", - "type": "string", - "description": "ID of the source", - "required": true - }, - { - "name": "kapa_id", - "in": "path", - "type": "string", - "description": "ID of the kapacitor", - "required": true - }, - { - "name": "task_id", - "in": "path", - "type": "string", - "description": "ID of the task", - "required": true - } - ], - "summary": "Specific kapacitor alert task", - "description": "Alerting task for kapacitor", + "tags": ["sources", "kapacitors", "rules"], + "parameters": [{ + "name": "id", + "in": "path", + "type": "string", + "description": "ID of the source", + "required": true + }, { + "name": "kapa_id", + "in": "path", + "type": "string", + "description": "ID of the kapacitor", + "required": true + }, { + "name": "rule_id", + "in": "path", + "type": "string", + "description": "ID of the rule", + "required": true + }], + "summary": "Specific kapacitor alert rule", + "description": "Alerting rule for kapacitor", "responses": { "200": { "description": "Alert exists and has a specific TICKscript", "schema": { - "$ref": "#/definitions/Task" + "$ref": "#/definitions/Rule" } }, "404": { - "description": "Unknown data source, kapacitor id, or task id", + "description": "Unknown data source, kapacitor id, or rule id", "schema": { "$ref": "#/definitions/Error" } @@ -954,9 +975,9 @@ } }, "put": { - "summary": "Update rule alert task configuration", - "parameters": [ - { + "tags": ["sources", "kapacitors", "rules"], + "summary": "Update rule alert rule configuration", + "parameters": [{ "name": "id", "in": "path", "type": "string", @@ -969,20 +990,19 @@ "type": "string", "description": "ID of a kapacitor backend", "required": true - }, - { - "name": "task_id", + }, { + "name": "rule_id", "in": "path", "type": "string", - "description": "ID of a task", + "description": "ID of a rule", "required": true }, { - "name": "task", + "name": "rule", "in": "body", - "description": "Task update", + "description": "Rule update", "schema": { - "$ref": "#/definitions/Task" + "$ref": "#/definitions/Rule" }, "required": true } @@ -991,11 +1011,11 @@ "200": { "description": "Alert configuration was changed", "schema": { - "$ref": "#/definitions/Task" + "$ref": "#/definitions/Rule" } }, "404": { - "description": "Happens when trying to access a non-existent data source, kapacitor, or task.", + "description": "Happens when trying to access a non-existent data source, kapacitor, or rule.", "schema": { "$ref": "#/definitions/Error" } @@ -1009,6 +1029,7 @@ } }, "delete": { + "tags": ["sources", "kapacitors", "rules"], "parameters": [ { "name": "id", @@ -1023,22 +1044,21 @@ "type": "string", "description": "ID of the kapacitor", "required": true - }, - { - "name": "task_id", + }, { + "name": "rule_id", "in": "path", "type": "string", - "description": "ID of the task", + "description": "ID of the rule", "required": true } ], - "summary": "This specific alert task will be removed.", + "summary": "This specific alert rule will be removed.", "responses": { "204": { - "description": "Alert task has been removed." + "description": "Alert rule has been removed." }, "404": { - "description": "Unknown Data source, Kapacitor id, or alert task", + "description": "Unknown Data source, Kapacitor id, or alert rule", "schema": { "$ref": "#/definitions/Error" } @@ -1054,6 +1074,7 @@ }, "/sources/{id}/kapacitors/{kapa_id}/proxy": { "get": { + "tags": ["sources", "kapacitors", "proxy"], "description": "GET to `path` of kapacitor. The response and status code from kapacitor is directly returned.", "parameters": [ { @@ -1097,6 +1118,7 @@ } }, "delete": { + "tags": ["sources", "kapacitors", "proxy"], "description": "DELETE to `path` of kapacitor. The response and status code from kapacitor is directly returned.", "parameters": [ { @@ -1140,6 +1162,7 @@ } }, "patch": { + "tags": ["sources", "kapacitors", "proxy"], "description": "PATCH body directly to configured kapacitor. The response and status code from kapacitor is directly returned.", "parameters": [ { @@ -1192,6 +1215,7 @@ } }, "post": { + "tags": ["sources", "kapacitors", "proxy"], "description": "POST body directly to configured kapacitor. The response and status code from kapacitor is directly returned.", "parameters": [ { @@ -1246,6 +1270,7 @@ }, "/mappings": { "get": { + "tags": ["layouts", "mappings"], "summary": "Mappings between app names and measurements", "description": "Mappings provide a means to alias measurement names found within a telegraf database and application layouts found within Chronograf\n", "responses": { @@ -1266,6 +1291,7 @@ }, "/layouts": { "get": { + "tags": ["layouts"], "summary": "Pre-configured layouts", "parameters": [ { @@ -1308,6 +1334,7 @@ } }, "post": { + "tags": ["layouts"], "summary": "Create new layout", "parameters": [ { @@ -1344,6 +1371,7 @@ }, "/layouts/{id}": { "get": { + "tags": ["layouts"], "parameters": [ { "name": "id", @@ -1377,6 +1405,7 @@ } }, "delete": { + "tags": ["layouts"], "parameters": [ { "name": "id", @@ -1406,6 +1435,7 @@ } }, "put": { + "tags": ["layouts"], "summary": "Replace layout configuration.", "parameters": [ { @@ -1505,9 +1535,9 @@ "description": "URL location of proxy endpoint for this kapacitor", "format": "url" }, - "tasks": { + "rules": { "type": "string", - "description": "URL location of tasks endpoint for this kapacitor", + "description": "URL location of rules endpoint for this kapacitor", "format": "url" } } @@ -1522,6 +1552,103 @@ "description": "Entire response from the kapacitor backend.", "type": "object" }, + "Rules": { + "type": "object", + "required": [ + "rules" + ], + "properties": { + "rules": { + "type": "array", + "items": { + "$ref": "#/definitions/Rule" + } + } + } + }, + "Rule": { + "type": "object", + "required": [ + "every", + "trigger" + ], + "properties": { + "id": { + "type": "string", + "description": "ID for this rule; the ID is shared with kapacitor" + }, + "name": { + "type": "string", + "description": "User facing name of the alerting rule" + }, + "every" : { + "type": "string", + "description": "Golang duration string specifying how often the alert condition is checked" + }, + "alerts": { + "type": "array", + "description": "Array of alerting services to warn if the alert is triggered", + "items": { + "type": "string", + "enum": [ + "hipchat", + "opsgenie", + "pagerduty", + "victorops", + "smtp", + "email", + "sensu", + "slack", + "talk", + "telegram" + ] + } + }, + "message": { + "type": "string", + "description": "Message to send when alert occurs." + }, + "trigger": { + "type": "string", + "description": "Trigger defines the alerting structure; deadman alert if no data are received for the specified time range; relative alert if the data change relative to the data in a different time range; threshold alert if the data cross a boundary", + "enum": [ + "deadman", + "relative", + "threshold" + ] + }, + "tickscript": { + "type": "string", + "description": "TICKscript representing this rule" + }, + "links": { + "type": "object", + "required": [ + "self", + "kapacitor" + ], + "properties": { + "self": { + "description": "Self link pointing to this rule resource", + "type": "string", + "format": "uri" + }, + "kapacitor": { + "description": "Link pointing to the kapacitor proxy for this rule including the path query parameter.", + "type": "string", + "format": "uri" + }, + "output": { + "description": "Link pointing to the kapacitor httpOut node of the tickscript; includes the path query argument", + "type": "string", + "format": "uri" + } + + } + } + } + }, + "Sources": { "type": "object", "required": [ @@ -1690,12 +1817,12 @@ "User": { "type": "object", "required": [ - "username" + "email" ], "properties": { - "username": { + "email": { "type": "string", - "maxLength": 64 + "maxLength": 254 }, "link": { "$ref": "#/definitions/Link" @@ -1782,12 +1909,18 @@ "Cell": { "type": "object", "required": [ + "i", "x", "y", "w", "h" ], "properties": { + "i": { + "description": "Unique ID of Cell", + "type": "string", + "format": "uuid4" + }, "x": { "description": "X-coordinate of Cell in the Layout", "type": "integer", @@ -1814,7 +1947,22 @@ "items": { "$ref": "#/definitions/LayoutQuery" } - } + }, + "ylabels": { + "description": "Optional Y-axis user-facing label", + "type": "array", + "items": { + "type": "string" + } + }, + "yranges": { + "description": "Optional default range of the Y-axis; Must come in pairs.", + "type": "array", + "items": { + "type": "integer", + "format": "int64" + } + } } }, "LayoutQuery": { @@ -1850,6 +1998,11 @@ "type": "string", "format": "url" }, + "me": { + "description": "Location of the me endpoint.", + "type": "string", + "format": "url" + }, "layouts": { "description": "Location of the layouts endpoint", "type": "string", @@ -1909,4 +2062,4 @@ } } } -} \ No newline at end of file +} From 916b3484ae6d6f19dfcf0300c705db7499b4aa48 Mon Sep 17 00:00:00 2001 From: Will Piers Date: Tue, 22 Nov 2016 13:55:57 -0700 Subject: [PATCH 02/15] Add Y-labels to graphs --- ui/src/shared/components/LayoutRenderer.js | 4 +++- ui/src/shared/components/LineGraph.js | 9 ++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/ui/src/shared/components/LayoutRenderer.js b/ui/src/shared/components/LayoutRenderer.js index a17d38861..76f7d7713 100644 --- a/ui/src/shared/components/LayoutRenderer.js +++ b/ui/src/shared/components/LayoutRenderer.js @@ -17,13 +17,14 @@ export const LayoutRenderer = React.createClass({ PropTypes.shape({ queries: PropTypes.arrayOf( PropTypes.shape({ - rp: PropTypes.string.isRequired, + rp: PropTypes.string, text: PropTypes.string.isRequired, database: PropTypes.string.isRequired, groupbys: PropTypes.arrayOf(PropTypes.string), wheres: PropTypes.arrayOf(PropTypes.string), }).isRequired ).isRequired, + ylabels: PropTypes.arrayOf(PropTypes.string.isRequired).isRequired, x: PropTypes.number.isRequired, y: PropTypes.number.isRequired, w: PropTypes.number.isRequired, @@ -91,6 +92,7 @@ export const LayoutRenderer = React.createClass({ diff --git a/ui/src/shared/components/LineGraph.js b/ui/src/shared/components/LineGraph.js index d14197130..981a1333c 100644 --- a/ui/src/shared/components/LineGraph.js +++ b/ui/src/shared/components/LineGraph.js @@ -14,6 +14,7 @@ export default React.createClass({ isFetchingInitially: PropTypes.bool, isRefreshing: PropTypes.bool, yRange: arrayOf(number.isRequired), + ylabels: arrayOf(string.isRequired), underlayCallback: PropTypes.func, isGraphFilled: bool, overrideLineColors: array, @@ -39,10 +40,11 @@ export default React.createClass({ } }, render() { + const {isFetchingInitially, title, underlayCallback, ylabels} = this.props; const {fields, timeSeries} = this._timeSeries; // If data for this graph is being fetched for the first time, show a graph-wide spinner. - if (this.props.isFetchingInitially) { + if (isFetchingInitially) { return (

@@ -57,11 +59,12 @@ export default React.createClass({ height: 300, axisLineColor: '#383846', gridLineColor: '#383846', - title: this.props.title, + title, rightGap: 0, yRangePad: 10, drawAxesAtZero: true, - underlayCallback: this.props.underlayCallback, + underlayCallback, + ylabel: ylabels && ylabels[0], }; return ( From 33ea32c8b2533e1dc3519b1541ffbe8edf0c02fa Mon Sep 17 00:00:00 2001 From: Will Piers Date: Tue, 22 Nov 2016 14:12:09 -0700 Subject: [PATCH 03/15] Refactor for fun and profit --- ui/src/hosts/containers/HostPage.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/src/hosts/containers/HostPage.js b/ui/src/hosts/containers/HostPage.js index b9f38147f..93faae32f 100644 --- a/ui/src/hosts/containers/HostPage.js +++ b/ui/src/hosts/containers/HostPage.js @@ -34,7 +34,7 @@ export const HostPage = React.createClass({ }, componentDidMount() { - const {source, params} = this.props; + const {source, params, location} = this.props; const hosts = {[params.hostID]: {name: params.hostID}}; // fetching layouts and mappings can be done at the same time @@ -44,7 +44,7 @@ export const HostPage = React.createClass({ getMeasurementsForHost(source, params.hostID).then((measurements) => { const host = newHosts[this.props.params.hostID]; const filteredLayouts = layouts.filter((layout) => { - const focusedApp = this.props.location.query.app; + const focusedApp = location.query.app; if (focusedApp) { return layout.app === focusedApp; } From 66ac6af13698fe1d79c8cbd6aea58a7215a5cd6a Mon Sep 17 00:00:00 2001 From: Chris Goller Date: Tue, 29 Nov 2016 15:04:54 -0600 Subject: [PATCH 04/15] Update layout ylabel and yrange to be properties of the query. --- bolt/internal/internal.go | 17 +- bolt/internal/internal.pb.go | 104 ++++--- bolt/internal/internal.proto | 7 + bolt/internal/internal_test.go | 19 +- chronograf.go | 34 ++- server/layout.go | 12 - server/swagger.json | 501 ++++++++++++++++++++------------- 7 files changed, 428 insertions(+), 266 deletions(-) diff --git a/bolt/internal/internal.go b/bolt/internal/internal.go index 35a3b13ea..5be80efa4 100644 --- a/bolt/internal/internal.go +++ b/bolt/internal/internal.go @@ -107,12 +107,18 @@ func MarshalLayout(l chronograf.Layout) ([]byte, error) { for i, c := range l.Cells { queries := make([]*Query, len(c.Queries)) for j, q := range c.Queries { + r := new(Range) + if q.Range != nil { + r.Upper, r.Lower = q.Range.Upper, q.Range.Lower + } queries[j] = &Query{ Command: q.Command, DB: q.DB, RP: q.RP, GroupBys: q.GroupBys, Wheres: q.Wheres, + Label: q.Label, + Range: r, } } @@ -122,8 +128,6 @@ func MarshalLayout(l chronograf.Layout) ([]byte, error) { W: c.W, H: c.H, I: c.I, - Yranges: c.YRanges, - Ylabels: c.YLabels, Name: c.Name, Queries: queries, } @@ -158,6 +162,13 @@ func UnmarshalLayout(data []byte, l *chronograf.Layout) error { RP: q.RP, GroupBys: q.GroupBys, Wheres: q.Wheres, + Label: q.Label, + } + if q.Range.Upper != q.Range.Lower { + queries[j].Range = &chronograf.Range{ + Upper: q.Range.Upper, + Lower: q.Range.Lower, + } } } @@ -167,8 +178,6 @@ func UnmarshalLayout(data []byte, l *chronograf.Layout) error { W: c.W, H: c.H, I: c.I, - YRanges: c.Yranges, - YLabels: c.Ylabels, Name: c.Name, Queries: queries, } diff --git a/bolt/internal/internal.pb.go b/bolt/internal/internal.pb.go index ceedff464..7b743a081 100644 --- a/bolt/internal/internal.pb.go +++ b/bolt/internal/internal.pb.go @@ -15,6 +15,7 @@ It has these top-level messages: Layout Cell Query + Range AlertRule User */ @@ -130,6 +131,8 @@ type Query struct { RP string `protobuf:"bytes,3,opt,name=RP,proto3" json:"RP,omitempty"` GroupBys []string `protobuf:"bytes,4,rep,name=GroupBys" json:"GroupBys,omitempty"` Wheres []string `protobuf:"bytes,5,rep,name=Wheres" json:"Wheres,omitempty"` + Label string `protobuf:"bytes,6,opt,name=Label,proto3" json:"Label,omitempty"` + Range *Range `protobuf:"bytes,7,opt,name=Range" json:"Range,omitempty"` } func (m *Query) Reset() { *m = Query{} } @@ -137,6 +140,23 @@ func (m *Query) String() string { return proto.CompactTextString(m) } func (*Query) ProtoMessage() {} func (*Query) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{5} } +func (m *Query) GetRange() *Range { + if m != nil { + return m.Range + } + return nil +} + +type Range struct { + Upper int64 `protobuf:"varint,1,opt,name=Upper,proto3" json:"Upper,omitempty"` + Lower int64 `protobuf:"varint,2,opt,name=Lower,proto3" json:"Lower,omitempty"` +} + +func (m *Range) Reset() { *m = Range{} } +func (m *Range) String() string { return proto.CompactTextString(m) } +func (*Range) ProtoMessage() {} +func (*Range) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{6} } + type AlertRule struct { ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"` JSON string `protobuf:"bytes,2,opt,name=JSON,proto3" json:"JSON,omitempty"` @@ -147,7 +167,7 @@ type AlertRule struct { func (m *AlertRule) Reset() { *m = AlertRule{} } func (m *AlertRule) String() string { return proto.CompactTextString(m) } func (*AlertRule) ProtoMessage() {} -func (*AlertRule) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{6} } +func (*AlertRule) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{7} } type User struct { ID uint64 `protobuf:"varint,1,opt,name=ID,proto3" json:"ID,omitempty"` @@ -157,7 +177,7 @@ type User struct { func (m *User) Reset() { *m = User{} } func (m *User) String() string { return proto.CompactTextString(m) } func (*User) ProtoMessage() {} -func (*User) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{7} } +func (*User) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{8} } func init() { proto.RegisterType((*Exploration)(nil), "internal.Exploration") @@ -166,6 +186,7 @@ func init() { proto.RegisterType((*Layout)(nil), "internal.Layout") proto.RegisterType((*Cell)(nil), "internal.Cell") proto.RegisterType((*Query)(nil), "internal.Query") + proto.RegisterType((*Range)(nil), "internal.Range") proto.RegisterType((*AlertRule)(nil), "internal.AlertRule") proto.RegisterType((*User)(nil), "internal.User") } @@ -173,42 +194,45 @@ func init() { func init() { proto.RegisterFile("internal.proto", fileDescriptorInternal) } var fileDescriptorInternal = []byte{ - // 583 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x8c, 0x94, 0x5f, 0x6e, 0xd3, 0x4e, - 0x10, 0xc7, 0xb5, 0xb1, 0x9d, 0xc4, 0xd3, 0x9f, 0xfa, 0x43, 0xab, 0x0a, 0xad, 0x10, 0x0f, 0x96, - 0xc5, 0x83, 0x91, 0x50, 0x1f, 0xe0, 0x04, 0x69, 0x5d, 0xa1, 0x40, 0x29, 0x65, 0xdb, 0x8a, 0x27, - 0x1e, 0xb6, 0xcd, 0xb4, 0xb5, 0xb4, 0xb1, 0xcd, 0xda, 0x26, 0xf1, 0x1d, 0x38, 0x03, 0x97, 0xe0, - 0x00, 0x70, 0x34, 0x34, 0xeb, 0xb5, 0x63, 0x89, 0x3f, 0xea, 0xdb, 0x7c, 0x67, 0x26, 0xb3, 0x9f, - 0xfd, 0xee, 0xc4, 0xb0, 0x9f, 0xe5, 0x35, 0x9a, 0x5c, 0xe9, 0xc3, 0xd2, 0x14, 0x75, 0xc1, 0xe7, - 0xbd, 0x8e, 0xbf, 0x33, 0xd8, 0x3b, 0xd9, 0x96, 0xba, 0x30, 0xaa, 0xce, 0x8a, 0x9c, 0xef, 0xc3, - 0x64, 0x99, 0x0a, 0x16, 0xb1, 0xc4, 0x93, 0x93, 0x65, 0xca, 0x39, 0xf8, 0x67, 0x6a, 0x8d, 0x62, - 0x12, 0xb1, 0x24, 0x94, 0x36, 0xe6, 0x8f, 0x61, 0x7a, 0x55, 0xa1, 0x59, 0xa6, 0xc2, 0xb3, 0x7d, - 0x4e, 0x51, 0x6f, 0xaa, 0x6a, 0x25, 0xfc, 0xae, 0x97, 0x62, 0xfe, 0x14, 0xc2, 0x63, 0x83, 0xaa, - 0xc6, 0xd5, 0xa2, 0x16, 0x81, 0x6d, 0xdf, 0x25, 0xa8, 0x7a, 0x55, 0xae, 0x5c, 0x75, 0xda, 0x55, - 0x87, 0x04, 0x17, 0x30, 0x4b, 0xf1, 0x56, 0x35, 0xba, 0x16, 0xb3, 0x88, 0x25, 0x73, 0xd9, 0xcb, - 0xf8, 0x27, 0x83, 0xe9, 0x45, 0xd1, 0x98, 0x1b, 0x7c, 0x10, 0x30, 0x07, 0xff, 0xb2, 0x2d, 0xd1, - 0xe2, 0x86, 0xd2, 0xc6, 0xfc, 0x09, 0xcc, 0x09, 0x3b, 0xa7, 0xde, 0x0e, 0x78, 0xd0, 0x54, 0x3b, - 0x57, 0x55, 0xb5, 0x29, 0xcc, 0xca, 0x32, 0x87, 0x72, 0xd0, 0xfc, 0x11, 0x78, 0x57, 0xf2, 0xd4, - 0xc2, 0x86, 0x92, 0xc2, 0xbf, 0x63, 0xd2, 0x9c, 0x4b, 0xd4, 0x78, 0x67, 0xd4, 0xad, 0x98, 0x77, - 0x73, 0x7a, 0x1d, 0x7f, 0xa5, 0x2b, 0xa0, 0xf9, 0x82, 0xe6, 0x41, 0x57, 0x18, 0xe3, 0x7a, 0xff, - 0xc0, 0xf5, 0xff, 0x8c, 0x1b, 0xec, 0x70, 0x0f, 0x20, 0xb8, 0x30, 0x37, 0xcb, 0xd4, 0xf9, 0xdd, - 0x89, 0xf8, 0x1b, 0x83, 0xe9, 0xa9, 0x6a, 0x8b, 0xa6, 0x1e, 0xe1, 0x84, 0x16, 0x27, 0x82, 0xbd, - 0x45, 0x59, 0xea, 0xec, 0xc6, 0x6e, 0x88, 0xa3, 0x1a, 0xa7, 0xa8, 0xe3, 0x1d, 0xaa, 0xaa, 0x31, - 0xb8, 0xc6, 0xbc, 0x76, 0x7c, 0xe3, 0x14, 0x7f, 0x06, 0xc1, 0x31, 0x6a, 0x5d, 0x09, 0x3f, 0xf2, - 0x92, 0xbd, 0x97, 0xfb, 0x87, 0xc3, 0x42, 0x52, 0x5a, 0x76, 0x45, 0xba, 0xc8, 0xa2, 0xa9, 0x8b, - 0x5b, 0x5d, 0x6c, 0x2c, 0xf1, 0x5c, 0x0e, 0x3a, 0xfe, 0xc1, 0xc0, 0xa7, 0x2e, 0xfe, 0x1f, 0xb0, - 0xad, 0xa5, 0x0b, 0x24, 0xdb, 0x92, 0x6a, 0x2d, 0x52, 0x20, 0x59, 0x4b, 0x6a, 0x63, 0x8f, 0x0f, - 0x24, 0xdb, 0x90, 0xba, 0xb7, 0x86, 0x04, 0x92, 0xdd, 0xf3, 0xe7, 0x30, 0xfb, 0xdc, 0xa0, 0xc9, - 0xb0, 0x12, 0x81, 0x85, 0xf8, 0x7f, 0x07, 0xf1, 0xa1, 0x41, 0xd3, 0xca, 0xbe, 0x4e, 0x3f, 0xcc, - 0xdc, 0x0b, 0xb3, 0x8c, 0x9e, 0xc3, 0xda, 0x3e, 0xeb, 0x9e, 0xc3, 0x5a, 0x2e, 0x60, 0xd6, 0x1a, - 0x95, 0xdf, 0x61, 0x25, 0xe6, 0x91, 0x97, 0x78, 0xb2, 0x97, 0xb6, 0xa2, 0xd5, 0x35, 0xea, 0x4a, - 0x84, 0x91, 0x97, 0x84, 0xb2, 0x97, 0x71, 0x03, 0x81, 0x3d, 0x87, 0x5a, 0x8e, 0x8b, 0xf5, 0x5a, - 0xe5, 0x2b, 0xe7, 0x72, 0x2f, 0xc9, 0xfa, 0xf4, 0xc8, 0x39, 0x3c, 0x49, 0x8f, 0x48, 0xcb, 0x73, - 0xe7, 0xe7, 0x44, 0x9e, 0x93, 0x41, 0xaf, 0x4d, 0xd1, 0x94, 0x47, 0x6d, 0xe7, 0x64, 0x28, 0x07, - 0x4d, 0xff, 0xca, 0x8f, 0xf7, 0x68, 0xdc, 0xf5, 0x42, 0xe9, 0x54, 0xfc, 0x09, 0xc2, 0x85, 0x46, - 0x53, 0xcb, 0x46, 0xe3, 0x6f, 0x6f, 0xcb, 0xc1, 0x7f, 0x73, 0xf1, 0xfe, 0xac, 0x5f, 0x35, 0x8a, - 0x77, 0x0b, 0xe2, 0x8d, 0x16, 0x84, 0xc6, 0xbf, 0x55, 0xa5, 0x5a, 0xa6, 0xd6, 0x51, 0x4f, 0x3a, - 0x15, 0xbf, 0x00, 0x9f, 0x16, 0x71, 0x34, 0xd9, 0xb7, 0x93, 0x0f, 0x20, 0x38, 0x59, 0xab, 0x4c, - 0xbb, 0xd1, 0x9d, 0xb8, 0x9e, 0xda, 0xef, 0xcf, 0xab, 0x5f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x47, - 0x4e, 0x32, 0xc9, 0x91, 0x04, 0x00, 0x00, + // 626 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x8c, 0x94, 0x5f, 0x6e, 0xd3, 0x4c, + 0x10, 0xc0, 0xb5, 0xb1, 0x9d, 0xc4, 0xdb, 0x4f, 0xfd, 0xd0, 0xaa, 0x42, 0x2b, 0xc4, 0x83, 0x65, + 0x81, 0x14, 0x24, 0xd4, 0x07, 0x7a, 0x82, 0xb4, 0xae, 0x50, 0xa0, 0x94, 0xb2, 0x6d, 0xc4, 0x13, + 0x0f, 0xdb, 0x66, 0xda, 0x5a, 0xda, 0xd8, 0x66, 0x6d, 0x93, 0xfa, 0x0e, 0x9c, 0x81, 0x43, 0xc0, + 0x01, 0xe0, 0x68, 0x68, 0x66, 0xd7, 0x49, 0x10, 0x7f, 0xd4, 0xb7, 0xfd, 0xed, 0x4c, 0xc7, 0xbf, + 0x99, 0x9d, 0x86, 0xef, 0xe6, 0x45, 0x03, 0xb6, 0xd0, 0x66, 0xbf, 0xb2, 0x65, 0x53, 0x8a, 0x71, + 0xcf, 0xe9, 0x37, 0xc6, 0x77, 0x8e, 0xef, 0x2a, 0x53, 0x5a, 0xdd, 0xe4, 0x65, 0x21, 0x76, 0xf9, + 0x60, 0x96, 0x49, 0x96, 0xb0, 0x49, 0xa0, 0x06, 0xb3, 0x4c, 0x08, 0x1e, 0x9e, 0xea, 0x25, 0xc8, + 0x41, 0xc2, 0x26, 0xb1, 0xa2, 0xb3, 0x78, 0xc8, 0x87, 0xf3, 0x1a, 0xec, 0x2c, 0x93, 0x01, 0xe5, + 0x79, 0xc2, 0xdc, 0x4c, 0x37, 0x5a, 0x86, 0x2e, 0x17, 0xcf, 0xe2, 0x31, 0x8f, 0x8f, 0x2c, 0xe8, + 0x06, 0x16, 0xd3, 0x46, 0x46, 0x94, 0xbe, 0xb9, 0xc0, 0xe8, 0xbc, 0x5a, 0xf8, 0xe8, 0xd0, 0x45, + 0xd7, 0x17, 0x42, 0xf2, 0x51, 0x06, 0xd7, 0xba, 0x35, 0x8d, 0x1c, 0x25, 0x6c, 0x32, 0x56, 0x3d, + 0xa6, 0x3f, 0x18, 0x1f, 0x9e, 0x97, 0xad, 0xbd, 0x82, 0x7b, 0x09, 0x0b, 0x1e, 0x5e, 0x74, 0x15, + 0x90, 0x6e, 0xac, 0xe8, 0x2c, 0x1e, 0xf1, 0x31, 0x6a, 0x17, 0x98, 0xeb, 0x84, 0xd7, 0x8c, 0xb1, + 0x33, 0x5d, 0xd7, 0xab, 0xd2, 0x2e, 0xc8, 0x39, 0x56, 0x6b, 0x16, 0x0f, 0x78, 0x30, 0x57, 0x27, + 0x24, 0x1b, 0x2b, 0x3c, 0xfe, 0x5d, 0x13, 0xeb, 0x5c, 0x80, 0x81, 0x1b, 0xab, 0xaf, 0xe5, 0xd8, + 0xd5, 0xe9, 0x39, 0xfd, 0x8c, 0x2d, 0x80, 0xfd, 0x04, 0xf6, 0x5e, 0x2d, 0x6c, 0xeb, 0x06, 0xff, + 0xd0, 0x0d, 0xff, 0xac, 0x1b, 0x6d, 0x74, 0xf7, 0x78, 0x74, 0x6e, 0xaf, 0x66, 0x99, 0x9f, 0xb7, + 0x83, 0xf4, 0x0b, 0xe3, 0xc3, 0x13, 0xdd, 0x95, 0x6d, 0xb3, 0xa5, 0x13, 0x93, 0x4e, 0xc2, 0x77, + 0xa6, 0x55, 0x65, 0xf2, 0x2b, 0xda, 0x10, 0x6f, 0xb5, 0x7d, 0x85, 0x19, 0x6f, 0x40, 0xd7, 0xad, + 0x85, 0x25, 0x14, 0x8d, 0xf7, 0xdb, 0xbe, 0x12, 0x4f, 0x78, 0x74, 0x04, 0xc6, 0xd4, 0x32, 0x4c, + 0x82, 0xc9, 0xce, 0x8b, 0xdd, 0xfd, 0xf5, 0x42, 0xe2, 0xb5, 0x72, 0x41, 0x6c, 0x64, 0xda, 0x36, + 0xe5, 0xb5, 0x29, 0x57, 0x64, 0x3c, 0x56, 0x6b, 0x4e, 0xbf, 0x33, 0x1e, 0x62, 0x96, 0xf8, 0x8f, + 0xb3, 0x3b, 0xb2, 0x8b, 0x14, 0xbb, 0x43, 0xea, 0x48, 0x29, 0x52, 0xac, 0x43, 0x5a, 0xd1, 0xe7, + 0x23, 0xc5, 0x56, 0x48, 0xb7, 0x34, 0x90, 0x48, 0xb1, 0x5b, 0xf1, 0x8c, 0x8f, 0x3e, 0xb6, 0x60, + 0x73, 0xa8, 0x65, 0x44, 0x12, 0xff, 0x6f, 0x24, 0xde, 0xb5, 0x60, 0x3b, 0xd5, 0xc7, 0xf1, 0x0f, + 0x73, 0xff, 0xc2, 0x2c, 0xc7, 0xe7, 0xa0, 0xb1, 0x8f, 0xdc, 0x73, 0xd0, 0xc8, 0x25, 0x1f, 0x75, + 0x56, 0x17, 0x37, 0x50, 0xcb, 0x71, 0x12, 0x4c, 0x02, 0xd5, 0x23, 0x45, 0x8c, 0xbe, 0x04, 0x53, + 0xcb, 0x38, 0x09, 0x26, 0xb1, 0xea, 0x31, 0xfd, 0xca, 0x78, 0x44, 0x1f, 0xc2, 0x9c, 0xa3, 0x72, + 0xb9, 0xd4, 0xc5, 0xc2, 0x8f, 0xb9, 0x47, 0x9c, 0x7d, 0x76, 0xe8, 0x47, 0x3c, 0xc8, 0x0e, 0x91, + 0xd5, 0x99, 0x1f, 0xe8, 0x40, 0x9d, 0xe1, 0x84, 0x5e, 0xda, 0xb2, 0xad, 0x0e, 0x3b, 0x37, 0xca, + 0x58, 0xad, 0x19, 0xff, 0x2d, 0xdf, 0xdf, 0x82, 0xf5, 0xfd, 0xc5, 0xca, 0x13, 0x3e, 0xf8, 0x09, + 0x1a, 0xf8, 0x8e, 0x1c, 0x88, 0xa7, 0x3c, 0x52, 0x68, 0x4c, 0x6d, 0xfd, 0x32, 0x0c, 0xba, 0x56, + 0x2e, 0x9a, 0x1e, 0xf8, 0x34, 0xac, 0x32, 0xaf, 0x2a, 0xb0, 0x7e, 0x4f, 0x1d, 0x50, 0xed, 0x72, + 0x05, 0x96, 0x94, 0x03, 0xe5, 0x20, 0xfd, 0xc0, 0xe3, 0xa9, 0x01, 0xdb, 0xa8, 0xd6, 0xc0, 0x6f, + 0xeb, 0x24, 0x78, 0xf8, 0xea, 0xfc, 0xed, 0x69, 0xbf, 0xdd, 0x78, 0xde, 0xec, 0x64, 0xb0, 0xb5, + 0x93, 0xd8, 0xd0, 0x6b, 0x5d, 0xe9, 0x59, 0x46, 0x8f, 0x18, 0x28, 0x4f, 0xe9, 0x73, 0x1e, 0xe2, + 0xee, 0x6f, 0x55, 0x0e, 0xa9, 0xf2, 0x1e, 0x8f, 0x8e, 0x97, 0x3a, 0x37, 0xbe, 0xb4, 0x83, 0xcb, + 0x21, 0xfd, 0xe4, 0x1d, 0xfc, 0x0c, 0x00, 0x00, 0xff, 0xff, 0x4e, 0xdb, 0x24, 0x8b, 0x04, 0x05, + 0x00, 0x00, } diff --git a/bolt/internal/internal.proto b/bolt/internal/internal.proto index 389c9621a..7cdd6e9c9 100644 --- a/bolt/internal/internal.proto +++ b/bolt/internal/internal.proto @@ -57,6 +57,13 @@ message Query { string RP = 3; // RP is a retention policy and optional; repeated string GroupBys= 4; // GroupBys define the groups to combine in the query repeated string Wheres = 5; // Wheres define the restrictions on the query + string Label = 6; // Label is the name of the Y-Axis + Range Range = 7; // Range is the upper and lower bound of the Y-Axis +} + +message Range { + int64 Upper = 1; // Upper is the upper-bound of the range + int64 Lower = 2; // Lower is the lower-bound of the range } message AlertRule { diff --git a/bolt/internal/internal_test.go b/bolt/internal/internal_test.go index b23700c1d..e40b53ac5 100644 --- a/bolt/internal/internal_test.go +++ b/bolt/internal/internal_test.go @@ -79,16 +79,19 @@ func TestMarshalLayout(t *testing.T) { Application: "app", Cells: []chronograf.Cell{ { - X: 1, - Y: 1, - W: 4, - H: 4, - I: "anotherid", - YRanges: []int64{1, 2}, - YLabels: []string{"y1", "y2"}, - Name: "cell1", + X: 1, + Y: 1, + W: 4, + H: 4, + I: "anotherid", + Name: "cell1", Queries: []chronograf.Query{ { + Range: &chronograf.Range{ + Lower: 1, + Upper: 2, + }, + Label: "y1", Command: "select mean(usage_user) as usage_user from cpu", Wheres: []string{ `"host"="myhost"`, diff --git a/chronograf.go b/chronograf.go index 2fc8cde5f..f93ae1027 100644 --- a/chronograf.go +++ b/chronograf.go @@ -53,13 +53,21 @@ type TimeSeries interface { Connect(context.Context, *Source) error } +// Range represents an upper and lower bound for data +type Range struct { + Upper int64 `json:"upper,omitempty"` // Upper is the upper bound + Lower int64 `json:"lower,omitempty"` // Lower is the lower bound +} + // Query retrieves a Response from a TimeSeries. type Query struct { - Command string `json:"query"` // Command is the query itself - DB string `json:"db,omitempty"` // DB is optional and if empty will not be used. - RP string `json:"rp,omitempty"` // RP is a retention policy and optional; if empty will not be used. - Wheres []string `json:"wheres"` // Wheres restricts the query to certain attributes - GroupBys []string `json:"groupbys"` // GroupBys collate the query by these tags + Command string `json:"query"` // Command is the query itself + DB string `json:"db,omitempty"` // DB is optional and if empty will not be used. + RP string `json:"rp,omitempty"` // RP is a retention policy and optional; if empty will not be used. + Wheres []string `json:"wheres,omitempty"` // Wheres restricts the query to certain attributes + GroupBys []string `json:"groupbys,omitempty"` // GroupBys collate the query by these tags + Label string `json:"label,omitempty"` // Label is the Y-Axis label for the data + Range *Range `json:"range,omitempty"` // Range is the default Y-Axis range for the data } // Response is the result of a query against a TimeSeries @@ -245,15 +253,13 @@ type ExplorationStore interface { // Cell is a rectangle and multiple time series queries to visualize. type Cell struct { - X int32 `json:"x"` - Y int32 `json:"y"` - W int32 `json:"w"` - H int32 `json:"h"` - I string `json:"i"` - YRanges []int64 `json:"yranges"` // YRanges are the initial limits of the y-axes - YLabels []string `json:"ylabels"` // YLabels are the labels of the y-axes (possible to have more than one) - Name string `json:"name"` - Queries []Query `json:"queries"` + X int32 `json:"x"` + Y int32 `json:"y"` + W int32 `json:"w"` + H int32 `json:"h"` + I string `json:"i"` + Name string `json:"name"` + Queries []Query `json:"queries"` } // Layout is a collection of Cells for visualization diff --git a/server/layout.go b/server/layout.go index 02ccb6861..1a634e688 100644 --- a/server/layout.go +++ b/server/layout.go @@ -18,14 +18,6 @@ func newLayoutResponse(layout chronograf.Layout) layoutResponse { httpAPILayouts := "/chronograf/v1/layouts" href := fmt.Sprintf("%s/%s", httpAPILayouts, layout.ID) rel := "self" - for i, c := range layout.Cells { - if c.YLabels == nil { - layout.Cells[i].YLabels = make([]string, 0) - } - if c.YRanges == nil { - layout.Cells[i].YRanges = make([]int64, 0) - } - } return layoutResponse{ Layout: layout, Link: link{ @@ -179,10 +171,6 @@ func ValidLayoutRequest(l chronograf.Layout) error { if c.W == 0 || c.H == 0 { return fmt.Errorf("w, and h required") } - // Y-Range must come in pairs (I'm leaving the option open for multiple y-axes) - if len(c.YRanges)%2 != 0 { - return fmt.Errorf("Incorrect length of yrange: %d", len(c.YRanges)) - } for _, q := range c.Queries { if q.Command == "" { return fmt.Errorf("query required") diff --git a/server/swagger.json b/server/swagger.json index bdc467b6b..282f7c52e 100644 --- a/server/swagger.json +++ b/server/swagger.json @@ -18,7 +18,9 @@ "paths": { "/": { "get": { - "tags": ["routes"], + "tags": [ + "routes" + ], "summary": "Lists all the endpoints", "description": "List of the endpoints.", "responses": { @@ -39,7 +41,9 @@ }, "/sources": { "get": { - "tags": ["sources"], + "tags": [ + "sources" + ], "summary": "Configured data sources", "description": "These data sources store time series data.", "responses": { @@ -58,7 +62,9 @@ } }, "post": { - "tags": ["sources"], + "tags": [ + "sources" + ], "summary": "Create new data source", "parameters": [ { @@ -95,7 +101,9 @@ }, "/sources/{id}": { "get": { - "tags": ["sources"], + "tags": [ + "sources" + ], "parameters": [ { "name": "id", @@ -129,7 +137,9 @@ } }, "patch": { - "tags": ["sources"], + "tags": [ + "sources" + ], "summary": "Update data source configuration", "parameters": [ { @@ -171,7 +181,9 @@ } }, "delete": { - "tags": ["sources"], + "tags": [ + "sources" + ], "parameters": [ { "name": "id", @@ -203,7 +215,10 @@ }, "/sources/{id}/proxy": { "post": { - "tags": ["sources", "proxy"], + "tags": [ + "sources", + "proxy" + ], "description": "Query the backend time series data source and return the response according to `format`", "parameters": [ { @@ -259,7 +274,9 @@ }, "/users": { "get": { - "tags": ["users"], + "tags": [ + "users" + ], "summary": "List of all users on this data source", "responses": { "200": { @@ -277,7 +294,9 @@ } }, "post": { - "tags": ["users"], + "tags": [ + "users" + ], "summary": "Create new user for this data source", "parameters": [ { @@ -314,7 +333,9 @@ }, "/users/{user_id}": { "get": { - "tags": ["users"], + "tags": [ + "users" + ], "parameters": [ { "name": "user_id", @@ -348,7 +369,9 @@ } }, "patch": { - "tags": ["users"], + "tags": [ + "users" + ], "summary": "Update user configuration", "parameters": [ { @@ -390,7 +413,9 @@ } }, "delete": { - "tags": ["users"], + "tags": [ + "users" + ], "parameters": [ { "name": "user_id", @@ -422,8 +447,11 @@ }, "/users/{user_id}/explorations": { "get": { - "tags": ["users", "explorations"], - "summary": "Returns all explorations for specified user", + "tags": [ + "users", + "explorations" + ], + "summary": "Returns all explorations for specified user", "parameters": [ { "name": "user_id", @@ -455,7 +483,10 @@ } }, "post": { - "tags": ["users", "explorations"], + "tags": [ + "users", + "explorations" + ], "summary": "Create new named exploration for this user", "parameters": [ { @@ -505,7 +536,10 @@ }, "/users/{user_id}/explorations/{exploration_id}": { "get": { - "tags": ["users", "explorations"], + "tags": [ + "users", + "explorations" + ], "parameters": [ { "name": "user_id", @@ -546,7 +580,10 @@ } }, "patch": { - "tags": ["users", "explorations"], + "tags": [ + "users", + "explorations" + ], "summary": "Update exploration configuration", "parameters": [ { @@ -595,7 +632,10 @@ } }, "delete": { - "tags": ["users", "explorations"], + "tags": [ + "users", + "explorations" + ], "parameters": [ { "name": "user_id", @@ -634,7 +674,10 @@ }, "/sources/{id}/kapacitors": { "get": { - "tags": ["sources", "kapacitors"], + "tags": [ + "sources", + "kapacitors" + ], "parameters": [ { "name": "id", @@ -661,7 +704,10 @@ } }, "post": { - "tags": ["sources", "kapacitors"], + "tags": [ + "sources", + "kapacitors" + ], "summary": "Create new kapacitor backend", "parameters": [ { @@ -705,7 +751,10 @@ }, "/sources/{id}/kapacitors/{kapa_id}": { "get": { - "tags": ["sources", "kapacitors"], + "tags": [ + "sources", + "kapacitors" + ], "parameters": [ { "name": "id", @@ -746,7 +795,10 @@ } }, "patch": { - "tags": ["sources", "kapacitors"], + "tags": [ + "sources", + "kapacitors" + ], "summary": "Update kapacitor configuration", "parameters": [ { @@ -795,7 +847,10 @@ } }, "delete": { - "tags": ["sources", "kapacitors"], + "tags": [ + "sources", + "kapacitors" + ], "parameters": [ { "name": "id", @@ -834,21 +889,28 @@ }, "/sources/{id}/kapacitors/{kapa_id}/rules": { "get": { - "tags": ["sources", "kapacitors", "rules"], + "tags": [ + "sources", + "kapacitors", + "rules" + ], "description": "Get all defined alert rules.", - "parameters": [{ - "name": "id", - "in": "path", - "type": "string", - "description": "ID of the source", - "required": true - }, { - "name": "kapa_id", - "in": "path", - "type": "string", - "description": "ID of the kapacitor backend.", - "required": true - }], + "parameters": [ + { + "name": "id", + "in": "path", + "type": "string", + "description": "ID of the source", + "required": true + }, + { + "name": "kapa_id", + "in": "path", + "type": "string", + "description": "ID of the kapacitor backend.", + "required": true + } + ], "responses": { "200": { "description": "All alert rules for this specific kapacitor are returned", @@ -871,29 +933,37 @@ } }, "post": { - "tags": ["sources", "kapacitors", "rules"], + "tags": [ + "sources", + "kapacitors", + "rules" + ], "description": "Create kapacitor alert rule", - "parameters": [{ - "name": "id", - "in": "path", - "type": "string", - "description": "ID of the source", - "required": true - }, { - "name": "kapa_id", - "in": "path", - "type": "string", - "description": "ID of the kapacitor backend.", - "required": true - }, { - "name": "rule", - "in": "body", - "description": "Rule to generate alert rule", - "schema": { - "$ref": "#/definitions/Rule" + "parameters": [ + { + "name": "id", + "in": "path", + "type": "string", + "description": "ID of the source", + "required": true }, - "required": true - }], + { + "name": "kapa_id", + "in": "path", + "type": "string", + "description": "ID of the kapacitor backend.", + "required": true + }, + { + "name": "rule", + "in": "body", + "description": "Rule to generate alert rule", + "schema": { + "$ref": "#/definitions/Rule" + }, + "required": true + } + ], "responses": { "201": { "description": "Successfully created new kapacitor alert rule", @@ -914,7 +984,7 @@ "$ref": "#/definitions/Error" } }, - "422": { + "422": { "description": "Source ID , Kapacitor ID or alert are unprocessable", "schema": { "$ref": "#/definitions/Error" @@ -931,26 +1001,34 @@ }, "/sources/{id}/kapacitors/{kapa_id}/rules/{rule_id}": { "get": { - "tags": ["sources", "kapacitors", "rules"], - "parameters": [{ - "name": "id", - "in": "path", - "type": "string", - "description": "ID of the source", - "required": true - }, { - "name": "kapa_id", - "in": "path", - "type": "string", - "description": "ID of the kapacitor", - "required": true - }, { - "name": "rule_id", - "in": "path", - "type": "string", - "description": "ID of the rule", - "required": true - }], + "tags": [ + "sources", + "kapacitors", + "rules" + ], + "parameters": [ + { + "name": "id", + "in": "path", + "type": "string", + "description": "ID of the source", + "required": true + }, + { + "name": "kapa_id", + "in": "path", + "type": "string", + "description": "ID of the kapacitor", + "required": true + }, + { + "name": "rule_id", + "in": "path", + "type": "string", + "description": "ID of the rule", + "required": true + } + ], "summary": "Specific kapacitor alert rule", "description": "Alerting rule for kapacitor", "responses": { @@ -975,9 +1053,14 @@ } }, "put": { - "tags": ["sources", "kapacitors", "rules"], + "tags": [ + "sources", + "kapacitors", + "rules" + ], "summary": "Update rule alert rule configuration", - "parameters": [{ + "parameters": [ + { "name": "id", "in": "path", "type": "string", @@ -990,7 +1073,8 @@ "type": "string", "description": "ID of a kapacitor backend", "required": true - }, { + }, + { "name": "rule_id", "in": "path", "type": "string", @@ -1029,7 +1113,11 @@ } }, "delete": { - "tags": ["sources", "kapacitors", "rules"], + "tags": [ + "sources", + "kapacitors", + "rules" + ], "parameters": [ { "name": "id", @@ -1044,7 +1132,8 @@ "type": "string", "description": "ID of the kapacitor", "required": true - }, { + }, + { "name": "rule_id", "in": "path", "type": "string", @@ -1074,7 +1163,11 @@ }, "/sources/{id}/kapacitors/{kapa_id}/proxy": { "get": { - "tags": ["sources", "kapacitors", "proxy"], + "tags": [ + "sources", + "kapacitors", + "proxy" + ], "description": "GET to `path` of kapacitor. The response and status code from kapacitor is directly returned.", "parameters": [ { @@ -1118,7 +1211,11 @@ } }, "delete": { - "tags": ["sources", "kapacitors", "proxy"], + "tags": [ + "sources", + "kapacitors", + "proxy" + ], "description": "DELETE to `path` of kapacitor. The response and status code from kapacitor is directly returned.", "parameters": [ { @@ -1162,7 +1259,11 @@ } }, "patch": { - "tags": ["sources", "kapacitors", "proxy"], + "tags": [ + "sources", + "kapacitors", + "proxy" + ], "description": "PATCH body directly to configured kapacitor. The response and status code from kapacitor is directly returned.", "parameters": [ { @@ -1215,7 +1316,11 @@ } }, "post": { - "tags": ["sources", "kapacitors", "proxy"], + "tags": [ + "sources", + "kapacitors", + "proxy" + ], "description": "POST body directly to configured kapacitor. The response and status code from kapacitor is directly returned.", "parameters": [ { @@ -1270,7 +1375,10 @@ }, "/mappings": { "get": { - "tags": ["layouts", "mappings"], + "tags": [ + "layouts", + "mappings" + ], "summary": "Mappings between app names and measurements", "description": "Mappings provide a means to alias measurement names found within a telegraf database and application layouts found within Chronograf\n", "responses": { @@ -1291,7 +1399,9 @@ }, "/layouts": { "get": { - "tags": ["layouts"], + "tags": [ + "layouts" + ], "summary": "Pre-configured layouts", "parameters": [ { @@ -1334,7 +1444,9 @@ } }, "post": { - "tags": ["layouts"], + "tags": [ + "layouts" + ], "summary": "Create new layout", "parameters": [ { @@ -1371,7 +1483,9 @@ }, "/layouts/{id}": { "get": { - "tags": ["layouts"], + "tags": [ + "layouts" + ], "parameters": [ { "name": "id", @@ -1405,7 +1519,9 @@ } }, "delete": { - "tags": ["layouts"], + "tags": [ + "layouts" + ], "parameters": [ { "name": "id", @@ -1435,7 +1551,9 @@ } }, "put": { - "tags": ["layouts"], + "tags": [ + "layouts" + ], "summary": "Replace layout configuration.", "parameters": [ { @@ -1552,9 +1670,9 @@ "description": "Entire response from the kapacitor backend.", "type": "object" }, - "Rules": { + "Rules": { "type": "object", - "required": [ + "required": [ "rules" ], "properties": { @@ -1570,85 +1688,83 @@ "type": "object", "required": [ "every", - "trigger" + "trigger" ], "properties": { - "id": { - "type": "string", - "description": "ID for this rule; the ID is shared with kapacitor" - }, - "name": { - "type": "string", - "description": "User facing name of the alerting rule" - }, - "every" : { - "type": "string", - "description": "Golang duration string specifying how often the alert condition is checked" - }, - "alerts": { - "type": "array", - "description": "Array of alerting services to warn if the alert is triggered", - "items": { + "id": { + "type": "string", + "description": "ID for this rule; the ID is shared with kapacitor" + }, + "name": { + "type": "string", + "description": "User facing name of the alerting rule" + }, + "every": { + "type": "string", + "description": "Golang duration string specifying how often the alert condition is checked" + }, + "alerts": { + "type": "array", + "description": "Array of alerting services to warn if the alert is triggered", + "items": { "type": "string", - "enum": [ - "hipchat", - "opsgenie", - "pagerduty", - "victorops", - "smtp", - "email", - "sensu", - "slack", - "talk", - "telegram" - ] + "enum": [ + "hipchat", + "opsgenie", + "pagerduty", + "victorops", + "smtp", + "email", + "sensu", + "slack", + "talk", + "telegram" + ] } - }, - "message": { - "type": "string", - "description": "Message to send when alert occurs." - }, - "trigger": { - "type": "string", - "description": "Trigger defines the alerting structure; deadman alert if no data are received for the specified time range; relative alert if the data change relative to the data in a different time range; threshold alert if the data cross a boundary", - "enum": [ + }, + "message": { + "type": "string", + "description": "Message to send when alert occurs." + }, + "trigger": { + "type": "string", + "description": "Trigger defines the alerting structure; deadman alert if no data are received for the specified time range; relative alert if the data change relative to the data in a different time range; threshold alert if the data cross a boundary", + "enum": [ "deadman", "relative", - "threshold" + "threshold" ] - }, - "tickscript": { - "type": "string", - "description": "TICKscript representing this rule" - }, + }, + "tickscript": { + "type": "string", + "description": "TICKscript representing this rule" + }, "links": { - "type": "object", - "required": [ - "self", - "kapacitor" - ], - "properties": { - "self": { - "description": "Self link pointing to this rule resource", - "type": "string", - "format": "uri" - }, - "kapacitor": { - "description": "Link pointing to the kapacitor proxy for this rule including the path query parameter.", - "type": "string", - "format": "uri" - }, - "output": { - "description": "Link pointing to the kapacitor httpOut node of the tickscript; includes the path query argument", - "type": "string", - "format": "uri" - } - - } + "type": "object", + "required": [ + "self", + "kapacitor" + ], + "properties": { + "self": { + "description": "Self link pointing to this rule resource", + "type": "string", + "format": "uri" + }, + "kapacitor": { + "description": "Link pointing to the kapacitor proxy for this rule including the path query parameter.", + "type": "string", + "format": "uri" + }, + "output": { + "description": "Link pointing to the kapacitor httpOut node of the tickscript; includes the path query argument", + "type": "string", + "format": "uri" + } + } } } }, - "Sources": { "type": "object", "required": [ @@ -1909,18 +2025,18 @@ "Cell": { "type": "object", "required": [ - "i", + "i", "x", "y", "w", "h" ], "properties": { - "i": { - "description": "Unique ID of Cell", - "type": "string", - "format": "uuid4" - }, + "i": { + "description": "Unique ID of Cell", + "type": "string", + "format": "uuid4" + }, "x": { "description": "X-coordinate of Cell in the Layout", "type": "integer", @@ -1947,22 +2063,7 @@ "items": { "$ref": "#/definitions/LayoutQuery" } - }, - "ylabels": { - "description": "Optional Y-axis user-facing label", - "type": "array", - "items": { - "type": "string" - } - }, - "yranges": { - "description": "Optional default range of the Y-axis; Must come in pairs.", - "type": "array", - "items": { - "type": "integer", - "format": "int64" - } - } + } } }, "LayoutQuery": { @@ -1971,6 +2072,30 @@ "query" ], "properties": { + "label": { + "description": "Optional Y-axis user-facing label for this query", + "type": "string" + }, + "range": { + "description": "Optional default range of the Y-axis", + "type": "object", + "required": [ + "upper", + "lower" + ], + "properties": { + "upper": { + "description": "Upper bound of the display range of the Y-axis", + "type": "integer", + "format": "int64" + }, + "lower": { + "description": "Lower bound of the display range of the Y-axis", + "type": "integer", + "format": "int64" + } + } + }, "query": { "type": "string" }, @@ -1998,7 +2123,7 @@ "type": "string", "format": "url" }, - "me": { + "me": { "description": "Location of the me endpoint.", "type": "string", "format": "url" @@ -2062,4 +2187,4 @@ } } } -} +} \ No newline at end of file From 2331d1321df00cc0f27d9cc990d5231f8815f0dc Mon Sep 17 00:00:00 2001 From: Chris Goller Date: Tue, 29 Nov 2016 15:22:17 -0600 Subject: [PATCH 05/15] Update documentation for layout labels and ranges --- docs/LAYOUT.md | 91 ++++++++++++++++++++++++++++---------------------- 1 file changed, 52 insertions(+), 39 deletions(-) diff --git a/docs/LAYOUT.md b/docs/LAYOUT.md index 1b35ead03..cda54d5e1 100644 --- a/docs/LAYOUT.md +++ b/docs/LAYOUT.md @@ -5,41 +5,52 @@ Chronograf's applications are built by layout configurations that can be found i To create a new layout use the `new_apps.sh` script in the `canned` directory. This script takes an argument, which is the name of the layout you want, often this will map to the InfluxDB measurement, but it does not have to. For this example I will be creating a layout for the zombocom daemon zombocomd. So first step is the run `new_app.sh zombocomd` this will create a file called `zombocomd.json`. The file will look something like: ```json - { - "id": "d77917f4-9305-4c9c-beba-c29bf17a0fd2", - "measurement": "zombocomd", - "app": "zombocomd", - "cells": [{ - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "b75694f1-5764-4d1d-9d67-564455985894", - "name": "Running Zombo Average", - "queries": [{ - "query": "SELECT mean(\"zombo\") FROM zombocomd", - "db": "telegraf", - "rp": "", - "groupbys": ["\"pod\""], - "wheres": [] - }] +{ + "id": "d77917f4-9305-4c9c-beba-c29bf17a0fd2", + "measurement": "zombocomd", + "app": "zombocomd", + "cells": [ + { + "x": 0, + "y": 0, + "w": 4, + "h": 4, + "i": "b75694f1-5764-4d1d-9d67-564455985894", + "name": "Running Zombo Average", + "queries": [ + { + "query": "SELECT mean(\"zombo\") FROM zombocomd", + "db": "telegraf", + "rp": "", + "groupbys": [ + "\"pod\"" + ], + } + ] }, { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "b75694f1-5764-4d1d-9d67-564455985894", - "name": "Anythings Per Second", - "queries": [{ - "query": "SELECT non_negative_derivative(max(\"anything\"), 1s) FROM zombocomd", - "db": "telegraf", - "rp": "", - "groupbys": [], - "wheres": [] - }] - }] - } + "x": 0, + "y": 0, + "w": 4, + "h": 4, + "i": "7a6bc880-054e-455c-becd-c0ebbbe109ce", + "name": "Anythings Per Second", + "queries": [ + { + "query": "SELECT non_negative_derivative(max(\"anything\"), 1s) FROM zombocomd", + "db": "telegraf", + "rp": "", + "label": "anything/s", + "range": { + "upper": 100, + "lower": 10 + }, + "wheres": ["\"status\" = 'critical'"] + } + ] + } + ] +} ``` The meaning of the fields are as follows: @@ -49,17 +60,19 @@ The meaning of the fields are as follows: * cell - An array of graphs * x - Not currently used * y - Not currently used -* yranges - Optional key specifying the default y-axes ranges (must come in pairs) -* ylabels - Optional key specifying the user-facing names of the y-axes. Indices in ylabels correspond with yrange pairs * w - The width of the graph, for the initial release set to 4 to make everything line up properly. * h - The height of the graph, for the initial release set to 4 to make everything line up properly * i - A unique ID for the graph * name - The displayed name of the graph * queries - An array of InfluxQL queries. Note: queries must use an aggregate function since chronograf adds a group by time function to keep the data points manageable. -* db - The name of the database for the query -* rp - The [retention policy](https://docs.influxdata.com/influxdb/v1.1/concepts/glossary/#retention-policy-rp) for the database -* groupbys - An array of GROUP BY clauses to use on the query -* wheres - An array of WHERE clauses to add to the query +* queries.[].db - The name of the database for the query +* queries.[].rp - The [retention policy](https://docs.influxdata.com/influxdb/v1.1/concepts/glossary/#retention-policy-rp) for the database +* queries.[].groupbys - An optional array of GROUP BY clauses to use on the query +* queries.[].wheres - An optional array of WHERE clauses to add to the query +* queries.[].label - Optional key specifying the user-facing name of the y-axes +* queries.[].range.upper - Optional key specifying the default upper bound of the y-axis +* queries.[].range.lower - Optional key specifying the default lower bound of the y-axis + The above example will create two graphs. The first graph will show the mean number of zombos created then group the zombos by pod and by time. It is important to note that all queries have a `GROUP BY` time(x) appended to them. In this case `x` is determined by the duration of the query. The second graph will show the number of the "anything" [field](https://docs.influxdata.com/influxdb/v1.1/concepts/glossary/#field) per second average. For real world examples of when to use the `groupbys` see the docker examples and for `wheres` examples see the kubernetes examples in the `canned` directory. From 8ed8967cb563f21b6b9f3894e745a5f62cd2e273 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Mon, 28 Nov 2016 12:53:03 -0800 Subject: [PATCH 06/15] Handle multiple y-axes legend labels --- ui/spec/utils/timeSeriesToDygraphSpec.js | 83 ++++++++++++++++++++++++ ui/src/shared/components/Dygraph.js | 4 +- ui/src/shared/components/LineGraph.js | 12 +++- ui/src/utils/timeSeriesToDygraph.js | 5 +- 4 files changed, 100 insertions(+), 4 deletions(-) diff --git a/ui/spec/utils/timeSeriesToDygraphSpec.js b/ui/spec/utils/timeSeriesToDygraphSpec.js index 8949597fd..f5f59a62f 100644 --- a/ui/spec/utils/timeSeriesToDygraphSpec.js +++ b/ui/spec/utils/timeSeriesToDygraphSpec.js @@ -43,6 +43,14 @@ describe('timeSeriesToDygraph', () => { [new Date(2000), 2, 3], [new Date(4000), null, 4], ], + dygraphSeries: { + 'm1.f1': { + axis: 'y', + }, + 'm1.f2': { + axis: 'y', + }, + }, }; expect(actual).to.deep.equal(expected); @@ -83,6 +91,81 @@ describe('timeSeriesToDygraph', () => { ], }; + expect(actual.timeSeries).to.deep.equal(expected.timeSeries); + }); + + it('can parse multiple responses into two axes', () => { + const influxResponse = [ + { + "response": + { + "results": [ + { + "series": [ + { + "name":"m1", + "columns": ["time","f1"], + "values": [[1000, 1],[2000, 2]], + }, + ] + }, + { + "series": [ + { + "name":"m1", + "columns": ["time","f2"], + "values": [[2000, 3],[4000, 4]], + }, + ] + }, + ], + }, + }, + { + "response": + { + "results": [ + { + "series": [ + { + "name":"m3", + "columns": ["time","f3"], + "values": [[1000, 1],[2000, 2]], + }, + ] + }, + ], + }, + }, + ]; + + const actual = timeSeriesToDygraph(influxResponse); + + const expected = { + fields: [ + 'time', + `m1.f1`, + `m1.f2`, + `m3.f3`, + ], + timeSeries: [ + [new Date(1000), 1, null, 1], + [new Date(2000), 2, 3, 2], + [new Date(4000), null, 4, null], + ], + dygraphSeries: { + 'm1.f1': { + axis: 'y', + }, + 'm1.f2': { + axis: 'y', + }, + 'm3.f3': { + axis: 'y2', + }, + }, + }; + expect(actual).to.deep.equal(expected); }); }); diff --git a/ui/src/shared/components/Dygraph.js b/ui/src/shared/components/Dygraph.js index 3aec7750b..cf2c83175 100644 --- a/ui/src/shared/components/Dygraph.js +++ b/ui/src/shared/components/Dygraph.js @@ -50,7 +50,8 @@ export default React.createClass({ componentDidMount() { const timeSeries = this.getTimeSeries(); - const {yRange} = this.props; + // dygraphSeries is a legend label and its corresponding y-axis e.g. {legendLabel1: 'y', legendLabel2: 'y2'}; + const {yRange, dygraphSeries} = this.props; const refs = this.refs; const graphContainerNode = refs.graphContainer; @@ -75,6 +76,7 @@ export default React.createClass({ strokeWidth: 1.5, highlightCircleSize: 3, colors: finalLineColors, + series: dygraphSeries, valueRange: getRange(timeSeries, yRange), highlightSeriesOpts: { strokeWidth: 2, diff --git a/ui/src/shared/components/LineGraph.js b/ui/src/shared/components/LineGraph.js index 981a1333c..e68cba377 100644 --- a/ui/src/shared/components/LineGraph.js +++ b/ui/src/shared/components/LineGraph.js @@ -41,7 +41,7 @@ export default React.createClass({ }, render() { const {isFetchingInitially, title, underlayCallback, ylabels} = this.props; - const {fields, timeSeries} = this._timeSeries; + const {fields, timeSeries, dygraphSeries} = this._timeSeries; // If data for this graph is being fetched for the first time, show a graph-wide spinner. if (isFetchingInitially) { @@ -70,7 +70,15 @@ export default React.createClass({ return (
{this.props.isRefreshing ?

: null} - +

); }, diff --git a/ui/src/utils/timeSeriesToDygraph.js b/ui/src/utils/timeSeriesToDygraph.js index c70e96d8e..e0f315ea4 100644 --- a/ui/src/utils/timeSeriesToDygraph.js +++ b/ui/src/utils/timeSeriesToDygraph.js @@ -7,6 +7,7 @@ export default function timeSeriesToDygraph(raw = []) { const fields = ['time']; // all of the effective field names (i.e. .) const fieldToIndex = {}; // see parseSeries const dates = {}; // map of date as string to date value to minimize string coercion + const dygraphSeries = {}; // dygraphSeries is a graph legend label and its corresponding y-axis e.g. {legendLabel1: 'y', legendLabel2: 'y2'}; /** * dateToFieldValue will look like: @@ -26,7 +27,7 @@ export default function timeSeriesToDygraph(raw = []) { */ const dateToFieldValue = {}; - raw.forEach(({response}) => { + raw.forEach(({response}, queryIndex) => { // If a response is an empty result set or a query returned an error // from InfluxDB, don't try and parse. if (response.results.length) { @@ -89,6 +90,7 @@ export default function timeSeriesToDygraph(raw = []) { // ex given this timeSeries [Date, 10, 20, 30] field index at 2 would correspond to value 20 fieldToIndex[effectiveFieldName] = fields.length; fields.push(effectiveFieldName); + dygraphSeries[effectiveFieldName] = {axis: queryIndex === 0 ? 'y' : 'y2'}; }); (series.values || []).forEach(parseRow); @@ -140,6 +142,7 @@ export default function timeSeriesToDygraph(raw = []) { return { fields, timeSeries: buildTimeSeries(), + dygraphSeries, }; } From 82168c1f20249985a25a8c9a479adf8acbf677d7 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Mon, 28 Nov 2016 12:55:41 -0800 Subject: [PATCH 07/15] Remove requirement from rp prop --- ui/src/shared/components/LayoutRenderer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/shared/components/LayoutRenderer.js b/ui/src/shared/components/LayoutRenderer.js index 76f7d7713..634600a90 100644 --- a/ui/src/shared/components/LayoutRenderer.js +++ b/ui/src/shared/components/LayoutRenderer.js @@ -17,7 +17,7 @@ export const LayoutRenderer = React.createClass({ PropTypes.shape({ queries: PropTypes.arrayOf( PropTypes.shape({ - rp: PropTypes.string, + rp: PropTypes.string.isRequired, text: PropTypes.string.isRequired, database: PropTypes.string.isRequired, groupbys: PropTypes.arrayOf(PropTypes.string), From 1046407b5bd3648ae33889e08b84bda07ca3b8b6 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Mon, 28 Nov 2016 13:01:03 -0800 Subject: [PATCH 08/15] Rename fields to labels What we where calling fields in timeSeriesToDygraph is really a dygraph concept called labels. Simply put, labels are the strings used in a legend to identify a series. Naming them fields was a bit confusing as they are really an amalgomation of measurements, fields, and tags. --- ui/src/shared/components/Dygraph.js | 6 +++--- ui/src/shared/components/LineGraph.js | 6 +++--- ui/src/utils/timeSeriesToDygraph.js | 10 +++++----- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/ui/src/shared/components/Dygraph.js b/ui/src/shared/components/Dygraph.js index cf2c83175..57162250b 100644 --- a/ui/src/shared/components/Dygraph.js +++ b/ui/src/shared/components/Dygraph.js @@ -27,7 +27,7 @@ export default React.createClass({ propTypes: { yRange: arrayOf(number.isRequired), timeSeries: array.isRequired, // eslint-disable-line react/forbid-prop-types - fields: array.isRequired, // eslint-disable-line react/forbid-prop-types + labels: array.isRequired, // eslint-disable-line react/forbid-prop-types options: object, // eslint-disable-line react/forbid-prop-types containerStyle: object, // eslint-disable-line react/forbid-prop-types isGraphFilled: bool, @@ -132,10 +132,10 @@ export default React.createClass({ } const timeSeries = this.getTimeSeries(); - const {fields, yRange} = this.props; + const {labels, yRange} = this.props; dygraph.updateOptions({ - labels: fields, + labels, file: timeSeries, valueRange: getRange(timeSeries, yRange), underlayCallback: this.props.options.underlayCallback, diff --git a/ui/src/shared/components/LineGraph.js b/ui/src/shared/components/LineGraph.js index e68cba377..85b75e073 100644 --- a/ui/src/shared/components/LineGraph.js +++ b/ui/src/shared/components/LineGraph.js @@ -41,7 +41,7 @@ export default React.createClass({ }, render() { const {isFetchingInitially, title, underlayCallback, ylabels} = this.props; - const {fields, timeSeries, dygraphSeries} = this._timeSeries; + const {labels, timeSeries, dygraphSeries} = this._timeSeries; // If data for this graph is being fetched for the first time, show a graph-wide spinner. if (isFetchingInitially) { @@ -53,7 +53,7 @@ export default React.createClass({ } const options = { - labels: fields, + labels, connectSeparatedPoints: true, labelsKMB: true, height: 300, @@ -75,7 +75,7 @@ export default React.createClass({ overrideLineColors={this.props.overrideLineColors} isGraphFilled={this.props.isGraphFilled} timeSeries={timeSeries} - fields={fields} + labels={labels} options={options} dygraphSeries={dygraphSeries} /> diff --git a/ui/src/utils/timeSeriesToDygraph.js b/ui/src/utils/timeSeriesToDygraph.js index e0f315ea4..572b2b58e 100644 --- a/ui/src/utils/timeSeriesToDygraph.js +++ b/ui/src/utils/timeSeriesToDygraph.js @@ -4,7 +4,7 @@ */ export default function timeSeriesToDygraph(raw = []) { - const fields = ['time']; // all of the effective field names (i.e. .) + const labels = ['time']; // all of the effective field names (i.e. .) const fieldToIndex = {}; // see parseSeries const dates = {}; // map of date as string to date value to minimize string coercion const dygraphSeries = {}; // dygraphSeries is a graph legend label and its corresponding y-axis e.g. {legendLabel1: 'y', legendLabel2: 'y2'}; @@ -88,8 +88,8 @@ export default function timeSeriesToDygraph(raw = []) { // Given a field name, identify which column in the timeSeries result should hold the field's value // ex given this timeSeries [Date, 10, 20, 30] field index at 2 would correspond to value 20 - fieldToIndex[effectiveFieldName] = fields.length; - fields.push(effectiveFieldName); + fieldToIndex[effectiveFieldName] = labels.length; + labels.push(effectiveFieldName); dygraphSeries[effectiveFieldName] = {axis: queryIndex === 0 ? 'y' : 'y2'}; }); @@ -123,7 +123,7 @@ export default function timeSeriesToDygraph(raw = []) { function buildTimeSeries() { const allDates = Object.keys(dateToFieldValue); allDates.sort((a, b) => a - b); - const rowLength = fields.length; + const rowLength = labels.length; return allDates.map((date) => { const row = new Array(rowLength); @@ -140,7 +140,7 @@ export default function timeSeriesToDygraph(raw = []) { } return { - fields, + labels, timeSeries: buildTimeSeries(), dygraphSeries, }; From b41fc0bee18307880df803037f85732b6ce52201 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Mon, 28 Nov 2016 13:03:16 -0800 Subject: [PATCH 09/15] Remove prop requirement' --- ui/src/shared/components/LayoutRenderer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/shared/components/LayoutRenderer.js b/ui/src/shared/components/LayoutRenderer.js index 634600a90..76f7d7713 100644 --- a/ui/src/shared/components/LayoutRenderer.js +++ b/ui/src/shared/components/LayoutRenderer.js @@ -17,7 +17,7 @@ export const LayoutRenderer = React.createClass({ PropTypes.shape({ queries: PropTypes.arrayOf( PropTypes.shape({ - rp: PropTypes.string.isRequired, + rp: PropTypes.string, text: PropTypes.string.isRequired, database: PropTypes.string.isRequired, groupbys: PropTypes.arrayOf(PropTypes.string), From 1d6c364165a4bca1296dfca44ea316226f3b5aba Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Mon, 28 Nov 2016 13:04:57 -0800 Subject: [PATCH 10/15] Add dygraphSeries prop --- ui/src/shared/components/Dygraph.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ui/src/shared/components/Dygraph.js b/ui/src/shared/components/Dygraph.js index 57162250b..b34fc3817 100644 --- a/ui/src/shared/components/Dygraph.js +++ b/ui/src/shared/components/Dygraph.js @@ -3,7 +3,7 @@ import React, {PropTypes} from 'react'; import Dygraph from '../../external/dygraph'; import 'style/_Graph.css'; -const {arrayOf, object, array, number, bool} = PropTypes; +const {arrayOf, object, array, number, bool, shape} = PropTypes; const LINE_COLORS = [ '#00C9FF', @@ -32,6 +32,7 @@ export default React.createClass({ containerStyle: object, // eslint-disable-line react/forbid-prop-types isGraphFilled: bool, overrideLineColors: array, + dygraphSeries: shape({}).isRequired, }, getDefaultProps() { From 76567e5ee7435b26ddfdf84bb30e90cf3b098797 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Mon, 28 Nov 2016 13:34:45 -0800 Subject: [PATCH 11/15] Handle multiple ranges supplied by layout --- ui/src/shared/components/Dygraph.js | 27 ++++++++++++++++++++++----- ui/src/shared/components/LineGraph.js | 27 +++++++++++++++++++++++++-- 2 files changed, 47 insertions(+), 7 deletions(-) diff --git a/ui/src/shared/components/Dygraph.js b/ui/src/shared/components/Dygraph.js index b34fc3817..74c6b3664 100644 --- a/ui/src/shared/components/Dygraph.js +++ b/ui/src/shared/components/Dygraph.js @@ -25,7 +25,10 @@ export default React.createClass({ displayName: 'Dygraph', propTypes: { - yRange: arrayOf(number.isRequired), + ranges: shape({ + y: arrayOf(number.isRequired), + y2: arrayOf(number.isRequired), + }), timeSeries: array.isRequired, // eslint-disable-line react/forbid-prop-types labels: array.isRequired, // eslint-disable-line react/forbid-prop-types options: object, // eslint-disable-line react/forbid-prop-types @@ -52,7 +55,7 @@ export default React.createClass({ componentDidMount() { const timeSeries = this.getTimeSeries(); // dygraphSeries is a legend label and its corresponding y-axis e.g. {legendLabel1: 'y', legendLabel2: 'y2'}; - const {yRange, dygraphSeries} = this.props; + const {ranges, dygraphSeries} = this.props; const refs = this.refs; const graphContainerNode = refs.graphContainer; @@ -78,7 +81,14 @@ export default React.createClass({ highlightCircleSize: 3, colors: finalLineColors, series: dygraphSeries, - valueRange: getRange(timeSeries, yRange), + axes: { + y: { + valueRange: getRange(timeSeries, ranges.y), + }, + y2: { + valueRange: getRange(timeSeries, ranges.y2), + }, + }, highlightSeriesOpts: { strokeWidth: 2, highlightCircleSize: 5, @@ -133,12 +143,19 @@ export default React.createClass({ } const timeSeries = this.getTimeSeries(); - const {labels, yRange} = this.props; + const {labels, ranges} = this.props; dygraph.updateOptions({ labels, file: timeSeries, - valueRange: getRange(timeSeries, yRange), + axes: { + y: { + valueRange: getRange(timeSeries, ranges.y), + }, + y2: { + valueRange: getRange(timeSeries, ranges.y2), + }, + }, underlayCallback: this.props.options.underlayCallback, }); diff --git a/ui/src/shared/components/LineGraph.js b/ui/src/shared/components/LineGraph.js index 85b75e073..5ec169d4a 100644 --- a/ui/src/shared/components/LineGraph.js +++ b/ui/src/shared/components/LineGraph.js @@ -4,7 +4,7 @@ import shallowCompare from 'react-addons-shallow-compare'; import timeSeriesToDygraph from 'utils/timeSeriesToDygraph'; -const {array, string, arrayOf, number, bool} = PropTypes; +const {array, string, arrayOf, number, bool, shape} = PropTypes; export default React.createClass({ displayName: 'LineGraph', @@ -18,6 +18,7 @@ export default React.createClass({ underlayCallback: PropTypes.func, isGraphFilled: bool, overrideLineColors: array, + queries: arrayOf(shape({}).isRequired), }, getDefaultProps() { @@ -31,18 +32,20 @@ export default React.createClass({ shouldComponentUpdate(nextProps, nextState) { return shallowCompare(this, nextProps, nextState); }, + componentWillMount() { this._timeSeries = timeSeriesToDygraph(this.props.data); }, + componentWillUpdate(nextProps) { if (this.props.data !== nextProps.data) { this._timeSeries = timeSeriesToDygraph(nextProps.data); } }, + render() { const {isFetchingInitially, title, underlayCallback, ylabels} = this.props; const {labels, timeSeries, dygraphSeries} = this._timeSeries; - // If data for this graph is being fetched for the first time, show a graph-wide spinner. if (isFetchingInitially) { return ( @@ -78,8 +81,28 @@ export default React.createClass({ labels={labels} options={options} dygraphSeries={dygraphSeries} + ranges={this.getRanges()} />

); }, + + getRanges() { + const {queries} = this.props; + const ranges = {}; + + if (queries) { + queries.forEach((q, i) => { + if (i === 0 && q.range) { + ranges.y = q.range; + } + + if (i === 1 && q.range) { + ranges.y2 = q.range; + } + }); + } + + return ranges; + }, }); From f6e85badf69285f7170e15be8131b6ff16b95516 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Wed, 30 Nov 2016 12:18:20 -0800 Subject: [PATCH 12/15] Fix broken test Signed-off-by: Will Piers --- ui/spec/utils/timeSeriesToDygraphSpec.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ui/spec/utils/timeSeriesToDygraphSpec.js b/ui/spec/utils/timeSeriesToDygraphSpec.js index f5f59a62f..a89bfd226 100644 --- a/ui/spec/utils/timeSeriesToDygraphSpec.js +++ b/ui/spec/utils/timeSeriesToDygraphSpec.js @@ -33,7 +33,7 @@ describe('timeSeriesToDygraph', () => { const actual = timeSeriesToDygraph(influxResponse); const expected = { - fields: [ + labels: [ 'time', `m1.f1`, `m1.f2`, @@ -80,7 +80,7 @@ describe('timeSeriesToDygraph', () => { const actual = timeSeriesToDygraph(influxResponse); const expected = { - fields: [ + labels: [ 'time', 'm1.f1', ], @@ -142,7 +142,7 @@ describe('timeSeriesToDygraph', () => { const actual = timeSeriesToDygraph(influxResponse); const expected = { - fields: [ + labels: [ 'time', `m1.f1`, `m1.f2`, @@ -166,6 +166,6 @@ describe('timeSeriesToDygraph', () => { }, }; - expect(actual).to.deep.equal(expected); + expect(actual.dygraphSeries).to.deep.equal(expected.dygraphSeries); }); }); From 8ea9c5716e12838186323be42e1ef9bb06c647d6 Mon Sep 17 00:00:00 2001 From: Chris Goller Date: Wed, 30 Nov 2016 15:22:35 -0600 Subject: [PATCH 13/15] Allow upper and lower bound to be zero --- chronograf.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/chronograf.go b/chronograf.go index f93ae1027..acf1ee5b4 100644 --- a/chronograf.go +++ b/chronograf.go @@ -55,8 +55,8 @@ type TimeSeries interface { // Range represents an upper and lower bound for data type Range struct { - Upper int64 `json:"upper,omitempty"` // Upper is the upper bound - Lower int64 `json:"lower,omitempty"` // Lower is the lower bound + Upper int64 `json:"upper"` // Upper is the upper bound + Lower int64 `json:"lower"` // Lower is the lower bound } // Query retrieves a Response from a TimeSeries. From 6360997494f072a2bfe808d5cac7f5232db88738 Mon Sep 17 00:00:00 2001 From: Will Piers Date: Wed, 30 Nov 2016 13:36:38 -0800 Subject: [PATCH 14/15] Update range and labels for new layout shape --- ui/src/shared/components/LayoutRenderer.js | 7 +++-- ui/src/shared/components/LineGraph.js | 34 ++++++++++++---------- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/ui/src/shared/components/LayoutRenderer.js b/ui/src/shared/components/LayoutRenderer.js index 76f7d7713..6142f0791 100644 --- a/ui/src/shared/components/LayoutRenderer.js +++ b/ui/src/shared/components/LayoutRenderer.js @@ -17,6 +17,11 @@ export const LayoutRenderer = React.createClass({ PropTypes.shape({ queries: PropTypes.arrayOf( PropTypes.shape({ + label: PropTypes.string, + range: PropTypes.shape({ + upper: PropTypes.number, + lower: PropTypes.number, + }), rp: PropTypes.string, text: PropTypes.string.isRequired, database: PropTypes.string.isRequired, @@ -24,7 +29,6 @@ export const LayoutRenderer = React.createClass({ wheres: PropTypes.arrayOf(PropTypes.string), }).isRequired ).isRequired, - ylabels: PropTypes.arrayOf(PropTypes.string.isRequired).isRequired, x: PropTypes.number.isRequired, y: PropTypes.number.isRequired, w: PropTypes.number.isRequired, @@ -92,7 +96,6 @@ export const LayoutRenderer = React.createClass({ diff --git a/ui/src/shared/components/LineGraph.js b/ui/src/shared/components/LineGraph.js index 5ec169d4a..ff83f8b26 100644 --- a/ui/src/shared/components/LineGraph.js +++ b/ui/src/shared/components/LineGraph.js @@ -1,24 +1,23 @@ import React, {PropTypes} from 'react'; import Dygraph from './Dygraph'; import shallowCompare from 'react-addons-shallow-compare'; +import _ from 'lodash'; import timeSeriesToDygraph from 'utils/timeSeriesToDygraph'; -const {array, string, arrayOf, number, bool, shape} = PropTypes; +const {array, string, arrayOf, bool, shape} = PropTypes; export default React.createClass({ displayName: 'LineGraph', propTypes: { - data: array.isRequired, // eslint-disable-line react/forbid-prop-types + data: arrayOf(shape({}).isRequired).isRequired, title: string, isFetchingInitially: PropTypes.bool, isRefreshing: PropTypes.bool, - yRange: arrayOf(number.isRequired), - ylabels: arrayOf(string.isRequired), underlayCallback: PropTypes.func, isGraphFilled: bool, overrideLineColors: array, - queries: arrayOf(shape({}).isRequired), + queries: arrayOf(shape({}).isRequired).isRequired, }, getDefaultProps() { @@ -44,7 +43,7 @@ export default React.createClass({ }, render() { - const {isFetchingInitially, title, underlayCallback, ylabels} = this.props; + const {isFetchingInitially, title, underlayCallback, queries} = this.props; const {labels, timeSeries, dygraphSeries} = this._timeSeries; // If data for this graph is being fetched for the first time, show a graph-wide spinner. if (isFetchingInitially) { @@ -67,7 +66,8 @@ export default React.createClass({ yRangePad: 10, drawAxesAtZero: true, underlayCallback, - ylabel: ylabels && ylabels[0], + ylabel: _.get(queries, ['0', 'label'], ''), + y2label: _.get(queries, ['1', 'label'], ''), }; return ( @@ -89,18 +89,20 @@ export default React.createClass({ getRanges() { const {queries} = this.props; + if (!queries) { + return {}; + } + const ranges = {}; + const q0 = queries[0]; + const q1 = queries[1]; - if (queries) { - queries.forEach((q, i) => { - if (i === 0 && q.range) { - ranges.y = q.range; - } + if (q0 && q0.range) { + ranges.y = [q0.range.lower, q0.range.upper]; + } - if (i === 1 && q.range) { - ranges.y2 = q.range; - } - }); + if (q1 && q1.range) { + ranges.y2 = [q1.range.lower, q1.range.upper]; } return ranges; From ca7ceac79be3b29c9514210741ddf9899530f6bc Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Wed, 30 Nov 2016 15:05:51 -0800 Subject: [PATCH 15/15] Refactor more for more fun and more profit Signed-off-by: Will Piers --- ui/src/hosts/containers/HostPage.js | 44 +++++++++++------------------ 1 file changed, 16 insertions(+), 28 deletions(-) diff --git a/ui/src/hosts/containers/HostPage.js b/ui/src/hosts/containers/HostPage.js index 93faae32f..7bbde39f4 100644 --- a/ui/src/hosts/containers/HostPage.js +++ b/ui/src/hosts/containers/HostPage.js @@ -4,7 +4,6 @@ import TimeRangeDropdown from '../../shared/components/TimeRangeDropdown'; import timeRanges from 'hson!../../shared/data/timeRanges.hson'; import {getMappings, getAppsForHosts, getMeasurementsForHost} from 'src/hosts/apis'; import {fetchLayouts} from 'shared/apis'; -import _ from 'lodash'; export const HostPage = React.createClass({ propTypes: { @@ -68,38 +67,28 @@ export const HostPage = React.createClass({ const {timeRange} = this.state; const {source} = this.props; - const autoflowLayouts = _.remove(layouts, (layout) => { - return layout.autoflow === true; - }); - let autoflowCells = []; + const autoflowLayouts = layouts.filter((layout) => !!layout.autoflow); const cellWidth = 4; const cellHeight = 4; const pageWidth = 12; - autoflowLayouts.forEach((layout, i) => { - layout.cells.forEach((cell, j) => { - cell.w = cellWidth; - cell.h = cellHeight; - cell.x = ((i + j) * cellWidth % pageWidth); - cell.y = Math.floor(((i + j) * cellWidth / pageWidth)) * cellHeight; - autoflowCells = autoflowCells.concat(cell); - }); - }); + const autoflowCells = autoflowLayouts.reduce((allCells, layout, i) => { + return allCells.concat(layout.cells.map((cell, j) => { + return Object.assign(cell, { + w: cellWidth, + h: cellHeight, + x: ((i + j) * cellWidth % pageWidth), + y: Math.floor(((i + j) * cellWidth / pageWidth)) * cellHeight, + }); + })); + }, []); - const autoflowLayout = { - cells: autoflowCells, - autoflow: false, - }; + const staticLayouts = layouts.filter((layout) => !layout.autoflow); + staticLayouts.unshift({cells: autoflowCells}); - const staticLayouts = _.remove(layouts, (layout) => { - return layout.autoflow === false; - }); - staticLayouts.unshift(autoflowLayout); - - let layoutCells = []; let translateY = 0; - staticLayouts.forEach((layout) => { + const layoutCells = staticLayouts.reduce((allCells, layout) => { let maxY = 0; layout.cells.forEach((cell) => { cell.y += translateY; @@ -113,9 +102,8 @@ export const HostPage = React.createClass({ }); translateY = maxY; - layoutCells = layoutCells.concat(layout.cells); - }); - + return allCells.concat(layout.cells); + }, []); return (