From e513e614816bf7925fedba1dbddafdcb1a56c3c7 Mon Sep 17 00:00:00 2001 From: Tim Raymond Date: Wed, 19 Jul 2017 10:27:21 -0400 Subject: [PATCH 001/107] Add Axes to Dashboard Cells The frontend would like to store viewport information for each cell so that visualizations are zoomed to the proper extents upon rendering. This adds a property to cells called "axes" which takes the following shape: ``` { "axes" : { "y" : { "bounds" : [ 0, 2 ] }, "y2" : { "bounds" : [ 1, 3 ] } } } ``` Bounds specify the visible range for the axis, and are a 2-tuple of the form [lower, upper]. Bounds are not implicitly inclusive or exclusive--that determination is left for clients to make. Also, there are no restrictions on the naming of axes. --- bolt/internal/internal.go | 22 +++++ bolt/internal/internal.pb.go | 170 ++++++++++++++++++++--------------- bolt/internal/internal.proto | 21 +++-- chronograf.go | 22 +++-- 4 files changed, 146 insertions(+), 89 deletions(-) diff --git a/bolt/internal/internal.go b/bolt/internal/internal.go index 11f35474e..4b1f7b5e7 100644 --- a/bolt/internal/internal.go +++ b/bolt/internal/internal.go @@ -179,6 +179,19 @@ func MarshalDashboard(d chronograf.Dashboard) ([]byte, error) { } } + axes := make(map[string]*DashboardRange, len(c.Axes)) + for a, r := range c.Axes { + // need to explicitly allocate a new array because r.Bounds is + // over-written and the resulting slices from previous iterations will + // point to later iteration's data. It is _not_ enough to simply re-slice + // r.Bounds + axis := [2]int32{} + copy(axis[:], r.Bounds[:2]) + axes[a] = &DashboardRange{ + Bounds: axis[:], + } + } + cells[i] = &DashboardCell{ ID: c.ID, X: c.X, @@ -188,6 +201,7 @@ func MarshalDashboard(d chronograf.Dashboard) ([]byte, error) { Name: c.Name, Queries: queries, Type: c.Type, + Axes: axes, } } templates := make([]*Template, len(d.Templates)) @@ -251,6 +265,13 @@ func UnmarshalDashboard(data []byte, d *chronograf.Dashboard) error { } } + axes := make(map[string]chronograf.DashboardRange, len(c.Axes)) + for a, r := range c.Axes { + axis := chronograf.DashboardRange{} + copy(axis.Bounds[:], r.Bounds[:2]) + axes[a] = axis + } + cells[i] = chronograf.DashboardCell{ ID: c.ID, X: c.X, @@ -260,6 +281,7 @@ func UnmarshalDashboard(data []byte, d *chronograf.Dashboard) error { Name: c.Name, Queries: queries, Type: c.Type, + Axes: axes, } } diff --git a/bolt/internal/internal.pb.go b/bolt/internal/internal.pb.go index ac2b29b1a..64e5c3709 100644 --- a/bolt/internal/internal.pb.go +++ b/bolt/internal/internal.pb.go @@ -12,6 +12,7 @@ It has these top-level messages: Source Dashboard DashboardCell + DashboardRange Template TemplateValue TemplateQuery @@ -85,14 +86,15 @@ func (m *Dashboard) GetTemplates() []*Template { } type DashboardCell struct { - X int32 `protobuf:"varint,1,opt,name=x,proto3" json:"x,omitempty"` - Y int32 `protobuf:"varint,2,opt,name=y,proto3" json:"y,omitempty"` - W int32 `protobuf:"varint,3,opt,name=w,proto3" json:"w,omitempty"` - H int32 `protobuf:"varint,4,opt,name=h,proto3" json:"h,omitempty"` - Queries []*Query `protobuf:"bytes,5,rep,name=queries" json:"queries,omitempty"` - Name string `protobuf:"bytes,6,opt,name=name,proto3" json:"name,omitempty"` - Type string `protobuf:"bytes,7,opt,name=type,proto3" json:"type,omitempty"` - ID string `protobuf:"bytes,8,opt,name=ID,proto3" json:"ID,omitempty"` + X int32 `protobuf:"varint,1,opt,name=x,proto3" json:"x,omitempty"` + Y int32 `protobuf:"varint,2,opt,name=y,proto3" json:"y,omitempty"` + W int32 `protobuf:"varint,3,opt,name=w,proto3" json:"w,omitempty"` + H int32 `protobuf:"varint,4,opt,name=h,proto3" json:"h,omitempty"` + Queries []*Query `protobuf:"bytes,5,rep,name=queries" json:"queries,omitempty"` + Name string `protobuf:"bytes,6,opt,name=name,proto3" json:"name,omitempty"` + Type string `protobuf:"bytes,7,opt,name=type,proto3" json:"type,omitempty"` + ID string `protobuf:"bytes,8,opt,name=ID,proto3" json:"ID,omitempty"` + Axes map[string]*DashboardRange `protobuf:"bytes,9,rep,name=axes" json:"axes,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value"` } func (m *DashboardCell) Reset() { *m = DashboardCell{} } @@ -107,6 +109,22 @@ func (m *DashboardCell) GetQueries() []*Query { return nil } +func (m *DashboardCell) GetAxes() map[string]*DashboardRange { + if m != nil { + return m.Axes + } + return nil +} + +type DashboardRange struct { + Bounds []int32 `protobuf:"varint,1,rep,name=bounds" json:"bounds,omitempty"` +} + +func (m *DashboardRange) Reset() { *m = DashboardRange{} } +func (m *DashboardRange) String() string { return proto.CompactTextString(m) } +func (*DashboardRange) ProtoMessage() {} +func (*DashboardRange) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{3} } + type Template struct { ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"` TempVar string `protobuf:"bytes,2,opt,name=temp_var,json=tempVar,proto3" json:"temp_var,omitempty"` @@ -119,7 +137,7 @@ type Template struct { func (m *Template) Reset() { *m = Template{} } func (m *Template) String() string { return proto.CompactTextString(m) } func (*Template) ProtoMessage() {} -func (*Template) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{3} } +func (*Template) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{4} } func (m *Template) GetValues() []*TemplateValue { if m != nil { @@ -144,7 +162,7 @@ type TemplateValue struct { func (m *TemplateValue) Reset() { *m = TemplateValue{} } func (m *TemplateValue) String() string { return proto.CompactTextString(m) } func (*TemplateValue) ProtoMessage() {} -func (*TemplateValue) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{4} } +func (*TemplateValue) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{5} } type TemplateQuery struct { Command string `protobuf:"bytes,1,opt,name=command,proto3" json:"command,omitempty"` @@ -158,7 +176,7 @@ type TemplateQuery struct { func (m *TemplateQuery) Reset() { *m = TemplateQuery{} } func (m *TemplateQuery) String() string { return proto.CompactTextString(m) } func (*TemplateQuery) ProtoMessage() {} -func (*TemplateQuery) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{5} } +func (*TemplateQuery) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{6} } type Server struct { ID int64 `protobuf:"varint,1,opt,name=ID,proto3" json:"ID,omitempty"` @@ -173,7 +191,7 @@ type Server struct { func (m *Server) Reset() { *m = Server{} } func (m *Server) String() string { return proto.CompactTextString(m) } func (*Server) ProtoMessage() {} -func (*Server) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{6} } +func (*Server) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{7} } type Layout struct { ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"` @@ -186,7 +204,7 @@ type Layout struct { func (m *Layout) Reset() { *m = Layout{} } func (m *Layout) String() string { return proto.CompactTextString(m) } func (*Layout) ProtoMessage() {} -func (*Layout) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{7} } +func (*Layout) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{8} } func (m *Layout) GetCells() []*Cell { if m != nil { @@ -211,7 +229,7 @@ type Cell struct { func (m *Cell) Reset() { *m = Cell{} } func (m *Cell) String() string { return proto.CompactTextString(m) } func (*Cell) ProtoMessage() {} -func (*Cell) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{8} } +func (*Cell) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{9} } func (m *Cell) GetQueries() []*Query { if m != nil { @@ -233,7 +251,7 @@ type Query struct { func (m *Query) Reset() { *m = Query{} } func (m *Query) String() string { return proto.CompactTextString(m) } func (*Query) ProtoMessage() {} -func (*Query) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{9} } +func (*Query) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{10} } func (m *Query) GetRange() *Range { if m != nil { @@ -250,7 +268,7 @@ type Range struct { 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{10} } +func (*Range) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{11} } type AlertRule struct { ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"` @@ -262,7 +280,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{11} } +func (*AlertRule) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{12} } type User struct { ID uint64 `protobuf:"varint,1,opt,name=ID,proto3" json:"ID,omitempty"` @@ -272,12 +290,13 @@ 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{12} } +func (*User) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{13} } func init() { proto.RegisterType((*Source)(nil), "internal.Source") proto.RegisterType((*Dashboard)(nil), "internal.Dashboard") proto.RegisterType((*DashboardCell)(nil), "internal.DashboardCell") + proto.RegisterType((*DashboardRange)(nil), "internal.DashboardRange") proto.RegisterType((*Template)(nil), "internal.Template") proto.RegisterType((*TemplateValue)(nil), "internal.TemplateValue") proto.RegisterType((*TemplateQuery)(nil), "internal.TemplateQuery") @@ -293,59 +312,64 @@ func init() { func init() { proto.RegisterFile("internal.proto", fileDescriptorInternal) } var fileDescriptorInternal = []byte{ - // 858 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x55, 0xdd, 0x6e, 0xe3, 0x44, - 0x14, 0xd6, 0xc4, 0x76, 0x62, 0x9f, 0xee, 0x16, 0x34, 0x5a, 0xb1, 0x06, 0x6e, 0x22, 0x0b, 0xa4, - 0x82, 0x44, 0x41, 0xec, 0x13, 0xb4, 0xb5, 0x84, 0x42, 0xbb, 0x4b, 0x99, 0xb4, 0xe5, 0x0a, 0xad, - 0x26, 0xc9, 0x49, 0x6b, 0xed, 0x24, 0x36, 0x63, 0xbb, 0x59, 0xbf, 0x02, 0x57, 0x3c, 0x01, 0x12, - 0x12, 0x57, 0x5c, 0xf2, 0x02, 0x3c, 0x04, 0x2f, 0x84, 0xce, 0xcc, 0xf8, 0x27, 0x6c, 0x41, 0x7b, - 0xb5, 0x77, 0xf3, 0x9d, 0x33, 0xf9, 0xe6, 0xfc, 0x7c, 0x9f, 0x03, 0x87, 0xd9, 0xb6, 0x42, 0xbd, - 0x95, 0xea, 0xb8, 0xd0, 0x79, 0x95, 0xf3, 0xb0, 0xc5, 0xc9, 0xcf, 0x23, 0x18, 0xcf, 0xf3, 0x5a, - 0x2f, 0x91, 0x1f, 0xc2, 0x68, 0x96, 0xc6, 0x6c, 0xca, 0x8e, 0x3c, 0x31, 0x9a, 0xa5, 0x9c, 0x83, - 0xff, 0x42, 0x6e, 0x30, 0x1e, 0x4d, 0xd9, 0x51, 0x24, 0xcc, 0x99, 0x62, 0x57, 0x4d, 0x81, 0xb1, - 0x67, 0x63, 0x74, 0xe6, 0x1f, 0x41, 0x78, 0x5d, 0x12, 0xdb, 0x06, 0x63, 0xdf, 0xc4, 0x3b, 0x4c, - 0xb9, 0x4b, 0x59, 0x96, 0xbb, 0x5c, 0xaf, 0xe2, 0xc0, 0xe6, 0x5a, 0xcc, 0xdf, 0x07, 0xef, 0x5a, - 0x5c, 0xc4, 0x63, 0x13, 0xa6, 0x23, 0x8f, 0x61, 0x92, 0xe2, 0x5a, 0xd6, 0xaa, 0x8a, 0x27, 0x53, - 0x76, 0x14, 0x8a, 0x16, 0x12, 0xcf, 0x15, 0x2a, 0xbc, 0xd5, 0x72, 0x1d, 0x87, 0x96, 0xa7, 0xc5, - 0xfc, 0x18, 0xf8, 0x6c, 0x5b, 0xe2, 0xb2, 0xd6, 0x38, 0x7f, 0x95, 0x15, 0x37, 0xa8, 0xb3, 0x75, - 0x13, 0x47, 0x86, 0xe0, 0x81, 0x0c, 0xbd, 0xf2, 0x1c, 0x2b, 0x49, 0x6f, 0x83, 0xa1, 0x6a, 0x61, - 0xf2, 0x0b, 0x83, 0x28, 0x95, 0xe5, 0xdd, 0x22, 0x97, 0x7a, 0xf5, 0x56, 0xf3, 0xf8, 0x02, 0x82, - 0x25, 0x2a, 0x55, 0xc6, 0xde, 0xd4, 0x3b, 0x3a, 0xf8, 0xfa, 0xe9, 0x71, 0x37, 0xe8, 0x8e, 0xe7, - 0x0c, 0x95, 0x12, 0xf6, 0x16, 0xff, 0x0a, 0xa2, 0x0a, 0x37, 0x85, 0x92, 0x15, 0x96, 0xb1, 0x6f, - 0x7e, 0xc2, 0xfb, 0x9f, 0x5c, 0xb9, 0x94, 0xe8, 0x2f, 0x25, 0x7f, 0x30, 0x78, 0xbc, 0x47, 0xc5, - 0x1f, 0x01, 0x7b, 0x6d, 0xaa, 0x0a, 0x04, 0x7b, 0x4d, 0xa8, 0x31, 0x15, 0x05, 0x82, 0x35, 0x84, - 0x76, 0x66, 0x37, 0x81, 0x60, 0x3b, 0x42, 0x77, 0x66, 0x23, 0x81, 0x60, 0x77, 0xfc, 0x33, 0x98, - 0xfc, 0x54, 0xa3, 0xce, 0xb0, 0x8c, 0x03, 0xf3, 0xf2, 0x7b, 0xfd, 0xcb, 0xdf, 0xd7, 0xa8, 0x1b, - 0xd1, 0xe6, 0xa9, 0x53, 0xb3, 0x4d, 0xbb, 0x1a, 0x73, 0xa6, 0x58, 0x45, 0x9b, 0x9f, 0xd8, 0x18, - 0x9d, 0xdd, 0x84, 0xec, 0x3e, 0x46, 0xb3, 0x34, 0xf9, 0x8b, 0xd1, 0x9a, 0x6c, 0xe9, 0x83, 0xf1, - 0x99, 0x24, 0xff, 0x10, 0x42, 0x6a, 0xeb, 0xe5, 0xbd, 0xd4, 0x6e, 0x84, 0x13, 0xc2, 0x37, 0x52, - 0xf3, 0x2f, 0x61, 0x7c, 0x2f, 0x55, 0x8d, 0x0f, 0x8c, 0xb1, 0xa5, 0xbb, 0xa1, 0xbc, 0x70, 0xd7, - 0xba, 0x62, 0xfc, 0x41, 0x31, 0x4f, 0x20, 0x50, 0x72, 0x81, 0xca, 0xe9, 0xcc, 0x02, 0x5a, 0x10, - 0x75, 0xd5, 0x98, 0x5e, 0x1e, 0x64, 0xb6, 0xbd, 0xdb, 0x5b, 0xc9, 0x35, 0x3c, 0xde, 0x7b, 0xb1, - 0x7b, 0x89, 0xed, 0xbf, 0x64, 0xea, 0x70, 0x6d, 0x58, 0x40, 0x12, 0x2d, 0x51, 0xe1, 0xb2, 0xc2, - 0x95, 0x59, 0x41, 0x28, 0x3a, 0x9c, 0xfc, 0xc6, 0x7a, 0x5e, 0xf3, 0x1e, 0x89, 0x70, 0x99, 0x6f, - 0x36, 0x72, 0xbb, 0x72, 0xd4, 0x2d, 0xa4, 0xb9, 0xad, 0x16, 0x8e, 0x7a, 0xb4, 0x5a, 0x10, 0xd6, - 0x85, 0x33, 0xdc, 0x48, 0x17, 0x7c, 0x0a, 0x07, 0x1b, 0x94, 0x65, 0xad, 0x71, 0x83, 0xdb, 0xca, - 0x8d, 0x60, 0x18, 0xe2, 0x4f, 0x61, 0x52, 0xc9, 0xdb, 0x97, 0xaf, 0xb0, 0x71, 0xb3, 0x18, 0x57, - 0xf2, 0xf6, 0x1c, 0x1b, 0xfe, 0x31, 0x44, 0xeb, 0x0c, 0xd5, 0xca, 0xa4, 0xec, 0x72, 0x43, 0x13, - 0x38, 0xc7, 0x26, 0xf9, 0x9d, 0xc1, 0x78, 0x8e, 0xfa, 0x1e, 0xf5, 0x5b, 0x29, 0x7f, 0xe8, 0x7a, - 0xef, 0x7f, 0x5c, 0xef, 0x3f, 0xec, 0xfa, 0xa0, 0x77, 0xfd, 0x13, 0x08, 0xe6, 0x7a, 0x39, 0x4b, - 0x4d, 0x45, 0x9e, 0xb0, 0x80, 0x7f, 0x00, 0xe3, 0x93, 0x65, 0x95, 0xdd, 0xa3, 0xfb, 0x14, 0x38, - 0x94, 0xfc, 0xca, 0x60, 0x7c, 0x21, 0x9b, 0xbc, 0xae, 0xde, 0x50, 0xd8, 0x14, 0x0e, 0x4e, 0x8a, - 0x42, 0x65, 0x4b, 0x59, 0x65, 0xf9, 0xd6, 0x55, 0x3b, 0x0c, 0xd1, 0x8d, 0xe7, 0x83, 0xd9, 0xd9, - 0xba, 0x87, 0x21, 0xfe, 0x09, 0x04, 0x67, 0xc6, 0xd0, 0xd6, 0x9d, 0x87, 0xbd, 0x5e, 0xac, 0x8f, - 0x4d, 0x92, 0x1a, 0x3c, 0xa9, 0xab, 0x7c, 0xad, 0xf2, 0x9d, 0xe9, 0x24, 0x14, 0x1d, 0x4e, 0xfe, - 0x66, 0xe0, 0xbf, 0x2b, 0xa3, 0x3e, 0x02, 0x96, 0xb9, 0x45, 0xb2, 0xac, 0xb3, 0xed, 0x64, 0x60, - 0xdb, 0x18, 0x26, 0x8d, 0x96, 0xdb, 0x5b, 0x2c, 0xe3, 0x70, 0xea, 0x1d, 0x79, 0xa2, 0x85, 0x26, - 0x63, 0x3c, 0x52, 0xc6, 0xd1, 0xd4, 0x23, 0x05, 0x3a, 0xd8, 0x69, 0x1e, 0x7a, 0xcd, 0x27, 0x7f, - 0x32, 0x08, 0x3a, 0xe5, 0x9e, 0xed, 0x2b, 0xf7, 0xac, 0x57, 0x6e, 0x7a, 0xda, 0x2a, 0x37, 0x3d, - 0x25, 0x2c, 0x2e, 0x5b, 0xe5, 0x8a, 0x4b, 0x9a, 0xda, 0x37, 0x3a, 0xaf, 0x8b, 0xd3, 0xc6, 0x8e, - 0x37, 0x12, 0x1d, 0xa6, 0x75, 0xff, 0x70, 0x87, 0xda, 0xf5, 0x1c, 0x09, 0x87, 0x48, 0x1c, 0x17, - 0xc6, 0xd5, 0xb6, 0x4b, 0x0b, 0xf8, 0xa7, 0x10, 0x08, 0xea, 0xc2, 0xb4, 0xba, 0x37, 0x20, 0x13, - 0x16, 0x36, 0x9b, 0x3c, 0x73, 0xd7, 0x88, 0xe5, 0xba, 0x28, 0x50, 0x3b, 0x4d, 0x5b, 0x60, 0xb8, - 0xf3, 0x1d, 0xda, 0xcf, 0x91, 0x27, 0x2c, 0x48, 0x7e, 0x84, 0xe8, 0x44, 0xa1, 0xae, 0x44, 0xad, - 0xde, 0xfc, 0x88, 0x71, 0xf0, 0xbf, 0x9d, 0x7f, 0xf7, 0xa2, 0x75, 0x02, 0x9d, 0x7b, 0xfd, 0x7a, - 0xff, 0xd2, 0xef, 0xb9, 0x2c, 0xe4, 0x2c, 0x35, 0x8b, 0xf5, 0x84, 0x43, 0xc9, 0xe7, 0xe0, 0x93, - 0x4f, 0x06, 0xcc, 0xfe, 0x7f, 0x79, 0x6c, 0x31, 0x36, 0xff, 0xd6, 0xcf, 0xfe, 0x09, 0x00, 0x00, - 0xff, 0xff, 0xa7, 0xc6, 0x53, 0x22, 0xbf, 0x07, 0x00, 0x00, + // 937 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xbc, 0x56, 0x51, 0x8f, 0xdb, 0x44, + 0x10, 0xd6, 0xc6, 0x76, 0x12, 0xcf, 0xb5, 0x07, 0x5a, 0x55, 0x74, 0x29, 0x2f, 0xc1, 0x02, 0x29, + 0x20, 0x11, 0x50, 0x2b, 0x24, 0xc4, 0x5b, 0x7a, 0x41, 0x28, 0xdc, 0xb5, 0x5c, 0x37, 0x77, 0xc7, + 0x13, 0xaa, 0x36, 0xc9, 0xe4, 0xce, 0xaa, 0x13, 0x9b, 0xb5, 0x7d, 0x39, 0xff, 0x05, 0x9e, 0xf8, + 0x05, 0x48, 0x48, 0xfc, 0x02, 0xc4, 0x3b, 0x3f, 0x82, 0x3f, 0x84, 0x66, 0x77, 0x6d, 0x27, 0x34, + 0x45, 0x7d, 0xe2, 0xe9, 0xf6, 0x9b, 0xd9, 0xcc, 0x78, 0xbf, 0xf9, 0xe6, 0xd3, 0xc1, 0x71, 0xbc, + 0x29, 0x50, 0x6f, 0x54, 0x32, 0xca, 0x74, 0x5a, 0xa4, 0xbc, 0x5f, 0xe3, 0xe8, 0xe7, 0x0e, 0x74, + 0x67, 0x69, 0xa9, 0x17, 0xc8, 0x8f, 0xa1, 0x33, 0x9d, 0x08, 0x36, 0x60, 0x43, 0x4f, 0x76, 0xa6, + 0x13, 0xce, 0xc1, 0x7f, 0xae, 0xd6, 0x28, 0x3a, 0x03, 0x36, 0x0c, 0xa5, 0x39, 0x53, 0xec, 0xa2, + 0xca, 0x50, 0x78, 0x36, 0x46, 0x67, 0xfe, 0x08, 0xfa, 0x97, 0x39, 0x55, 0x5b, 0xa3, 0xf0, 0x4d, + 0xbc, 0xc1, 0x94, 0x3b, 0x57, 0x79, 0xbe, 0x4d, 0xf5, 0x52, 0x04, 0x36, 0x57, 0x63, 0xfe, 0x2e, + 0x78, 0x97, 0xf2, 0x4c, 0x74, 0x4d, 0x98, 0x8e, 0x5c, 0x40, 0x6f, 0x82, 0x2b, 0x55, 0x26, 0x85, + 0xe8, 0x0d, 0xd8, 0xb0, 0x2f, 0x6b, 0x48, 0x75, 0x2e, 0x30, 0xc1, 0x6b, 0xad, 0x56, 0xa2, 0x6f, + 0xeb, 0xd4, 0x98, 0x8f, 0x80, 0x4f, 0x37, 0x39, 0x2e, 0x4a, 0x8d, 0xb3, 0x57, 0x71, 0x76, 0x85, + 0x3a, 0x5e, 0x55, 0x22, 0x34, 0x05, 0x0e, 0x64, 0xa8, 0xcb, 0x33, 0x2c, 0x14, 0xf5, 0x06, 0x53, + 0xaa, 0x86, 0xd1, 0x2f, 0x0c, 0xc2, 0x89, 0xca, 0x6f, 0xe6, 0xa9, 0xd2, 0xcb, 0xb7, 0xe2, 0xe3, + 0x33, 0x08, 0x16, 0x98, 0x24, 0xb9, 0xf0, 0x06, 0xde, 0xf0, 0xe8, 0xf1, 0xc3, 0x51, 0x43, 0x74, + 0x53, 0xe7, 0x04, 0x93, 0x44, 0xda, 0x5b, 0xfc, 0x0b, 0x08, 0x0b, 0x5c, 0x67, 0x89, 0x2a, 0x30, + 0x17, 0xbe, 0xf9, 0x09, 0x6f, 0x7f, 0x72, 0xe1, 0x52, 0xb2, 0xbd, 0x14, 0xfd, 0xd9, 0x81, 0xfb, + 0x7b, 0xa5, 0xf8, 0x3d, 0x60, 0x77, 0xe6, 0xab, 0x02, 0xc9, 0xee, 0x08, 0x55, 0xe6, 0x8b, 0x02, + 0xc9, 0x2a, 0x42, 0x5b, 0x33, 0x9b, 0x40, 0xb2, 0x2d, 0xa1, 0x1b, 0x33, 0x91, 0x40, 0xb2, 0x1b, + 0xfe, 0x09, 0xf4, 0x7e, 0x2a, 0x51, 0xc7, 0x98, 0x8b, 0xc0, 0x74, 0x7e, 0xa7, 0xed, 0xfc, 0xa2, + 0x44, 0x5d, 0xc9, 0x3a, 0x4f, 0x2f, 0x35, 0xd3, 0xb4, 0xa3, 0x31, 0x67, 0x8a, 0x15, 0x34, 0xf9, + 0x9e, 0x8d, 0xd1, 0xd9, 0x31, 0x64, 0xe7, 0x41, 0x0c, 0x7d, 0x09, 0xbe, 0xba, 0xc3, 0x5c, 0x84, + 0xa6, 0xfe, 0x87, 0x6f, 0x20, 0x63, 0x34, 0xbe, 0xc3, 0xfc, 0x9b, 0x4d, 0xa1, 0x2b, 0x69, 0xae, + 0x3f, 0x7a, 0x01, 0x61, 0x13, 0x22, 0x55, 0xbc, 0xc2, 0xca, 0x3c, 0x30, 0x94, 0x74, 0xe4, 0x23, + 0x08, 0x6e, 0x55, 0x52, 0x5a, 0xe2, 0x8f, 0x1e, 0x8b, 0x03, 0x65, 0xa5, 0xda, 0x5c, 0xa3, 0xb4, + 0xd7, 0xbe, 0xee, 0x7c, 0xc5, 0xa2, 0x21, 0x1c, 0xef, 0x27, 0xf9, 0x7b, 0xd0, 0x9d, 0xa7, 0xe5, + 0x66, 0x99, 0x0b, 0x36, 0xf0, 0x86, 0x81, 0x74, 0x28, 0xfa, 0x8b, 0x91, 0xb4, 0x2c, 0xdd, 0x3b, + 0x23, 0xb7, 0x0f, 0x7a, 0x1f, 0xfa, 0x34, 0x8a, 0x97, 0xb7, 0x4a, 0xbb, 0xb1, 0xf7, 0x08, 0x5f, + 0x29, 0xcd, 0x3f, 0x87, 0xae, 0x69, 0x77, 0x60, 0xf4, 0x75, 0xb9, 0x2b, 0xca, 0x4b, 0x77, 0xad, + 0x21, 0xd0, 0xdf, 0x21, 0xf0, 0x01, 0x04, 0x89, 0x9a, 0x63, 0xe2, 0x76, 0xc3, 0x02, 0x12, 0x15, + 0x4d, 0xa2, 0x32, 0xfc, 0x1f, 0xac, 0x6c, 0xe7, 0x65, 0x6f, 0x45, 0x97, 0x70, 0x7f, 0xaf, 0x63, + 0xd3, 0x89, 0xed, 0x77, 0x6a, 0x49, 0x0c, 0x1d, 0x55, 0xb4, 0x56, 0x39, 0x26, 0xb8, 0x28, 0x70, + 0x69, 0x64, 0xd3, 0x97, 0x0d, 0x8e, 0x7e, 0x63, 0x6d, 0x5d, 0xd3, 0x8f, 0x16, 0x67, 0x91, 0xae, + 0xd7, 0x6a, 0xb3, 0x74, 0xa5, 0x6b, 0x48, 0xbc, 0x2d, 0xe7, 0xae, 0x74, 0x67, 0x39, 0x27, 0xac, + 0x33, 0x67, 0x12, 0x1d, 0x9d, 0xf1, 0x01, 0x1c, 0xad, 0x51, 0xe5, 0xa5, 0xc6, 0x35, 0x6e, 0x0a, + 0x47, 0xc1, 0x6e, 0x88, 0x3f, 0x84, 0x5e, 0xa1, 0xae, 0x5f, 0xd2, 0xe8, 0x2d, 0x17, 0xdd, 0x42, + 0x5d, 0x9f, 0x62, 0xc5, 0x3f, 0x80, 0x70, 0x15, 0x63, 0xb2, 0x34, 0x29, 0x2b, 0xc8, 0xbe, 0x09, + 0x9c, 0x62, 0x15, 0xfd, 0xce, 0xa0, 0x3b, 0x43, 0x7d, 0x8b, 0xfa, 0xad, 0xb6, 0x75, 0xd7, 0xa9, + 0xbc, 0xff, 0x70, 0x2a, 0xff, 0xb0, 0x53, 0x05, 0xad, 0x53, 0x3d, 0x80, 0x60, 0xa6, 0x17, 0xd3, + 0x89, 0xf9, 0x22, 0x4f, 0x5a, 0x40, 0x1a, 0x1b, 0x2f, 0x8a, 0xf8, 0x16, 0x9d, 0x7d, 0x39, 0x14, + 0xfd, 0xca, 0xa0, 0x7b, 0xa6, 0xaa, 0xb4, 0x2c, 0x5e, 0x53, 0xd8, 0x00, 0x8e, 0xc6, 0x59, 0x96, + 0xc4, 0x0b, 0x55, 0xc4, 0xe9, 0xc6, 0x7d, 0xed, 0x6e, 0x88, 0x6e, 0x3c, 0xdb, 0xe1, 0xce, 0x7e, + 0xf7, 0x6e, 0x88, 0x7f, 0x04, 0xc1, 0x89, 0x31, 0x21, 0xeb, 0x28, 0xc7, 0xad, 0x5e, 0xac, 0xf7, + 0x98, 0x24, 0x3d, 0x70, 0x5c, 0x16, 0xe9, 0x2a, 0x49, 0xb7, 0xe6, 0x25, 0x7d, 0xd9, 0xe0, 0xe8, + 0x6f, 0x06, 0xfe, 0xff, 0x65, 0x2e, 0xf7, 0x80, 0xc5, 0x6e, 0x90, 0x2c, 0x6e, 0xac, 0xa6, 0xb7, + 0x63, 0x35, 0x02, 0x7a, 0x95, 0xa6, 0xa5, 0xcd, 0x45, 0x7f, 0xe0, 0x0d, 0x3d, 0x59, 0x43, 0x93, + 0x31, 0x3b, 0x62, 0x3d, 0x26, 0x94, 0x35, 0x6c, 0x34, 0x0f, 0xad, 0xe6, 0xa3, 0x3f, 0x18, 0x04, + 0x8d, 0x72, 0x4f, 0xf6, 0x95, 0x7b, 0xd2, 0x2a, 0x77, 0xf2, 0xb4, 0x56, 0xee, 0xe4, 0x29, 0x61, + 0x79, 0x5e, 0x2b, 0x57, 0x9e, 0x13, 0x6b, 0xdf, 0xea, 0xb4, 0xcc, 0x9e, 0x56, 0x96, 0xde, 0x50, + 0x36, 0x98, 0xc6, 0xfd, 0xc3, 0x0d, 0x6a, 0xf7, 0xe6, 0x50, 0x3a, 0x44, 0xe2, 0x38, 0x33, 0x5b, + 0x6d, 0x5f, 0x69, 0x01, 0xff, 0x18, 0x02, 0xe3, 0x44, 0xe6, 0xa9, 0x7b, 0x04, 0x39, 0xf7, 0x32, + 0x7f, 0xa2, 0x27, 0xee, 0x1a, 0x55, 0xb9, 0xcc, 0x32, 0xd4, 0x4e, 0xd3, 0x16, 0x98, 0xda, 0xe9, + 0x16, 0xad, 0x1d, 0x79, 0xd2, 0x82, 0xe8, 0x47, 0x08, 0xc7, 0x09, 0xea, 0x42, 0x96, 0xc9, 0xeb, + 0x26, 0xc6, 0xc1, 0xff, 0x6e, 0xf6, 0xfd, 0xf3, 0x7a, 0x13, 0xe8, 0xdc, 0xea, 0xd7, 0xfb, 0x97, + 0x7e, 0x4f, 0x55, 0xa6, 0xa6, 0x13, 0x33, 0x58, 0x4f, 0x3a, 0x14, 0x7d, 0x0a, 0x3e, 0xed, 0xc9, + 0x4e, 0x65, 0xff, 0x4d, 0x3b, 0x36, 0xef, 0x9a, 0xff, 0x30, 0x9e, 0xfc, 0x13, 0x00, 0x00, 0xff, + 0xff, 0xa5, 0xc4, 0x18, 0xb5, 0x73, 0x08, 0x00, 0x00, } diff --git a/bolt/internal/internal.proto b/bolt/internal/internal.proto index 40a305718..95294dc4a 100644 --- a/bolt/internal/internal.proto +++ b/bolt/internal/internal.proto @@ -22,14 +22,19 @@ message Dashboard { } message DashboardCell { - int32 x = 1; // X-coordinate of Cell in the Dashboard - int32 y = 2; // Y-coordinate of Cell in the Dashboard - int32 w = 3; // Width of Cell in the Dashboard - int32 h = 4; // Height of Cell in the Dashboard - repeated Query queries = 5; // Time-series data queries for Dashboard - string name = 6; // User-facing name for this Dashboard - string type = 7; // Dashboard visualization type - string ID = 8; // id is the unique id of the dashboard. MIGRATED FIELD added in 1.2.0-beta6 + int32 x = 1; // X-coordinate of Cell in the Dashboard + int32 y = 2; // Y-coordinate of Cell in the Dashboard + int32 w = 3; // Width of Cell in the Dashboard + int32 h = 4; // Height of Cell in the Dashboard + repeated Query queries = 5; // Time-series data queries for Dashboard + string name = 6; // User-facing name for this Dashboard + string type = 7; // Dashboard visualization type + string ID = 8; // id is the unique id of the dashboard. MIGRATED FIELD added in 1.2.0-beta6 + map axes = 9; // Axes represent the graphical viewport for a cell's visualizations +} + +message DashboardRange { + repeated int32 bounds = 1; // bounds are an ordered 2-tuple consisting of lower and upper axis extents, respectively } message Template { diff --git a/chronograf.go b/chronograf.go index 233351684..e3993c58e 100644 --- a/chronograf.go +++ b/chronograf.go @@ -565,16 +565,22 @@ type Dashboard struct { Name string `json:"name"` } +// DashboardRange represents the visible extents of a visualization +type DashboardRange struct { + Bounds [2]int32 `json:"bounds"` // bounds are an ordered 2-tuple consisting of lower and upper axis extents, respectively +} + // DashboardCell holds visual and query information for a cell type DashboardCell struct { - ID string `json:"i"` - X int32 `json:"x"` - Y int32 `json:"y"` - W int32 `json:"w"` - H int32 `json:"h"` - Name string `json:"name"` - Queries []DashboardQuery `json:"queries"` - Type string `json:"type"` + ID string `json:"i"` + X int32 `json:"x"` + Y int32 `json:"y"` + W int32 `json:"w"` + H int32 `json:"h"` + Name string `json:"name"` + Queries []DashboardQuery `json:"queries"` + Axes map[string]DashboardRange `json:"axes"` + Type string `json:"type"` } // DashboardsStore is the storage and retrieval of dashboards From bab28c72712f3170891819170f5230f6d363baa2 Mon Sep 17 00:00:00 2001 From: Tim Raymond Date: Wed, 19 Jul 2017 11:18:01 -0400 Subject: [PATCH 002/107] Update Swagger with Cell Axes Cells now have axes which represent their visualization's viewport. This updates the Swagger documentation to reflect this. Things to be aware of ===================== The form of "axes" is that of a map, which is represented in Swagger by an "additionalProperties" key (search for "string to model mapping" here: https://swagger.io/specification/). --- server/swagger.json | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/server/swagger.json b/server/swagger.json index 769a6be2c..524e5c819 100644 --- a/server/swagger.json +++ b/server/swagger.json @@ -3999,6 +3999,13 @@ "$ref": "#/definitions/DashboardQuery" } }, + "axes": { + "description": "The viewport for a cell's visualizations", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/DashboardRange" + } + }, "type": { "description": "Cell visualization type", "type": "string", @@ -4080,6 +4087,22 @@ } } }, + "DashboardRange": { + "type": "object", + "description": "A description of a particular axis for a visualization", + "properties": { + "bounds": { + "type": "array", + "minItems": 0, + "maxItems": 2, + "description": "The extents of an axis in the form [lower, upper]. Clients determine whether bounds are to be inclusive or exclusive of their limits", + "items": { + "type": "integer", + "format": "int32" + } + } + } + }, "Routes": { "type": "object", "properties": { From b5f2bea66f85436c940b7cb267daa09c98582fab Mon Sep 17 00:00:00 2001 From: Tim Raymond Date: Thu, 20 Jul 2017 14:39:19 -0400 Subject: [PATCH 003/107] s/DashboardRange/Axis, format protobufs, 64-bit "Axis" is a more consistent and appropriate name. Also, the formatting the protobufs was all over the place, so this has been made consistent using a first-column \t. Furthermore, a vim modeline was added to the bottom to make it easier for editors to autoconfigure themselves to the right format, since protobufs are not something that we edit everyday. Also, 32-bit values have been substituted for 64-bit values in Protobuf definitions. --- bolt/internal/internal.go | 4 +- bolt/internal/internal.proto | 121 ++++++++++++++++++----------------- chronograf.go | 24 +++---- 3 files changed, 77 insertions(+), 72 deletions(-) diff --git a/bolt/internal/internal.go b/bolt/internal/internal.go index 4b1f7b5e7..a9ef81cd5 100644 --- a/bolt/internal/internal.go +++ b/bolt/internal/internal.go @@ -265,9 +265,9 @@ func UnmarshalDashboard(data []byte, d *chronograf.Dashboard) error { } } - axes := make(map[string]chronograf.DashboardRange, len(c.Axes)) + axes := make(map[string]chronograf.Axis, len(c.Axes)) for a, r := range c.Axes { - axis := chronograf.DashboardRange{} + axis := chronograf.Axis{} copy(axis.Bounds[:], r.Bounds[:2]) axes[a] = axis } diff --git a/bolt/internal/internal.proto b/bolt/internal/internal.proto index 95294dc4a..2b5981363 100644 --- a/bolt/internal/internal.proto +++ b/bolt/internal/internal.proto @@ -18,47 +18,47 @@ message Dashboard { int64 ID = 1; // ID is the unique ID of the dashboard string Name = 2; // Name is the user-defined name of the dashboard repeated DashboardCell cells = 3; // a representation of all visual data required for rendering the dashboard - repeated Template templates = 4; // Templates replace template variables within InfluxQL + repeated Template templates = 4; // Templates replace template variables within InfluxQL } message DashboardCell { - int32 x = 1; // X-coordinate of Cell in the Dashboard - int32 y = 2; // Y-coordinate of Cell in the Dashboard - int32 w = 3; // Width of Cell in the Dashboard - int32 h = 4; // Height of Cell in the Dashboard - repeated Query queries = 5; // Time-series data queries for Dashboard - string name = 6; // User-facing name for this Dashboard - string type = 7; // Dashboard visualization type - string ID = 8; // id is the unique id of the dashboard. MIGRATED FIELD added in 1.2.0-beta6 - map axes = 9; // Axes represent the graphical viewport for a cell's visualizations + int32 x = 1; // X-coordinate of Cell in the Dashboard + int32 y = 2; // Y-coordinate of Cell in the Dashboard + int32 w = 3; // Width of Cell in the Dashboard + int32 h = 4; // Height of Cell in the Dashboard + repeated Query queries = 5; // Time-series data queries for Dashboard + string name = 6; // User-facing name for this Dashboard + string type = 7; // Dashboard visualization type + string ID = 8; // id is the unique id of the dashboard. MIGRATED FIELD added in 1.2.0-beta6 + map axes = 9; // Axes represent the graphical viewport for a cell's visualizations } -message DashboardRange { - repeated int32 bounds = 1; // bounds are an ordered 2-tuple consisting of lower and upper axis extents, respectively +message Axis { + repeated int64 bounds = 1; // bounds are an ordered 2-tuple consisting of lower and upper axis extents, respectively } message Template { - string ID = 1; // ID is the unique ID associated with this template - string temp_var = 2; - repeated TemplateValue values = 3; - string type = 4; // Type can be fieldKeys, tagKeys, tagValues, CSV, constant, query, measurements, databases - string label = 5; // Label is a user-facing description of the Template - TemplateQuery query = 6; // Query is used to generate the choices for a template + string ID = 1; // ID is the unique ID associated with this template + string temp_var = 2; + repeated TemplateValue values = 3; + string type = 4; // Type can be fieldKeys, tagKeys, tagValues, CSV, constant, query, measurements, databases + string label = 5; // Label is a user-facing description of the Template + TemplateQuery query = 6; // Query is used to generate the choices for a template } message TemplateValue { - string type = 1; // Type can be tagKey, tagValue, fieldKey, csv, measurement, database, constant - string value = 2; // Value is the specific value used to replace a template in an InfluxQL query - bool selected = 3; // Selected states that this variable has been picked to use for replacement + string type = 1; // Type can be tagKey, tagValue, fieldKey, csv, measurement, database, constant + string value = 2; // Value is the specific value used to replace a template in an InfluxQL query + bool selected = 3; // Selected states that this variable has been picked to use for replacement } message TemplateQuery { - string command = 1; // Command is the query itself - string db = 2; // DB the database for the query (optional) - string rp = 3; // RP is a retention policy and optional; - string measurement = 4; // Measurement is the optinally selected measurement for the query - string tag_key = 5; // TagKey is the optionally selected tag key for the query - string field_key = 6; // FieldKey is the optionally selected field key for the query + string command = 1; // Command is the query itself + string db = 2; // DB the database for the query (optional) + string rp = 3; // RP is a retention policy and optional; + string measurement = 4; // Measurement is the optinally selected measurement for the query + string tag_key = 5; // TagKey is the optionally selected tag key for the query + string field_key = 6; // FieldKey is the optionally selected field key for the query } message Server { @@ -67,54 +67,59 @@ message Server { string Username = 3; // Username is the username to connect to the server string Password = 4; string URL = 5; // URL is the path to the server - int64 SrcID = 6; // SrcID is the ID of the data source + int64 SrcID = 6; // SrcID is the ID of the data source bool Active = 7; // is this the currently active server for the source } message Layout { - string ID = 1; // ID is the unique ID of the layout. - string Application = 2; // Application is the user facing name of this Layout. - string Measurement = 3; // Measurement is the descriptive name of the time series data. - repeated Cell Cells = 4; // Cells are the individual visualization elements. - bool Autoflow = 5; // Autoflow indicates whether the frontend should layout the cells automatically. + string ID = 1; // ID is the unique ID of the layout. + string Application = 2; // Application is the user facing name of this Layout. + string Measurement = 3; // Measurement is the descriptive name of the time series data. + repeated Cell Cells = 4; // Cells are the individual visualization elements. + bool Autoflow = 5; // Autoflow indicates whether the frontend should layout the cells automatically. } message Cell { - int32 x = 1; // X-coordinate of Cell in the Layout - int32 y = 2; // Y-coordinate of Cell in the Layout - int32 w = 3; // Width of Cell in the Layout - int32 h = 4; // Height of Cell in the Layout - 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 - string type = 10; // Cell visualization type + int32 x = 1; // X-coordinate of Cell in the Layout + int32 y = 2; // Y-coordinate of Cell in the Layout + int32 w = 3; // Width of Cell in the Layout + int32 h = 4; // Height of Cell in the Layout + 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 + string type = 10; // Cell visualization type } message Query { - string Command = 1; // Command is the query itself - string DB = 2; // DB the database for the query (optional) - 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 + string Command = 1; // Command is the query itself + string DB = 2; // DB the database for the query (optional) + 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 + int64 Upper = 1; // Upper is the upper-bound of the range + int64 Lower = 2; // Lower is the lower-bound of the range } message AlertRule { - string ID = 1; // ID is the unique ID of this alert rule - string JSON = 2; // JSON byte representation of the alert - int64 SrcID = 3; // SrcID is the id of the source this alert is associated with - int64 KapaID = 4; // KapaID is the id of the kapacitor this alert is associated with + string ID = 1; // ID is the unique ID of this alert rule + string JSON = 2; // JSON byte representation of the alert + int64 SrcID = 3; // SrcID is the id of the source this alert is associated with + int64 KapaID = 4; // KapaID is the id of the kapacitor this alert is associated with } message User { - uint64 ID = 1; // ID is the unique ID of this user - string Name = 2; // Name is the user's login name + uint64 ID = 1; // ID is the unique ID of this user + string Name = 2; // Name is the user's login name } + +// The following is a vim modeline, it autoconfigures vim to have the +// appropriate tabbing and whitespace management to edit this file +// +// vim: ai:ts=4:noet:sts=4 diff --git a/chronograf.go b/chronograf.go index e3993c58e..c1401790d 100644 --- a/chronograf.go +++ b/chronograf.go @@ -565,22 +565,22 @@ type Dashboard struct { Name string `json:"name"` } -// DashboardRange represents the visible extents of a visualization -type DashboardRange struct { - Bounds [2]int32 `json:"bounds"` // bounds are an ordered 2-tuple consisting of lower and upper axis extents, respectively +// Axis represents the visible extents of a visualization +type Axis struct { + Bounds [2]int64 `json:"bounds"` // bounds are an ordered 2-tuple consisting of lower and upper axis extents, respectively } // DashboardCell holds visual and query information for a cell type DashboardCell struct { - ID string `json:"i"` - X int32 `json:"x"` - Y int32 `json:"y"` - W int32 `json:"w"` - H int32 `json:"h"` - Name string `json:"name"` - Queries []DashboardQuery `json:"queries"` - Axes map[string]DashboardRange `json:"axes"` - Type string `json:"type"` + ID string `json:"i"` + X int32 `json:"x"` + Y int32 `json:"y"` + W int32 `json:"w"` + H int32 `json:"h"` + Name string `json:"name"` + Queries []DashboardQuery `json:"queries"` + Axes map[string]Axis `json:"axes"` + Type string `json:"type"` } // DashboardsStore is the storage and retrieval of dashboards From 7e6ddaaa9f6d4a5b3e295b77c0fc2064ddda2bba Mon Sep 17 00:00:00 2001 From: Tim Raymond Date: Fri, 21 Jul 2017 11:08:52 -0400 Subject: [PATCH 004/107] Add tests for Dashboard protobuf & misc renaming There were previously no tests around Dashboard serialization to protobuf, so this patch adds coverage for that. Also, the `go-cmp` package has been introduced to replace our usage of `reflect.DeepEqual` going forward because it has better comparison features that make it more stable across Go versions and produces nice diffs in tests when they fail, reducing the need for debug lines and manual inspection. --- Gopkg.lock | 7 +- bolt/internal/internal.go | 6 +- bolt/internal/internal.pb.go | 156 +- bolt/internal/internal_test.go | 43 + vendor/github.com/google/go-cmp/.travis.yml | 18 + .../github.com/google/go-cmp/CONTRIBUTING.md | 23 + vendor/github.com/google/go-cmp/LICENSE | 27 + vendor/github.com/google/go-cmp/README.md | 41 + .../github.com/google/go-cmp/cmp/compare.go | 519 +++++ .../google/go-cmp/cmp/compare_test.go | 1775 +++++++++++++++++ .../google/go-cmp/cmp/example_test.go | 266 +++ .../github.com/google/go-cmp/cmp/options.go | 304 +++ .../google/go-cmp/cmp/options_test.go | 231 +++ vendor/github.com/google/go-cmp/cmp/path.go | 261 +++ .../github.com/google/go-cmp/cmp/reporter.go | 406 ++++ .../google/go-cmp/cmp/reporter_test.go | 227 +++ .../google/go-cmp/cmp/unsafe_panic.go | 15 + .../google/go-cmp/cmp/unsafe_reflect.go | 23 + 18 files changed, 4266 insertions(+), 82 deletions(-) create mode 100644 vendor/github.com/google/go-cmp/.travis.yml create mode 100644 vendor/github.com/google/go-cmp/CONTRIBUTING.md create mode 100644 vendor/github.com/google/go-cmp/LICENSE create mode 100644 vendor/github.com/google/go-cmp/README.md create mode 100644 vendor/github.com/google/go-cmp/cmp/compare.go create mode 100644 vendor/github.com/google/go-cmp/cmp/compare_test.go create mode 100644 vendor/github.com/google/go-cmp/cmp/example_test.go create mode 100644 vendor/github.com/google/go-cmp/cmp/options.go create mode 100644 vendor/github.com/google/go-cmp/cmp/options_test.go create mode 100644 vendor/github.com/google/go-cmp/cmp/path.go create mode 100644 vendor/github.com/google/go-cmp/cmp/reporter.go create mode 100644 vendor/github.com/google/go-cmp/cmp/reporter_test.go create mode 100644 vendor/github.com/google/go-cmp/cmp/unsafe_panic.go create mode 100644 vendor/github.com/google/go-cmp/cmp/unsafe_reflect.go diff --git a/Gopkg.lock b/Gopkg.lock index 508227b85..d8021c010 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -47,6 +47,11 @@ packages = ["proto"] revision = "8ee79997227bf9b34611aee7946ae64735e6fd93" +[[projects]] + name = "github.com/google/go-cmp" + packages = ["cmp"] + revision = "79b2d888f100ec053545168aa94bcfb322e8bfc8" + [[projects]] name = "github.com/google/go-github" packages = ["github"] @@ -135,6 +140,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "bac138180cd86a0ae604cd3aa7b6ba300673478c880882bd58a4bd7f8bff518d" + inputs-digest = "f34fb88755292baba8b52c14bf5b9a028daff96a763368a7cf1de90004d33695" solver-name = "gps-cdcl" solver-version = 1 diff --git a/bolt/internal/internal.go b/bolt/internal/internal.go index a9ef81cd5..b9400a489 100644 --- a/bolt/internal/internal.go +++ b/bolt/internal/internal.go @@ -179,15 +179,15 @@ func MarshalDashboard(d chronograf.Dashboard) ([]byte, error) { } } - axes := make(map[string]*DashboardRange, len(c.Axes)) + axes := make(map[string]*Axis, len(c.Axes)) for a, r := range c.Axes { // need to explicitly allocate a new array because r.Bounds is // over-written and the resulting slices from previous iterations will // point to later iteration's data. It is _not_ enough to simply re-slice // r.Bounds - axis := [2]int32{} + axis := [2]int64{} copy(axis[:], r.Bounds[:2]) - axes[a] = &DashboardRange{ + axes[a] = &Axis{ Bounds: axis[:], } } diff --git a/bolt/internal/internal.pb.go b/bolt/internal/internal.pb.go index 64e5c3709..ccc0aedc9 100644 --- a/bolt/internal/internal.pb.go +++ b/bolt/internal/internal.pb.go @@ -12,7 +12,7 @@ It has these top-level messages: Source Dashboard DashboardCell - DashboardRange + Axis Template TemplateValue TemplateQuery @@ -86,15 +86,15 @@ func (m *Dashboard) GetTemplates() []*Template { } type DashboardCell struct { - X int32 `protobuf:"varint,1,opt,name=x,proto3" json:"x,omitempty"` - Y int32 `protobuf:"varint,2,opt,name=y,proto3" json:"y,omitempty"` - W int32 `protobuf:"varint,3,opt,name=w,proto3" json:"w,omitempty"` - H int32 `protobuf:"varint,4,opt,name=h,proto3" json:"h,omitempty"` - Queries []*Query `protobuf:"bytes,5,rep,name=queries" json:"queries,omitempty"` - Name string `protobuf:"bytes,6,opt,name=name,proto3" json:"name,omitempty"` - Type string `protobuf:"bytes,7,opt,name=type,proto3" json:"type,omitempty"` - ID string `protobuf:"bytes,8,opt,name=ID,proto3" json:"ID,omitempty"` - Axes map[string]*DashboardRange `protobuf:"bytes,9,rep,name=axes" json:"axes,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value"` + X int32 `protobuf:"varint,1,opt,name=x,proto3" json:"x,omitempty"` + Y int32 `protobuf:"varint,2,opt,name=y,proto3" json:"y,omitempty"` + W int32 `protobuf:"varint,3,opt,name=w,proto3" json:"w,omitempty"` + H int32 `protobuf:"varint,4,opt,name=h,proto3" json:"h,omitempty"` + Queries []*Query `protobuf:"bytes,5,rep,name=queries" json:"queries,omitempty"` + Name string `protobuf:"bytes,6,opt,name=name,proto3" json:"name,omitempty"` + Type string `protobuf:"bytes,7,opt,name=type,proto3" json:"type,omitempty"` + ID string `protobuf:"bytes,8,opt,name=ID,proto3" json:"ID,omitempty"` + Axes map[string]*Axis `protobuf:"bytes,9,rep,name=axes" json:"axes,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value"` } func (m *DashboardCell) Reset() { *m = DashboardCell{} } @@ -109,21 +109,21 @@ func (m *DashboardCell) GetQueries() []*Query { return nil } -func (m *DashboardCell) GetAxes() map[string]*DashboardRange { +func (m *DashboardCell) GetAxes() map[string]*Axis { if m != nil { return m.Axes } return nil } -type DashboardRange struct { - Bounds []int32 `protobuf:"varint,1,rep,name=bounds" json:"bounds,omitempty"` +type Axis struct { + Bounds []int64 `protobuf:"varint,1,rep,name=bounds" json:"bounds,omitempty"` } -func (m *DashboardRange) Reset() { *m = DashboardRange{} } -func (m *DashboardRange) String() string { return proto.CompactTextString(m) } -func (*DashboardRange) ProtoMessage() {} -func (*DashboardRange) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{3} } +func (m *Axis) Reset() { *m = Axis{} } +func (m *Axis) String() string { return proto.CompactTextString(m) } +func (*Axis) ProtoMessage() {} +func (*Axis) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{3} } type Template struct { ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"` @@ -296,7 +296,7 @@ func init() { proto.RegisterType((*Source)(nil), "internal.Source") proto.RegisterType((*Dashboard)(nil), "internal.Dashboard") proto.RegisterType((*DashboardCell)(nil), "internal.DashboardCell") - proto.RegisterType((*DashboardRange)(nil), "internal.DashboardRange") + proto.RegisterType((*Axis)(nil), "internal.Axis") proto.RegisterType((*Template)(nil), "internal.Template") proto.RegisterType((*TemplateValue)(nil), "internal.TemplateValue") proto.RegisterType((*TemplateQuery)(nil), "internal.TemplateQuery") @@ -312,64 +312,64 @@ func init() { func init() { proto.RegisterFile("internal.proto", fileDescriptorInternal) } var fileDescriptorInternal = []byte{ - // 937 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xbc, 0x56, 0x51, 0x8f, 0xdb, 0x44, - 0x10, 0xd6, 0xc6, 0x76, 0x12, 0xcf, 0xb5, 0x07, 0x5a, 0x55, 0x74, 0x29, 0x2f, 0xc1, 0x02, 0x29, - 0x20, 0x11, 0x50, 0x2b, 0x24, 0xc4, 0x5b, 0x7a, 0x41, 0x28, 0xdc, 0xb5, 0x5c, 0x37, 0x77, 0xc7, - 0x13, 0xaa, 0x36, 0xc9, 0xe4, 0xce, 0xaa, 0x13, 0x9b, 0xb5, 0x7d, 0x39, 0xff, 0x05, 0x9e, 0xf8, - 0x05, 0x48, 0x48, 0xfc, 0x02, 0xc4, 0x3b, 0x3f, 0x82, 0x3f, 0x84, 0x66, 0x77, 0x6d, 0x27, 0x34, - 0x45, 0x7d, 0xe2, 0xe9, 0xf6, 0x9b, 0xd9, 0xcc, 0x78, 0xbf, 0xf9, 0xe6, 0xd3, 0xc1, 0x71, 0xbc, - 0x29, 0x50, 0x6f, 0x54, 0x32, 0xca, 0x74, 0x5a, 0xa4, 0xbc, 0x5f, 0xe3, 0xe8, 0xe7, 0x0e, 0x74, - 0x67, 0x69, 0xa9, 0x17, 0xc8, 0x8f, 0xa1, 0x33, 0x9d, 0x08, 0x36, 0x60, 0x43, 0x4f, 0x76, 0xa6, - 0x13, 0xce, 0xc1, 0x7f, 0xae, 0xd6, 0x28, 0x3a, 0x03, 0x36, 0x0c, 0xa5, 0x39, 0x53, 0xec, 0xa2, - 0xca, 0x50, 0x78, 0x36, 0x46, 0x67, 0xfe, 0x08, 0xfa, 0x97, 0x39, 0x55, 0x5b, 0xa3, 0xf0, 0x4d, - 0xbc, 0xc1, 0x94, 0x3b, 0x57, 0x79, 0xbe, 0x4d, 0xf5, 0x52, 0x04, 0x36, 0x57, 0x63, 0xfe, 0x2e, - 0x78, 0x97, 0xf2, 0x4c, 0x74, 0x4d, 0x98, 0x8e, 0x5c, 0x40, 0x6f, 0x82, 0x2b, 0x55, 0x26, 0x85, - 0xe8, 0x0d, 0xd8, 0xb0, 0x2f, 0x6b, 0x48, 0x75, 0x2e, 0x30, 0xc1, 0x6b, 0xad, 0x56, 0xa2, 0x6f, - 0xeb, 0xd4, 0x98, 0x8f, 0x80, 0x4f, 0x37, 0x39, 0x2e, 0x4a, 0x8d, 0xb3, 0x57, 0x71, 0x76, 0x85, - 0x3a, 0x5e, 0x55, 0x22, 0x34, 0x05, 0x0e, 0x64, 0xa8, 0xcb, 0x33, 0x2c, 0x14, 0xf5, 0x06, 0x53, - 0xaa, 0x86, 0xd1, 0x2f, 0x0c, 0xc2, 0x89, 0xca, 0x6f, 0xe6, 0xa9, 0xd2, 0xcb, 0xb7, 0xe2, 0xe3, - 0x33, 0x08, 0x16, 0x98, 0x24, 0xb9, 0xf0, 0x06, 0xde, 0xf0, 0xe8, 0xf1, 0xc3, 0x51, 0x43, 0x74, - 0x53, 0xe7, 0x04, 0x93, 0x44, 0xda, 0x5b, 0xfc, 0x0b, 0x08, 0x0b, 0x5c, 0x67, 0x89, 0x2a, 0x30, - 0x17, 0xbe, 0xf9, 0x09, 0x6f, 0x7f, 0x72, 0xe1, 0x52, 0xb2, 0xbd, 0x14, 0xfd, 0xd9, 0x81, 0xfb, - 0x7b, 0xa5, 0xf8, 0x3d, 0x60, 0x77, 0xe6, 0xab, 0x02, 0xc9, 0xee, 0x08, 0x55, 0xe6, 0x8b, 0x02, - 0xc9, 0x2a, 0x42, 0x5b, 0x33, 0x9b, 0x40, 0xb2, 0x2d, 0xa1, 0x1b, 0x33, 0x91, 0x40, 0xb2, 0x1b, - 0xfe, 0x09, 0xf4, 0x7e, 0x2a, 0x51, 0xc7, 0x98, 0x8b, 0xc0, 0x74, 0x7e, 0xa7, 0xed, 0xfc, 0xa2, - 0x44, 0x5d, 0xc9, 0x3a, 0x4f, 0x2f, 0x35, 0xd3, 0xb4, 0xa3, 0x31, 0x67, 0x8a, 0x15, 0x34, 0xf9, - 0x9e, 0x8d, 0xd1, 0xd9, 0x31, 0x64, 0xe7, 0x41, 0x0c, 0x7d, 0x09, 0xbe, 0xba, 0xc3, 0x5c, 0x84, - 0xa6, 0xfe, 0x87, 0x6f, 0x20, 0x63, 0x34, 0xbe, 0xc3, 0xfc, 0x9b, 0x4d, 0xa1, 0x2b, 0x69, 0xae, - 0x3f, 0x7a, 0x01, 0x61, 0x13, 0x22, 0x55, 0xbc, 0xc2, 0xca, 0x3c, 0x30, 0x94, 0x74, 0xe4, 0x23, - 0x08, 0x6e, 0x55, 0x52, 0x5a, 0xe2, 0x8f, 0x1e, 0x8b, 0x03, 0x65, 0xa5, 0xda, 0x5c, 0xa3, 0xb4, - 0xd7, 0xbe, 0xee, 0x7c, 0xc5, 0xa2, 0x21, 0x1c, 0xef, 0x27, 0xf9, 0x7b, 0xd0, 0x9d, 0xa7, 0xe5, - 0x66, 0x99, 0x0b, 0x36, 0xf0, 0x86, 0x81, 0x74, 0x28, 0xfa, 0x8b, 0x91, 0xb4, 0x2c, 0xdd, 0x3b, - 0x23, 0xb7, 0x0f, 0x7a, 0x1f, 0xfa, 0x34, 0x8a, 0x97, 0xb7, 0x4a, 0xbb, 0xb1, 0xf7, 0x08, 0x5f, - 0x29, 0xcd, 0x3f, 0x87, 0xae, 0x69, 0x77, 0x60, 0xf4, 0x75, 0xb9, 0x2b, 0xca, 0x4b, 0x77, 0xad, - 0x21, 0xd0, 0xdf, 0x21, 0xf0, 0x01, 0x04, 0x89, 0x9a, 0x63, 0xe2, 0x76, 0xc3, 0x02, 0x12, 0x15, - 0x4d, 0xa2, 0x32, 0xfc, 0x1f, 0xac, 0x6c, 0xe7, 0x65, 0x6f, 0x45, 0x97, 0x70, 0x7f, 0xaf, 0x63, - 0xd3, 0x89, 0xed, 0x77, 0x6a, 0x49, 0x0c, 0x1d, 0x55, 0xb4, 0x56, 0x39, 0x26, 0xb8, 0x28, 0x70, - 0x69, 0x64, 0xd3, 0x97, 0x0d, 0x8e, 0x7e, 0x63, 0x6d, 0x5d, 0xd3, 0x8f, 0x16, 0x67, 0x91, 0xae, - 0xd7, 0x6a, 0xb3, 0x74, 0xa5, 0x6b, 0x48, 0xbc, 0x2d, 0xe7, 0xae, 0x74, 0x67, 0x39, 0x27, 0xac, - 0x33, 0x67, 0x12, 0x1d, 0x9d, 0xf1, 0x01, 0x1c, 0xad, 0x51, 0xe5, 0xa5, 0xc6, 0x35, 0x6e, 0x0a, - 0x47, 0xc1, 0x6e, 0x88, 0x3f, 0x84, 0x5e, 0xa1, 0xae, 0x5f, 0xd2, 0xe8, 0x2d, 0x17, 0xdd, 0x42, - 0x5d, 0x9f, 0x62, 0xc5, 0x3f, 0x80, 0x70, 0x15, 0x63, 0xb2, 0x34, 0x29, 0x2b, 0xc8, 0xbe, 0x09, - 0x9c, 0x62, 0x15, 0xfd, 0xce, 0xa0, 0x3b, 0x43, 0x7d, 0x8b, 0xfa, 0xad, 0xb6, 0x75, 0xd7, 0xa9, - 0xbc, 0xff, 0x70, 0x2a, 0xff, 0xb0, 0x53, 0x05, 0xad, 0x53, 0x3d, 0x80, 0x60, 0xa6, 0x17, 0xd3, - 0x89, 0xf9, 0x22, 0x4f, 0x5a, 0x40, 0x1a, 0x1b, 0x2f, 0x8a, 0xf8, 0x16, 0x9d, 0x7d, 0x39, 0x14, - 0xfd, 0xca, 0xa0, 0x7b, 0xa6, 0xaa, 0xb4, 0x2c, 0x5e, 0x53, 0xd8, 0x00, 0x8e, 0xc6, 0x59, 0x96, - 0xc4, 0x0b, 0x55, 0xc4, 0xe9, 0xc6, 0x7d, 0xed, 0x6e, 0x88, 0x6e, 0x3c, 0xdb, 0xe1, 0xce, 0x7e, - 0xf7, 0x6e, 0x88, 0x7f, 0x04, 0xc1, 0x89, 0x31, 0x21, 0xeb, 0x28, 0xc7, 0xad, 0x5e, 0xac, 0xf7, - 0x98, 0x24, 0x3d, 0x70, 0x5c, 0x16, 0xe9, 0x2a, 0x49, 0xb7, 0xe6, 0x25, 0x7d, 0xd9, 0xe0, 0xe8, - 0x6f, 0x06, 0xfe, 0xff, 0x65, 0x2e, 0xf7, 0x80, 0xc5, 0x6e, 0x90, 0x2c, 0x6e, 0xac, 0xa6, 0xb7, - 0x63, 0x35, 0x02, 0x7a, 0x95, 0xa6, 0xa5, 0xcd, 0x45, 0x7f, 0xe0, 0x0d, 0x3d, 0x59, 0x43, 0x93, - 0x31, 0x3b, 0x62, 0x3d, 0x26, 0x94, 0x35, 0x6c, 0x34, 0x0f, 0xad, 0xe6, 0xa3, 0x3f, 0x18, 0x04, - 0x8d, 0x72, 0x4f, 0xf6, 0x95, 0x7b, 0xd2, 0x2a, 0x77, 0xf2, 0xb4, 0x56, 0xee, 0xe4, 0x29, 0x61, - 0x79, 0x5e, 0x2b, 0x57, 0x9e, 0x13, 0x6b, 0xdf, 0xea, 0xb4, 0xcc, 0x9e, 0x56, 0x96, 0xde, 0x50, - 0x36, 0x98, 0xc6, 0xfd, 0xc3, 0x0d, 0x6a, 0xf7, 0xe6, 0x50, 0x3a, 0x44, 0xe2, 0x38, 0x33, 0x5b, - 0x6d, 0x5f, 0x69, 0x01, 0xff, 0x18, 0x02, 0xe3, 0x44, 0xe6, 0xa9, 0x7b, 0x04, 0x39, 0xf7, 0x32, - 0x7f, 0xa2, 0x27, 0xee, 0x1a, 0x55, 0xb9, 0xcc, 0x32, 0xd4, 0x4e, 0xd3, 0x16, 0x98, 0xda, 0xe9, - 0x16, 0xad, 0x1d, 0x79, 0xd2, 0x82, 0xe8, 0x47, 0x08, 0xc7, 0x09, 0xea, 0x42, 0x96, 0xc9, 0xeb, - 0x26, 0xc6, 0xc1, 0xff, 0x6e, 0xf6, 0xfd, 0xf3, 0x7a, 0x13, 0xe8, 0xdc, 0xea, 0xd7, 0xfb, 0x97, - 0x7e, 0x4f, 0x55, 0xa6, 0xa6, 0x13, 0x33, 0x58, 0x4f, 0x3a, 0x14, 0x7d, 0x0a, 0x3e, 0xed, 0xc9, - 0x4e, 0x65, 0xff, 0x4d, 0x3b, 0x36, 0xef, 0x9a, 0xff, 0x30, 0x9e, 0xfc, 0x13, 0x00, 0x00, 0xff, - 0xff, 0xa5, 0xc4, 0x18, 0xb5, 0x73, 0x08, 0x00, 0x00, + // 935 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xbc, 0x56, 0xdd, 0x8e, 0xdb, 0x44, + 0x14, 0xd6, 0xc4, 0x76, 0x12, 0x9f, 0x6d, 0x17, 0x34, 0xaa, 0xa8, 0x29, 0x12, 0x0a, 0x16, 0x48, + 0x01, 0x89, 0x05, 0xb5, 0x42, 0x42, 0xdc, 0x65, 0x37, 0xa8, 0x0a, 0xbb, 0x2d, 0xcb, 0x64, 0x77, + 0xb9, 0x42, 0xd5, 0x24, 0x39, 0xd9, 0xb5, 0xea, 0xd8, 0x66, 0x6c, 0x6f, 0xe2, 0x57, 0xe0, 0x8a, + 0x27, 0x40, 0x42, 0xe2, 0x8a, 0x4b, 0x5e, 0x80, 0x87, 0xe0, 0x85, 0xd0, 0x99, 0x19, 0xff, 0x84, + 0x6e, 0x51, 0xaf, 0x7a, 0x37, 0xdf, 0x39, 0x93, 0xef, 0x78, 0xbe, 0xf3, 0x9d, 0xa3, 0xc0, 0x61, + 0x94, 0x14, 0xa8, 0x12, 0x19, 0x1f, 0x65, 0x2a, 0x2d, 0x52, 0x3e, 0xac, 0x71, 0xf8, 0x4b, 0x0f, + 0xfa, 0xf3, 0xb4, 0x54, 0x4b, 0xe4, 0x87, 0xd0, 0x9b, 0x4d, 0x03, 0x36, 0x62, 0x63, 0x47, 0xf4, + 0x66, 0x53, 0xce, 0xc1, 0x7d, 0x2e, 0x37, 0x18, 0xf4, 0x46, 0x6c, 0xec, 0x0b, 0x7d, 0xa6, 0xd8, + 0x45, 0x95, 0x61, 0xe0, 0x98, 0x18, 0x9d, 0xf9, 0x23, 0x18, 0x5e, 0xe6, 0xc4, 0xb6, 0xc1, 0xc0, + 0xd5, 0xf1, 0x06, 0x53, 0xee, 0x5c, 0xe6, 0xf9, 0x36, 0x55, 0xab, 0xc0, 0x33, 0xb9, 0x1a, 0xf3, + 0x77, 0xc1, 0xb9, 0x14, 0x67, 0x41, 0x5f, 0x87, 0xe9, 0xc8, 0x03, 0x18, 0x4c, 0x71, 0x2d, 0xcb, + 0xb8, 0x08, 0x06, 0x23, 0x36, 0x1e, 0x8a, 0x1a, 0x12, 0xcf, 0x05, 0xc6, 0x78, 0xad, 0xe4, 0x3a, + 0x18, 0x1a, 0x9e, 0x1a, 0xf3, 0x23, 0xe0, 0xb3, 0x24, 0xc7, 0x65, 0xa9, 0x70, 0xfe, 0x32, 0xca, + 0xae, 0x50, 0x45, 0xeb, 0x2a, 0xf0, 0x35, 0xc1, 0x1d, 0x19, 0xaa, 0xf2, 0x0c, 0x0b, 0x49, 0xb5, + 0x41, 0x53, 0xd5, 0x30, 0xfc, 0x95, 0x81, 0x3f, 0x95, 0xf9, 0xcd, 0x22, 0x95, 0x6a, 0xf5, 0x46, + 0x7a, 0x7c, 0x0e, 0xde, 0x12, 0xe3, 0x38, 0x0f, 0x9c, 0x91, 0x33, 0x3e, 0x78, 0xfc, 0xf0, 0xa8, + 0x11, 0xba, 0xe1, 0x39, 0xc1, 0x38, 0x16, 0xe6, 0x16, 0xff, 0x12, 0xfc, 0x02, 0x37, 0x59, 0x2c, + 0x0b, 0xcc, 0x03, 0x57, 0xff, 0x84, 0xb7, 0x3f, 0xb9, 0xb0, 0x29, 0xd1, 0x5e, 0x0a, 0xff, 0xec, + 0xc1, 0xfd, 0x3d, 0x2a, 0x7e, 0x0f, 0xd8, 0x4e, 0x7f, 0x95, 0x27, 0xd8, 0x8e, 0x50, 0xa5, 0xbf, + 0xc8, 0x13, 0xac, 0x22, 0xb4, 0xd5, 0xbd, 0xf1, 0x04, 0xdb, 0x12, 0xba, 0xd1, 0x1d, 0xf1, 0x04, + 0xbb, 0xe1, 0x9f, 0xc2, 0xe0, 0xe7, 0x12, 0x55, 0x84, 0x79, 0xe0, 0xe9, 0xca, 0xef, 0xb4, 0x95, + 0x7f, 0x28, 0x51, 0x55, 0xa2, 0xce, 0xd3, 0x4b, 0x75, 0x37, 0x4d, 0x6b, 0xf4, 0x99, 0x62, 0x05, + 0x75, 0x7e, 0x60, 0x62, 0x74, 0xb6, 0x0a, 0x99, 0x7e, 0x90, 0x42, 0x5f, 0x81, 0x2b, 0x77, 0x98, + 0x07, 0xbe, 0xe6, 0xff, 0xe8, 0x35, 0x62, 0x1c, 0x4d, 0x76, 0x98, 0x7f, 0x9b, 0x14, 0xaa, 0x12, + 0xfa, 0xfa, 0xa3, 0xa7, 0xe0, 0x37, 0x21, 0x72, 0xc5, 0x4b, 0xac, 0xf4, 0x03, 0x7d, 0x41, 0x47, + 0xfe, 0x31, 0x78, 0xb7, 0x32, 0x2e, 0x8d, 0xf0, 0x07, 0x8f, 0x0f, 0x5b, 0xda, 0xc9, 0x2e, 0xca, + 0x85, 0x49, 0x7e, 0xd3, 0xfb, 0x9a, 0x85, 0x1f, 0x82, 0x4b, 0x21, 0xfe, 0x1e, 0xf4, 0x17, 0x69, + 0x99, 0xac, 0xf2, 0x80, 0x8d, 0x9c, 0xb1, 0x23, 0x2c, 0x0a, 0xff, 0x66, 0x64, 0x23, 0x23, 0x6d, + 0xa7, 0xbd, 0xe6, 0xe3, 0xdf, 0x87, 0x21, 0xc9, 0xfe, 0xe2, 0x56, 0x2a, 0xdb, 0xe2, 0x01, 0xe1, + 0x2b, 0xa9, 0xf8, 0x17, 0xd0, 0xd7, 0x45, 0xee, 0x68, 0x73, 0x4d, 0x77, 0x45, 0x79, 0x61, 0xaf, + 0x35, 0x62, 0xb9, 0x1d, 0xb1, 0x1e, 0x80, 0x17, 0xcb, 0x05, 0xc6, 0x76, 0x0e, 0x0c, 0x20, 0x03, + 0x91, 0xea, 0x95, 0xd6, 0xfa, 0x4e, 0x66, 0xd3, 0x1b, 0x73, 0x2b, 0xbc, 0x84, 0xfb, 0x7b, 0x15, + 0x9b, 0x4a, 0x6c, 0xbf, 0x52, 0x2b, 0x98, 0x6f, 0x05, 0xa2, 0x11, 0xca, 0x31, 0xc6, 0x65, 0x81, + 0x2b, 0x6d, 0x91, 0xa1, 0x68, 0x70, 0xf8, 0x3b, 0x6b, 0x79, 0x75, 0x3d, 0x1a, 0x92, 0x65, 0xba, + 0xd9, 0xc8, 0x64, 0x65, 0xa9, 0x6b, 0x48, 0xba, 0xad, 0x16, 0x96, 0xba, 0xb7, 0x5a, 0x10, 0x56, + 0x99, 0x5d, 0x08, 0x3d, 0x95, 0xf1, 0x11, 0x1c, 0x6c, 0x50, 0xe6, 0xa5, 0xc2, 0x0d, 0x26, 0x85, + 0x95, 0xa0, 0x1b, 0xe2, 0x0f, 0x61, 0x50, 0xc8, 0xeb, 0x17, 0xd4, 0x66, 0xa3, 0x45, 0xbf, 0x90, + 0xd7, 0xa7, 0x58, 0xf1, 0x0f, 0xc0, 0x5f, 0x47, 0x18, 0xaf, 0x74, 0xca, 0x98, 0x6f, 0xa8, 0x03, + 0xa7, 0x58, 0x85, 0x7f, 0x30, 0xe8, 0xcf, 0x51, 0xdd, 0xa2, 0x7a, 0xa3, 0xc9, 0xec, 0x6e, 0x25, + 0xe7, 0x7f, 0xb6, 0x92, 0x7b, 0xf7, 0x56, 0xf2, 0xda, 0xad, 0xf4, 0x00, 0xbc, 0xb9, 0x5a, 0xce, + 0xa6, 0xfa, 0x8b, 0x1c, 0x61, 0x00, 0x79, 0x6c, 0xb2, 0x2c, 0xa2, 0x5b, 0xb4, 0xab, 0xca, 0xa2, + 0xf0, 0x37, 0x06, 0xfd, 0x33, 0x59, 0xa5, 0x65, 0xf1, 0x8a, 0xc3, 0x46, 0x70, 0x30, 0xc9, 0xb2, + 0x38, 0x5a, 0xca, 0x22, 0x4a, 0x13, 0xfb, 0xb5, 0xdd, 0x10, 0xdd, 0x78, 0xd6, 0xd1, 0xce, 0x7c, + 0x77, 0x37, 0x44, 0xc3, 0x70, 0xa2, 0x17, 0x8e, 0xd9, 0x1e, 0x9d, 0x61, 0x30, 0x7b, 0x46, 0x27, + 0xe9, 0x81, 0x93, 0xb2, 0x48, 0xd7, 0x71, 0xba, 0xd5, 0x2f, 0x19, 0x8a, 0x06, 0x87, 0xff, 0x30, + 0x70, 0xdf, 0xd6, 0x22, 0xb9, 0x07, 0x2c, 0xb2, 0x8d, 0x64, 0x51, 0xb3, 0x56, 0x06, 0x9d, 0xb5, + 0x12, 0xc0, 0xa0, 0x52, 0x32, 0xb9, 0xc6, 0x3c, 0x18, 0xea, 0x59, 0xad, 0xa1, 0xce, 0xe8, 0x19, + 0x31, 0xfb, 0xc4, 0x17, 0x35, 0x6c, 0x3c, 0x0f, 0xad, 0xe7, 0xc3, 0xbf, 0x18, 0x78, 0x8d, 0x73, + 0x4f, 0xf6, 0x9d, 0x7b, 0xd2, 0x3a, 0x77, 0x7a, 0x5c, 0x3b, 0x77, 0x7a, 0x4c, 0x58, 0x9c, 0xd7, + 0xce, 0x15, 0xe7, 0xa4, 0xda, 0x53, 0x95, 0x96, 0xd9, 0x71, 0x65, 0xe4, 0xf5, 0x45, 0x83, 0xa9, + 0xdd, 0x3f, 0xde, 0xa0, 0xb2, 0x6f, 0xf6, 0x85, 0x45, 0x64, 0x8e, 0x33, 0x3d, 0xd5, 0xe6, 0x95, + 0x06, 0xf0, 0x4f, 0xc0, 0x13, 0xf4, 0x0a, 0xfd, 0xd4, 0x3d, 0x81, 0x74, 0x58, 0x98, 0x6c, 0xf8, + 0xc4, 0x5e, 0x23, 0x96, 0xcb, 0x2c, 0x43, 0x65, 0x3d, 0x6d, 0x80, 0xe6, 0x4e, 0xb7, 0x68, 0xd6, + 0x91, 0x23, 0x0c, 0x08, 0x7f, 0x02, 0x7f, 0x12, 0xa3, 0x2a, 0x44, 0x19, 0xbf, 0xba, 0xc4, 0x38, + 0xb8, 0xdf, 0xcd, 0xbf, 0x7f, 0x5e, 0x4f, 0x02, 0x9d, 0x5b, 0xff, 0x3a, 0xff, 0xf1, 0xef, 0xa9, + 0xcc, 0xe4, 0x6c, 0xaa, 0x1b, 0xeb, 0x08, 0x8b, 0xc2, 0xcf, 0xc0, 0xa5, 0x39, 0xe9, 0x30, 0xbb, + 0xaf, 0x9b, 0xb1, 0x45, 0x5f, 0xff, 0x9b, 0x78, 0xf2, 0x6f, 0x00, 0x00, 0x00, 0xff, 0xff, 0xa1, + 0xf4, 0x45, 0x04, 0x5f, 0x08, 0x00, 0x00, } diff --git a/bolt/internal/internal_test.go b/bolt/internal/internal_test.go index 77efbb9d9..5c0a3bfd9 100644 --- a/bolt/internal/internal_test.go +++ b/bolt/internal/internal_test.go @@ -4,6 +4,7 @@ import ( "reflect" "testing" + "github.com/google/go-cmp/cmp" "github.com/influxdata/chronograf" "github.com/influxdata/chronograf/bolt/internal" ) @@ -104,3 +105,45 @@ func TestMarshalLayout(t *testing.T) { t.Fatalf("source protobuf copy error: got %#v, expected %#v", vv, layout) } } + +func Test_MarshalDashboard(t *testing.T) { + dashboard := chronograf.Dashboard{ + ID: 1, + Cells: []chronograf.DashboardCell{ + { + ID: "9b5367de-c552-4322-a9e8-7f384cbd235c", + X: 0, + Y: 0, + W: 4, + H: 4, + Name: "Super awesome query", + Queries: []chronograf.DashboardQuery{ + { + Command: "select * from cpu", + Label: "CPU Utilization", + Range: &chronograf.Range{ + Upper: int64(100), + }, + }, + }, + Axes: map[string]chronograf.Axis{ + "y": chronograf.Axis{ + Bounds: [2]int64{0, 100}, + }, + }, + Type: "line", + }, + }, + Templates: []chronograf.Template{}, + Name: "Dashboard", + } + + var actual chronograf.Dashboard + if buf, err := internal.MarshalDashboard(dashboard); err != nil { + t.Fatal("Error marshaling dashboard: err", err) + } else if err := internal.UnmarshalDashboard(buf, &actual); err != nil { + t.Fatal("Error unmarshaling dashboard: err:", err) + } else if !cmp.Equal(dashboard, actual) { + t.Fatalf("Dashboard protobuf copy error: diff follows:\n%s", cmp.Diff(dashboard, actual)) + } +} diff --git a/vendor/github.com/google/go-cmp/.travis.yml b/vendor/github.com/google/go-cmp/.travis.yml new file mode 100644 index 000000000..9d9b7f916 --- /dev/null +++ b/vendor/github.com/google/go-cmp/.travis.yml @@ -0,0 +1,18 @@ +sudo: false +language: go +go: + - 1.x + - master +matrix: + include: + - go: 1.6.x + script: go test -v -race ./... + allow_failures: + - go: master + fast_finish: true +install: + - # Do nothing. This is needed to prevent default install action "go get -t -v ./..." from happening here (it is intended for this package to have no dependencies other than the standard library). +script: + - diff -u <(echo -n) <(gofmt -d -s .) + - go tool vet . + - go test -v -race ./... diff --git a/vendor/github.com/google/go-cmp/CONTRIBUTING.md b/vendor/github.com/google/go-cmp/CONTRIBUTING.md new file mode 100644 index 000000000..ae319c70a --- /dev/null +++ b/vendor/github.com/google/go-cmp/CONTRIBUTING.md @@ -0,0 +1,23 @@ +# How to Contribute + +We'd love to accept your patches and contributions to this project. There are +just a few small guidelines you need to follow. + +## Contributor License Agreement + +Contributions to this project must be accompanied by a Contributor License +Agreement. You (or your employer) retain the copyright to your contribution, +this simply gives us permission to use and redistribute your contributions as +part of the project. Head over to to see +your current agreements on file or to sign a new one. + +You generally only need to submit a CLA once, so if you've already submitted one +(even if it was for a different project), you probably don't need to do it +again. + +## Code reviews + +All submissions, including submissions by project members, require review. We +use GitHub pull requests for this purpose. Consult +[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more +information on using pull requests. diff --git a/vendor/github.com/google/go-cmp/LICENSE b/vendor/github.com/google/go-cmp/LICENSE new file mode 100644 index 000000000..32017f8fa --- /dev/null +++ b/vendor/github.com/google/go-cmp/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2017 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/google/go-cmp/README.md b/vendor/github.com/google/go-cmp/README.md new file mode 100644 index 000000000..41562b6da --- /dev/null +++ b/vendor/github.com/google/go-cmp/README.md @@ -0,0 +1,41 @@ +# Package for equality of Go values + +[![GoDoc](https://godoc.org/github.com/google/go-cmp/cmp?status.svg)][godoc] +[![Build Status](https://travis-ci.org/google/go-cmp.svg?branch=master)][travis] + +This package is intended to be a more powerful and safer alternative to +`reflect.DeepEqual` for comparing whether two values are semantically equal. + +The primary features of cmp are: + +* When the default behavior of equality does not suit the needs of the test, + custom equality functions can override the equality operation. + For example, an equality function may report floats as equal so long as they + are within some tolerance of each other. + +* Types that have an `Equal` method may use that method to determine equality. + This allows package authors to determine the equality operation for the types + that they define. + +* If no custom equality functions are used and no `Equal` method is defined, + equality is determined by recursively comparing the primitive kinds on both + values, much like `reflect.DeepEqual`. Unlike `reflect.DeepEqual`, unexported + fields are not compared; they result in panics unless suppressed by using + an `Ignore` option. + +This is not an official Google product. + +[godoc]: https://godoc.org/github.com/google/go-cmp/cmp +[travis]: https://travis-ci.org/google/go-cmp + +## Install + +``` +go get -u github.com/google/go-cmp/cmp +``` + +## License + +BSD - See [LICENSE][license] file + +[license]: https://github.com/google/go-cmp/blob/master/LICENSE diff --git a/vendor/github.com/google/go-cmp/cmp/compare.go b/vendor/github.com/google/go-cmp/cmp/compare.go new file mode 100644 index 000000000..694a9332f --- /dev/null +++ b/vendor/github.com/google/go-cmp/cmp/compare.go @@ -0,0 +1,519 @@ +// Copyright 2017, The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE.md file. + +// Package cmp determines equality of values. +// +// This package is intended to be a more powerful and safer alternative to +// reflect.DeepEqual for comparing whether two values are semantically equal. +// +// The primary features of cmp are: +// +// • When the default behavior of equality does not suit the needs of the test, +// custom equality functions can override the equality operation. +// For example, an equality function may report floats as equal so long as they +// are within some tolerance of each other. +// +// • Types that have an Equal method may use that method to determine equality. +// This allows package authors to determine the equality operation for the types +// that they define. +// +// • If no custom equality functions are used and no Equal method is defined, +// equality is determined by recursively comparing the primitive kinds on both +// values, much like reflect.DeepEqual. Unlike reflect.DeepEqual, unexported +// fields are not compared by default; they result in panics unless suppressed +// by using an Ignore option (see cmpopts.IgnoreUnexported) or explictly compared +// using the AllowUnexported option. +package cmp + +import ( + "fmt" + "reflect" +) + +// BUG: Maps with keys containing NaN values cannot be properly compared due to +// the reflection package's inability to retrieve such entries. Equal will panic +// anytime it comes across a NaN key, but this behavior may change. +// +// See https://golang.org/issue/11104 for more details. + +// Equal reports whether x and y are equal by recursively applying the +// following rules in the given order to x and y and all of their sub-values: +// +// • If two values are not of the same type, then they are never equal +// and the overall result is false. +// +// • Let S be the set of all Ignore, Transformer, and Comparer options that +// remain after applying all path filters, value filters, and type filters. +// If at least one Ignore exists in S, then the comparison is ignored. +// If the number of Transformer and Comparer options in S is greater than one, +// then Equal panics because it is ambiguous which option to use. +// If S contains a single Transformer, then apply that transformer on the +// current values and recursively call Equal on the transformed output values. +// If S contains a single Comparer, then use that Comparer to determine whether +// the current values are equal or not. +// Otherwise, S is empty and evaluation proceeds to the next rule. +// +// • If the values have an Equal method of the form "(T) Equal(T) bool" or +// "(T) Equal(I) bool" where T is assignable to I, then use the result of +// x.Equal(y). Otherwise, no such method exists and evaluation proceeds to +// the next rule. +// +// • Lastly, try to compare x and y based on their basic kinds. +// Simple kinds like booleans, integers, floats, complex numbers, strings, and +// channels are compared using the equivalent of the == operator in Go. +// Functions are only equal if they are both nil, otherwise they are unequal. +// Pointers are equal if the underlying values they point to are also equal. +// Interfaces are equal if their underlying concrete values are also equal. +// +// Structs are equal if all of their fields are equal. If a struct contains +// unexported fields, Equal panics unless the AllowUnexported option is used or +// an Ignore option (e.g., cmpopts.IgnoreUnexported) ignores that field. +// +// Arrays, slices, and maps are equal if they are both nil or both non-nil +// with the same length and the elements at each index or key are equal. +// Note that a non-nil empty slice and a nil slice are not equal. +// To equate empty slices and maps, consider using cmpopts.EquateEmpty. +// Map keys are equal according to the == operator. +// To use custom comparisons for map keys, consider using cmpopts.SortMaps. +func Equal(x, y interface{}, opts ...Option) bool { + s := newState(opts) + s.compareAny(reflect.ValueOf(x), reflect.ValueOf(y)) + return s.eq +} + +// Diff returns a human-readable report of the differences between two values. +// It returns an empty string if and only if Equal returns true for the same +// input values and options. The output string will use the "-" symbol to +// indicate elements removed from x, and the "+" symbol to indicate elements +// added to y. +// +// Do not depend on this output being stable. +func Diff(x, y interface{}, opts ...Option) string { + r := new(defaultReporter) + opts = append(opts[:len(opts):len(opts)], r) // Force copy when appending + eq := Equal(x, y, opts...) + d := r.String() + if (d == "") != eq { + panic("inconsistent difference and equality results") + } + return d +} + +type state struct { + eq bool // Current result of comparison + curPath Path // The current path in the value tree + + // dsCheck tracks the state needed to periodically perform checks that + // user provided func(T, T) bool functions are symmetric and deterministic. + // + // Checks occur every Nth function call, where N is a triangular number: + // 0 1 3 6 10 15 21 28 36 45 55 66 78 91 105 120 136 153 171 190 ... + // See https://en.wikipedia.org/wiki/Triangular_number + // + // This sequence ensures that the cost of checks drops significantly as + // the number of functions calls grows larger. + dsCheck struct{ curr, next int } + + // These fields, once set by processOption, will not change. + exporters map[reflect.Type]bool // Set of structs with unexported field visibility + optsIgn []option // List of all ignore options without value filters + opts []option // List of all other options + reporter reporter // Optional reporter used for difference formatting +} + +func newState(opts []Option) *state { + s := &state{eq: true} + for _, opt := range opts { + s.processOption(opt) + } + // Move Ignore options to the front so that they are evaluated first. + for i, j := 0, 0; i < len(s.opts); i++ { + if s.opts[i].op == nil { + s.opts[i], s.opts[j] = s.opts[j], s.opts[i] + j++ + } + } + return s +} + +func (s *state) processOption(opt Option) { + switch opt := opt.(type) { + case Options: + for _, o := range opt { + s.processOption(o) + } + case visibleStructs: + if s.exporters == nil { + s.exporters = make(map[reflect.Type]bool) + } + for t := range opt { + s.exporters[t] = true + } + case option: + if opt.typeFilter == nil && len(opt.pathFilters)+len(opt.valueFilters) == 0 { + panic(fmt.Sprintf("cannot use an unfiltered option: %v", opt)) + } + if opt.op == nil && len(opt.valueFilters) == 0 { + s.optsIgn = append(s.optsIgn, opt) + } else { + s.opts = append(s.opts, opt) + } + case reporter: + if s.reporter != nil { + panic("difference reporter already registered") + } + s.reporter = opt + default: + panic(fmt.Sprintf("unknown option %T", opt)) + } +} + +func (s *state) compareAny(vx, vy reflect.Value) { + // TODO: Support cyclic data structures. + + // Rule 0: Differing types are never equal. + if !vx.IsValid() || !vy.IsValid() { + s.report(vx.IsValid() == vy.IsValid(), vx, vy) + return + } + if vx.Type() != vy.Type() { + s.report(false, vx, vy) // Possible for path to be empty + return + } + t := vx.Type() + if len(s.curPath) == 0 { + s.curPath.push(&pathStep{typ: t}) + } + + // Rule 1: Check whether an option applies on this node in the value tree. + if s.tryOptions(&vx, &vy, t) { + return + } + + // Rule 2: Check whether the type has a valid Equal method. + if s.tryMethod(vx, vy, t) { + return + } + + // Rule 3: Recursively descend into each value's underlying kind. + switch t.Kind() { + case reflect.Bool: + s.report(vx.Bool() == vy.Bool(), vx, vy) + return + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + s.report(vx.Int() == vy.Int(), vx, vy) + return + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + s.report(vx.Uint() == vy.Uint(), vx, vy) + return + case reflect.Float32, reflect.Float64: + s.report(vx.Float() == vy.Float(), vx, vy) + return + case reflect.Complex64, reflect.Complex128: + s.report(vx.Complex() == vy.Complex(), vx, vy) + return + case reflect.String: + s.report(vx.String() == vy.String(), vx, vy) + return + case reflect.Chan, reflect.UnsafePointer: + s.report(vx.Pointer() == vy.Pointer(), vx, vy) + return + case reflect.Func: + s.report(vx.IsNil() && vy.IsNil(), vx, vy) + return + case reflect.Ptr: + if vx.IsNil() || vy.IsNil() { + s.report(vx.IsNil() && vy.IsNil(), vx, vy) + return + } + s.curPath.push(&indirect{pathStep{t.Elem()}}) + defer s.curPath.pop() + s.compareAny(vx.Elem(), vy.Elem()) + return + case reflect.Interface: + if vx.IsNil() || vy.IsNil() { + s.report(vx.IsNil() && vy.IsNil(), vx, vy) + return + } + if vx.Elem().Type() != vy.Elem().Type() { + s.report(false, vx.Elem(), vy.Elem()) + return + } + s.curPath.push(&typeAssertion{pathStep{vx.Elem().Type()}}) + defer s.curPath.pop() + s.compareAny(vx.Elem(), vy.Elem()) + return + case reflect.Slice: + if vx.IsNil() || vy.IsNil() { + s.report(vx.IsNil() && vy.IsNil(), vx, vy) + return + } + fallthrough + case reflect.Array: + s.compareArray(vx, vy, t) + return + case reflect.Map: + s.compareMap(vx, vy, t) + return + case reflect.Struct: + s.compareStruct(vx, vy, t) + return + default: + panic(fmt.Sprintf("%v kind not handled", t.Kind())) + } +} + +// tryOptions iterates through all of the options and evaluates whether any +// of them can be applied. This may modify the underlying values vx and vy +// if an unexported field is being forcibly exported. +func (s *state) tryOptions(vx, vy *reflect.Value, t reflect.Type) bool { + // Try all ignore options that do not depend on the value first. + // This avoids possible panics when processing unexported fields. + for _, opt := range s.optsIgn { + var v reflect.Value // Dummy value; should never be used + if s.applyFilters(v, v, t, opt) { + return true // Ignore option applied + } + } + + // Since the values must be used after this point, verify that the values + // are either exported or can be forcibly exported. + if sf, ok := s.curPath[len(s.curPath)-1].(*structField); ok && sf.unexported { + if !sf.force { + const help = "consider using AllowUnexported or cmpopts.IgnoreUnexported" + panic(fmt.Sprintf("cannot handle unexported field: %#v\n%s", s.curPath, help)) + } + + // Use unsafe pointer arithmetic to get read-write access to an + // unexported field in the struct. + *vx = unsafeRetrieveField(sf.pvx, sf.field) + *vy = unsafeRetrieveField(sf.pvy, sf.field) + } + + // Try all other options now. + optIdx := -1 // Index of Option to apply + for i, opt := range s.opts { + if !s.applyFilters(*vx, *vy, t, opt) { + continue + } + if opt.op == nil { + return true // Ignored comparison + } + if optIdx >= 0 { + panic(fmt.Sprintf("ambiguous set of options at %#v\n\n%v\n\n%v\n", s.curPath, s.opts[optIdx], opt)) + } + optIdx = i + } + if optIdx >= 0 { + s.applyOption(*vx, *vy, t, s.opts[optIdx]) + return true + } + return false +} + +func (s *state) applyFilters(vx, vy reflect.Value, t reflect.Type, opt option) bool { + if opt.typeFilter != nil { + if !t.AssignableTo(opt.typeFilter) { + return false + } + } + for _, f := range opt.pathFilters { + if !f(s.curPath) { + return false + } + } + for _, f := range opt.valueFilters { + if !t.AssignableTo(f.in) || !s.callFunc(f.fnc, vx, vy) { + return false + } + } + return true +} + +func (s *state) applyOption(vx, vy reflect.Value, t reflect.Type, opt option) { + switch op := opt.op.(type) { + case *transformer: + vx = op.fnc.Call([]reflect.Value{vx})[0] + vy = op.fnc.Call([]reflect.Value{vy})[0] + s.curPath.push(&transform{pathStep{op.fnc.Type().Out(0)}, op}) + defer s.curPath.pop() + s.compareAny(vx, vy) + return + case *comparer: + eq := s.callFunc(op.fnc, vx, vy) + s.report(eq, vx, vy) + return + } +} + +func (s *state) tryMethod(vx, vy reflect.Value, t reflect.Type) bool { + // Check if this type even has an Equal method. + m, ok := t.MethodByName("Equal") + ft := functionType(m.Type) + if !ok || (ft != equalFunc && ft != equalIfaceFunc) { + return false + } + + eq := s.callFunc(m.Func, vx, vy) + s.report(eq, vx, vy) + return true +} + +func (s *state) callFunc(f, x, y reflect.Value) bool { + got := f.Call([]reflect.Value{x, y})[0].Bool() + if s.dsCheck.curr == s.dsCheck.next { + // Swapping the input arguments is sufficient to check that + // f is symmetric and deterministic. + want := f.Call([]reflect.Value{y, x})[0].Bool() + if got != want { + fn := getFuncName(f.Pointer()) + panic(fmt.Sprintf("non-deterministic or non-symmetric function detected: %s", fn)) + } + s.dsCheck.curr = 0 + s.dsCheck.next++ + } + s.dsCheck.curr++ + return got +} + +func (s *state) compareArray(vx, vy reflect.Value, t reflect.Type) { + step := &sliceIndex{pathStep{t.Elem()}, 0} + s.curPath.push(step) + defer s.curPath.pop() + + // Regardless of the lengths, we always try to compare the elements. + // If one slice is longer, we will report the elements of the longer + // slice as different (relative to an invalid reflect.Value). + nmin := vx.Len() + if nmin > vy.Len() { + nmin = vy.Len() + } + for i := 0; i < nmin; i++ { + step.key = i + s.compareAny(vx.Index(i), vy.Index(i)) + } + for i := nmin; i < vx.Len(); i++ { + step.key = i + s.report(false, vx.Index(i), reflect.Value{}) + } + for i := nmin; i < vy.Len(); i++ { + step.key = i + s.report(false, reflect.Value{}, vy.Index(i)) + } +} + +func (s *state) compareMap(vx, vy reflect.Value, t reflect.Type) { + if vx.IsNil() || vy.IsNil() { + s.report(vx.IsNil() && vy.IsNil(), vx, vy) + return + } + + // We combine and sort the two map keys so that we can perform the + // comparisons in a deterministic order. + step := &mapIndex{pathStep: pathStep{t.Elem()}} + s.curPath.push(step) + defer s.curPath.pop() + for _, k := range sortKeys(append(vx.MapKeys(), vy.MapKeys()...)) { + step.key = k + vvx := vx.MapIndex(k) + vvy := vy.MapIndex(k) + switch { + case vvx.IsValid() && vvy.IsValid(): + s.compareAny(vvx, vvy) + case vvx.IsValid() && !vvy.IsValid(): + s.report(false, vvx, reflect.Value{}) + case !vvx.IsValid() && vvy.IsValid(): + s.report(false, reflect.Value{}, vvy) + default: + // It is possible for both vvx and vvy to be invalid if the + // key contained a NaN value in it. There is no way in + // reflection to be able to retrieve these values. + // See https://golang.org/issue/11104 + panic(fmt.Sprintf("%#v has map key with NaNs", s.curPath)) + } + } +} + +func (s *state) compareStruct(vx, vy reflect.Value, t reflect.Type) { + var vax, vay reflect.Value // Addressable versions of vx and vy + + step := &structField{} + s.curPath.push(step) + defer s.curPath.pop() + for i := 0; i < t.NumField(); i++ { + vvx := vx.Field(i) + vvy := vy.Field(i) + step.typ = t.Field(i).Type + step.name = t.Field(i).Name + step.idx = i + step.unexported = !isExported(step.name) + if step.unexported { + // Defer checking of unexported fields until later to give an + // Ignore a chance to ignore the field. + if !vax.IsValid() || !vay.IsValid() { + // For unsafeRetrieveField to work, the parent struct must + // be addressable. Create a new copy of the values if + // necessary to make them addressable. + vax = makeAddressable(vx) + vay = makeAddressable(vy) + } + step.force = s.exporters[t] + step.pvx = vax + step.pvy = vay + step.field = t.Field(i) + } + s.compareAny(vvx, vvy) + } +} + +// report records the result of a single comparison. +// It also calls Report if any reporter is registered. +func (s *state) report(eq bool, vx, vy reflect.Value) { + s.eq = s.eq && eq + if s.reporter != nil { + s.reporter.Report(vx, vy, eq, s.curPath) + } +} + +// makeAddressable returns a value that is always addressable. +// It returns the input verbatim if it is already addressable, +// otherwise it creates a new value and returns an addressable copy. +func makeAddressable(v reflect.Value) reflect.Value { + if v.CanAddr() { + return v + } + vc := reflect.New(v.Type()).Elem() + vc.Set(v) + return vc +} + +type funcType int + +const ( + invalidFunc funcType = iota + equalFunc // func(T, T) bool + equalIfaceFunc // func(T, I) bool + transformFunc // func(T) R + valueFilterFunc = equalFunc // func(T, T) bool +) + +var boolType = reflect.TypeOf(true) + +// functionType identifies which type of function signature this is. +func functionType(t reflect.Type) funcType { + if t == nil || t.Kind() != reflect.Func || t.IsVariadic() { + return invalidFunc + } + ni, no := t.NumIn(), t.NumOut() + switch { + case ni == 2 && no == 1 && t.In(0) == t.In(1) && t.Out(0) == boolType: + return equalFunc // or valueFilterFunc + case ni == 2 && no == 1 && t.In(0).AssignableTo(t.In(1)) && t.Out(0) == boolType: + return equalIfaceFunc + case ni == 1 && no == 1: + return transformFunc + default: + return invalidFunc + } +} diff --git a/vendor/github.com/google/go-cmp/cmp/compare_test.go b/vendor/github.com/google/go-cmp/cmp/compare_test.go new file mode 100644 index 000000000..113e50ec5 --- /dev/null +++ b/vendor/github.com/google/go-cmp/cmp/compare_test.go @@ -0,0 +1,1775 @@ +// Copyright 2017, The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE.md file. + +package cmp_test + +import ( + "bytes" + "fmt" + "io" + "math/rand" + "reflect" + "regexp" + "sort" + "strings" + "sync" + "testing" + "time" + "unicode" + "unicode/utf8" + + "github.com/google/go-cmp/cmp" + pb "github.com/google/go-cmp/cmp/internal/testprotos" + ts "github.com/google/go-cmp/cmp/internal/teststructs" +) + +var now = time.Now() +var boolType = reflect.TypeOf(true) +var mutexType = reflect.TypeOf(sync.Mutex{}) + +func intPtr(n int) *int { return &n } + +func equalRegexp(x, y *regexp.Regexp) bool { + if x == nil || y == nil { + return x == nil && y == nil + } + return x.String() == y.String() +} + +func IgnoreUnexported(typs ...interface{}) cmp.Option { + m := make(map[reflect.Type]bool) + for _, typ := range typs { + t := reflect.TypeOf(typ) + if t.Kind() != reflect.Struct { + panic(fmt.Sprintf("invalid struct type: %T", typ)) + } + m[t] = true + } + return cmp.FilterPath(func(p cmp.Path) bool { + if len(p) < 2 { + return false + } + sf, ok := p[len(p)-1].(cmp.StructField) + if !ok { + return false + } + return m[p[len(p)-2].Type()] && !isExported(sf.Name()) + }, cmp.Ignore()) +} + +func isExported(id string) bool { + r, _ := utf8.DecodeRuneInString(id) + return unicode.IsUpper(r) +} + +type test struct { + label string // Test description + x, y interface{} // Input values to compare + opts []cmp.Option // Input options + wantDiff string // The exact difference string + wantPanic string // Sub-string of an expected panic message +} + +func TestDiff(t *testing.T) { + var tests []test + tests = append(tests, comparerTests()...) + tests = append(tests, transformerTests()...) + tests = append(tests, embeddedTests()...) + tests = append(tests, methodTests()...) + tests = append(tests, project1Tests()...) + tests = append(tests, project2Tests()...) + tests = append(tests, project3Tests()...) + tests = append(tests, project4Tests()...) + + for _, tt := range tests { + tRun(t, tt.label, func(t *testing.T) { + var gotDiff, gotPanic string + func() { + defer func() { + if ex := recover(); ex != nil { + if s, ok := ex.(string); ok { + gotPanic = s + } else { + panic(ex) + } + } + }() + gotDiff = cmp.Diff(tt.x, tt.y, tt.opts...) + }() + if tt.wantPanic == "" { + if gotPanic != "" { + t.Fatalf("unexpected panic message: %s", gotPanic) + } + if strings.TrimSpace(gotDiff) != strings.TrimSpace(tt.wantDiff) { + t.Fatalf("difference message:\ngot:\n%s\nwant:\n%s", gotDiff, tt.wantDiff) + } + } else { + if !strings.Contains(gotPanic, tt.wantPanic) { + t.Fatalf("panic message:\ngot: %s\nwant: %s", gotPanic, tt.wantPanic) + } + } + }) + } +} + +func comparerTests() []test { + const label = "Comparer" + + return []test{{ + label: label, + x: 1, + y: 1, + wantDiff: "", + }, { + label: label, + x: 1, + y: 1, + opts: []cmp.Option{cmp.Ignore()}, + wantPanic: "cannot use an unfiltered option", + }, { + label: label, + x: 1, + y: 1, + opts: []cmp.Option{cmp.Comparer(func(_, _ interface{}) bool { return true })}, + wantPanic: "cannot use an unfiltered option", + }, { + label: label, + x: 1, + y: 1, + opts: []cmp.Option{cmp.Transformer("", func(x interface{}) interface{} { return x })}, + wantPanic: "cannot use an unfiltered option", + }, { + label: label, + x: 1, + y: 1, + opts: []cmp.Option{ + cmp.Comparer(func(x, y int) bool { return true }), + cmp.Transformer("", func(x int) float64 { return float64(x) }), + }, + wantPanic: "ambiguous set of options", + }, { + label: label, + x: 1, + y: 1, + opts: []cmp.Option{ + cmp.FilterPath(func(p cmp.Path) bool { + return len(p) > 0 && p[len(p)-1].Type().Kind() == reflect.Int + }, cmp.Options{cmp.Ignore(), cmp.Ignore(), cmp.Ignore()}), + cmp.Comparer(func(x, y int) bool { return true }), + cmp.Transformer("", func(x int) float64 { return float64(x) }), + }, + }, { + label: label, + opts: []cmp.Option{struct{ cmp.Option }{}}, + wantPanic: "unknown option", + }, { + label: label, + x: struct{ A, B, C int }{1, 2, 3}, + y: struct{ A, B, C int }{1, 2, 3}, + wantDiff: "", + }, { + label: label, + x: struct{ A, B, C int }{1, 2, 3}, + y: struct{ A, B, C int }{1, 2, 4}, + wantDiff: "root.C:\n\t-: 3\n\t+: 4\n", + }, { + label: label, + x: struct{ a, b, c int }{1, 2, 3}, + y: struct{ a, b, c int }{1, 2, 4}, + wantPanic: "cannot handle unexported field", + }, { + label: label, + x: &struct{ A *int }{intPtr(4)}, + y: &struct{ A *int }{intPtr(4)}, + }, { + label: label, + x: &struct{ A *int }{intPtr(4)}, + y: &struct{ A *int }{intPtr(5)}, + wantDiff: "*root.A:\n\t-: 4\n\t+: 5\n", + }, { + label: label, + x: &struct{ A *int }{intPtr(4)}, + y: &struct{ A *int }{intPtr(5)}, + opts: []cmp.Option{ + cmp.Comparer(func(x, y int) bool { return true }), + }, + }, { + label: label, + x: &struct{ A *int }{intPtr(4)}, + y: &struct{ A *int }{intPtr(5)}, + opts: []cmp.Option{ + cmp.Comparer(func(x, y *int) bool { return x != nil && y != nil }), + }, + }, { + label: label, + x: &struct{ R *bytes.Buffer }{}, + y: &struct{ R *bytes.Buffer }{}, + }, { + label: label, + x: &struct{ R *bytes.Buffer }{new(bytes.Buffer)}, + y: &struct{ R *bytes.Buffer }{}, + wantDiff: "root.R:\n\t-: \"\"\n\t+: \n", + }, { + label: label, + x: &struct{ R *bytes.Buffer }{new(bytes.Buffer)}, + y: &struct{ R *bytes.Buffer }{}, + opts: []cmp.Option{ + cmp.Comparer(func(x, y io.Reader) bool { return true }), + }, + }, { + label: label, + x: &struct{ R bytes.Buffer }{}, + y: &struct{ R bytes.Buffer }{}, + wantPanic: "cannot handle unexported field", + }, { + label: label, + x: &struct{ R bytes.Buffer }{}, + y: &struct{ R bytes.Buffer }{}, + opts: []cmp.Option{ + cmp.Comparer(func(x, y io.Reader) bool { return true }), + }, + wantPanic: "cannot handle unexported field", + }, { + label: label, + x: &struct{ R bytes.Buffer }{}, + y: &struct{ R bytes.Buffer }{}, + opts: []cmp.Option{ + cmp.Transformer("Ref", func(x bytes.Buffer) *bytes.Buffer { return &x }), + cmp.Comparer(func(x, y io.Reader) bool { return true }), + }, + }, { + label: label, + x: []*regexp.Regexp{nil, regexp.MustCompile("a*b*c*")}, + y: []*regexp.Regexp{nil, regexp.MustCompile("a*b*c*")}, + wantPanic: "cannot handle unexported field", + }, { + label: label, + x: []*regexp.Regexp{nil, regexp.MustCompile("a*b*c*")}, + y: []*regexp.Regexp{nil, regexp.MustCompile("a*b*c*")}, + opts: []cmp.Option{cmp.Comparer(equalRegexp)}, + wantDiff: "", + }, { + label: label, + x: []*regexp.Regexp{nil, regexp.MustCompile("a*b*c*")}, + y: []*regexp.Regexp{nil, regexp.MustCompile("a*b*d*")}, + opts: []cmp.Option{cmp.Comparer(equalRegexp)}, + wantDiff: "{[]*regexp.Regexp}[1]:\n\t-: \"a*b*c*\"\n\t+: \"a*b*d*\"\n", + }, { + label: label, + x: func() ***int { + a := 0 + b := &a + c := &b + return &c + }(), + y: func() ***int { + a := 0 + b := &a + c := &b + return &c + }(), + }, { + label: label, + x: func() ***int { + a := 0 + b := &a + c := &b + return &c + }(), + y: func() ***int { + a := 1 + b := &a + c := &b + return &c + }(), + wantDiff: ` +***{***int}: + -: 0 + +: 1`, + }, { + label: label, + x: []int{1, 2, 3, 4, 5}[:3], + y: []int{1, 2, 3}, + }, { + label: label, + x: struct{ fmt.Stringer }{bytes.NewBufferString("hello")}, + y: struct{ fmt.Stringer }{regexp.MustCompile("hello")}, + opts: []cmp.Option{cmp.Comparer(func(x, y fmt.Stringer) bool { return x.String() == y.String() })}, + }, { + label: label, + x: struct{ fmt.Stringer }{bytes.NewBufferString("hello")}, + y: struct{ fmt.Stringer }{regexp.MustCompile("hello2")}, + opts: []cmp.Option{cmp.Comparer(func(x, y fmt.Stringer) bool { return x.String() == y.String() })}, + wantDiff: ` +root: + -: "hello" + +: "hello2"`, + }, { + label: label, + x: make([]int, 1000), + y: make([]int, 1000), + opts: []cmp.Option{ + cmp.Comparer(func(_, _ int) bool { + return rand.Intn(2) == 0 + }), + }, + wantPanic: "non-deterministic or non-symmetric function detected", + }, { + label: label, + x: make([]int, 1000), + y: make([]int, 1000), + opts: []cmp.Option{ + cmp.FilterValues(func(_, _ int) bool { + return rand.Intn(2) == 0 + }, cmp.Ignore()), + }, + wantPanic: "non-deterministic or non-symmetric function detected", + }} +} + +func transformerTests() []test { + const label = "Transformer/" + + return []test{{ + label: label, + x: uint8(0), + y: uint8(1), + opts: []cmp.Option{ + cmp.Transformer("", func(in uint8) uint16 { return uint16(in) }), + cmp.Transformer("", func(in uint16) uint32 { return uint32(in) }), + cmp.Transformer("", func(in uint32) uint64 { return uint64(in) }), + }, + wantDiff: ` +λ(λ(λ({uint8}))): + -: 0x00 + +: 0x01`, + }, { + label: label, + x: 0, + y: 1, + opts: []cmp.Option{ + cmp.Transformer("", func(in int) int { return in / 2 }), + cmp.Transformer("", func(in int) int { return in }), + }, + wantPanic: "ambiguous set of options", + }, { + label: label, + x: []int{0, -5, 0, -1}, + y: []int{1, 3, 0, -5}, + opts: []cmp.Option{ + cmp.FilterValues( + func(x, y int) bool { return x+y >= 0 }, + cmp.Transformer("", func(in int) int64 { return int64(in / 2) }), + ), + cmp.FilterValues( + func(x, y int) bool { return x+y < 0 }, + cmp.Transformer("", func(in int) int64 { return int64(in) }), + ), + }, + wantDiff: ` +λ({[]int}[1]): + -: -5 + +: 3 +λ({[]int}[3]): + -: -1 + +: -5`, + }, { + label: label, + x: 0, + y: 1, + opts: []cmp.Option{ + cmp.Transformer("", func(in int) interface{} { + if in == 0 { + return "string" + } + return in + }), + }, + wantDiff: ` +λ({int}): + -: "string" + +: 1`, + }} +} + +func embeddedTests() []test { + const label = "EmbeddedStruct/" + + privateStruct := *new(ts.ParentStructA).PrivateStruct() + + createStructA := func(i int) ts.ParentStructA { + s := ts.ParentStructA{} + s.PrivateStruct().Public = 1 + i + s.PrivateStruct().SetPrivate(2 + i) + return s + } + + createStructB := func(i int) ts.ParentStructB { + s := ts.ParentStructB{} + s.PublicStruct.Public = 1 + i + s.PublicStruct.SetPrivate(2 + i) + return s + } + + createStructC := func(i int) ts.ParentStructC { + s := ts.ParentStructC{} + s.PrivateStruct().Public = 1 + i + s.PrivateStruct().SetPrivate(2 + i) + s.Public = 3 + i + s.SetPrivate(4 + i) + return s + } + + createStructD := func(i int) ts.ParentStructD { + s := ts.ParentStructD{} + s.PublicStruct.Public = 1 + i + s.PublicStruct.SetPrivate(2 + i) + s.Public = 3 + i + s.SetPrivate(4 + i) + return s + } + + createStructE := func(i int) ts.ParentStructE { + s := ts.ParentStructE{} + s.PrivateStruct().Public = 1 + i + s.PrivateStruct().SetPrivate(2 + i) + s.PublicStruct.Public = 3 + i + s.PublicStruct.SetPrivate(4 + i) + return s + } + + createStructF := func(i int) ts.ParentStructF { + s := ts.ParentStructF{} + s.PrivateStruct().Public = 1 + i + s.PrivateStruct().SetPrivate(2 + i) + s.PublicStruct.Public = 3 + i + s.PublicStruct.SetPrivate(4 + i) + s.Public = 5 + i + s.SetPrivate(6 + i) + return s + } + + createStructG := func(i int) *ts.ParentStructG { + s := ts.NewParentStructG() + s.PrivateStruct().Public = 1 + i + s.PrivateStruct().SetPrivate(2 + i) + return s + } + + createStructH := func(i int) *ts.ParentStructH { + s := ts.NewParentStructH() + s.PublicStruct.Public = 1 + i + s.PublicStruct.SetPrivate(2 + i) + return s + } + + createStructI := func(i int) *ts.ParentStructI { + s := ts.NewParentStructI() + s.PrivateStruct().Public = 1 + i + s.PrivateStruct().SetPrivate(2 + i) + s.PublicStruct.Public = 3 + i + s.PublicStruct.SetPrivate(4 + i) + return s + } + + createStructJ := func(i int) *ts.ParentStructJ { + s := ts.NewParentStructJ() + s.PrivateStruct().Public = 1 + i + s.PrivateStruct().SetPrivate(2 + i) + s.PublicStruct.Public = 3 + i + s.PublicStruct.SetPrivate(4 + i) + s.Private().Public = 5 + i + s.Private().SetPrivate(6 + i) + s.Public.Public = 7 + i + s.Public.SetPrivate(8 + i) + return s + } + + return []test{{ + label: label + "ParentStructA", + x: ts.ParentStructA{}, + y: ts.ParentStructA{}, + wantPanic: "cannot handle unexported field", + }, { + label: label + "ParentStructA", + x: ts.ParentStructA{}, + y: ts.ParentStructA{}, + opts: []cmp.Option{ + IgnoreUnexported(ts.ParentStructA{}), + }, + }, { + label: label + "ParentStructA", + x: createStructA(0), + y: createStructA(0), + opts: []cmp.Option{ + cmp.AllowUnexported(ts.ParentStructA{}), + }, + wantPanic: "cannot handle unexported field", + }, { + label: label + "ParentStructA", + x: createStructA(0), + y: createStructA(0), + opts: []cmp.Option{ + cmp.AllowUnexported(ts.ParentStructA{}, privateStruct), + }, + }, { + label: label + "ParentStructA", + x: createStructA(0), + y: createStructA(1), + opts: []cmp.Option{ + cmp.AllowUnexported(ts.ParentStructA{}, privateStruct), + }, + wantDiff: ` +{teststructs.ParentStructA}.privateStruct.Public: + -: 1 + +: 2 +{teststructs.ParentStructA}.privateStruct.private: + -: 2 + +: 3`, + }, { + label: label + "ParentStructB", + x: ts.ParentStructB{}, + y: ts.ParentStructB{}, + opts: []cmp.Option{ + IgnoreUnexported(ts.ParentStructB{}), + }, + wantPanic: "cannot handle unexported field", + }, { + label: label + "ParentStructB", + x: ts.ParentStructB{}, + y: ts.ParentStructB{}, + opts: []cmp.Option{ + IgnoreUnexported(ts.ParentStructB{}), + IgnoreUnexported(ts.PublicStruct{}), + }, + }, { + label: label + "ParentStructB", + x: createStructB(0), + y: createStructB(0), + opts: []cmp.Option{ + cmp.AllowUnexported(ts.ParentStructB{}), + }, + wantPanic: "cannot handle unexported field", + }, { + label: label + "ParentStructB", + x: createStructB(0), + y: createStructB(0), + opts: []cmp.Option{ + cmp.AllowUnexported(ts.ParentStructB{}, ts.PublicStruct{}), + }, + }, { + label: label + "ParentStructB", + x: createStructB(0), + y: createStructB(1), + opts: []cmp.Option{ + cmp.AllowUnexported(ts.ParentStructB{}, ts.PublicStruct{}), + }, + wantDiff: ` +{teststructs.ParentStructB}.PublicStruct.Public: + -: 1 + +: 2 +{teststructs.ParentStructB}.PublicStruct.private: + -: 2 + +: 3`, + }, { + label: label + "ParentStructC", + x: ts.ParentStructC{}, + y: ts.ParentStructC{}, + wantPanic: "cannot handle unexported field", + }, { + label: label + "ParentStructC", + x: ts.ParentStructC{}, + y: ts.ParentStructC{}, + opts: []cmp.Option{ + IgnoreUnexported(ts.ParentStructC{}), + }, + }, { + label: label + "ParentStructC", + x: createStructC(0), + y: createStructC(0), + opts: []cmp.Option{ + cmp.AllowUnexported(ts.ParentStructC{}), + }, + wantPanic: "cannot handle unexported field", + }, { + label: label + "ParentStructC", + x: createStructC(0), + y: createStructC(0), + opts: []cmp.Option{ + cmp.AllowUnexported(ts.ParentStructC{}, privateStruct), + }, + }, { + label: label + "ParentStructC", + x: createStructC(0), + y: createStructC(1), + opts: []cmp.Option{ + cmp.AllowUnexported(ts.ParentStructC{}, privateStruct), + }, + wantDiff: ` +{teststructs.ParentStructC}.privateStruct.Public: + -: 1 + +: 2 +{teststructs.ParentStructC}.privateStruct.private: + -: 2 + +: 3 +{teststructs.ParentStructC}.Public: + -: 3 + +: 4 +{teststructs.ParentStructC}.private: + -: 4 + +: 5`, + }, { + label: label + "ParentStructD", + x: ts.ParentStructD{}, + y: ts.ParentStructD{}, + opts: []cmp.Option{ + IgnoreUnexported(ts.ParentStructD{}), + }, + wantPanic: "cannot handle unexported field", + }, { + label: label + "ParentStructD", + x: ts.ParentStructD{}, + y: ts.ParentStructD{}, + opts: []cmp.Option{ + IgnoreUnexported(ts.ParentStructD{}), + IgnoreUnexported(ts.PublicStruct{}), + }, + }, { + label: label + "ParentStructD", + x: createStructD(0), + y: createStructD(0), + opts: []cmp.Option{ + cmp.AllowUnexported(ts.ParentStructD{}), + }, + wantPanic: "cannot handle unexported field", + }, { + label: label + "ParentStructD", + x: createStructD(0), + y: createStructD(0), + opts: []cmp.Option{ + cmp.AllowUnexported(ts.ParentStructD{}, ts.PublicStruct{}), + }, + }, { + label: label + "ParentStructD", + x: createStructD(0), + y: createStructD(1), + opts: []cmp.Option{ + cmp.AllowUnexported(ts.ParentStructD{}, ts.PublicStruct{}), + }, + wantDiff: ` +{teststructs.ParentStructD}.PublicStruct.Public: + -: 1 + +: 2 +{teststructs.ParentStructD}.PublicStruct.private: + -: 2 + +: 3 +{teststructs.ParentStructD}.Public: + -: 3 + +: 4 +{teststructs.ParentStructD}.private: + -: 4 + +: 5`, + }, { + label: label + "ParentStructE", + x: ts.ParentStructE{}, + y: ts.ParentStructE{}, + opts: []cmp.Option{ + IgnoreUnexported(ts.ParentStructE{}), + }, + wantPanic: "cannot handle unexported field", + }, { + label: label + "ParentStructE", + x: ts.ParentStructE{}, + y: ts.ParentStructE{}, + opts: []cmp.Option{ + IgnoreUnexported(ts.ParentStructE{}), + IgnoreUnexported(ts.PublicStruct{}), + }, + }, { + label: label + "ParentStructE", + x: createStructE(0), + y: createStructE(0), + opts: []cmp.Option{ + cmp.AllowUnexported(ts.ParentStructE{}), + }, + wantPanic: "cannot handle unexported field", + }, { + label: label + "ParentStructE", + x: createStructE(0), + y: createStructE(0), + opts: []cmp.Option{ + cmp.AllowUnexported(ts.ParentStructE{}, ts.PublicStruct{}), + }, + wantPanic: "cannot handle unexported field", + }, { + label: label + "ParentStructE", + x: createStructE(0), + y: createStructE(0), + opts: []cmp.Option{ + cmp.AllowUnexported(ts.ParentStructE{}, ts.PublicStruct{}, privateStruct), + }, + }, { + label: label + "ParentStructE", + x: createStructE(0), + y: createStructE(1), + opts: []cmp.Option{ + cmp.AllowUnexported(ts.ParentStructE{}, ts.PublicStruct{}, privateStruct), + }, + wantDiff: ` +{teststructs.ParentStructE}.privateStruct.Public: + -: 1 + +: 2 +{teststructs.ParentStructE}.privateStruct.private: + -: 2 + +: 3 +{teststructs.ParentStructE}.PublicStruct.Public: + -: 3 + +: 4 +{teststructs.ParentStructE}.PublicStruct.private: + -: 4 + +: 5`, + }, { + label: label + "ParentStructF", + x: ts.ParentStructF{}, + y: ts.ParentStructF{}, + opts: []cmp.Option{ + IgnoreUnexported(ts.ParentStructF{}), + }, + wantPanic: "cannot handle unexported field", + }, { + label: label + "ParentStructF", + x: ts.ParentStructF{}, + y: ts.ParentStructF{}, + opts: []cmp.Option{ + IgnoreUnexported(ts.ParentStructF{}), + IgnoreUnexported(ts.PublicStruct{}), + }, + }, { + label: label + "ParentStructF", + x: createStructF(0), + y: createStructF(0), + opts: []cmp.Option{ + cmp.AllowUnexported(ts.ParentStructF{}), + }, + wantPanic: "cannot handle unexported field", + }, { + label: label + "ParentStructF", + x: createStructF(0), + y: createStructF(0), + opts: []cmp.Option{ + cmp.AllowUnexported(ts.ParentStructF{}, ts.PublicStruct{}), + }, + wantPanic: "cannot handle unexported field", + }, { + label: label + "ParentStructF", + x: createStructF(0), + y: createStructF(0), + opts: []cmp.Option{ + cmp.AllowUnexported(ts.ParentStructF{}, ts.PublicStruct{}, privateStruct), + }, + }, { + label: label + "ParentStructF", + x: createStructF(0), + y: createStructF(1), + opts: []cmp.Option{ + cmp.AllowUnexported(ts.ParentStructF{}, ts.PublicStruct{}, privateStruct), + }, + wantDiff: ` +{teststructs.ParentStructF}.privateStruct.Public: + -: 1 + +: 2 +{teststructs.ParentStructF}.privateStruct.private: + -: 2 + +: 3 +{teststructs.ParentStructF}.PublicStruct.Public: + -: 3 + +: 4 +{teststructs.ParentStructF}.PublicStruct.private: + -: 4 + +: 5 +{teststructs.ParentStructF}.Public: + -: 5 + +: 6 +{teststructs.ParentStructF}.private: + -: 6 + +: 7`, + }, { + label: label + "ParentStructG", + x: ts.ParentStructG{}, + y: ts.ParentStructG{}, + wantPanic: "cannot handle unexported field", + }, { + label: label + "ParentStructG", + x: ts.ParentStructG{}, + y: ts.ParentStructG{}, + opts: []cmp.Option{ + IgnoreUnexported(ts.ParentStructG{}), + }, + }, { + label: label + "ParentStructG", + x: createStructG(0), + y: createStructG(0), + opts: []cmp.Option{ + cmp.AllowUnexported(ts.ParentStructG{}), + }, + wantPanic: "cannot handle unexported field", + }, { + label: label + "ParentStructG", + x: createStructG(0), + y: createStructG(0), + opts: []cmp.Option{ + cmp.AllowUnexported(ts.ParentStructG{}, privateStruct), + }, + }, { + label: label + "ParentStructG", + x: createStructG(0), + y: createStructG(1), + opts: []cmp.Option{ + cmp.AllowUnexported(ts.ParentStructG{}, privateStruct), + }, + wantDiff: ` +{*teststructs.ParentStructG}.privateStruct.Public: + -: 1 + +: 2 +{*teststructs.ParentStructG}.privateStruct.private: + -: 2 + +: 3`, + }, { + label: label + "ParentStructH", + x: ts.ParentStructH{}, + y: ts.ParentStructH{}, + }, { + label: label + "ParentStructH", + x: createStructH(0), + y: createStructH(0), + wantPanic: "cannot handle unexported field", + }, { + label: label + "ParentStructH", + x: ts.ParentStructH{}, + y: ts.ParentStructH{}, + opts: []cmp.Option{ + IgnoreUnexported(ts.ParentStructH{}), + }, + }, { + label: label + "ParentStructH", + x: createStructH(0), + y: createStructH(0), + opts: []cmp.Option{ + cmp.AllowUnexported(ts.ParentStructH{}), + }, + wantPanic: "cannot handle unexported field", + }, { + label: label + "ParentStructH", + x: createStructH(0), + y: createStructH(0), + opts: []cmp.Option{ + cmp.AllowUnexported(ts.ParentStructH{}, ts.PublicStruct{}), + }, + }, { + label: label + "ParentStructH", + x: createStructH(0), + y: createStructH(1), + opts: []cmp.Option{ + cmp.AllowUnexported(ts.ParentStructH{}, ts.PublicStruct{}), + }, + wantDiff: ` +{*teststructs.ParentStructH}.PublicStruct.Public: + -: 1 + +: 2 +{*teststructs.ParentStructH}.PublicStruct.private: + -: 2 + +: 3`, + }, { + label: label + "ParentStructI", + x: ts.ParentStructI{}, + y: ts.ParentStructI{}, + wantPanic: "cannot handle unexported field", + }, { + label: label + "ParentStructI", + x: ts.ParentStructI{}, + y: ts.ParentStructI{}, + opts: []cmp.Option{ + IgnoreUnexported(ts.ParentStructI{}), + }, + }, { + label: label + "ParentStructI", + x: createStructI(0), + y: createStructI(0), + opts: []cmp.Option{ + IgnoreUnexported(ts.ParentStructI{}), + }, + wantPanic: "cannot handle unexported field", + }, { + label: label + "ParentStructI", + x: createStructI(0), + y: createStructI(0), + opts: []cmp.Option{ + IgnoreUnexported(ts.ParentStructI{}, ts.PublicStruct{}), + }, + }, { + label: label + "ParentStructI", + x: createStructI(0), + y: createStructI(0), + opts: []cmp.Option{ + cmp.AllowUnexported(ts.ParentStructI{}), + }, + wantPanic: "cannot handle unexported field", + }, { + label: label + "ParentStructI", + x: createStructI(0), + y: createStructI(0), + opts: []cmp.Option{ + cmp.AllowUnexported(ts.ParentStructI{}, ts.PublicStruct{}, privateStruct), + }, + }, { + label: label + "ParentStructI", + x: createStructI(0), + y: createStructI(1), + opts: []cmp.Option{ + cmp.AllowUnexported(ts.ParentStructI{}, ts.PublicStruct{}, privateStruct), + }, + wantDiff: ` +{*teststructs.ParentStructI}.privateStruct.Public: + -: 1 + +: 2 +{*teststructs.ParentStructI}.privateStruct.private: + -: 2 + +: 3 +{*teststructs.ParentStructI}.PublicStruct.Public: + -: 3 + +: 4 +{*teststructs.ParentStructI}.PublicStruct.private: + -: 4 + +: 5`, + }, { + label: label + "ParentStructJ", + x: ts.ParentStructJ{}, + y: ts.ParentStructJ{}, + wantPanic: "cannot handle unexported field", + }, { + label: label + "ParentStructJ", + x: ts.ParentStructJ{}, + y: ts.ParentStructJ{}, + opts: []cmp.Option{ + IgnoreUnexported(ts.ParentStructJ{}), + }, + wantPanic: "cannot handle unexported field", + }, { + label: label + "ParentStructJ", + x: ts.ParentStructJ{}, + y: ts.ParentStructJ{}, + opts: []cmp.Option{ + IgnoreUnexported(ts.ParentStructJ{}, ts.PublicStruct{}), + }, + }, { + label: label + "ParentStructJ", + x: createStructJ(0), + y: createStructJ(0), + opts: []cmp.Option{ + cmp.AllowUnexported(ts.ParentStructJ{}, ts.PublicStruct{}), + }, + wantPanic: "cannot handle unexported field", + }, { + label: label + "ParentStructJ", + x: createStructJ(0), + y: createStructJ(0), + opts: []cmp.Option{ + cmp.AllowUnexported(ts.ParentStructJ{}, ts.PublicStruct{}, privateStruct), + }, + }, { + label: label + "ParentStructJ", + x: createStructJ(0), + y: createStructJ(1), + opts: []cmp.Option{ + cmp.AllowUnexported(ts.ParentStructJ{}, ts.PublicStruct{}, privateStruct), + }, + wantDiff: ` +{*teststructs.ParentStructJ}.privateStruct.Public: + -: 1 + +: 2 +{*teststructs.ParentStructJ}.privateStruct.private: + -: 2 + +: 3 +{*teststructs.ParentStructJ}.PublicStruct.Public: + -: 3 + +: 4 +{*teststructs.ParentStructJ}.PublicStruct.private: + -: 4 + +: 5 +{*teststructs.ParentStructJ}.Public.Public: + -: 7 + +: 8 +{*teststructs.ParentStructJ}.Public.private: + -: 8 + +: 9 +{*teststructs.ParentStructJ}.private.Public: + -: 5 + +: 6 +{*teststructs.ParentStructJ}.private.private: + -: 6 + +: 7`, + }} +} + +func methodTests() []test { + const label = "EqualMethod/" + + // A common mistake that the Equal method is on a pointer receiver, + // but only a non-pointer value is present in the struct. + // A transform can be used to forcibly reference the value. + derefTransform := cmp.FilterPath(func(p cmp.Path) bool { + if len(p) == 0 { + return false + } + t := p[len(p)-1].Type() + if _, ok := t.MethodByName("Equal"); ok || t.Kind() == reflect.Ptr { + return false + } + if m, ok := reflect.PtrTo(t).MethodByName("Equal"); ok { + tf := m.Func.Type() + return !tf.IsVariadic() && tf.NumIn() == 2 && tf.NumOut() == 1 && + tf.In(0).AssignableTo(tf.In(1)) && tf.Out(0) == boolType + } + return false + }, cmp.Transformer("Ref", func(x interface{}) interface{} { + v := reflect.ValueOf(x) + vp := reflect.New(v.Type()) + vp.Elem().Set(v) + return vp.Interface() + })) + + // For each of these types, there is an Equal method defined, which always + // returns true, while the underlying data are fundamentally different. + // Since the method should be called, these are expected to be equal. + return []test{{ + label: label + "StructA", + x: ts.StructA{"NotEqual"}, + y: ts.StructA{"not_equal"}, + }, { + label: label + "StructA", + x: &ts.StructA{"NotEqual"}, + y: &ts.StructA{"not_equal"}, + }, { + label: label + "StructB", + x: ts.StructB{"NotEqual"}, + y: ts.StructB{"not_equal"}, + wantDiff: ` +{teststructs.StructB}.X: + -: "NotEqual" + +: "not_equal"`, + }, { + label: label + "StructB", + x: ts.StructB{"NotEqual"}, + y: ts.StructB{"not_equal"}, + opts: []cmp.Option{derefTransform}, + }, { + label: label + "StructB", + x: &ts.StructB{"NotEqual"}, + y: &ts.StructB{"not_equal"}, + }, { + label: label + "StructC", + x: ts.StructC{"NotEqual"}, + y: ts.StructC{"not_equal"}, + }, { + label: label + "StructC", + x: &ts.StructC{"NotEqual"}, + y: &ts.StructC{"not_equal"}, + }, { + label: label + "StructD", + x: ts.StructD{"NotEqual"}, + y: ts.StructD{"not_equal"}, + wantDiff: ` +{teststructs.StructD}.X: + -: "NotEqual" + +: "not_equal"`, + }, { + label: label + "StructD", + x: ts.StructD{"NotEqual"}, + y: ts.StructD{"not_equal"}, + opts: []cmp.Option{derefTransform}, + }, { + label: label + "StructD", + x: &ts.StructD{"NotEqual"}, + y: &ts.StructD{"not_equal"}, + }, { + label: label + "StructE", + x: ts.StructE{"NotEqual"}, + y: ts.StructE{"not_equal"}, + wantDiff: ` +{teststructs.StructE}.X: + -: "NotEqual" + +: "not_equal"`, + }, { + label: label + "StructE", + x: ts.StructE{"NotEqual"}, + y: ts.StructE{"not_equal"}, + opts: []cmp.Option{derefTransform}, + }, { + label: label + "StructE", + x: &ts.StructE{"NotEqual"}, + y: &ts.StructE{"not_equal"}, + }, { + label: label + "StructF", + x: ts.StructF{"NotEqual"}, + y: ts.StructF{"not_equal"}, + wantDiff: ` +{teststructs.StructF}.X: + -: "NotEqual" + +: "not_equal"`, + }, { + label: label + "StructF", + x: &ts.StructF{"NotEqual"}, + y: &ts.StructF{"not_equal"}, + }, { + label: label + "StructA1", + x: ts.StructA1{ts.StructA{"NotEqual"}, "equal"}, + y: ts.StructA1{ts.StructA{"not_equal"}, "equal"}, + }, { + label: label + "StructA1", + x: ts.StructA1{ts.StructA{"NotEqual"}, "NotEqual"}, + y: ts.StructA1{ts.StructA{"not_equal"}, "not_equal"}, + wantDiff: "{teststructs.StructA1}.X:\n\t-: \"NotEqual\"\n\t+: \"not_equal\"\n", + }, { + label: label + "StructA1", + x: &ts.StructA1{ts.StructA{"NotEqual"}, "equal"}, + y: &ts.StructA1{ts.StructA{"not_equal"}, "equal"}, + }, { + label: label + "StructA1", + x: &ts.StructA1{ts.StructA{"NotEqual"}, "NotEqual"}, + y: &ts.StructA1{ts.StructA{"not_equal"}, "not_equal"}, + wantDiff: "{*teststructs.StructA1}.X:\n\t-: \"NotEqual\"\n\t+: \"not_equal\"\n", + }, { + label: label + "StructB1", + x: ts.StructB1{ts.StructB{"NotEqual"}, "equal"}, + y: ts.StructB1{ts.StructB{"not_equal"}, "equal"}, + opts: []cmp.Option{derefTransform}, + }, { + label: label + "StructB1", + x: ts.StructB1{ts.StructB{"NotEqual"}, "NotEqual"}, + y: ts.StructB1{ts.StructB{"not_equal"}, "not_equal"}, + opts: []cmp.Option{derefTransform}, + wantDiff: "{teststructs.StructB1}.X:\n\t-: \"NotEqual\"\n\t+: \"not_equal\"\n", + }, { + label: label + "StructB1", + x: &ts.StructB1{ts.StructB{"NotEqual"}, "equal"}, + y: &ts.StructB1{ts.StructB{"not_equal"}, "equal"}, + opts: []cmp.Option{derefTransform}, + }, { + label: label + "StructB1", + x: &ts.StructB1{ts.StructB{"NotEqual"}, "NotEqual"}, + y: &ts.StructB1{ts.StructB{"not_equal"}, "not_equal"}, + opts: []cmp.Option{derefTransform}, + wantDiff: "{*teststructs.StructB1}.X:\n\t-: \"NotEqual\"\n\t+: \"not_equal\"\n", + }, { + label: label + "StructC1", + x: ts.StructC1{ts.StructC{"NotEqual"}, "NotEqual"}, + y: ts.StructC1{ts.StructC{"not_equal"}, "not_equal"}, + }, { + label: label + "StructC1", + x: &ts.StructC1{ts.StructC{"NotEqual"}, "NotEqual"}, + y: &ts.StructC1{ts.StructC{"not_equal"}, "not_equal"}, + }, { + label: label + "StructD1", + x: ts.StructD1{ts.StructD{"NotEqual"}, "NotEqual"}, + y: ts.StructD1{ts.StructD{"not_equal"}, "not_equal"}, + wantDiff: ` +{teststructs.StructD1}.StructD.X: + -: "NotEqual" + +: "not_equal" +{teststructs.StructD1}.X: + -: "NotEqual" + +: "not_equal"`, + }, { + label: label + "StructD1", + x: ts.StructD1{ts.StructD{"NotEqual"}, "NotEqual"}, + y: ts.StructD1{ts.StructD{"not_equal"}, "not_equal"}, + opts: []cmp.Option{derefTransform}, + }, { + label: label + "StructD1", + x: &ts.StructD1{ts.StructD{"NotEqual"}, "NotEqual"}, + y: &ts.StructD1{ts.StructD{"not_equal"}, "not_equal"}, + }, { + label: label + "StructE1", + x: ts.StructE1{ts.StructE{"NotEqual"}, "NotEqual"}, + y: ts.StructE1{ts.StructE{"not_equal"}, "not_equal"}, + wantDiff: ` +{teststructs.StructE1}.StructE.X: + -: "NotEqual" + +: "not_equal" +{teststructs.StructE1}.X: + -: "NotEqual" + +: "not_equal"`, + }, { + label: label + "StructE1", + x: ts.StructE1{ts.StructE{"NotEqual"}, "NotEqual"}, + y: ts.StructE1{ts.StructE{"not_equal"}, "not_equal"}, + opts: []cmp.Option{derefTransform}, + }, { + label: label + "StructE1", + x: &ts.StructE1{ts.StructE{"NotEqual"}, "NotEqual"}, + y: &ts.StructE1{ts.StructE{"not_equal"}, "not_equal"}, + }, { + label: label + "StructF1", + x: ts.StructF1{ts.StructF{"NotEqual"}, "NotEqual"}, + y: ts.StructF1{ts.StructF{"not_equal"}, "not_equal"}, + wantDiff: ` +{teststructs.StructF1}.StructF.X: + -: "NotEqual" + +: "not_equal" +{teststructs.StructF1}.X: + -: "NotEqual" + +: "not_equal"`, + }, { + label: label + "StructF1", + x: &ts.StructF1{ts.StructF{"NotEqual"}, "NotEqual"}, + y: &ts.StructF1{ts.StructF{"not_equal"}, "not_equal"}, + }, { + label: label + "StructA2", + x: ts.StructA2{&ts.StructA{"NotEqual"}, "equal"}, + y: ts.StructA2{&ts.StructA{"not_equal"}, "equal"}, + }, { + label: label + "StructA2", + x: ts.StructA2{&ts.StructA{"NotEqual"}, "NotEqual"}, + y: ts.StructA2{&ts.StructA{"not_equal"}, "not_equal"}, + wantDiff: "{teststructs.StructA2}.X:\n\t-: \"NotEqual\"\n\t+: \"not_equal\"\n", + }, { + label: label + "StructA2", + x: &ts.StructA2{&ts.StructA{"NotEqual"}, "equal"}, + y: &ts.StructA2{&ts.StructA{"not_equal"}, "equal"}, + }, { + label: label + "StructA2", + x: &ts.StructA2{&ts.StructA{"NotEqual"}, "NotEqual"}, + y: &ts.StructA2{&ts.StructA{"not_equal"}, "not_equal"}, + wantDiff: "{*teststructs.StructA2}.X:\n\t-: \"NotEqual\"\n\t+: \"not_equal\"\n", + }, { + label: label + "StructB2", + x: ts.StructB2{&ts.StructB{"NotEqual"}, "equal"}, + y: ts.StructB2{&ts.StructB{"not_equal"}, "equal"}, + }, { + label: label + "StructB2", + x: ts.StructB2{&ts.StructB{"NotEqual"}, "NotEqual"}, + y: ts.StructB2{&ts.StructB{"not_equal"}, "not_equal"}, + wantDiff: "{teststructs.StructB2}.X:\n\t-: \"NotEqual\"\n\t+: \"not_equal\"\n", + }, { + label: label + "StructB2", + x: &ts.StructB2{&ts.StructB{"NotEqual"}, "equal"}, + y: &ts.StructB2{&ts.StructB{"not_equal"}, "equal"}, + }, { + label: label + "StructB2", + x: &ts.StructB2{&ts.StructB{"NotEqual"}, "NotEqual"}, + y: &ts.StructB2{&ts.StructB{"not_equal"}, "not_equal"}, + wantDiff: "{*teststructs.StructB2}.X:\n\t-: \"NotEqual\"\n\t+: \"not_equal\"\n", + }, { + label: label + "StructC2", + x: ts.StructC2{&ts.StructC{"NotEqual"}, "NotEqual"}, + y: ts.StructC2{&ts.StructC{"not_equal"}, "not_equal"}, + }, { + label: label + "StructC2", + x: &ts.StructC2{&ts.StructC{"NotEqual"}, "NotEqual"}, + y: &ts.StructC2{&ts.StructC{"not_equal"}, "not_equal"}, + }, { + label: label + "StructD2", + x: ts.StructD2{&ts.StructD{"NotEqual"}, "NotEqual"}, + y: ts.StructD2{&ts.StructD{"not_equal"}, "not_equal"}, + }, { + label: label + "StructD2", + x: &ts.StructD2{&ts.StructD{"NotEqual"}, "NotEqual"}, + y: &ts.StructD2{&ts.StructD{"not_equal"}, "not_equal"}, + }, { + label: label + "StructE2", + x: ts.StructE2{&ts.StructE{"NotEqual"}, "NotEqual"}, + y: ts.StructE2{&ts.StructE{"not_equal"}, "not_equal"}, + }, { + label: label + "StructE2", + x: &ts.StructE2{&ts.StructE{"NotEqual"}, "NotEqual"}, + y: &ts.StructE2{&ts.StructE{"not_equal"}, "not_equal"}, + }, { + label: label + "StructF2", + x: ts.StructF2{&ts.StructF{"NotEqual"}, "NotEqual"}, + y: ts.StructF2{&ts.StructF{"not_equal"}, "not_equal"}, + }, { + label: label + "StructF2", + x: &ts.StructF2{&ts.StructF{"NotEqual"}, "NotEqual"}, + y: &ts.StructF2{&ts.StructF{"not_equal"}, "not_equal"}, + }, { + label: label + "StructNo", + x: ts.StructNo{"NotEqual"}, + y: ts.StructNo{"not_equal"}, + wantDiff: "{teststructs.StructNo}.X:\n\t-: \"NotEqual\"\n\t+: \"not_equal\"\n", + }, { + label: label + "AssignA", + x: ts.AssignA(func() int { return 0 }), + y: ts.AssignA(func() int { return 1 }), + }, { + label: label + "AssignB", + x: ts.AssignB(struct{ A int }{0}), + y: ts.AssignB(struct{ A int }{1}), + }, { + label: label + "AssignC", + x: ts.AssignC(make(chan bool)), + y: ts.AssignC(make(chan bool)), + }, { + label: label + "AssignD", + x: ts.AssignD(make(chan bool)), + y: ts.AssignD(make(chan bool)), + }} +} + +func project1Tests() []test { + const label = "Project1" + + ignoreUnexported := IgnoreUnexported( + ts.EagleImmutable{}, + ts.DreamerImmutable{}, + ts.SlapImmutable{}, + ts.GoatImmutable{}, + ts.DonkeyImmutable{}, + ts.LoveRadius{}, + ts.SummerLove{}, + ts.SummerLoveSummary{}, + ) + + createEagle := func() ts.Eagle { + return ts.Eagle{ + Name: "eagle", + Hounds: []string{"buford", "tannen"}, + Desc: "some description", + Dreamers: []ts.Dreamer{{}, { + Name: "dreamer2", + Animal: []interface{}{ + ts.Goat{ + Target: "corporation", + Immutable: &ts.GoatImmutable{ + ID: "southbay", + State: (*pb.Goat_States)(intPtr(5)), + Started: now, + }, + }, + ts.Donkey{}, + }, + Amoeba: 53, + }}, + Slaps: []ts.Slap{{ + Name: "slapID", + Args: &pb.MetaData{Stringer: pb.Stringer{"metadata"}}, + Immutable: &ts.SlapImmutable{ + ID: "immutableSlap", + MildSlap: true, + Started: now, + LoveRadius: &ts.LoveRadius{ + Summer: &ts.SummerLove{ + Summary: &ts.SummerLoveSummary{ + Devices: []string{"foo", "bar", "baz"}, + ChangeType: []pb.SummerType{1, 2, 3}, + }, + }, + }, + }, + }}, + Immutable: &ts.EagleImmutable{ + ID: "eagleID", + Birthday: now, + MissingCall: (*pb.Eagle_MissingCalls)(intPtr(55)), + }, + } + } + + return []test{{ + label: label, + x: ts.Eagle{Slaps: []ts.Slap{{ + Args: &pb.MetaData{Stringer: pb.Stringer{"metadata"}}, + }}}, + y: ts.Eagle{Slaps: []ts.Slap{{ + Args: &pb.MetaData{Stringer: pb.Stringer{"metadata"}}, + }}}, + wantPanic: "cannot handle unexported field", + }, { + label: label, + x: ts.Eagle{Slaps: []ts.Slap{{ + Args: &pb.MetaData{Stringer: pb.Stringer{"metadata"}}, + }}}, + y: ts.Eagle{Slaps: []ts.Slap{{ + Args: &pb.MetaData{Stringer: pb.Stringer{"metadata"}}, + }}}, + opts: []cmp.Option{cmp.Comparer(pb.Equal)}, + wantDiff: "", + }, { + label: label, + x: ts.Eagle{Slaps: []ts.Slap{{}, {}, {}, {}, { + Args: &pb.MetaData{Stringer: pb.Stringer{"metadata"}}, + }}}, + y: ts.Eagle{Slaps: []ts.Slap{{}, {}, {}, {}, { + Args: &pb.MetaData{Stringer: pb.Stringer{"metadata2"}}, + }}}, + opts: []cmp.Option{cmp.Comparer(pb.Equal)}, + wantDiff: "{teststructs.Eagle}.Slaps[4].Args:\n\t-: \"metadata\"\n\t+: \"metadata2\"\n", + }, { + label: label, + x: createEagle(), + y: createEagle(), + opts: []cmp.Option{ignoreUnexported, cmp.Comparer(pb.Equal)}, + }, { + label: label, + x: func() ts.Eagle { + eg := createEagle() + eg.Dreamers[1].Animal[0].(ts.Goat).Immutable.ID = "southbay2" + eg.Dreamers[1].Animal[0].(ts.Goat).Immutable.State = (*pb.Goat_States)(intPtr(6)) + eg.Slaps[0].Immutable.MildSlap = false + return eg + }(), + y: func() ts.Eagle { + eg := createEagle() + devs := eg.Slaps[0].Immutable.LoveRadius.Summer.Summary.Devices + eg.Slaps[0].Immutable.LoveRadius.Summer.Summary.Devices = devs[:1] + return eg + }(), + opts: []cmp.Option{ignoreUnexported, cmp.Comparer(pb.Equal)}, + wantDiff: ` +{teststructs.Eagle}.Dreamers[1].Animal[0].(teststructs.Goat).Immutable.ID: + -: "southbay2" + +: "southbay" +*{teststructs.Eagle}.Dreamers[1].Animal[0].(teststructs.Goat).Immutable.State: + -: 6 + +: 5 +{teststructs.Eagle}.Slaps[0].Immutable.MildSlap: + -: false + +: true +{teststructs.Eagle}.Slaps[0].Immutable.LoveRadius.Summer.Summary.Devices[1]: + -: "bar" + +: +{teststructs.Eagle}.Slaps[0].Immutable.LoveRadius.Summer.Summary.Devices[2]: + -: "baz" + +: `, + }} +} + +type germSorter []*pb.Germ + +func (gs germSorter) Len() int { return len(gs) } +func (gs germSorter) Less(i, j int) bool { return gs[i].String() < gs[j].String() } +func (gs germSorter) Swap(i, j int) { gs[i], gs[j] = gs[j], gs[i] } + +func project2Tests() []test { + const label = "Project2" + + sortGerms := cmp.FilterValues(func(x, y []*pb.Germ) bool { + ok1 := sort.IsSorted(germSorter(x)) + ok2 := sort.IsSorted(germSorter(y)) + return !ok1 || !ok2 + }, cmp.Transformer("Sort", func(in []*pb.Germ) []*pb.Germ { + out := append([]*pb.Germ(nil), in...) // Make copy + sort.Sort(germSorter(out)) + return out + })) + + equalDish := cmp.Comparer(func(x, y *ts.Dish) bool { + if x == nil || y == nil { + return x == nil && y == nil + } + px, err1 := x.Proto() + py, err2 := y.Proto() + if err1 != nil || err2 != nil { + return err1 == err2 + } + return pb.Equal(px, py) + }) + + createBatch := func() ts.GermBatch { + return ts.GermBatch{ + DirtyGerms: map[int32][]*pb.Germ{ + 17: { + {Stringer: pb.Stringer{"germ1"}}, + }, + 18: { + {Stringer: pb.Stringer{"germ2"}}, + {Stringer: pb.Stringer{"germ3"}}, + {Stringer: pb.Stringer{"germ4"}}, + }, + }, + GermMap: map[int32]*pb.Germ{ + 13: {Stringer: pb.Stringer{"germ13"}}, + 21: {Stringer: pb.Stringer{"germ21"}}, + }, + DishMap: map[int32]*ts.Dish{ + 0: ts.CreateDish(nil, io.EOF), + 1: ts.CreateDish(nil, io.ErrUnexpectedEOF), + 2: ts.CreateDish(&pb.Dish{Stringer: pb.Stringer{"dish"}}, nil), + }, + HasPreviousResult: true, + DirtyID: 10, + GermStrain: 421, + InfectedAt: now, + } + } + + return []test{{ + label: label, + x: createBatch(), + y: createBatch(), + wantPanic: "cannot handle unexported field", + }, { + label: label, + x: createBatch(), + y: createBatch(), + opts: []cmp.Option{cmp.Comparer(pb.Equal), sortGerms, equalDish}, + }, { + label: label, + x: createBatch(), + y: func() ts.GermBatch { + gb := createBatch() + s := gb.DirtyGerms[18] + s[0], s[1], s[2] = s[1], s[2], s[0] + return gb + }(), + opts: []cmp.Option{cmp.Comparer(pb.Equal), equalDish}, + wantDiff: ` +{teststructs.GermBatch}.DirtyGerms[18][0]: + -: "germ2" + +: "germ3" +{teststructs.GermBatch}.DirtyGerms[18][1]: + -: "germ3" + +: "germ4" +{teststructs.GermBatch}.DirtyGerms[18][2]: + -: "germ4" + +: "germ2"`, + }, { + label: label, + x: createBatch(), + y: func() ts.GermBatch { + gb := createBatch() + s := gb.DirtyGerms[18] + s[0], s[1], s[2] = s[1], s[2], s[0] + return gb + }(), + opts: []cmp.Option{cmp.Comparer(pb.Equal), sortGerms, equalDish}, + }, { + label: label, + x: func() ts.GermBatch { + gb := createBatch() + delete(gb.DirtyGerms, 17) + gb.DishMap[1] = nil + return gb + }(), + y: func() ts.GermBatch { + gb := createBatch() + gb.DirtyGerms[18] = gb.DirtyGerms[18][:2] + gb.GermStrain = 22 + return gb + }(), + opts: []cmp.Option{cmp.Comparer(pb.Equal), sortGerms, equalDish}, + wantDiff: ` +{teststructs.GermBatch}.DirtyGerms[17]: + -: + +: []*testprotos.Germ{"germ1"} +{teststructs.GermBatch}.DirtyGerms[18][2]: + -: "germ4" + +: +{teststructs.GermBatch}.DishMap[1]: + -: (*teststructs.Dish)(nil) + +: &teststructs.Dish{err: &errors.errorString{s: "unexpected EOF"}} +{teststructs.GermBatch}.GermStrain: + -: 421 + +: 22`, + }} +} + +func project3Tests() []test { + const label = "Project3" + + allowVisibility := cmp.AllowUnexported(ts.Dirt{}) + + ignoreLocker := cmp.FilterPath(func(p cmp.Path) bool { + return len(p) > 0 && p[len(p)-1].Type() == mutexType + }, cmp.Ignore()) + + transformProtos := cmp.Transformer("", func(x pb.Dirt) *pb.Dirt { + return &x + }) + + equalTable := cmp.Comparer(func(x, y ts.Table) bool { + tx, ok1 := x.(*ts.MockTable) + ty, ok2 := y.(*ts.MockTable) + if !ok1 || !ok2 { + panic("table type must be MockTable") + } + return cmp.Equal(tx.State(), ty.State()) + }) + + createDirt := func() (d ts.Dirt) { + d.SetTable(ts.CreateMockTable([]string{"a", "b", "c"})) + d.SetTimestamp(12345) + d.Discord = 554 + d.Proto = pb.Dirt{Stringer: pb.Stringer{"proto"}} + d.SetWizard(map[string]*pb.Wizard{ + "harry": {Stringer: pb.Stringer{"potter"}}, + "albus": {Stringer: pb.Stringer{"dumbledore"}}, + }) + d.SetLastTime(54321) + return d + } + + return []test{{ + label: label, + x: createDirt(), + y: createDirt(), + wantPanic: "cannot handle unexported field", + }, { + label: label, + x: createDirt(), + y: createDirt(), + opts: []cmp.Option{allowVisibility, ignoreLocker, cmp.Comparer(pb.Equal), equalTable}, + wantPanic: "cannot handle unexported field", + }, { + label: label, + x: createDirt(), + y: createDirt(), + opts: []cmp.Option{allowVisibility, transformProtos, ignoreLocker, cmp.Comparer(pb.Equal), equalTable}, + }, { + label: label, + x: func() ts.Dirt { + d := createDirt() + d.SetTable(ts.CreateMockTable([]string{"a", "c"})) + d.Proto = pb.Dirt{Stringer: pb.Stringer{"blah"}} + return d + }(), + y: func() ts.Dirt { + d := createDirt() + d.Discord = 500 + d.SetWizard(map[string]*pb.Wizard{ + "harry": {Stringer: pb.Stringer{"otter"}}, + }) + return d + }(), + opts: []cmp.Option{allowVisibility, transformProtos, ignoreLocker, cmp.Comparer(pb.Equal), equalTable}, + wantDiff: ` +{teststructs.Dirt}.table: + -: &teststructs.MockTable{state: []string{"a", "c"}} + +: &teststructs.MockTable{state: []string{"a", "b", "c"}} +{teststructs.Dirt}.Discord: + -: 554 + +: 500 +λ({teststructs.Dirt}.Proto): + -: "blah" + +: "proto" +{teststructs.Dirt}.wizard["albus"]: + -: "dumbledore" + +: +{teststructs.Dirt}.wizard["harry"]: + -: "potter" + +: "otter"`, + }} +} + +func project4Tests() []test { + const label = "Project4" + + allowVisibility := cmp.AllowUnexported( + ts.Cartel{}, + ts.Headquarter{}, + ts.Poison{}, + ) + + transformProtos := cmp.Transformer("", func(x pb.Restrictions) *pb.Restrictions { + return &x + }) + + createCartel := func() ts.Cartel { + var p ts.Poison + p.SetPoisonType(5) + p.SetExpiration(now) + p.SetManufactuer("acme") + + var hq ts.Headquarter + hq.SetID(5) + hq.SetLocation("moon") + hq.SetSubDivisions([]string{"alpha", "bravo", "charlie"}) + hq.SetMetaData(&pb.MetaData{Stringer: pb.Stringer{"metadata"}}) + hq.SetPublicMessage([]byte{1, 2, 3, 4, 5}) + hq.SetHorseBack("abcdef") + hq.SetStatus(44) + + var c ts.Cartel + c.Headquarter = hq + c.SetSource("mars") + c.SetCreationTime(now) + c.SetBoss("al capone") + c.SetPoisons([]*ts.Poison{&p}) + + return c + } + + return []test{{ + label: label, + x: createCartel(), + y: createCartel(), + wantPanic: "cannot handle unexported field", + }, { + label: label, + x: createCartel(), + y: createCartel(), + opts: []cmp.Option{allowVisibility, cmp.Comparer(pb.Equal)}, + wantPanic: "cannot handle unexported field", + }, { + label: label, + x: createCartel(), + y: createCartel(), + opts: []cmp.Option{allowVisibility, transformProtos, cmp.Comparer(pb.Equal)}, + }, { + label: label, + x: func() ts.Cartel { + d := createCartel() + var p1, p2 ts.Poison + p1.SetPoisonType(1) + p1.SetExpiration(now) + p1.SetManufactuer("acme") + p2.SetPoisonType(2) + p2.SetManufactuer("acme2") + d.SetPoisons([]*ts.Poison{&p1, &p2}) + return d + }(), + y: func() ts.Cartel { + d := createCartel() + d.SetSubDivisions([]string{"bravo", "charlie"}) + d.SetPublicMessage([]byte{1, 2, 4, 3, 5}) + return d + }(), + opts: []cmp.Option{allowVisibility, transformProtos, cmp.Comparer(pb.Equal)}, + wantDiff: ` +{teststructs.Cartel}.Headquarter.subDivisions[0]: + -: "alpha" + +: "bravo" +{teststructs.Cartel}.Headquarter.subDivisions[1]: + -: "bravo" + +: "charlie" +{teststructs.Cartel}.Headquarter.subDivisions[2]: + -: "charlie" + +: +{teststructs.Cartel}.Headquarter.publicMessage[2]: + -: 0x03 + +: 0x04 +{teststructs.Cartel}.Headquarter.publicMessage[3]: + -: 0x04 + +: 0x03 +{teststructs.Cartel}.poisons[0].poisonType: + -: 1 + +: 5 +{teststructs.Cartel}.poisons[1]: + -: &teststructs.Poison{poisonType: 2, manufactuer: "acme2"} + +: `, + }} +} + +// TODO: Delete this hack when we drop Go1.6 support. +func tRun(t *testing.T, name string, f func(t *testing.T)) { + type runner interface { + Run(string, func(t *testing.T)) bool + } + var ti interface{} = t + if r, ok := ti.(runner); ok { + r.Run(name, f) + } else { + t.Logf("Test: %s", name) + f(t) + } +} diff --git a/vendor/github.com/google/go-cmp/cmp/example_test.go b/vendor/github.com/google/go-cmp/cmp/example_test.go new file mode 100644 index 000000000..e341bc690 --- /dev/null +++ b/vendor/github.com/google/go-cmp/cmp/example_test.go @@ -0,0 +1,266 @@ +// Copyright 2017, The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE.md file. + +package cmp_test + +import ( + "fmt" + "math" + "reflect" + "sort" + "strings" + + "github.com/google/go-cmp/cmp" +) + +// TODO: Re-write these examples in terms of how you actually use the +// fundamental options and filters and not in terms of what cool things you can +// do with them since that overlaps with cmp/cmpopts. + +// Approximate equality for floats can be handled by defining a custom +// comparer on floats that determines two values to be equal if they are within +// some range of each other. +// +// This example is for demonstrative purposes; use cmpopts.EquateApprox instead. +func ExampleOption_approximateFloats() { + // This Comparer only operates on float64. + // To handle float32s, either define a similar function for that type + // or use a Transformer to convert float32s into float64s. + opt := cmp.Comparer(func(x, y float64) bool { + delta := math.Abs(x - y) + mean := math.Abs(x+y) / 2.0 + return delta/mean < 0.00001 + }) + + x := []float64{1.0, 1.1, 1.2, math.Pi} + y := []float64{1.0, 1.1, 1.2, 3.14159265359} // Accurate enough to Pi + z := []float64{1.0, 1.1, 1.2, 3.1415} // Diverges too far from Pi + + fmt.Println(cmp.Equal(x, y, opt)) + fmt.Println(cmp.Equal(y, z, opt)) + fmt.Println(cmp.Equal(z, x, opt)) + + // Output: + // true + // false + // false +} + +// Normal floating-point arithmetic defines == to be false when comparing +// NaN with itself. In certain cases, this is not the desired property. +// +// This example is for demonstrative purposes; use cmpopts.EquateNaNs instead. +func ExampleOption_equalNaNs() { + // This Comparer only operates on float64. + // To handle float32s, either define a similar function for that type + // or use a Transformer to convert float32s into float64s. + opt := cmp.Comparer(func(x, y float64) bool { + return (math.IsNaN(x) && math.IsNaN(y)) || x == y + }) + + x := []float64{1.0, math.NaN(), math.E, -0.0, +0.0} + y := []float64{1.0, math.NaN(), math.E, -0.0, +0.0} + z := []float64{1.0, math.NaN(), math.Pi, -0.0, +0.0} // Pi constant instead of E + + fmt.Println(cmp.Equal(x, y, opt)) + fmt.Println(cmp.Equal(y, z, opt)) + fmt.Println(cmp.Equal(z, x, opt)) + + // Output: + // true + // false + // false +} + +// To have floating-point comparisons combine both properties of NaN being +// equal to itself and also approximate equality of values, filters are needed +// to restrict the scope of the comparison so that they are composable. +// +// This example is for demonstrative purposes; +// use cmpopts.EquateNaNs and cmpopts.EquateApprox instead. +func ExampleOption_equalNaNsAndApproximateFloats() { + alwaysEqual := cmp.Comparer(func(_, _ interface{}) bool { return true }) + + opts := cmp.Options{ + // This option declares that a float64 comparison is equal only if + // both inputs are NaN. + cmp.FilterValues(func(x, y float64) bool { + return math.IsNaN(x) && math.IsNaN(y) + }, alwaysEqual), + + // This option declares approximate equality on float64s only if + // both inputs are not NaN. + cmp.FilterValues(func(x, y float64) bool { + return !math.IsNaN(x) && !math.IsNaN(y) + }, cmp.Comparer(func(x, y float64) bool { + delta := math.Abs(x - y) + mean := math.Abs(x+y) / 2.0 + return delta/mean < 0.00001 + })), + } + + x := []float64{math.NaN(), 1.0, 1.1, 1.2, math.Pi} + y := []float64{math.NaN(), 1.0, 1.1, 1.2, 3.14159265359} // Accurate enough to Pi + z := []float64{math.NaN(), 1.0, 1.1, 1.2, 3.1415} // Diverges too far from Pi + + fmt.Println(cmp.Equal(x, y, opts)) + fmt.Println(cmp.Equal(y, z, opts)) + fmt.Println(cmp.Equal(z, x, opts)) + + // Output: + // true + // false + // false +} + +// Sometimes, an empty map or slice is considered equal to an allocated one +// of zero length. +// +// This example is for demonstrative purposes; use cmpopts.EquateEmpty instead. +func ExampleOption_equalEmpty() { + alwaysEqual := cmp.Comparer(func(_, _ interface{}) bool { return true }) + + // This option handles slices and maps of any type. + opt := cmp.FilterValues(func(x, y interface{}) bool { + vx, vy := reflect.ValueOf(x), reflect.ValueOf(y) + return (vx.IsValid() && vy.IsValid() && vx.Type() == vy.Type()) && + (vx.Kind() == reflect.Slice || vx.Kind() == reflect.Map) && + (vx.Len() == 0 && vy.Len() == 0) + }, alwaysEqual) + + type S struct { + A []int + B map[string]bool + } + x := S{nil, make(map[string]bool, 100)} + y := S{make([]int, 0, 200), nil} + z := S{[]int{0}, nil} // []int has a single element (i.e., not empty) + + fmt.Println(cmp.Equal(x, y, opt)) + fmt.Println(cmp.Equal(y, z, opt)) + fmt.Println(cmp.Equal(z, x, opt)) + + // Output: + // true + // false + // false +} + +// Two slices may be considered equal if they have the same elements, +// regardless of the order that they appear in. Transformations can be used +// to sort the slice. +// +// This example is for demonstrative purposes; use cmpopts.SortSlices instead. +func ExampleOption_sortedSlice() { + // This Transformer sorts a []int. + // Since the transformer transforms []int into []int, there is problem where + // this is recursively applied forever. To prevent this, use a FilterValues + // to first check for the condition upon which the transformer ought to apply. + trans := cmp.FilterValues(func(x, y []int) bool { + return !sort.IntsAreSorted(x) || !sort.IntsAreSorted(y) + }, cmp.Transformer("Sort", func(in []int) []int { + out := append([]int(nil), in...) // Copy input to avoid mutating it + sort.Ints(out) + return out + })) + + x := struct{ Ints []int }{[]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}} + y := struct{ Ints []int }{[]int{2, 8, 0, 9, 6, 1, 4, 7, 3, 5}} + z := struct{ Ints []int }{[]int{0, 0, 1, 2, 3, 4, 5, 6, 7, 8}} + + fmt.Println(cmp.Equal(x, y, trans)) + fmt.Println(cmp.Equal(y, z, trans)) + fmt.Println(cmp.Equal(z, x, trans)) + + // Output: + // true + // false + // false +} + +type otherString string + +func (x otherString) Equal(y otherString) bool { + return strings.ToLower(string(x)) == strings.ToLower(string(y)) +} + +// If the Equal method defined on a type is not suitable, the type can be be +// dynamically transformed to be stripped of the Equal method (or any method +// for that matter). +func ExampleOption_avoidEqualMethod() { + // Suppose otherString.Equal performs a case-insensitive equality, + // which is too loose for our needs. + // We can avoid the methods of otherString by declaring a new type. + type myString otherString + + // This transformer converts otherString to myString, allowing Equal to use + // other Options to determine equality. + trans := cmp.Transformer("", func(in otherString) myString { + return myString(in) + }) + + x := []otherString{"foo", "bar", "baz"} + y := []otherString{"fOO", "bAr", "Baz"} // Same as before, but with different case + + fmt.Println(cmp.Equal(x, y)) // Equal because of case-insensitivity + fmt.Println(cmp.Equal(x, y, trans)) // Not equal because of more exact equality + + // Output: + // true + // false +} + +func roundF64(z float64) float64 { + if z < 0 { + return math.Ceil(z - 0.5) + } + return math.Floor(z + 0.5) +} + +// The complex numbers complex64 and complex128 can really just be decomposed +// into a pair of float32 or float64 values. It would be convenient to be able +// define only a single comparator on float64 and have float32, complex64, and +// complex128 all be able to use that comparator. Transformations can be used +// to handle this. +func ExampleOption_transformComplex() { + opts := []cmp.Option{ + // This transformer decomposes complex128 into a pair of float64s. + cmp.Transformer("T1", func(in complex128) (out struct{ Real, Imag float64 }) { + out.Real, out.Imag = real(in), imag(in) + return out + }), + // This transformer converts complex64 to complex128 to allow the + // above transform to take effect. + cmp.Transformer("T2", func(in complex64) complex128 { + return complex128(in) + }), + // This transformer converts float32 to float64. + cmp.Transformer("T3", func(in float32) float64 { + return float64(in) + }), + // This equality function compares float64s as rounded integers. + cmp.Comparer(func(x, y float64) bool { + return roundF64(x) == roundF64(y) + }), + } + + x := []interface{}{ + complex128(3.0), complex64(5.1 + 2.9i), float32(-1.2), float64(12.3), + } + y := []interface{}{ + complex128(3.1), complex64(4.9 + 3.1i), float32(-1.3), float64(11.7), + } + z := []interface{}{ + complex128(3.8), complex64(4.9 + 3.1i), float32(-1.3), float64(11.7), + } + + fmt.Println(cmp.Equal(x, y, opts...)) + fmt.Println(cmp.Equal(y, z, opts...)) + fmt.Println(cmp.Equal(z, x, opts...)) + + // Output: + // true + // false + // false +} diff --git a/vendor/github.com/google/go-cmp/cmp/options.go b/vendor/github.com/google/go-cmp/cmp/options.go new file mode 100644 index 000000000..0d05c87cf --- /dev/null +++ b/vendor/github.com/google/go-cmp/cmp/options.go @@ -0,0 +1,304 @@ +// Copyright 2017, The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE.md file. + +package cmp + +import ( + "fmt" + "reflect" + "runtime" + "strings" +) + +// Option configures for specific behavior of Equal and Diff. In particular, +// the fundamental Option functions (Ignore, Transformer, and Comparer), +// configure how equality is determined. +// +// The fundamental options may be composed with filters (FilterPath and +// FilterValues) to control the scope over which they are applied. +// +// The cmp/cmpopts package provides helper functions for creating options that +// may be used with Equal and Diff. +type Option interface { + // Prevent Option from being equivalent to interface{}, which provides + // a small type checking benefit by preventing Equal(opt, x, y). + option() +} + +// Options is a list of Option values that also satisfies the Option interface. +// Helper comparison packages may return an Options value when packing multiple +// Option values into a single Option. When this package processes an Options, +// it will be implicitly expanded into a flat list. +// +// Applying a filter on an Options is equivalent to applying that same filter +// on all individual options held within. +type Options []Option + +func (Options) option() {} + +type ( + pathFilter func(Path) bool + valueFilter struct { + in reflect.Type // T + fnc reflect.Value // func(T, T) bool + } +) + +type option struct { + typeFilter reflect.Type + pathFilters []pathFilter + valueFilters []valueFilter + + // op is the operation to perform. If nil, then this acts as an ignore. + op interface{} // nil | *transformer | *comparer +} + +func (option) option() {} + +func (o option) String() string { + // TODO: Add information about the caller? + // TODO: Maintain the order that filters were added? + + var ss []string + switch op := o.op.(type) { + case *transformer: + fn := getFuncName(op.fnc.Pointer()) + ss = append(ss, fmt.Sprintf("Transformer(%s, %s)", op.name, fn)) + case *comparer: + fn := getFuncName(op.fnc.Pointer()) + ss = append(ss, fmt.Sprintf("Comparer(%s)", fn)) + default: + ss = append(ss, "Ignore()") + } + + for _, f := range o.pathFilters { + fn := getFuncName(reflect.ValueOf(f).Pointer()) + ss = append(ss, fmt.Sprintf("FilterPath(%s)", fn)) + } + for _, f := range o.valueFilters { + fn := getFuncName(f.fnc.Pointer()) + ss = append(ss, fmt.Sprintf("FilterValues(%s)", fn)) + } + return strings.Join(ss, "\n\t") +} + +// getFuncName returns a short function name from the pointer. +// The string parsing logic works up until Go1.9. +func getFuncName(p uintptr) string { + fnc := runtime.FuncForPC(p) + if fnc == nil { + return "" + } + name := fnc.Name() // E.g., "long/path/name/mypkg.(mytype).(long/path/name/mypkg.myfunc)-fm" + if strings.HasSuffix(name, ")-fm") || strings.HasSuffix(name, ")·fm") { + // Strip the package name from method name. + name = strings.TrimSuffix(name, ")-fm") + name = strings.TrimSuffix(name, ")·fm") + if i := strings.LastIndexByte(name, '('); i >= 0 { + methodName := name[i+1:] // E.g., "long/path/name/mypkg.myfunc" + if j := strings.LastIndexByte(methodName, '.'); j >= 0 { + methodName = methodName[j+1:] // E.g., "myfunc" + } + name = name[:i] + methodName // E.g., "long/path/name/mypkg.(mytype)." + "myfunc" + } + } + if i := strings.LastIndexByte(name, '/'); i >= 0 { + // Strip the package name. + name = name[i+1:] // E.g., "mypkg.(mytype).myfunc" + } + return name +} + +// FilterPath returns a new Option where opt is only evaluated if filter f +// returns true for the current Path in the value tree. +// +// The option passed in may be an Ignore, Transformer, Comparer, Options, or +// a previously filtered Option. +func FilterPath(f func(Path) bool, opt Option) Option { + if f == nil { + panic("invalid path filter function") + } + switch opt := opt.(type) { + case Options: + var opts []Option + for _, o := range opt { + opts = append(opts, FilterPath(f, o)) // Append to slice copy + } + return Options(opts) + case option: + n := len(opt.pathFilters) + opt.pathFilters = append(opt.pathFilters[:n:n], f) // Append to copy + return opt + default: + panic(fmt.Sprintf("unknown option type: %T", opt)) + } +} + +// FilterValues returns a new Option where opt is only evaluated if filter f, +// which is a function of the form "func(T, T) bool", returns true for the +// current pair of values being compared. If the type of the values is not +// assignable to T, then this filter implicitly returns false. +// +// The filter function must be +// symmetric (i.e., agnostic to the order of the inputs) and +// deterministic (i.e., produces the same result when given the same inputs). +// If T is an interface, it is possible that f is called with two values with +// different concrete types that both implement T. +// +// The option passed in may be an Ignore, Transformer, Comparer, Options, or +// a previously filtered Option. +func FilterValues(f interface{}, opt Option) Option { + v := reflect.ValueOf(f) + if functionType(v.Type()) != valueFilterFunc || v.IsNil() { + panic(fmt.Sprintf("invalid values filter function: %T", f)) + } + switch opt := opt.(type) { + case Options: + var opts []Option + for _, o := range opt { + opts = append(opts, FilterValues(f, o)) // Append to slice copy + } + return Options(opts) + case option: + n := len(opt.valueFilters) + vf := valueFilter{v.Type().In(0), v} + opt.valueFilters = append(opt.valueFilters[:n:n], vf) // Append to copy + return opt + default: + panic(fmt.Sprintf("unknown option type: %T", opt)) + } +} + +// Ignore is an Option that causes all comparisons to be ignored. +// This value is intended to be combined with FilterPath or FilterValues. +// It is an error to pass an unfiltered Ignore option to Equal. +func Ignore() Option { + return option{} +} + +// Transformer returns an Option that applies a transformation function that +// converts values of a certain type into that of another. +// +// The transformer f must be a function "func(T) R" that converts values of +// type T to those of type R and is implicitly filtered to input values +// assignable to T. The transformer must not mutate T in any way. +// If T and R are the same type, an additional filter must be applied to +// act as the base case to prevent an infinite recursion applying the same +// transform to itself (see the SortedSlice example). +// +// The name is a user provided label that is used as the Transform.Name in the +// transformation PathStep. If empty, an arbitrary name is used. +func Transformer(name string, f interface{}) Option { + v := reflect.ValueOf(f) + if functionType(v.Type()) != transformFunc || v.IsNil() { + panic(fmt.Sprintf("invalid transformer function: %T", f)) + } + if name == "" { + name = "λ" // Lambda-symbol as place-holder for anonymous transformer + } + if !isValid(name) { + panic(fmt.Sprintf("invalid name: %q", name)) + } + opt := option{op: &transformer{name, reflect.ValueOf(f)}} + if ti := v.Type().In(0); ti.Kind() != reflect.Interface || ti.NumMethod() > 0 { + opt.typeFilter = ti + } + return opt +} + +type transformer struct { + name string + fnc reflect.Value // func(T) R +} + +// Comparer returns an Option that determines whether two values are equal +// to each other. +// +// The comparer f must be a function "func(T, T) bool" and is implicitly +// filtered to input values assignable to T. If T is an interface, it is +// possible that f is called with two values of different concrete types that +// both implement T. +// +// The equality function must be: +// • Symmetric: equal(x, y) == equal(y, x) +// • Deterministic: equal(x, y) == equal(x, y) +// • Pure: equal(x, y) does not modify x or y +func Comparer(f interface{}) Option { + v := reflect.ValueOf(f) + if functionType(v.Type()) != equalFunc || v.IsNil() { + panic(fmt.Sprintf("invalid comparer function: %T", f)) + } + opt := option{op: &comparer{v}} + if ti := v.Type().In(0); ti.Kind() != reflect.Interface || ti.NumMethod() > 0 { + opt.typeFilter = ti + } + return opt +} + +type comparer struct { + fnc reflect.Value // func(T, T) bool +} + +// AllowUnexported returns an Option that forcibly allows operations on +// unexported fields in certain structs, which are specified by passing in a +// value of each struct type. +// +// Users of this option must understand that comparing on unexported fields +// from external packages is not safe since changes in the internal +// implementation of some external package may cause the result of Equal +// to unexpectedly change. However, it may be valid to use this option on types +// defined in an internal package where the semantic meaning of an unexported +// field is in the control of the user. +// +// For some cases, a custom Comparer should be used instead that defines +// equality as a function of the public API of a type rather than the underlying +// unexported implementation. +// +// For example, the reflect.Type documentation defines equality to be determined +// by the == operator on the interface (essentially performing a shallow pointer +// comparison) and most attempts to compare *regexp.Regexp types are interested +// in only checking that the regular expression strings are equal. +// Both of these are accomplished using Comparers: +// +// Comparer(func(x, y reflect.Type) bool { return x == y }) +// Comparer(func(x, y *regexp.Regexp) bool { return x.String() == y.String() }) +// +// In other cases, the cmpopts.IgnoreUnexported option can be used to ignore +// all unexported fields on specified struct types. +func AllowUnexported(types ...interface{}) Option { + if !supportAllowUnexported { + panic("AllowUnexported is not supported on App Engine Classic or GopherJS") + } + m := make(map[reflect.Type]bool) + for _, typ := range types { + t := reflect.TypeOf(typ) + if t.Kind() != reflect.Struct { + panic(fmt.Sprintf("invalid struct type: %T", typ)) + } + m[t] = true + } + return visibleStructs(m) +} + +type visibleStructs map[reflect.Type]bool + +func (visibleStructs) option() {} + +// reporter is an Option that configures how differences are reported. +// +// TODO: Not exported yet, see concerns in defaultReporter.Report. +type reporter interface { + Option + + // Report is called for every comparison made and will be provided with + // the two values being compared, the equality result, and the + // current path in the value tree. It is possible for x or y to be an + // invalid reflect.Value if one of the values is non-existent; + // which is possible with maps and slices. + Report(x, y reflect.Value, eq bool, p Path) + + // TODO: Perhaps add PushStep and PopStep and change Report to only accept + // a PathStep instead of the full-path? This change allows us to provide + // better output closer to what pretty.Compare is able to achieve. +} diff --git a/vendor/github.com/google/go-cmp/cmp/options_test.go b/vendor/github.com/google/go-cmp/cmp/options_test.go new file mode 100644 index 000000000..2bde79844 --- /dev/null +++ b/vendor/github.com/google/go-cmp/cmp/options_test.go @@ -0,0 +1,231 @@ +// Copyright 2017, The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE.md file. + +package cmp + +import ( + "io" + "reflect" + "strings" + "testing" + + ts "github.com/google/go-cmp/cmp/internal/teststructs" +) + +// Test that the creation of Option values with non-sensible inputs produces +// a run-time panic with a decent error message +func TestOptionPanic(t *testing.T) { + type myBool bool + tests := []struct { + label string // Test description + fnc interface{} // Option function to call + args []interface{} // Arguments to pass in + wantPanic string // Expected panic message + }{{ + label: "AllowUnexported", + fnc: AllowUnexported, + args: []interface{}{}, + }, { + label: "AllowUnexported", + fnc: AllowUnexported, + args: []interface{}{1}, + wantPanic: "invalid struct type", + }, { + label: "AllowUnexported", + fnc: AllowUnexported, + args: []interface{}{ts.StructA{}}, + }, { + label: "AllowUnexported", + fnc: AllowUnexported, + args: []interface{}{ts.StructA{}, ts.StructB{}, ts.StructA{}}, + }, { + label: "AllowUnexported", + fnc: AllowUnexported, + args: []interface{}{ts.StructA{}, &ts.StructB{}, ts.StructA{}}, + wantPanic: "invalid struct type", + }, { + label: "Comparer", + fnc: Comparer, + args: []interface{}{5}, + wantPanic: "invalid comparer function", + }, { + label: "Comparer", + fnc: Comparer, + args: []interface{}{func(x, y interface{}) bool { return true }}, + }, { + label: "Comparer", + fnc: Comparer, + args: []interface{}{func(x, y io.Reader) bool { return true }}, + }, { + label: "Comparer", + fnc: Comparer, + args: []interface{}{func(x, y io.Reader) myBool { return true }}, + wantPanic: "invalid comparer function", + }, { + label: "Comparer", + fnc: Comparer, + args: []interface{}{func(x string, y interface{}) bool { return true }}, + wantPanic: "invalid comparer function", + }, { + label: "Comparer", + fnc: Comparer, + args: []interface{}{(func(int, int) bool)(nil)}, + wantPanic: "invalid comparer function", + }, { + label: "Transformer", + fnc: Transformer, + args: []interface{}{"", 0}, + wantPanic: "invalid transformer function", + }, { + label: "Transformer", + fnc: Transformer, + args: []interface{}{"", func(int) int { return 0 }}, + }, { + label: "Transformer", + fnc: Transformer, + args: []interface{}{"", func(bool) bool { return true }}, + }, { + label: "Transformer", + fnc: Transformer, + args: []interface{}{"", func(int) bool { return true }}, + }, { + label: "Transformer", + fnc: Transformer, + args: []interface{}{"", func(int, int) bool { return true }}, + wantPanic: "invalid transformer function", + }, { + label: "Transformer", + fnc: Transformer, + args: []interface{}{"", (func(int) uint)(nil)}, + wantPanic: "invalid transformer function", + }, { + label: "Transformer", + fnc: Transformer, + args: []interface{}{"Func", func(Path) Path { return nil }}, + }, { + label: "Transformer", + fnc: Transformer, + args: []interface{}{"世界", func(int) bool { return true }}, + }, { + label: "Transformer", + fnc: Transformer, + args: []interface{}{"/*", func(int) bool { return true }}, + wantPanic: "invalid name", + }, { + label: "Transformer", + fnc: Transformer, + args: []interface{}{"_", func(int) bool { return true }}, + wantPanic: "invalid name", + }, { + label: "FilterPath", + fnc: FilterPath, + args: []interface{}{(func(Path) bool)(nil), Ignore()}, + wantPanic: "invalid path filter function", + }, { + label: "FilterPath", + fnc: FilterPath, + args: []interface{}{func(Path) bool { return true }, Ignore()}, + }, { + label: "FilterPath", + fnc: FilterPath, + args: []interface{}{func(Path) bool { return true }, &defaultReporter{}}, + wantPanic: "unknown option type", + }, { + label: "FilterPath", + fnc: FilterPath, + args: []interface{}{func(Path) bool { return true }, Options{Ignore(), Ignore()}}, + }, { + label: "FilterPath", + fnc: FilterPath, + args: []interface{}{func(Path) bool { return true }, Options{Ignore(), &defaultReporter{}}}, + wantPanic: "unknown option type", + }, { + label: "FilterValues", + fnc: FilterValues, + args: []interface{}{0, Ignore()}, + wantPanic: "invalid values filter function", + }, { + label: "FilterValues", + fnc: FilterValues, + args: []interface{}{func(x, y int) bool { return true }, Ignore()}, + }, { + label: "FilterValues", + fnc: FilterValues, + args: []interface{}{func(x, y interface{}) bool { return true }, Ignore()}, + }, { + label: "FilterValues", + fnc: FilterValues, + args: []interface{}{func(x, y interface{}) myBool { return true }, Ignore()}, + wantPanic: "invalid values filter function", + }, { + label: "FilterValues", + fnc: FilterValues, + args: []interface{}{func(x io.Reader, y interface{}) bool { return true }, Ignore()}, + wantPanic: "invalid values filter function", + }, { + label: "FilterValues", + fnc: FilterValues, + args: []interface{}{(func(int, int) bool)(nil), Ignore()}, + wantPanic: "invalid values filter function", + }, { + label: "FilterValues", + fnc: FilterValues, + args: []interface{}{func(int, int) bool { return true }, &defaultReporter{}}, + wantPanic: "unknown option type", + }, { + label: "FilterValues", + fnc: FilterValues, + args: []interface{}{func(int, int) bool { return true }, Options{Ignore(), Ignore()}}, + }, { + label: "FilterValues", + fnc: FilterValues, + args: []interface{}{func(int, int) bool { return true }, Options{Ignore(), &defaultReporter{}}}, + wantPanic: "unknown option type", + }} + + for _, tt := range tests { + tRun(t, tt.label, func(t *testing.T) { + var gotPanic string + func() { + defer func() { + if ex := recover(); ex != nil { + if s, ok := ex.(string); ok { + gotPanic = s + } else { + panic(ex) + } + } + }() + var vargs []reflect.Value + for _, arg := range tt.args { + vargs = append(vargs, reflect.ValueOf(arg)) + } + reflect.ValueOf(tt.fnc).Call(vargs) + }() + if tt.wantPanic == "" { + if gotPanic != "" { + t.Fatalf("unexpected panic message: %s", gotPanic) + } + } else { + if !strings.Contains(gotPanic, tt.wantPanic) { + t.Fatalf("panic message:\ngot: %s\nwant: %s", gotPanic, tt.wantPanic) + } + } + }) + } +} + +// TODO: Delete this hack when we drop Go1.6 support. +func tRun(t *testing.T, name string, f func(t *testing.T)) { + type runner interface { + Run(string, func(t *testing.T)) bool + } + var ti interface{} = t + if r, ok := ti.(runner); ok { + r.Run(name, f) + } else { + t.Logf("Test: %s", name) + f(t) + } +} diff --git a/vendor/github.com/google/go-cmp/cmp/path.go b/vendor/github.com/google/go-cmp/cmp/path.go new file mode 100644 index 000000000..5a88dbda1 --- /dev/null +++ b/vendor/github.com/google/go-cmp/cmp/path.go @@ -0,0 +1,261 @@ +// Copyright 2017, The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE.md file. + +package cmp + +import ( + "fmt" + "reflect" + "strings" + "unicode" + "unicode/utf8" +) + +type ( + // Path is a list of PathSteps describing the sequence of operations to get + // from some root type to the current position in the value tree. + // The first Path element is always an operation-less PathStep that exists + // simply to identify the initial type. + // + // When traversing structs with embedded structs, the embedded struct will + // always be accessed as a field before traversing the fields of the + // embedded struct themselves. That is, an exported field from the + // embedded struct will never be accessed directly from the parent struct. + Path []PathStep + + // PathStep is a union-type for specific operations to traverse + // a value's tree structure. Users of this package never need to implement + // these types as values of this type will be returned by this package. + PathStep interface { + String() string + Type() reflect.Type // Resulting type after performing the path step + isPathStep() + } + + // SliceIndex is an index operation on a slice or array at some index Key. + SliceIndex interface { + PathStep + Key() int + isSliceIndex() + } + // MapIndex is an index operation on a map at some index Key. + MapIndex interface { + PathStep + Key() reflect.Value + isMapIndex() + } + // TypeAssertion represents a type assertion on an interface. + TypeAssertion interface { + PathStep + isTypeAssertion() + } + // StructField represents a struct field access on a field called Name. + StructField interface { + PathStep + Name() string + Index() int + isStructField() + } + // Indirect represents pointer indirection on the parent type. + Indirect interface { + PathStep + isIndirect() + } + // Transform is a transformation from the parent type to the current type. + Transform interface { + PathStep + Name() string + Func() reflect.Value + isTransform() + } +) + +func (pa *Path) push(s PathStep) { + *pa = append(*pa, s) +} + +func (pa *Path) pop() { + *pa = (*pa)[:len(*pa)-1] +} + +// Last returns the last PathStep in the Path. +// If the path is empty, this returns a non-nil PathStep that reports a nil Type. +func (pa Path) Last() PathStep { + if len(pa) > 0 { + return pa[len(pa)-1] + } + return pathStep{} +} + +// String returns the simplified path to a node. +// The simplified path only contains struct field accesses. +// +// For example: +// MyMap.MySlices.MyField +func (pa Path) String() string { + var ss []string + for _, s := range pa { + if _, ok := s.(*structField); ok { + ss = append(ss, s.String()) + } + } + return strings.TrimPrefix(strings.Join(ss, ""), ".") +} + +// GoString returns the path to a specific node using Go syntax. +// +// For example: +// (*root.MyMap["key"].(*mypkg.MyStruct).MySlices)[2][3].MyField +func (pa Path) GoString() string { + var ssPre, ssPost []string + var numIndirect int + for i, s := range pa { + var nextStep PathStep + if i+1 < len(pa) { + nextStep = pa[i+1] + } + switch s := s.(type) { + case *indirect: + numIndirect++ + pPre, pPost := "(", ")" + switch nextStep.(type) { + case *indirect: + continue // Next step is indirection, so let them batch up + case *structField: + numIndirect-- // Automatic indirection on struct fields + case nil: + pPre, pPost = "", "" // Last step; no need for parenthesis + } + if numIndirect > 0 { + ssPre = append(ssPre, pPre+strings.Repeat("*", numIndirect)) + ssPost = append(ssPost, pPost) + } + numIndirect = 0 + continue + case *transform: + ssPre = append(ssPre, s.trans.name+"(") + ssPost = append(ssPost, ")") + continue + case *typeAssertion: + // Elide type assertions immediately following a transform to + // prevent overly verbose path printouts. + // Some transforms return interface{} because of Go's lack of + // generics, but typically take in and return the exact same + // concrete type. Other times, the transform creates an anonymous + // struct, which will be very verbose to print. + if _, ok := nextStep.(*transform); ok { + continue + } + } + ssPost = append(ssPost, s.String()) + } + for i, j := 0, len(ssPre)-1; i < j; i, j = i+1, j-1 { + ssPre[i], ssPre[j] = ssPre[j], ssPre[i] + } + return strings.Join(ssPre, "") + strings.Join(ssPost, "") +} + +type ( + pathStep struct { + typ reflect.Type + } + + sliceIndex struct { + pathStep + key int + } + mapIndex struct { + pathStep + key reflect.Value + } + typeAssertion struct { + pathStep + } + structField struct { + pathStep + name string + idx int + + // These fields are used for forcibly accessing an unexported field. + // pvx, pvy, and field are only valid if unexported is true. + unexported bool + force bool // Forcibly allow visibility + pvx, pvy reflect.Value // Parent values + field reflect.StructField // Field information + } + indirect struct { + pathStep + } + transform struct { + pathStep + trans *transformer + } +) + +func (ps pathStep) Type() reflect.Type { return ps.typ } +func (ps pathStep) String() string { + if ps.typ == nil { + return "" + } + s := ps.typ.String() + if s == "" || strings.ContainsAny(s, "{}\n") { + return "root" // Type too simple or complex to print + } + return fmt.Sprintf("{%s}", s) +} + +func (si sliceIndex) String() string { return fmt.Sprintf("[%d]", si.key) } +func (mi mapIndex) String() string { return fmt.Sprintf("[%#v]", mi.key) } +func (ta typeAssertion) String() string { return fmt.Sprintf(".(%v)", ta.typ) } +func (sf structField) String() string { return fmt.Sprintf(".%s", sf.name) } +func (in indirect) String() string { return "*" } +func (tf transform) String() string { return fmt.Sprintf("%s()", tf.trans.name) } + +func (si sliceIndex) Key() int { return si.key } +func (mi mapIndex) Key() reflect.Value { return mi.key } +func (sf structField) Name() string { return sf.name } +func (sf structField) Index() int { return sf.idx } +func (tf transform) Name() string { return tf.trans.name } +func (tf transform) Func() reflect.Value { return tf.trans.fnc } + +func (pathStep) isPathStep() {} +func (sliceIndex) isSliceIndex() {} +func (mapIndex) isMapIndex() {} +func (typeAssertion) isTypeAssertion() {} +func (structField) isStructField() {} +func (indirect) isIndirect() {} +func (transform) isTransform() {} + +var ( + _ SliceIndex = sliceIndex{} + _ MapIndex = mapIndex{} + _ TypeAssertion = typeAssertion{} + _ StructField = structField{} + _ Indirect = indirect{} + _ Transform = transform{} + + _ PathStep = sliceIndex{} + _ PathStep = mapIndex{} + _ PathStep = typeAssertion{} + _ PathStep = structField{} + _ PathStep = indirect{} + _ PathStep = transform{} +) + +// isExported reports whether the identifier is exported. +func isExported(id string) bool { + r, _ := utf8.DecodeRuneInString(id) + return unicode.IsUpper(r) +} + +// isValid reports whether the identifier is valid. +// Empty and underscore-only strings are not valid. +func isValid(id string) bool { + ok := id != "" && id != "_" + for j, c := range id { + ok = ok && (j > 0 || !unicode.IsDigit(c)) + ok = ok && (c == '_' || unicode.IsLetter(c) || unicode.IsDigit(c)) + } + return ok +} diff --git a/vendor/github.com/google/go-cmp/cmp/reporter.go b/vendor/github.com/google/go-cmp/cmp/reporter.go new file mode 100644 index 000000000..1b812f537 --- /dev/null +++ b/vendor/github.com/google/go-cmp/cmp/reporter.go @@ -0,0 +1,406 @@ +// Copyright 2017, The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE.md file. + +package cmp + +import ( + "fmt" + "math" + "reflect" + "sort" + "strings" +) + +// TODO: Can we leave the interface for a reporter here in the cmp package +// and somehow extract the implementation of defaultReporter into cmp/report? + +type defaultReporter struct { + Option + diffs []string // List of differences, possibly truncated + ndiffs int // Total number of differences + nbytes int // Number of bytes in diffs + nlines int // Number of lines in diffs +} + +var _ reporter = (*defaultReporter)(nil) + +func (r *defaultReporter) Report(x, y reflect.Value, eq bool, p Path) { + // TODO: Is there a way to nicely print added/modified/removed elements + // from a slice? This will most certainly require support from the + // equality logic, but what would be the right API for this? + // + // The current API is equivalent to a Hamming distance for measuring the + // difference between two sequences of symbols. That is, the only operation + // we can represent is substitution. The new API would need to handle a + // Levenshtein distance, such that insertions, deletions, and substitutions + // are permitted. Furthermore, this will require an algorithm for computing + // the edit distance. Unfortunately, the time complexity for a minimal + // edit distance algorithm is not much better than O(n^2). + // There are approximations for the algorithm that can run much faster. + // See literature on computing Levenshtein distance. + // + // Passing in a pair of x and y is actually good for representing insertion + // and deletion by the fact that x or y may be an invalid value. However, + // we may need to pass in two paths px and py, to indicate the paths + // relative to x and y. Alternative, since we only perform the Levenshtein + // distance on slices, maybe we alter the SliceIndex type to record + // two different indexes. + + // TODO: Perhaps we should coalesce differences on primitive kinds + // together if the number of differences exceeds some ratio. + // For example, comparing two SHA256s leads to many byte differences. + + if eq { + // TODO: Maybe print some equal results for context? + return // Ignore equal results + } + const maxBytes = 4096 + const maxLines = 256 + r.ndiffs++ + if r.nbytes < maxBytes && r.nlines < maxLines { + sx := prettyPrint(x, true) + sy := prettyPrint(y, true) + if sx == sy { + // Use of Stringer is not helpful, so rely on more exact formatting. + sx = prettyPrint(x, false) + sy = prettyPrint(y, false) + } + s := fmt.Sprintf("%#v:\n\t-: %s\n\t+: %s\n", p, sx, sy) + r.diffs = append(r.diffs, s) + r.nbytes += len(s) + r.nlines += strings.Count(s, "\n") + } +} + +func (r *defaultReporter) String() string { + s := strings.Join(r.diffs, "") + if r.ndiffs == len(r.diffs) { + return s + } + return fmt.Sprintf("%s... %d more differences ...", s, len(r.diffs)-r.ndiffs) +} + +var stringerIface = reflect.TypeOf((*fmt.Stringer)(nil)).Elem() + +func prettyPrint(v reflect.Value, useStringer bool) string { + return formatAny(v, formatConfig{useStringer, true, true, true}, nil) +} + +type formatConfig struct { + useStringer bool // Should the String method be used if available? + printType bool // Should we print the type before the value? + followPointers bool // Should we recursively follow pointers? + realPointers bool // Should we print the real address of pointers? +} + +// formatAny prints the value v in a pretty formatted manner. +// This is similar to fmt.Sprintf("%+v", v) except this: +// * Prints the type unless it can be elided. +// * Avoids printing struct fields that are zero. +// * Prints a nil-slice as being nil, not empty. +// * Prints map entries in deterministic order. +func formatAny(v reflect.Value, conf formatConfig, visited map[uintptr]bool) string { + // TODO: Should this be a multi-line printout in certain situations? + + if !v.IsValid() { + return "" + } + if conf.useStringer && v.Type().Implements(stringerIface) { + if v.Kind() == reflect.Ptr && v.IsNil() { + return "" + } + return fmt.Sprintf("%q", v.Interface().(fmt.Stringer).String()) + } + + switch v.Kind() { + case reflect.Bool: + return fmt.Sprint(v.Bool()) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return fmt.Sprint(v.Int()) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + if v.Type().PkgPath() == "" || v.Kind() == reflect.Uintptr { + return formatHex(v.Uint()) // Unnamed uints are usually bytes or words + } + return fmt.Sprint(v.Uint()) // Named uints are usually enumerations + case reflect.Float32, reflect.Float64: + return fmt.Sprint(v.Float()) + case reflect.Complex64, reflect.Complex128: + return fmt.Sprint(v.Complex()) + case reflect.String: + return fmt.Sprintf("%q", v) + case reflect.UnsafePointer, reflect.Chan, reflect.Func: + return formatPointer(v, conf) + case reflect.Ptr: + if v.IsNil() { + if conf.printType { + return fmt.Sprintf("(%v)(nil)", v.Type()) + } + return "" + } + if visited[v.Pointer()] || !conf.followPointers { + return formatPointer(v, conf) + } + visited = insertPointer(visited, v.Pointer()) + return "&" + formatAny(v.Elem(), conf, visited) + case reflect.Interface: + if v.IsNil() { + if conf.printType { + return fmt.Sprintf("%v(nil)", v.Type()) + } + return "" + } + return formatAny(v.Elem(), conf, visited) + case reflect.Slice: + if v.IsNil() { + if conf.printType { + return fmt.Sprintf("%v(nil)", v.Type()) + } + return "" + } + if visited[v.Pointer()] { + return formatPointer(v, conf) + } + visited = insertPointer(visited, v.Pointer()) + fallthrough + case reflect.Array: + var ss []string + subConf := conf + subConf.printType = v.Type().Elem().Kind() == reflect.Interface + for i := 0; i < v.Len(); i++ { + s := formatAny(v.Index(i), subConf, visited) + ss = append(ss, s) + } + s := fmt.Sprintf("{%s}", strings.Join(ss, ", ")) + if conf.printType { + return v.Type().String() + s + } + return s + case reflect.Map: + if v.IsNil() { + if conf.printType { + return fmt.Sprintf("%v(nil)", v.Type()) + } + return "" + } + if visited[v.Pointer()] { + return formatPointer(v, conf) + } + visited = insertPointer(visited, v.Pointer()) + + var ss []string + subConf := conf + subConf.printType = v.Type().Elem().Kind() == reflect.Interface + for _, k := range sortKeys(v.MapKeys()) { + sk := formatAny(k, formatConfig{realPointers: conf.realPointers}, visited) + sv := formatAny(v.MapIndex(k), subConf, visited) + ss = append(ss, fmt.Sprintf("%s: %s", sk, sv)) + } + s := fmt.Sprintf("{%s}", strings.Join(ss, ", ")) + if conf.printType { + return v.Type().String() + s + } + return s + case reflect.Struct: + var ss []string + subConf := conf + subConf.printType = true + for i := 0; i < v.NumField(); i++ { + vv := v.Field(i) + if isZero(vv) { + continue // Elide zero value fields + } + name := v.Type().Field(i).Name + subConf.useStringer = conf.useStringer && isExported(name) + s := formatAny(vv, subConf, visited) + ss = append(ss, fmt.Sprintf("%s: %s", name, s)) + } + s := fmt.Sprintf("{%s}", strings.Join(ss, ", ")) + if conf.printType { + return v.Type().String() + s + } + return s + default: + panic(fmt.Sprintf("%v kind not handled", v.Kind())) + } +} + +func formatPointer(v reflect.Value, conf formatConfig) string { + p := v.Pointer() + if !conf.realPointers { + p = 0 // For deterministic printing purposes + } + s := formatHex(uint64(p)) + if conf.printType { + return fmt.Sprintf("(%v)(%s)", v.Type(), s) + } + return s +} + +func formatHex(u uint64) string { + var f string + switch { + case u <= 0xff: + f = "0x%02x" + case u <= 0xffff: + f = "0x%04x" + case u <= 0xffffff: + f = "0x%06x" + case u <= 0xffffffff: + f = "0x%08x" + case u <= 0xffffffffff: + f = "0x%010x" + case u <= 0xffffffffffff: + f = "0x%012x" + case u <= 0xffffffffffffff: + f = "0x%014x" + case u <= 0xffffffffffffffff: + f = "0x%016x" + } + return fmt.Sprintf(f, u) +} + +// insertPointer insert p into m, allocating m if necessary. +func insertPointer(m map[uintptr]bool, p uintptr) map[uintptr]bool { + if m == nil { + m = make(map[uintptr]bool) + } + m[p] = true + return m +} + +// isZero reports whether v is the zero value. +// This does not rely on Interface and so can be used on unexported fields. +func isZero(v reflect.Value) bool { + switch v.Kind() { + case reflect.Bool: + return v.Bool() == false + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Complex64, reflect.Complex128: + return v.Complex() == 0 + case reflect.String: + return v.String() == "" + case reflect.UnsafePointer: + return v.Pointer() == 0 + case reflect.Chan, reflect.Func, reflect.Interface, reflect.Ptr, reflect.Map, reflect.Slice: + return v.IsNil() + case reflect.Array: + for i := 0; i < v.Len(); i++ { + if !isZero(v.Index(i)) { + return false + } + } + return true + case reflect.Struct: + for i := 0; i < v.NumField(); i++ { + if !isZero(v.Field(i)) { + return false + } + } + return true + } + return false +} + +// isLess is a generic function for sorting arbitrary map keys. +// The inputs must be of the same type and must be comparable. +func isLess(x, y reflect.Value) bool { + switch x.Type().Kind() { + case reflect.Bool: + return !x.Bool() && y.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return x.Int() < y.Int() + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return x.Uint() < y.Uint() + case reflect.Float32, reflect.Float64: + fx, fy := x.Float(), y.Float() + return fx < fy || math.IsNaN(fx) && !math.IsNaN(fy) + case reflect.Complex64, reflect.Complex128: + cx, cy := x.Complex(), y.Complex() + rx, ix, ry, iy := real(cx), imag(cx), real(cy), imag(cy) + if rx == ry || (math.IsNaN(rx) && math.IsNaN(ry)) { + return ix < iy || math.IsNaN(ix) && !math.IsNaN(iy) + } + return rx < ry || math.IsNaN(rx) && !math.IsNaN(ry) + case reflect.Ptr, reflect.UnsafePointer, reflect.Chan: + return x.Pointer() < y.Pointer() + case reflect.String: + return x.String() < y.String() + case reflect.Array: + for i := 0; i < x.Len(); i++ { + if isLess(x.Index(i), y.Index(i)) { + return true + } + if isLess(y.Index(i), x.Index(i)) { + return false + } + } + return false + case reflect.Struct: + for i := 0; i < x.NumField(); i++ { + if isLess(x.Field(i), y.Field(i)) { + return true + } + if isLess(y.Field(i), x.Field(i)) { + return false + } + } + return false + case reflect.Interface: + vx, vy := x.Elem(), y.Elem() + if !vx.IsValid() || !vy.IsValid() { + return !vx.IsValid() && vy.IsValid() + } + tx, ty := vx.Type(), vy.Type() + if tx == ty { + return isLess(x.Elem(), y.Elem()) + } + if tx.Kind() != ty.Kind() { + return vx.Kind() < vy.Kind() + } + if tx.String() != ty.String() { + return tx.String() < ty.String() + } + if tx.PkgPath() != ty.PkgPath() { + return tx.PkgPath() < ty.PkgPath() + } + // This can happen in rare situations, so we fallback to just comparing + // the unique pointer for a reflect.Type. This guarantees deterministic + // ordering within a program, but it is obviously not stable. + return reflect.ValueOf(vx.Type()).Pointer() < reflect.ValueOf(vy.Type()).Pointer() + default: + // Must be Func, Map, or Slice; which are not comparable. + panic(fmt.Sprintf("%T is not comparable", x.Type())) + } +} + +// sortKey sorts a list of map keys, deduplicating keys if necessary. +func sortKeys(vs []reflect.Value) []reflect.Value { + if len(vs) == 0 { + return vs + } + + // Sort the map keys. + sort.Sort(valueSorter(vs)) + + // Deduplicate keys (fails for NaNs). + vs2 := vs[:1] + for _, v := range vs[1:] { + if v.Interface() != vs2[len(vs2)-1].Interface() { + vs2 = append(vs2, v) + } + } + return vs2 +} + +// TODO: Use sort.Slice once Google AppEngine is on Go1.8 or above. +type valueSorter []reflect.Value + +func (vs valueSorter) Len() int { return len(vs) } +func (vs valueSorter) Less(i, j int) bool { return isLess(vs[i], vs[j]) } +func (vs valueSorter) Swap(i, j int) { vs[i], vs[j] = vs[j], vs[i] } diff --git a/vendor/github.com/google/go-cmp/cmp/reporter_test.go b/vendor/github.com/google/go-cmp/cmp/reporter_test.go new file mode 100644 index 000000000..731007791 --- /dev/null +++ b/vendor/github.com/google/go-cmp/cmp/reporter_test.go @@ -0,0 +1,227 @@ +// Copyright 2017, The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE.md file. + +package cmp + +import ( + "bytes" + "io" + "math" + "reflect" + "testing" +) + +func TestFormatAny(t *testing.T) { + type key struct { + a int + b string + c chan bool + } + + tests := []struct { + in interface{} + want string + }{{ + in: []int{}, + want: "[]int{}", + }, { + in: []int(nil), + want: "[]int(nil)", + }, { + in: []int{1, 2, 3, 4, 5}, + want: "[]int{1, 2, 3, 4, 5}", + }, { + in: []interface{}{1, true, "hello", struct{ A, B int }{1, 2}}, + want: "[]interface {}{1, true, \"hello\", struct { A int; B int }{A: 1, B: 2}}", + }, { + in: []struct{ A, B int }{{1, 2}, {0, 4}, {}}, + want: "[]struct { A int; B int }{{A: 1, B: 2}, {B: 4}, {}}", + }, { + in: map[*int]string{new(int): "hello"}, + want: "map[*int]string{0x00: \"hello\"}", + }, { + in: map[key]string{{}: "hello"}, + want: "map[cmp.key]string{{}: \"hello\"}", + }, { + in: map[key]string{{a: 5, b: "key", c: make(chan bool)}: "hello"}, + want: "map[cmp.key]string{{a: 5, b: \"key\", c: (chan bool)(0x00)}: \"hello\"}", + }, { + in: map[io.Reader]string{new(bytes.Reader): "hello"}, + want: "map[io.Reader]string{0x00: \"hello\"}", + }, { + in: func() interface{} { + var a = []interface{}{nil} + a[0] = a + return a + }(), + want: "[]interface {}{([]interface {})(0x00)}", + }, { + in: func() interface{} { + type A *A + var a A + a = &a + return a + }(), + want: "&(cmp.A)(0x00)", + }, { + in: func() interface{} { + type A map[*A]A + a := make(A) + a[&a] = a + return a + }(), + want: "cmp.A{0x00: 0x00}", + }, { + in: func() interface{} { + var a [2]interface{} + a[0] = &a + return a + }(), + want: "[2]interface {}{&[2]interface {}{(*[2]interface {})(0x00), interface {}(nil)}, interface {}(nil)}", + }} + + for i, tt := range tests { + got := formatAny(reflect.ValueOf(tt.in), formatConfig{true, true, true, false}, nil) + if got != tt.want { + t.Errorf("test %d, pretty print:\ngot %q\nwant %q", i, got, tt.want) + } + } +} + +func TestSortKeys(t *testing.T) { + type ( + MyString string + MyArray [2]int + MyStruct struct { + A MyString + B MyArray + C chan float64 + } + EmptyStruct struct{} + ) + + opts := []Option{ + Comparer(func(x, y float64) bool { + if math.IsNaN(x) && math.IsNaN(y) { + return true + } + return x == y + }), + Comparer(func(x, y complex128) bool { + rx, ix, ry, iy := real(x), imag(x), real(y), imag(y) + if math.IsNaN(rx) && math.IsNaN(ry) { + rx, ry = 0, 0 + } + if math.IsNaN(ix) && math.IsNaN(iy) { + ix, iy = 0, 0 + } + return rx == ry && ix == iy + }), + Comparer(func(x, y chan bool) bool { return true }), + Comparer(func(x, y chan int) bool { return true }), + Comparer(func(x, y chan float64) bool { return true }), + Comparer(func(x, y chan interface{}) bool { return true }), + Comparer(func(x, y *int) bool { return true }), + } + + tests := []struct { + in map[interface{}]bool // Set of keys to sort + want []interface{} + }{{ + in: map[interface{}]bool{1: true, 2: true, 3: true}, + want: []interface{}{1, 2, 3}, + }, { + in: map[interface{}]bool{ + nil: true, + true: true, + false: true, + -5: true, + -55: true, + -555: true, + uint(1): true, + uint(11): true, + uint(111): true, + "abc": true, + "abcd": true, + "abcde": true, + "foo": true, + "bar": true, + MyString("abc"): true, + MyString("abcd"): true, + MyString("abcde"): true, + new(int): true, + new(int): true, + make(chan bool): true, + make(chan bool): true, + make(chan int): true, + make(chan interface{}): true, + math.Inf(+1): true, + math.Inf(-1): true, + 1.2345: true, + 12.345: true, + 123.45: true, + 1234.5: true, + 0 + 0i: true, + 1 + 0i: true, + 2 + 0i: true, + 0 + 1i: true, + 0 + 2i: true, + 0 + 3i: true, + [2]int{2, 3}: true, + [2]int{4, 0}: true, + [2]int{2, 4}: true, + MyArray([2]int{2, 4}): true, + EmptyStruct{}: true, + MyStruct{ + "bravo", [2]int{2, 3}, make(chan float64), + }: true, + MyStruct{ + "alpha", [2]int{3, 3}, make(chan float64), + }: true, + }, + want: []interface{}{ + nil, false, true, + -555, -55, -5, uint(1), uint(11), uint(111), + math.Inf(-1), 1.2345, 12.345, 123.45, 1234.5, math.Inf(+1), + (0 + 0i), (0 + 1i), (0 + 2i), (0 + 3i), (1 + 0i), (2 + 0i), + [2]int{2, 3}, [2]int{2, 4}, [2]int{4, 0}, MyArray([2]int{2, 4}), + make(chan bool), make(chan bool), make(chan int), make(chan interface{}), + new(int), new(int), + MyString("abc"), MyString("abcd"), MyString("abcde"), "abc", "abcd", "abcde", "bar", "foo", + EmptyStruct{}, + MyStruct{"alpha", [2]int{3, 3}, make(chan float64)}, + MyStruct{"bravo", [2]int{2, 3}, make(chan float64)}, + }, + }, { + // NaN values cannot be properly deduplicated. + // This is okay since map entries with NaN in the keys cannot be + // retrieved anyways. + in: map[interface{}]bool{ + math.NaN(): true, + math.NaN(): true, + complex(0, math.NaN()): true, + complex(0, math.NaN()): true, + complex(math.NaN(), 0): true, + complex(math.NaN(), 0): true, + complex(math.NaN(), math.NaN()): true, + }, + want: []interface{}{ + math.NaN(), math.NaN(), math.NaN(), math.NaN(), + complex(math.NaN(), math.NaN()), complex(math.NaN(), math.NaN()), + complex(math.NaN(), 0), complex(math.NaN(), 0), complex(math.NaN(), 0), complex(math.NaN(), 0), + complex(0, math.NaN()), complex(0, math.NaN()), complex(0, math.NaN()), complex(0, math.NaN()), + }, + }} + + for i, tt := range tests { + keys := append(reflect.ValueOf(tt.in).MapKeys(), reflect.ValueOf(tt.in).MapKeys()...) + var got []interface{} + for _, k := range sortKeys(keys) { + got = append(got, k.Interface()) + } + if !Equal(got, tt.want, opts...) { + t.Errorf("test %d, output mismatch:\ngot %#v\nwant %#v", i, got, tt.want) + } + } +} diff --git a/vendor/github.com/google/go-cmp/cmp/unsafe_panic.go b/vendor/github.com/google/go-cmp/cmp/unsafe_panic.go new file mode 100644 index 000000000..0d44987f5 --- /dev/null +++ b/vendor/github.com/google/go-cmp/cmp/unsafe_panic.go @@ -0,0 +1,15 @@ +// Copyright 2017, The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE.md file. + +// +build appengine js + +package cmp + +import "reflect" + +const supportAllowUnexported = false + +func unsafeRetrieveField(reflect.Value, reflect.StructField) reflect.Value { + panic("unsafeRetrieveField is not implemented") +} diff --git a/vendor/github.com/google/go-cmp/cmp/unsafe_reflect.go b/vendor/github.com/google/go-cmp/cmp/unsafe_reflect.go new file mode 100644 index 000000000..81fb82632 --- /dev/null +++ b/vendor/github.com/google/go-cmp/cmp/unsafe_reflect.go @@ -0,0 +1,23 @@ +// Copyright 2017, The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE.md file. + +// +build !appengine,!js + +package cmp + +import ( + "reflect" + "unsafe" +) + +const supportAllowUnexported = true + +// unsafeRetrieveField uses unsafe to forcibly retrieve any field from a struct +// such that the value has read-write permissions. +// +// The parent struct, v, must be addressable, while f must be a StructField +// describing the field to retrieve. +func unsafeRetrieveField(v reflect.Value, f reflect.StructField) reflect.Value { + return reflect.NewAt(f.Type, unsafe.Pointer(v.UnsafeAddr()+f.Offset)).Elem() +} From ead7c103ba45948a8c49629b48a2964f51b6d124 Mon Sep 17 00:00:00 2001 From: Tim Raymond Date: Fri, 21 Jul 2017 12:09:49 -0400 Subject: [PATCH 005/107] Enforce only "x", "y", and "y2" axes For the forseeable future, we will only be using the "x", "y", and "y2" axes, even though the underlying serialization can support arbitrary axes (for the future). This ensures that only "x", "y", and "y2" axes are present and updates the Swagger docs to reflect that fact --- chronograf.go | 1 + server/cells.go | 16 ++++++++++- server/cells_test.go | 60 +++++++++++++++++++++++++++++++++++++++ server/dashboards_test.go | 21 ++++++++++++-- server/swagger.json | 54 ++++++++++++++++++++--------------- 5 files changed, 126 insertions(+), 26 deletions(-) create mode 100644 server/cells_test.go diff --git a/chronograf.go b/chronograf.go index c1401790d..8a62763ab 100644 --- a/chronograf.go +++ b/chronograf.go @@ -29,6 +29,7 @@ const ( ErrAlertNotFound = Error("alert not found") ErrAuthentication = Error("user not authenticated") ErrUninitialized = Error("client uninitialized. Call Open() method") + ErrInvalidAxis = Error("Unexpected axis in cell. Valid axes are 'x', 'y', and 'y2'") ) // Error is a domain error encountered while processing chronograf requests diff --git a/server/cells.go b/server/cells.go index b6a79290b..f60437c91 100644 --- a/server/cells.go +++ b/server/cells.go @@ -43,9 +43,23 @@ func newCellResponses(dID chronograf.DashboardID, dcells []chronograf.DashboardC return cells } -// ValidDashboardCellRequest verifies that the dashboard cells have a query +// ValidDashboardCellRequest verifies that the dashboard cells have a query and +// have the correct axes specified func ValidDashboardCellRequest(c *chronograf.DashboardCell) error { CorrectWidthHeight(c) + return HasCorrectAxes(c) +} + +// HasCorrectAxes verifies that only permitted axes exist within a DashboardCell +func HasCorrectAxes(c *chronograf.DashboardCell) error { + for axis, _ := range c.Axes { + switch axis { + case "x", "y", "y2": + // no-op + default: + return chronograf.ErrInvalidAxis + } + } return nil } diff --git a/server/cells_test.go b/server/cells_test.go new file mode 100644 index 000000000..be694b3df --- /dev/null +++ b/server/cells_test.go @@ -0,0 +1,60 @@ +package server_test + +import ( + "testing" + + "github.com/influxdata/chronograf" + "github.com/influxdata/chronograf/server" +) + +func Test_Cells_CorrectAxis(t *testing.T) { + t.Parallel() + + axisTests := []struct { + name string + cell *chronograf.DashboardCell + shouldFail bool + }{ + { + "correct axes", + &chronograf.DashboardCell{ + Axes: map[string]chronograf.Axis{ + "x": chronograf.Axis{ + Bounds: [2]int64{0, 100}, + }, + "y": chronograf.Axis{ + Bounds: [2]int64{0, 100}, + }, + "y2": chronograf.Axis{ + Bounds: [2]int64{0, 100}, + }, + }, + }, + false, + }, + { + "invalid axes present", + &chronograf.DashboardCell{ + Axes: map[string]chronograf.Axis{ + "axis of evil": chronograf.Axis{ + Bounds: [2]int64{666, 666}, + }, + "axis of awesome": chronograf.Axis{ + Bounds: [2]int64{1337, 31337}, + }, + }, + }, + true, + }, + } + + for _, test := range axisTests { + t.Run(test.name, func(tt *testing.T) { + if err := server.HasCorrectAxes(test.cell); err != nil && !test.shouldFail { + t.Errorf("%q: Unexpected error: err: %s", test.name, err) + } else if err == nil && test.shouldFail { + t.Errorf("%q: Expected error and received none", test.name) + } + }) + } +} diff --git a/server/dashboards_test.go b/server/dashboards_test.go index 613d56d57..b5116913c 100644 --- a/server/dashboards_test.go +++ b/server/dashboards_test.go @@ -4,6 +4,7 @@ import ( "reflect" "testing" + "github.com/google/go-cmp/cmp" "github.com/influxdata/chronograf" ) @@ -219,6 +220,14 @@ func Test_newDashboardResponse(t *testing.T) { Command: "SELECT donors from hill_valley_preservation_society where time > '1985-10-25 08:00:00'", }, }, + Axes: map[string]chronograf.Axis{ + "x": chronograf.Axis{ + Bounds: [2]int64{0, 100}, + }, + "y": chronograf.Axis{ + Bounds: [2]int64{2, 95}, + }, + }, }, { ID: "b", @@ -257,6 +266,14 @@ func Test_newDashboardResponse(t *testing.T) { }, }, }, + Axes: map[string]chronograf.Axis{ + "x": chronograf.Axis{ + Bounds: [2]int64{0, 100}, + }, + "y": chronograf.Axis{ + Bounds: [2]int64{2, 95}, + }, + }, }, }, dashboardCellResponse{ @@ -301,8 +318,8 @@ func Test_newDashboardResponse(t *testing.T) { }, } for _, tt := range tests { - if got := newDashboardResponse(tt.d); !reflect.DeepEqual(got, tt.want) { - t.Errorf("%q. newDashboardResponse() = \n%#v\n\n, want\n\n%#v", tt.name, got, tt.want) + if got := newDashboardResponse(tt.d); !cmp.Equal(got, tt.want) { + t.Errorf("%q. newDashboardResponse() = diff:\n%s", tt.name, cmp.Diff(got, tt.want)) } } } diff --git a/server/swagger.json b/server/swagger.json index 524e5c819..1891640b8 100644 --- a/server/swagger.json +++ b/server/swagger.json @@ -3999,13 +3999,21 @@ "$ref": "#/definitions/DashboardQuery" } }, - "axes": { - "description": "The viewport for a cell's visualizations", - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/DashboardRange" - } - }, + "axes": { + "description": "The viewport for a cell's visualizations", + "type": "object", + "properties": { + "x": { + "$ref": "#/definitions/DashboardRange" + }, + "y": { + "$ref": "#/definitions/DashboardRange" + }, + "y2": { + "$ref": "#/definitions/DashboardRange" + } + } + }, "type": { "description": "Cell visualization type", "type": "string", @@ -4087,22 +4095,22 @@ } } }, - "DashboardRange": { - "type": "object", - "description": "A description of a particular axis for a visualization", - "properties": { - "bounds": { - "type": "array", - "minItems": 0, - "maxItems": 2, - "description": "The extents of an axis in the form [lower, upper]. Clients determine whether bounds are to be inclusive or exclusive of their limits", - "items": { - "type": "integer", - "format": "int32" - } - } - } - }, + "DashboardRange": { + "type": "object", + "description": "A description of a particular axis for a visualization", + "properties": { + "bounds": { + "type": "array", + "minItems": 0, + "maxItems": 2, + "description": "The extents of an axis in the form [lower, upper]. Clients determine whether bounds are to be inclusive or exclusive of their limits", + "items": { + "type": "integer", + "format": "int64" + } + } + } + }, "Routes": { "type": "object", "properties": { From c336805943a5d46f3ce5a05bbcfd6ebddb6517e7 Mon Sep 17 00:00:00 2001 From: Hunter Trujillo Date: Thu, 6 Jul 2017 12:16:31 -0600 Subject: [PATCH 006/107] Refactor Overlay Controls into a separate component for later. Add Display Options component. Replace Overlay Controls with Display Options. --- .../components/CellEditorOverlay.js | 39 ++++++---- .../dashboards/components/DisplayOptions.js | 8 ++ .../components/GraphTypeSelector.js | 34 ++++++++ .../dashboards/components/OverlayControls.js | 78 +++++++++---------- 4 files changed, 106 insertions(+), 53 deletions(-) create mode 100644 ui/src/dashboards/components/DisplayOptions.js create mode 100644 ui/src/dashboards/components/GraphTypeSelector.js diff --git a/ui/src/dashboards/components/CellEditorOverlay.js b/ui/src/dashboards/components/CellEditorOverlay.js index 791ab7252..a82e46e91 100644 --- a/ui/src/dashboards/components/CellEditorOverlay.js +++ b/ui/src/dashboards/components/CellEditorOverlay.js @@ -7,6 +7,7 @@ import ResizeContainer from 'shared/components/ResizeContainer' import QueryMaker from 'src/data_explorer/components/QueryMaker' import Visualization from 'src/data_explorer/components/Visualization' import OverlayControls from 'src/dashboards/components/OverlayControls' +import DisplayOptions from 'src/dashboards/components/DisplayOptions' import * as queryModifiers from 'src/utils/queryTransitions' import defaultQueryConfig from 'src/utils/defaultQueryConfig' @@ -28,7 +29,7 @@ class CellEditorOverlay extends Component { this.handleSaveCell = ::this.handleSaveCell - this.handleSelectGraphType = ::this.handleSelectGraphType + this.handleSelectDisplayOptions = ::this.handleSelectDisplayOptions this.handleSetActiveQueryIndex = ::this.handleSetActiveQueryIndex this.handleEditRawText = ::this.handleEditRawText @@ -43,6 +44,7 @@ class CellEditorOverlay extends Component { cellWorkingType: type, queriesWorkingDraft, activeQueryIndex: 0, + isDisplayOptionsTabOpen: false, } } @@ -112,6 +114,12 @@ class CellEditorOverlay extends Component { this.setState({cellWorkingType: graphType}) } + handleSelectDisplayOptions(isDisplayOptionsTabOpen) { + return () => { + this.setState({isDisplayOptionsTabOpen}) + } + } + handleSetActiveQueryIndex(activeQueryIndex) { this.setState({activeQueryIndex}) } @@ -146,6 +154,7 @@ class CellEditorOverlay extends Component { activeQueryIndex, cellWorkingName, cellWorkingType, + isDisplayOptionsTabOpen, queriesWorkingDraft, } = this.state @@ -181,23 +190,25 @@ class CellEditorOverlay extends Component { />
- + {isDisplayOptionsTabOpen + ? + : }
diff --git a/ui/src/dashboards/components/DisplayOptions.js b/ui/src/dashboards/components/DisplayOptions.js new file mode 100644 index 000000000..0f255f78a --- /dev/null +++ b/ui/src/dashboards/components/DisplayOptions.js @@ -0,0 +1,8 @@ +import React, {PropTypes} from 'react' + +const DisplayOptions = () => +
Display Options
+ +DisplayOptions.propTypes = {} + +export default DisplayOptions diff --git a/ui/src/dashboards/components/GraphTypeSelector.js b/ui/src/dashboards/components/GraphTypeSelector.js new file mode 100644 index 000000000..9ce9c3447 --- /dev/null +++ b/ui/src/dashboards/components/GraphTypeSelector.js @@ -0,0 +1,34 @@ +import React, {PropTypes} from 'react' +import classnames from 'classnames' + +import graphTypes from 'hson!shared/data/graphTypes.hson' + +const GraphTypeSelector = ({selectedGraphType, onSelectGraphType}) => +
+

Cell Editor

+
+

Visualization Type

+
    + {graphTypes.map(graphType => +
  • onSelectGraphType(graphType.type)} + > + {graphType.menuOption} +
  • + )} +
+
+
+ +const {func, string} = PropTypes + +GraphTypeSelector.propTypes = { + selectedGraphType: string.isRequired, + onSelectGraphType: func.isRequired, +} + +export default GraphTypeSelector diff --git a/ui/src/dashboards/components/OverlayControls.js b/ui/src/dashboards/components/OverlayControls.js index a13a29f72..e87a6a28d 100644 --- a/ui/src/dashboards/components/OverlayControls.js +++ b/ui/src/dashboards/components/OverlayControls.js @@ -3,51 +3,51 @@ import classnames from 'classnames' import ConfirmButtons from 'shared/components/ConfirmButtons' -import graphTypes from 'hson!shared/data/graphTypes.hson' - -const OverlayControls = props => { - const { - onCancel, - onSave, - selectedGraphType, - onSelectGraphType, - isSavable, - } = props - return ( -
-

Cell Editor

-
-

Visualization Type:

-
    - {graphTypes.map(graphType => -
  • onSelectGraphType(graphType.type)} - > - {graphType.menuOption} -
  • - )} -
- -
+const OverlayControls = ({ + onCancel, + onSave, + isDisplayOptionsTabOpen, + onSelectDisplayOptions, + isSavable, +}) => +
+

Cell Editor

+
+
    +
  • + Queries +
  • +
  • + Display Options +
  • +
+
- ) -} +
-const {func, string, bool} = PropTypes +const {func, bool} = PropTypes OverlayControls.propTypes = { onCancel: func.isRequired, onSave: func.isRequired, - selectedGraphType: string.isRequired, - onSelectGraphType: func.isRequired, + isDisplayOptionsTabOpen: bool.isRequired, + onSelectDisplayOptions: func.isRequired, isSavable: bool, } From 562729b6b5bc97de32497394a6a8cc7c3b74a8e5 Mon Sep 17 00:00:00 2001 From: Hunter Trujillo Date: Thu, 6 Jul 2017 12:27:02 -0600 Subject: [PATCH 007/107] Fix visualization selection functionality. --- .../dashboards/components/CellEditorOverlay.js | 6 +++++- ui/src/dashboards/components/DisplayOptions.js | 18 +++++++++++++++--- .../dashboards/components/GraphTypeSelector.js | 1 - 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/ui/src/dashboards/components/CellEditorOverlay.js b/ui/src/dashboards/components/CellEditorOverlay.js index a82e46e91..7e36b71a9 100644 --- a/ui/src/dashboards/components/CellEditorOverlay.js +++ b/ui/src/dashboards/components/CellEditorOverlay.js @@ -29,6 +29,7 @@ class CellEditorOverlay extends Component { this.handleSaveCell = ::this.handleSaveCell + this.handleSelectGraphType = ::this.handleSelectGraphType this.handleSelectDisplayOptions = ::this.handleSelectDisplayOptions this.handleSetActiveQueryIndex = ::this.handleSetActiveQueryIndex this.handleEditRawText = ::this.handleEditRawText @@ -197,7 +198,10 @@ class CellEditorOverlay extends Component { isSavable={queriesWorkingDraft.every(isQuerySavable)} /> {isDisplayOptionsTabOpen - ? + ? : -
Display Options
+import GraphTypeSelector from 'src/dashboards/components/GraphTypeSelector' -DisplayOptions.propTypes = {} +const DisplayOptions = ({selectedGraphType, onSelectGraphType}) => +
+ +
+ +const {func, string} = PropTypes + +DisplayOptions.propTypes = { + selectedGraphType: string.isRequired, + onSelectGraphType: func.isRequired, +} export default DisplayOptions diff --git a/ui/src/dashboards/components/GraphTypeSelector.js b/ui/src/dashboards/components/GraphTypeSelector.js index 9ce9c3447..41406082d 100644 --- a/ui/src/dashboards/components/GraphTypeSelector.js +++ b/ui/src/dashboards/components/GraphTypeSelector.js @@ -5,7 +5,6 @@ import graphTypes from 'hson!shared/data/graphTypes.hson' const GraphTypeSelector = ({selectedGraphType, onSelectGraphType}) =>
-

Cell Editor

Visualization Type

    From f2c434dd08e1ab00cf3a8674f07167b16fda4429 Mon Sep 17 00:00:00 2001 From: Hunter Trujillo Date: Thu, 6 Jul 2017 12:53:57 -0600 Subject: [PATCH 008/107] Add Ranger component. Add yRange prop. Add min / max inputs. --- .../components/CellEditorOverlay.js | 20 +++++++++++ .../dashboards/components/DisplayOptions.js | 16 +++++++-- ui/src/dashboards/components/Ranger.js | 35 +++++++++++++++++++ 3 files changed, 69 insertions(+), 2 deletions(-) create mode 100644 ui/src/dashboards/components/Ranger.js diff --git a/ui/src/dashboards/components/CellEditorOverlay.js b/ui/src/dashboards/components/CellEditorOverlay.js index 7e36b71a9..fefbf4c18 100644 --- a/ui/src/dashboards/components/CellEditorOverlay.js +++ b/ui/src/dashboards/components/CellEditorOverlay.js @@ -33,6 +33,7 @@ class CellEditorOverlay extends Component { this.handleSelectDisplayOptions = ::this.handleSelectDisplayOptions this.handleSetActiveQueryIndex = ::this.handleSetActiveQueryIndex this.handleEditRawText = ::this.handleEditRawText + this.handleSetRange = ::this.handleSetRange const {cell: {name, type, queries}} = props @@ -46,6 +47,10 @@ class CellEditorOverlay extends Component { queriesWorkingDraft, activeQueryIndex: 0, isDisplayOptionsTabOpen: false, + yRange: { + min: 'auto', + max: 'auto', + }, } } @@ -121,6 +126,17 @@ class CellEditorOverlay extends Component { } } + handleSetRange(e) { + const {min, max} = e.target.form + this.setState({ + yRange: { + min: min.value, + max: max.value, + }, + }) + e.preventDefault() + } + handleSetActiveQueryIndex(activeQueryIndex) { this.setState({activeQueryIndex}) } @@ -157,6 +173,7 @@ class CellEditorOverlay extends Component { cellWorkingType, isDisplayOptionsTabOpen, queriesWorkingDraft, + yRange, } = this.state const queryActions = { @@ -187,6 +204,7 @@ class CellEditorOverlay extends Component { cellType={cellWorkingType} cellName={cellWorkingName} editQueryStatus={editQueryStatus} + yRange={yRange} views={[]} />
    @@ -201,6 +219,8 @@ class CellEditorOverlay extends Component { ? : +const DisplayOptions = ({ + selectedGraphType, + onSelectGraphType, + onSetRange, + yRange, +}) =>
    +
    -const {func, string} = PropTypes +const {func, shape, string} = PropTypes DisplayOptions.propTypes = { selectedGraphType: string.isRequired, onSelectGraphType: func.isRequired, + onSetRange: func.isRequired, + yRange: shape({ + min: string, + max: string, + }).isRequired, } export default DisplayOptions diff --git a/ui/src/dashboards/components/Ranger.js b/ui/src/dashboards/components/Ranger.js new file mode 100644 index 000000000..808544ba1 --- /dev/null +++ b/ui/src/dashboards/components/Ranger.js @@ -0,0 +1,35 @@ +import React, {PropTypes} from 'react' + +const Ranger = ({onSetRange, yRange}) => +
    +
    + + + + +
    +
    + +const {func, shape, string} = PropTypes + +Ranger.propTypes = { + onSetRange: func.isRequired, + yRange: shape({ + min: string, + max: string, + }).isRequired, +} + +export default Ranger From 5082b5d4939fa0bb51c834f31b652d59cbe9dae9 Mon Sep 17 00:00:00 2001 From: Hunter Trujillo Date: Thu, 6 Jul 2017 14:10:26 -0600 Subject: [PATCH 009/107] Ranger improvements. --- ui/src/dashboards/components/Ranger.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/ui/src/dashboards/components/Ranger.js b/ui/src/dashboards/components/Ranger.js index 808544ba1..b96306181 100644 --- a/ui/src/dashboards/components/Ranger.js +++ b/ui/src/dashboards/components/Ranger.js @@ -2,17 +2,19 @@ import React, {PropTypes} from 'react' const Ranger = ({onSetRange, yRange}) =>
    -
    - + + - + Date: Fri, 7 Jul 2017 09:37:42 -0700 Subject: [PATCH 010/107] Add reducer for cell range --- ui/spec/dashboards/reducers/uiSpec.js | 30 ++++++++++++++++----------- ui/src/dashboards/actions/index.js | 9 ++++++++ ui/src/dashboards/reducers/ui.js | 24 +++++++++++++++++++++ 3 files changed, 51 insertions(+), 12 deletions(-) diff --git a/ui/spec/dashboards/reducers/uiSpec.js b/ui/spec/dashboards/reducers/uiSpec.js index 9fc364ff0..e3cb3a942 100644 --- a/ui/spec/dashboards/reducers/uiSpec.js +++ b/ui/spec/dashboards/reducers/uiSpec.js @@ -11,6 +11,7 @@ import { renameDashboardCell, syncDashboardCell, templateVariableSelected, + editCellRanges, } from 'src/dashboards/actions' let state @@ -59,22 +60,11 @@ const c1 = { w: 4, h: 4, id: 1, + i: 'im-a-cell-id-index', isEditing: false, name: 'Gigawatts', } const cells = [c1] -const tempVar = { - ...d1.templates[0], - id: '1', - type: 'measurement', - label: 'test query', - tempVar: '$HOSTS', - query: { - db: 'db1', - text: 'SHOW TAGS WHERE HUNTER = "coo"', - }, - values: ['h1', 'h2', 'h3'], -} describe('DataExplorer.Reducers.UI', () => { it('can load the dashboards', () => { @@ -180,4 +170,20 @@ describe('DataExplorer.Reducers.UI', () => { expect(actual.dashboards[0].templates[0].values[1].selected).to.equal(false) expect(actual.dashboards[0].templates[0].values[2].selected).to.equal(true) }) + + it('an set the range', () => { + const dash = {..._.cloneDeep(d1), cells: [c1]} + + state = { + dashboards: [dash], + } + + const y = [1, 2] + const y2 = [null, null] + + const actual = reducer(state, editCellRanges(dash, c1, {y, y2})) + + expect(actual.dashboards[0].cells[0].ranges.y).to.deep.equal(y) + expect(actual.dashboards[0].cells[0].ranges.y2).to.deep.equal(y2) + }) }) diff --git a/ui/src/dashboards/actions/index.js b/ui/src/dashboards/actions/index.js index 1ee23908f..63ded813f 100644 --- a/ui/src/dashboards/actions/index.js +++ b/ui/src/dashboards/actions/index.js @@ -79,6 +79,15 @@ export const addDashboardCell = (dashboard, cell) => ({ }, }) +export const editCellRanges = (dashboard, cell, ranges) => ({ + type: 'EDIT_CELL_RANGES', + payload: { + dashboard, + cell, + ranges, + }, +}) + export const editDashboardCell = (dashboard, x, y, isEditing) => ({ type: 'EDIT_DASHBOARD_CELL', // x and y coords are used as a alternative to cell ids, which are not diff --git a/ui/src/dashboards/reducers/ui.js b/ui/src/dashboards/reducers/ui.js index 92ae13c68..810e82518 100644 --- a/ui/src/dashboards/reducers/ui.js +++ b/ui/src/dashboards/reducers/ui.js @@ -239,6 +239,30 @@ export default function ui(state = initialState, action) { return {...state, dashboards} } + + case 'EDIT_CELL_RANGES': { + const {dashboard, cell, ranges} = action.payload + + const dashboards = state.dashboards.map( + d => + (d.id === dashboard.id + ? { + ...d, + cells: d.cells.map( + c => + (c.i === cell.i + ? { + ...c, + ranges, + } + : c) + ), + } + : d) + ) + + return {...state, dashboards} + } } return state From dda829a7c1660dcf30fd4f7668d6c14b3035e509 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Mon, 10 Jul 2017 11:16:55 -0700 Subject: [PATCH 011/107] Add temporary styles --- ui/src/dashboards/components/DisplayOptions.js | 13 +++++++++++-- ui/src/dashboards/components/GraphTypeSelector.js | 11 ++++++----- ui/src/dashboards/components/OverlayControls.js | 3 ++- ui/src/dashboards/components/Ranger.js | 5 +++-- 4 files changed, 22 insertions(+), 10 deletions(-) diff --git a/ui/src/dashboards/components/DisplayOptions.js b/ui/src/dashboards/components/DisplayOptions.js index ba92f91db..f34f42957 100644 --- a/ui/src/dashboards/components/DisplayOptions.js +++ b/ui/src/dashboards/components/DisplayOptions.js @@ -3,19 +3,28 @@ import React, {PropTypes} from 'react' import GraphTypeSelector from 'src/dashboards/components/GraphTypeSelector' import Ranger from 'src/dashboards/components/Ranger' +const style = { + height: '100%', + margin: '0 60px', + display: 'flex', + backgroundColor: '#202028', + justifyContent: 'space-around', +} + const DisplayOptions = ({ selectedGraphType, onSelectGraphType, onSetRange, yRange, -}) => -
    +}) => ( +
    +) const {func, shape, string} = PropTypes diff --git a/ui/src/dashboards/components/GraphTypeSelector.js b/ui/src/dashboards/components/GraphTypeSelector.js index 41406082d..e57640e09 100644 --- a/ui/src/dashboards/components/GraphTypeSelector.js +++ b/ui/src/dashboards/components/GraphTypeSelector.js @@ -3,12 +3,12 @@ import classnames from 'classnames' import graphTypes from 'hson!shared/data/graphTypes.hson' -const GraphTypeSelector = ({selectedGraphType, onSelectGraphType}) => -
    -
    +const GraphTypeSelector = ({selectedGraphType, onSelectGraphType}) => ( +
    +

    Visualization Type

      - {graphTypes.map(graphType => + {graphTypes.map(graphType => (
    • > {graphType.menuOption}
    • - )} + ))}
    +) const {func, string} = PropTypes diff --git a/ui/src/dashboards/components/OverlayControls.js b/ui/src/dashboards/components/OverlayControls.js index e87a6a28d..8254f28e9 100644 --- a/ui/src/dashboards/components/OverlayControls.js +++ b/ui/src/dashboards/components/OverlayControls.js @@ -9,7 +9,7 @@ const OverlayControls = ({ isDisplayOptionsTabOpen, onSelectDisplayOptions, isSavable, -}) => +}) => (

    Cell Editor

    @@ -40,6 +40,7 @@ const OverlayControls = ({ />
    +) const {func, bool} = PropTypes diff --git a/ui/src/dashboards/components/Ranger.js b/ui/src/dashboards/components/Ranger.js index b96306181..6f50df0e8 100644 --- a/ui/src/dashboards/components/Ranger.js +++ b/ui/src/dashboards/components/Ranger.js @@ -1,7 +1,7 @@ import React, {PropTypes} from 'react' -const Ranger = ({onSetRange, yRange}) => -
    +const Ranger = ({onSetRange, yRange}) => ( +
    />
    +) const {func, shape, string} = PropTypes From 059f858b9cd996d7078f7abd9cb836d96d675ba6 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Mon, 10 Jul 2017 13:19:30 -0700 Subject: [PATCH 012/107] Hook up ranges to CEO --- ui/spec/dashboards/reducers/uiSpec.js | 6 +-- ui/src/dashboards/actions/index.js | 8 ++-- .../components/CellEditorOverlay.js | 46 +++++++++++-------- .../dashboards/components/DisplayOptions.js | 18 +++++--- ui/src/dashboards/components/Ranger.js | 22 +++++---- ui/src/dashboards/containers/DashboardPage.js | 7 ++- ui/src/dashboards/reducers/ui.js | 8 ++-- 7 files changed, 70 insertions(+), 45 deletions(-) diff --git a/ui/spec/dashboards/reducers/uiSpec.js b/ui/spec/dashboards/reducers/uiSpec.js index e3cb3a942..5d21fb829 100644 --- a/ui/spec/dashboards/reducers/uiSpec.js +++ b/ui/spec/dashboards/reducers/uiSpec.js @@ -181,9 +181,9 @@ describe('DataExplorer.Reducers.UI', () => { const y = [1, 2] const y2 = [null, null] - const actual = reducer(state, editCellRanges(dash, c1, {y, y2})) + const actual = reducer(state, editCellRanges(dash.id, c1.i, {y, y2})) - expect(actual.dashboards[0].cells[0].ranges.y).to.deep.equal(y) - expect(actual.dashboards[0].cells[0].ranges.y2).to.deep.equal(y2) + expect(actual.dashboards[0].cells[0].yRanges.y).to.deep.equal(y) + expect(actual.dashboards[0].cells[0].yRanges.y2).to.deep.equal(y2) }) }) diff --git a/ui/src/dashboards/actions/index.js b/ui/src/dashboards/actions/index.js index 63ded813f..2a4841759 100644 --- a/ui/src/dashboards/actions/index.js +++ b/ui/src/dashboards/actions/index.js @@ -79,12 +79,12 @@ export const addDashboardCell = (dashboard, cell) => ({ }, }) -export const editCellRanges = (dashboard, cell, ranges) => ({ +export const editCellRanges = (dashboardID, cellID, yRanges) => ({ type: 'EDIT_CELL_RANGES', payload: { - dashboard, - cell, - ranges, + dashboardID, + cellID, + yRanges, }, }) diff --git a/ui/src/dashboards/components/CellEditorOverlay.js b/ui/src/dashboards/components/CellEditorOverlay.js index fefbf4c18..7ef54ddfc 100644 --- a/ui/src/dashboards/components/CellEditorOverlay.js +++ b/ui/src/dashboards/components/CellEditorOverlay.js @@ -28,6 +28,7 @@ class CellEditorOverlay extends Component { this.handleDeleteQuery = ::this.handleDeleteQuery this.handleSaveCell = ::this.handleSaveCell + this.handleEditCellRanges = ::this.handleEditCellRanges this.handleSelectGraphType = ::this.handleSelectGraphType this.handleSelectDisplayOptions = ::this.handleSelectDisplayOptions @@ -35,7 +36,7 @@ class CellEditorOverlay extends Component { this.handleEditRawText = ::this.handleEditRawText this.handleSetRange = ::this.handleSetRange - const {cell: {name, type, queries}} = props + const {cell: {name, type, queries, yRanges}} = props const queriesWorkingDraft = _.cloneDeep( queries.map(({queryConfig}) => ({...queryConfig, id: uuid.v4()})) @@ -47,9 +48,8 @@ class CellEditorOverlay extends Component { queriesWorkingDraft, activeQueryIndex: 0, isDisplayOptionsTabOpen: false, - yRange: { - min: 'auto', - max: 'auto', + yRanges: { + y: yRanges && yRanges.y ? yRanges.y : ['', ''], }, } } @@ -81,6 +81,24 @@ class CellEditorOverlay extends Component { } } + handleEditCellRanges(e) { + e.preventDefault() + const {dashboardID, cell: {i}} = this.props + const {yRanges} = this.state + + this.props.onEditCellRanges(+dashboardID, i, yRanges) + } + + handleSetRange(e) { + const {min, max} = e.target.form + this.setState({ + yRanges: { + y: [min.value, max.value], + }, + }) + e.preventDefault() + } + handleAddQuery(options) { const newQuery = Object.assign({}, defaultQueryConfig(uuid.v4()), options) const nextQueries = this.state.queriesWorkingDraft.concat(newQuery) @@ -126,17 +144,6 @@ class CellEditorOverlay extends Component { } } - handleSetRange(e) { - const {min, max} = e.target.form - this.setState({ - yRange: { - min: min.value, - max: max.value, - }, - }) - e.preventDefault() - } - handleSetActiveQueryIndex(activeQueryIndex) { this.setState({activeQueryIndex}) } @@ -173,7 +180,7 @@ class CellEditorOverlay extends Component { cellWorkingType, isDisplayOptionsTabOpen, queriesWorkingDraft, - yRange, + yRanges, } = this.state const queryActions = { @@ -204,7 +211,7 @@ class CellEditorOverlay extends Component { cellType={cellWorkingType} cellName={cellWorkingName} editQueryStatus={editQueryStatus} - yRange={yRange} + yRanges={yRanges} views={[]} />
    @@ -217,10 +224,11 @@ class CellEditorOverlay extends Component { /> {isDisplayOptionsTabOpen ? : (
    - +
    ) -const {func, shape, string} = PropTypes +const {array, func, shape, string} = PropTypes DisplayOptions.propTypes = { + onEditCellRanges: func.isRequired, selectedGraphType: string.isRequired, onSelectGraphType: func.isRequired, onSetRange: func.isRequired, - yRange: shape({ - min: string, - max: string, + yRanges: shape({ + y: array, + y2: array, }).isRequired, } diff --git a/ui/src/dashboards/components/Ranger.js b/ui/src/dashboards/components/Ranger.js index 6f50df0e8..349ab05ac 100644 --- a/ui/src/dashboards/components/Ranger.js +++ b/ui/src/dashboards/components/Ranger.js @@ -1,16 +1,17 @@ import React, {PropTypes} from 'react' -const Ranger = ({onSetRange, yRange}) => ( +const Ranger = ({onSetRange, yRanges, onEditCellRanges}) => (
    -
    + ( type="text" name="max" id="max" - value={yRange.max} + value={yRanges.y[1]} onChange={onSetRange} + placeholder="auto" /> +
    ) -const {func, shape, string} = PropTypes +const {array, func, shape} = PropTypes Ranger.propTypes = { + onEditCellRanges: func.isRequired, onSetRange: func.isRequired, - yRange: shape({ - min: string, - max: string, + yRanges: shape({ + y: array, + y2: array, }).isRequired, } diff --git a/ui/src/dashboards/containers/DashboardPage.js b/ui/src/dashboards/containers/DashboardPage.js index d610d371d..a509105d6 100644 --- a/ui/src/dashboards/containers/DashboardPage.js +++ b/ui/src/dashboards/containers/DashboardPage.js @@ -256,7 +256,7 @@ class DashboardPage extends Component { inPresentationMode, handleChooseAutoRefresh, handleClickPresentationButton, - params: {sourceID}, + params: {sourceID, dashboardID}, } = this.props const lowerType = lower && lower.includes('Z') ? 'timeStamp' : 'constant' @@ -330,6 +330,7 @@ class DashboardPage extends Component { {selectedCell ? : null} @@ -380,8 +382,8 @@ class DashboardPage extends Component { autoRefresh={autoRefresh} synchronizer={this.synchronizer} onAddCell={this.handleAddCell} - inPresentationMode={inPresentationMode} onEditCell={this.handleEditDashboardCell} + inPresentationMode={inPresentationMode} onPositionChange={this.handleUpdatePosition} onDeleteCell={this.handleDeleteDashboardCell} onRenameCell={this.handleRenameDashboardCell} @@ -421,6 +423,7 @@ DashboardPage.propTypes = { addDashboardCellAsync: func.isRequired, editDashboardCell: func.isRequired, renameDashboardCell: func.isRequired, + editCellRanges: func.isRequired, }).isRequired, dashboards: arrayOf( shape({ diff --git a/ui/src/dashboards/reducers/ui.js b/ui/src/dashboards/reducers/ui.js index 810e82518..d2db90831 100644 --- a/ui/src/dashboards/reducers/ui.js +++ b/ui/src/dashboards/reducers/ui.js @@ -241,19 +241,19 @@ export default function ui(state = initialState, action) { } case 'EDIT_CELL_RANGES': { - const {dashboard, cell, ranges} = action.payload + const {dashboardID, cellID, yRanges} = action.payload const dashboards = state.dashboards.map( d => - (d.id === dashboard.id + (d.id === dashboardID ? { ...d, cells: d.cells.map( c => - (c.i === cell.i + (c.i === cellID ? { ...c, - ranges, + yRanges, } : c) ), From ec3721fbb7778871a6e41135d480b1d5c21923bf Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Mon, 10 Jul 2017 13:19:38 -0700 Subject: [PATCH 013/107] Prettier --- ui/src/dashboards/containers/DashboardPage.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ui/src/dashboards/containers/DashboardPage.js b/ui/src/dashboards/containers/DashboardPage.js index a509105d6..802f983aa 100644 --- a/ui/src/dashboards/containers/DashboardPage.js +++ b/ui/src/dashboards/containers/DashboardPage.js @@ -82,8 +82,7 @@ class DashboardPage extends Component { handleCloseTemplateManager(isEdited) { if ( - !isEdited || - (isEdited && confirm('Do you want to close without saving?')) // eslint-disable-line no-alert + !isEdited || (isEdited && confirm('Do you want to close without saving?')) // eslint-disable-line no-alert ) { this.setState({isTemplating: false}) } From 421ade1627704effb781b716570afb66a483e011 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Tue, 11 Jul 2017 11:36:26 -0700 Subject: [PATCH 014/107] Save range with entire cell --- ui/spec/dashboards/reducers/uiSpec.js | 17 ---------- ui/src/dashboards/actions/index.js | 9 ----- .../components/CellEditorOverlay.js | 26 +++++--------- .../dashboards/components/DisplayOptions.js | 8 +---- ui/src/dashboards/components/Ranger.js | 8 ++--- ui/src/dashboards/containers/DashboardPage.js | 2 -- ui/src/dashboards/reducers/ui.js | 24 ------------- ui/src/data_explorer/components/VisView.js | 5 ++- .../data_explorer/components/Visualization.js | 3 ++ ui/src/shared/components/Dygraph.js | 16 ++++----- ui/src/shared/components/LineGraph.js | 34 ++++--------------- ui/src/shared/parsing/getRangeForDygraph.js | 11 +++--- 12 files changed, 38 insertions(+), 125 deletions(-) diff --git a/ui/spec/dashboards/reducers/uiSpec.js b/ui/spec/dashboards/reducers/uiSpec.js index 5d21fb829..88183bb79 100644 --- a/ui/spec/dashboards/reducers/uiSpec.js +++ b/ui/spec/dashboards/reducers/uiSpec.js @@ -11,7 +11,6 @@ import { renameDashboardCell, syncDashboardCell, templateVariableSelected, - editCellRanges, } from 'src/dashboards/actions' let state @@ -170,20 +169,4 @@ describe('DataExplorer.Reducers.UI', () => { expect(actual.dashboards[0].templates[0].values[1].selected).to.equal(false) expect(actual.dashboards[0].templates[0].values[2].selected).to.equal(true) }) - - it('an set the range', () => { - const dash = {..._.cloneDeep(d1), cells: [c1]} - - state = { - dashboards: [dash], - } - - const y = [1, 2] - const y2 = [null, null] - - const actual = reducer(state, editCellRanges(dash.id, c1.i, {y, y2})) - - expect(actual.dashboards[0].cells[0].yRanges.y).to.deep.equal(y) - expect(actual.dashboards[0].cells[0].yRanges.y2).to.deep.equal(y2) - }) }) diff --git a/ui/src/dashboards/actions/index.js b/ui/src/dashboards/actions/index.js index 2a4841759..1ee23908f 100644 --- a/ui/src/dashboards/actions/index.js +++ b/ui/src/dashboards/actions/index.js @@ -79,15 +79,6 @@ export const addDashboardCell = (dashboard, cell) => ({ }, }) -export const editCellRanges = (dashboardID, cellID, yRanges) => ({ - type: 'EDIT_CELL_RANGES', - payload: { - dashboardID, - cellID, - yRanges, - }, -}) - export const editDashboardCell = (dashboard, x, y, isEditing) => ({ type: 'EDIT_DASHBOARD_CELL', // x and y coords are used as a alternative to cell ids, which are not diff --git a/ui/src/dashboards/components/CellEditorOverlay.js b/ui/src/dashboards/components/CellEditorOverlay.js index 7ef54ddfc..80b9468f2 100644 --- a/ui/src/dashboards/components/CellEditorOverlay.js +++ b/ui/src/dashboards/components/CellEditorOverlay.js @@ -28,7 +28,6 @@ class CellEditorOverlay extends Component { this.handleDeleteQuery = ::this.handleDeleteQuery this.handleSaveCell = ::this.handleSaveCell - this.handleEditCellRanges = ::this.handleEditCellRanges this.handleSelectGraphType = ::this.handleSelectGraphType this.handleSelectDisplayOptions = ::this.handleSelectDisplayOptions @@ -81,14 +80,6 @@ class CellEditorOverlay extends Component { } } - handleEditCellRanges(e) { - e.preventDefault() - const {dashboardID, cell: {i}} = this.props - const {yRanges} = this.state - - this.props.onEditCellRanges(+dashboardID, i, yRanges) - } - handleSetRange(e) { const {min, max} = e.target.form this.setState({ @@ -113,13 +104,16 @@ class CellEditorOverlay extends Component { } handleSaveCell() { - const {queriesWorkingDraft, cellWorkingType, cellWorkingName} = this.state + const { + queriesWorkingDraft, + cellWorkingType: name, + cellWorkingName: type, + yRanges, + } = this.state + const {cell} = this.props - const newCell = _.cloneDeep(cell) - newCell.name = cellWorkingName - newCell.type = cellWorkingType - newCell.queries = queriesWorkingDraft.map(q => { + const queries = queriesWorkingDraft.map(q => { const timeRange = q.range || {upper: null, lower: ':dashboardTime:'} const query = q.rawText || buildInfluxQLQuery(timeRange, q) const label = q.rawText ? '' : `${q.measurement}.${q.fields[0].field}` @@ -131,7 +125,7 @@ class CellEditorOverlay extends Component { } }) - this.props.onSave(newCell) + this.props.onSave({...cell, name, type, queries, yRanges}) } handleSelectGraphType(graphType) { @@ -224,7 +218,6 @@ class CellEditorOverlay extends Component { /> {isDisplayOptionsTabOpen ? - +
    ) const {array, func, shape, string} = PropTypes DisplayOptions.propTypes = { - onEditCellRanges: func.isRequired, selectedGraphType: string.isRequired, onSelectGraphType: func.isRequired, onSetRange: func.isRequired, diff --git a/ui/src/dashboards/components/Ranger.js b/ui/src/dashboards/components/Ranger.js index 349ab05ac..238ab74ee 100644 --- a/ui/src/dashboards/components/Ranger.js +++ b/ui/src/dashboards/components/Ranger.js @@ -1,8 +1,8 @@ import React, {PropTypes} from 'react' -const Ranger = ({onSetRange, yRanges, onEditCellRanges}) => ( +const Ranger = ({onSetRange, yRanges}) => (
    -
    + ( onChange={onSetRange} placeholder="auto" /> -
    ) @@ -33,7 +30,6 @@ const Ranger = ({onSetRange, yRanges, onEditCellRanges}) => ( const {array, func, shape} = PropTypes Ranger.propTypes = { - onEditCellRanges: func.isRequired, onSetRange: func.isRequired, yRanges: shape({ y: array, diff --git a/ui/src/dashboards/containers/DashboardPage.js b/ui/src/dashboards/containers/DashboardPage.js index 802f983aa..286674795 100644 --- a/ui/src/dashboards/containers/DashboardPage.js +++ b/ui/src/dashboards/containers/DashboardPage.js @@ -337,7 +337,6 @@ class DashboardPage extends Component { queryStatus={cellQueryStatus} onSave={this.handleSaveEditedCell} onCancel={this.handleDismissOverlay} - onEditCellRanges={dashboardActions.editCellRanges} editQueryStatus={dashboardActions.editCellQueryStatus} /> : null} @@ -422,7 +421,6 @@ DashboardPage.propTypes = { addDashboardCellAsync: func.isRequired, editDashboardCell: func.isRequired, renameDashboardCell: func.isRequired, - editCellRanges: func.isRequired, }).isRequired, dashboards: arrayOf( shape({ diff --git a/ui/src/dashboards/reducers/ui.js b/ui/src/dashboards/reducers/ui.js index d2db90831..92ae13c68 100644 --- a/ui/src/dashboards/reducers/ui.js +++ b/ui/src/dashboards/reducers/ui.js @@ -239,30 +239,6 @@ export default function ui(state = initialState, action) { return {...state, dashboards} } - - case 'EDIT_CELL_RANGES': { - const {dashboardID, cellID, yRanges} = action.payload - - const dashboards = state.dashboards.map( - d => - (d.id === dashboardID - ? { - ...d, - cells: d.cells.map( - c => - (c.i === cellID - ? { - ...c, - yRanges, - } - : c) - ), - } - : d) - ) - - return {...state, dashboards} - } } return state diff --git a/ui/src/data_explorer/components/VisView.js b/ui/src/data_explorer/components/VisView.js index 057d605d3..a32063cd5 100644 --- a/ui/src/data_explorer/components/VisView.js +++ b/ui/src/data_explorer/components/VisView.js @@ -10,6 +10,7 @@ const RefreshingSingleStat = AutoRefresh(SingleStat) const VisView = ({ view, queries, + yRanges, cellType, templates, autoRefresh, @@ -58,8 +59,9 @@ const VisView = ({ return ( (this.legendRef = el)} + legendRef={el => this.legendRef = el} onInputChange={this.handleLegendInputChange} onToggleFilter={this.handleToggleFilter} /> @@ -359,12 +359,12 @@ export default class Dygraph extends Component { } } -const {array, arrayOf, func, number, bool, shape, string} = PropTypes +const {array, arrayOf, func, bool, shape, string} = PropTypes Dygraph.propTypes = { ranges: shape({ - y: arrayOf(number), - y2: arrayOf(number), + y: arrayOf(string), + y2: arrayOf(string), }), timeSeries: array.isRequired, labels: array.isRequired, diff --git a/ui/src/shared/components/LineGraph.js b/ui/src/shared/components/LineGraph.js index 0ce517f8b..3d6af1b4f 100644 --- a/ui/src/shared/components/LineGraph.js +++ b/ui/src/shared/components/LineGraph.js @@ -15,9 +15,9 @@ export default React.createClass({ displayName: 'LineGraph', propTypes: { data: arrayOf(shape({}).isRequired).isRequired, - ranges: shape({ - y: arrayOf(number), - y2: arrayOf(number), + yRanges: shape({ + y: arrayOf(string), + y2: arrayOf(string), }), title: string, isFetchingInitially: bool, @@ -67,8 +67,7 @@ export default React.createClass({ componentWillUpdate(nextProps) { const {data, activeQueryIndex} = this.props if ( - data !== nextProps.data || - activeQueryIndex !== nextProps.activeQueryIndex + data !== nextProps.data || activeQueryIndex !== nextProps.activeQueryIndex ) { this._timeSeries = timeSeriesToDygraph( nextProps.data, @@ -81,7 +80,7 @@ export default React.createClass({ render() { const { data, - ranges, + yRanges, isFetchingInitially, isRefreshing, isGraphFilled, @@ -170,7 +169,7 @@ export default React.createClass({ labels={labels} options={showSingleStat ? singleStatOptions : options} dygraphSeries={dygraphSeries} - ranges={ranges || this.getRanges()} + ranges={yRanges} ruleValues={ruleValues} synchronizer={synchronizer} timeRange={timeRange} @@ -200,25 +199,4 @@ export default React.createClass({
    ) }, - - getRanges() { - const {queries} = this.props - if (!queries) { - return {} - } - - const ranges = {} - const q0 = queries[0] - const q1 = queries[1] - - if (q0 && q0.range) { - ranges.y = [q0.range.lower, q0.range.upper] - } - - if (q1 && q1.range) { - ranges.y2 = [q1.range.lower, q1.range.upper] - } - - return ranges - }, }) diff --git a/ui/src/shared/parsing/getRangeForDygraph.js b/ui/src/shared/parsing/getRangeForDygraph.js index f92566a63..6a162708d 100644 --- a/ui/src/shared/parsing/getRangeForDygraph.js +++ b/ui/src/shared/parsing/getRangeForDygraph.js @@ -2,13 +2,9 @@ const PADDING_FACTOR = 0.1 export default function getRange( timeSeries, - override, + userSelectedRange = [null, null], ruleValues = {value: null, rangeValue: null} ) { - if (override) { - return override - } - const {value, rangeValue, operator} = ruleValues const subtractPadding = val => +val - Math.abs(val * PADDING_FACTOR) @@ -57,5 +53,8 @@ export default function getRange( return [null, null] } - return range + const [userMin, userMax] = userSelectedRange + const [min, max] = range + + return [+userMin || min, +userMax || max] } From 7a091917e04187b6ddfcd2f8f5bb6e62c94e4399 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Tue, 11 Jul 2017 11:53:08 -0700 Subject: [PATCH 015/107] Mock persistence of ranges --- ui/src/dashboards/actions/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ui/src/dashboards/actions/index.js b/ui/src/dashboards/actions/index.js index 1ee23908f..917a93da7 100644 --- a/ui/src/dashboards/actions/index.js +++ b/ui/src/dashboards/actions/index.js @@ -178,7 +178,8 @@ export const putDashboardByID = dashboardID => async (dispatch, getState) => { export const updateDashboardCell = (dashboard, cell) => async dispatch => { try { const {data} = await updateDashboardCellAJAX(cell) - dispatch(syncDashboardCell(dashboard, data)) + // TODO: remove yRanges when server persists the ranges + dispatch(syncDashboardCell(dashboard, {...data, yRanges: cell.yRanges})) } catch (error) { console.error(error) dispatch(errorThrown(error)) From 263c6c9fe7b37dd497875c5ec7253231583c8404 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Tue, 11 Jul 2017 11:53:38 -0700 Subject: [PATCH 016/107] Wire up ranges to the rest of dashboard --- ui/src/shared/components/AutoRefresh.js | 4 ++++ ui/src/shared/components/LayoutRenderer.js | 3 ++- ui/src/shared/components/RefreshingGraph.js | 6 ++++++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/ui/src/shared/components/AutoRefresh.js b/ui/src/shared/components/AutoRefresh.js index e0c3265e1..d1cf4ef7e 100644 --- a/ui/src/shared/components/AutoRefresh.js +++ b/ui/src/shared/components/AutoRefresh.js @@ -43,6 +43,10 @@ const AutoRefresh = ComposedComponent => { text: string, }).isRequired ).isRequired, + yRanges: shape({ + y: arrayOf(string), + y2: arrayOf(string), + }), editQueryStatus: func, }, diff --git a/ui/src/shared/components/LayoutRenderer.js b/ui/src/shared/components/LayoutRenderer.js index bf5c154c8..bba4fbc82 100644 --- a/ui/src/shared/components/LayoutRenderer.js +++ b/ui/src/shared/components/LayoutRenderer.js @@ -151,7 +151,7 @@ class LayoutRenderer extends Component { } = this.props return cells.map(cell => { - const {type, h} = cell + const {type, h, yRanges} = cell return (
    @@ -175,6 +175,7 @@ class LayoutRenderer extends Component { type={type} queries={this.standardizeQueries(cell, source)} cellHeight={h} + yRanges={yRanges} />}
    diff --git a/ui/src/shared/components/RefreshingGraph.js b/ui/src/shared/components/RefreshingGraph.js index 65d5382da..746f77c44 100644 --- a/ui/src/shared/components/RefreshingGraph.js +++ b/ui/src/shared/components/RefreshingGraph.js @@ -15,6 +15,7 @@ const RefreshingGraph = ({ type, queries, cellHeight, + yRanges, }) => { if (type === 'single-stat') { return ( @@ -42,6 +43,7 @@ const RefreshingGraph = ({ isBarGraph={type === 'bar'} displayOptions={displayOptions} synchronizer={synchronizer} + yRanges={yRanges} /> ) } @@ -58,6 +60,10 @@ RefreshingGraph.propTypes = { type: string.isRequired, queries: arrayOf(shape()).isRequired, cellHeight: number.isRequired, + yRanges: shape({ + y: arrayOf(string), + y2: arrayOf(string), + }), } export default RefreshingGraph From cc3fb249b1b9ccbe3a4da0c02813372799a2dd8e Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Tue, 11 Jul 2017 11:53:51 -0700 Subject: [PATCH 017/107] Prettier --- ui/src/shared/components/AutoRefresh.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ui/src/shared/components/AutoRefresh.js b/ui/src/shared/components/AutoRefresh.js index d1cf4ef7e..3679a9fe8 100644 --- a/ui/src/shared/components/AutoRefresh.js +++ b/ui/src/shared/components/AutoRefresh.js @@ -173,8 +173,7 @@ const AutoRefresh = ComposedComponent => { } if ( - this._noResultsForQuery(timeSeries) || - !this.state.lastQuerySuccessful + this._noResultsForQuery(timeSeries) || !this.state.lastQuerySuccessful ) { return this.renderNoResults() } From cda5595d168382b764af1b8c6c6b1aec32d0a2df Mon Sep 17 00:00:00 2001 From: Alex P Date: Thu, 13 Jul 2017 14:32:11 -0700 Subject: [PATCH 018/107] Style CEO Display Options Using inline SVGs for visualization type selection, moved the whole graphTypes object into a different location and file structure --- .../dashboards/components/DisplayOptions.js | 13 +- .../components/GraphTypeSelector.js | 38 ++- .../dashboards/components/OverlayControls.js | 43 ++- ui/src/dashboards/components/Ranger.js | 50 ++-- ui/src/dashboards/graphics/graph.js | 266 ++++++++++++++++++ ui/src/shared/data/graphTypes.hson | 12 +- ui/src/style/chronograf.scss | 2 +- .../style/components/ceo-display-options.scss | 140 +++++++++ 8 files changed, 481 insertions(+), 83 deletions(-) create mode 100644 ui/src/dashboards/graphics/graph.js create mode 100644 ui/src/style/components/ceo-display-options.scss diff --git a/ui/src/dashboards/components/DisplayOptions.js b/ui/src/dashboards/components/DisplayOptions.js index c9b6758f0..e9f098cd0 100644 --- a/ui/src/dashboards/components/DisplayOptions.js +++ b/ui/src/dashboards/components/DisplayOptions.js @@ -3,28 +3,19 @@ import React, {PropTypes} from 'react' import GraphTypeSelector from 'src/dashboards/components/GraphTypeSelector' import Ranger from 'src/dashboards/components/Ranger' -const style = { - height: '100%', - margin: '0 60px', - display: 'flex', - backgroundColor: '#202028', - justifyContent: 'space-around', -} - const DisplayOptions = ({ selectedGraphType, onSelectGraphType, onSetRange, yRanges, -}) => ( -
    +}) => +
    -) const {array, func, shape, string} = PropTypes diff --git a/ui/src/dashboards/components/GraphTypeSelector.js b/ui/src/dashboards/components/GraphTypeSelector.js index e57640e09..15561447a 100644 --- a/ui/src/dashboards/components/GraphTypeSelector.js +++ b/ui/src/dashboards/components/GraphTypeSelector.js @@ -1,28 +1,26 @@ import React, {PropTypes} from 'react' import classnames from 'classnames' +import {graphTypes} from 'src/dashboards/graphics/graph' -import graphTypes from 'hson!shared/data/graphTypes.hson' - -const GraphTypeSelector = ({selectedGraphType, onSelectGraphType}) => ( -
    -
    -

    Visualization Type

    -
      - {graphTypes.map(graphType => ( -
    • onSelectGraphType(graphType.type)} - > - {graphType.menuOption} -
    • - ))} -
    +const GraphTypeSelector = ({selectedGraphType, onSelectGraphType}) => +
    +
    Visualization Type
    +
    + {graphTypes.map(graphType => +
    +
    onSelectGraphType(graphType.type)}> + {graphType.graphic} +

    {graphType.menuOption}

    +
    +
    + )}
    -) const {func, string} = PropTypes diff --git a/ui/src/dashboards/components/OverlayControls.js b/ui/src/dashboards/components/OverlayControls.js index 8254f28e9..7eb510c1f 100644 --- a/ui/src/dashboards/components/OverlayControls.js +++ b/ui/src/dashboards/components/OverlayControls.js @@ -9,30 +9,30 @@ const OverlayControls = ({ isDisplayOptionsTabOpen, onSelectDisplayOptions, isSavable, -}) => ( +}) =>

    Cell Editor

    +
      +
    • + Queries +
    • +
    • + Display Options +
    • +
    -
      -
    • - Queries -
    • -
    • - Display Options -
    • -
    -) const {func, bool} = PropTypes diff --git a/ui/src/dashboards/components/Ranger.js b/ui/src/dashboards/components/Ranger.js index 238ab74ee..c97375688 100644 --- a/ui/src/dashboards/components/Ranger.js +++ b/ui/src/dashboards/components/Ranger.js @@ -1,31 +1,35 @@ import React, {PropTypes} from 'react' -const Ranger = ({onSetRange, yRanges}) => ( -
    +const Ranger = ({onSetRange, yRanges}) => +
    +
    Y Axis Controls
    - - - - +
    + + +
    +
    + + +
    -) const {array, func, shape} = PropTypes diff --git a/ui/src/dashboards/graphics/graph.js b/ui/src/dashboards/graphics/graph.js new file mode 100644 index 000000000..f0975c157 --- /dev/null +++ b/ui/src/dashboards/graphics/graph.js @@ -0,0 +1,266 @@ +import React from 'react' + +export const graphTypes = [ + { + type: 'line', + menuOption: 'Line', + graphic: ( +
    + + + + + + + + +
    + ), + }, + { + type: 'line-stacked', + menuOption: 'Stacked', + graphic: ( +
    + + + + + + + + +
    + ), + }, + { + type: 'line-stepplot', + menuOption: 'Step-Plot', + graphic: ( +
    + + + + + + +
    + ), + }, + { + type: 'single-stat', + menuOption: 'SingleStat', + graphic: ( +
    + + + + + + +
    + ), + }, + { + type: 'line-plus-single-stat', + menuOption: 'Line + Stat', + graphic: ( +
    + + + + + + + + + + +
    + ), + }, + { + type: 'bar', + menuOption: 'Bar', + graphic: ( +
    + + + + + + + + + + + + + + +
    + ), + }, +] diff --git a/ui/src/shared/data/graphTypes.hson b/ui/src/shared/data/graphTypes.hson index 30a3565ab..31724eb72 100644 --- a/ui/src/shared/data/graphTypes.hson +++ b/ui/src/shared/data/graphTypes.hson @@ -1,8 +1,8 @@ [ - {type: "line", menuOption: "Line"}, - {type: "line-stacked", menuOption: "Stacked"}, - {type: "line-stepplot", menuOption: "Step-Plot"}, - {type: "single-stat", menuOption: "SingleStat"}, - {type: "line-plus-single-stat", menuOption: "Line + Stat"}, - {type: "bar", menuOption: "Bar"}, + {type: "line", menuOption: "Line", graphic: "Wogglez"}, + {type: "line-stacked", menuOption: "Stacked", graphic: "Rogglez"}, + {type: "line-stepplot", menuOption: "Step-Plot", graphic: "Fogglez"}, + {type: "single-stat", menuOption: "SingleStat", graphic: "Bogglez"}, + {type: "line-plus-single-stat", menuOption: "Line + Stat", graphic: "Togglez"}, + {type: "bar", menuOption: "Bar", graphic: "Zogglez"}, ] diff --git a/ui/src/style/chronograf.scss b/ui/src/style/chronograf.scss index 1725aadc9..55094c8cf 100644 --- a/ui/src/style/chronograf.scss +++ b/ui/src/style/chronograf.scss @@ -25,6 +25,7 @@ @import 'layout/flash-messages'; // Components +@import 'components/ceo-display-options'; @import 'components/confirm-buttons'; @import 'components/custom-time-range'; @import 'components/dygraphs'; @@ -47,7 +48,6 @@ @import 'components/tables'; // Pages - @import 'pages/config-endpoints'; @import 'pages/signup'; @import 'pages/auth-page'; diff --git a/ui/src/style/components/ceo-display-options.scss b/ui/src/style/components/ceo-display-options.scss new file mode 100644 index 000000000..62a9a6eac --- /dev/null +++ b/ui/src/style/components/ceo-display-options.scss @@ -0,0 +1,140 @@ +/* + Cell Editor Overlay - Display Options + ------------------------------------------------------ +*/ +.display-options { + height: 100%; + margin: 0 60px; + display: flex; + background-color: $g2-kevlar; + padding: 0 8px 8px 8px; + border-radius: 0 0 4px 4px; + flex-wrap: nowrap; + align-items: stretch; +} +.display-options--cell { + border-radius: 3px; + background-color: $g3-castle; + padding: 30px; + flex: 1 0 0; + margin-right: 8px; + + &:last-child { margin-right: 0; } +} +.display-options--cellx2 { + flex: 2 0 0; +} +.display-options--header { + margin: 0 0 12px 0; + font-weight: 400; + color: $g11-sidewalk; + @include no-user-select(); +} +.display-options--row { + display: flex; + align-items: center; + flex-wrap: nowrap; + margin-bottom: 8px; + + label { margin: 0; } + input { flex: 1 0 0; } +} + + +.viz-type-selector { + display: flex; + flex-wrap: wrap; + height: calc(100% - 22px); + margin: -4px; +} +.viz-type-selector--option { + flex: 1 0 33.3333%; + height: 50%; + padding: 4px; + + > div > p { + margin: 0; + font-size: 14px; + font-weight: 900; + position: absolute; + bottom: 6.25%; + left: 50%; + transform: translate(-50%, 50%); + display: inline-block; + } + + // Actual "card" + > div { + background-color: $g3-castle; + border: 2px solid $g4-onyx; + color: $g11-sidewalk; + border-radius: 3px; + width: 100%; + height: 100%; + display: block; + position: relative; + transition: + color 0.25s ease, + border-color 0.25s ease, + background-color 0.25s ease; + + &:hover { + cursor: pointer; + background-color: $g4-onyx; + border-color: $g5-pepper; + color: $g15-platinum; + } + } +} +// Active state "card" +.viz-type-selector--option.active > div, +.viz-type-selector--option.active > div:hover { + background-color: $g5-pepper; + border-color: $g7-graphite; + color: $g18-cloud; +} + +.viz-type-selector--graphic { + width: calc(100% - 48px); + height: 50%; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%,-50%); + + > svg { + transform: translate3d(0,0,0); + width: 100%; + height: 100%; + } +} +.viz-type-selector--graphic-line { + stroke-width: 3px; + fill: none; + stroke-linecap: round; + stroke-miterlimit: 10; + transition: all 0.5s ease; + + &.graphic-line-a {stroke: $g11-sidewalk;} + &.graphic-line-b {stroke: $g9-mountain;} + &.graphic-line-c {stroke: $g7-graphite;} +} +.viz-type-selector--graphic-fill { + opacity: 0.035; + transition: opacity 0.5s ease; + + &.graphic-fill-a {fill: $g11-sidewalk;} + &.graphic-fill-b {fill: $g9-mountain;} + &.graphic-fill-c {fill: $g7-graphite;} +} +.viz-type-selector--option.active .viz-type-selector--graphic { + .viz-type-selector--graphic-line.graphic-line-a {stroke: $c-pool;} + .viz-type-selector--graphic-line.graphic-line-b {stroke: $c-dreamsicle;} + .viz-type-selector--graphic-line.graphic-line-c {stroke: $c-rainforest;} + .viz-type-selector--graphic-fill.graphic-fill-a {fill: $c-pool;} + .viz-type-selector--graphic-fill.graphic-fill-b {fill: $c-dreamsicle;} + .viz-type-selector--graphic-fill.graphic-fill-c {fill: $c-rainforest;} + .viz-type-selector--graphic-fill.graphic-fill-a, + .viz-type-selector--graphic-fill.graphic-fill-b, + .viz-type-selector--graphic-fill.graphic-fill-c {opacity: 0.18;} +} From bae6cbf4f23b7b2614f144e3ab9db0836e35c35f Mon Sep 17 00:00:00 2001 From: Alex P Date: Thu, 13 Jul 2017 14:47:12 -0700 Subject: [PATCH 019/107] Uncross the wires --- ui/src/dashboards/components/CellEditorOverlay.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/src/dashboards/components/CellEditorOverlay.js b/ui/src/dashboards/components/CellEditorOverlay.js index 80b9468f2..caaa71355 100644 --- a/ui/src/dashboards/components/CellEditorOverlay.js +++ b/ui/src/dashboards/components/CellEditorOverlay.js @@ -106,8 +106,8 @@ class CellEditorOverlay extends Component { handleSaveCell() { const { queriesWorkingDraft, - cellWorkingType: name, - cellWorkingName: type, + cellWorkingType: type, + cellWorkingName: name, yRanges, } = this.state From 38b7d17df379ec3a4849b2a0dda4ec80f0f50abe Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Mon, 24 Jul 2017 11:31:32 -0700 Subject: [PATCH 020/107] Handle user submitted zero --- ui/spec/shared/parsing/getRangeForDygraphSpec.js | 2 +- ui/src/shared/parsing/getRangeForDygraph.js | 16 +++++++++++++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/ui/spec/shared/parsing/getRangeForDygraphSpec.js b/ui/spec/shared/parsing/getRangeForDygraphSpec.js index d467a7c79..2cec89bcf 100644 --- a/ui/spec/shared/parsing/getRangeForDygraphSpec.js +++ b/ui/spec/shared/parsing/getRangeForDygraphSpec.js @@ -6,7 +6,7 @@ const mid = 10 const min = 5 const kapacitor = {value: null, rangeValue: null, operator: null} -describe('getRangeForDygraphSpec', () => { +describe.only('getRangeForDygraphSpec', () => { it('gets the range for one timeSeries', () => { const timeSeries = [[date, min], [date, mid], [date, max]] const actual = getRange(timeSeries) diff --git a/ui/src/shared/parsing/getRangeForDygraph.js b/ui/src/shared/parsing/getRangeForDygraph.js index 6a162708d..2f2561e1b 100644 --- a/ui/src/shared/parsing/getRangeForDygraph.js +++ b/ui/src/shared/parsing/getRangeForDygraph.js @@ -1,10 +1,18 @@ const PADDING_FACTOR = 0.1 -export default function getRange( +const considerZero = (userNumber, number) => { + if (typeof userNumber === 'number') { + return userNumber + } + + return number +} + +const getRange = ( timeSeries, userSelectedRange = [null, null], ruleValues = {value: null, rangeValue: null} -) { +) => { const {value, rangeValue, operator} = ruleValues const subtractPadding = val => +val - Math.abs(val * PADDING_FACTOR) @@ -56,5 +64,7 @@ export default function getRange( const [userMin, userMax] = userSelectedRange const [min, max] = range - return [+userMin || min, +userMax || max] + return [considerZero(userMin, min), considerZero(userMax, max)] } + +export default getRange From 11d0ecac8b01e05bc886fe1e893a1074f55fca07 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Mon, 24 Jul 2017 15:01:23 -0700 Subject: [PATCH 021/107] WIP encorporate new shape for yRanges => axes --- .../components/CellEditorOverlay.js | 29 ++++++++----- .../dashboards/components/DisplayOptions.js | 11 ++--- ui/src/dashboards/components/Ranger.js | 26 +++++++----- ui/src/data_explorer/components/VisView.js | 41 ++++--------------- .../data_explorer/components/Visualization.js | 12 ++++-- ui/src/shared/components/AutoRefresh.js | 12 ++++-- ui/src/shared/components/Dygraph.js | 32 ++++++++++----- ui/src/shared/components/LineGraph.js | 21 ++++++---- ui/src/shared/components/RefreshingGraph.js | 11 ++--- ui/src/shared/parsing/getRangeForDygraph.js | 8 +++- 10 files changed, 107 insertions(+), 96 deletions(-) diff --git a/ui/src/dashboards/components/CellEditorOverlay.js b/ui/src/dashboards/components/CellEditorOverlay.js index caaa71355..ae97b8d83 100644 --- a/ui/src/dashboards/components/CellEditorOverlay.js +++ b/ui/src/dashboards/components/CellEditorOverlay.js @@ -35,7 +35,7 @@ class CellEditorOverlay extends Component { this.handleEditRawText = ::this.handleEditRawText this.handleSetRange = ::this.handleSetRange - const {cell: {name, type, queries, yRanges}} = props + const {cell: {name, type, queries, axes}} = props const queriesWorkingDraft = _.cloneDeep( queries.map(({queryConfig}) => ({...queryConfig, id: uuid.v4()})) @@ -47,9 +47,7 @@ class CellEditorOverlay extends Component { queriesWorkingDraft, activeQueryIndex: 0, isDisplayOptionsTabOpen: false, - yRanges: { - y: yRanges && yRanges.y ? yRanges.y : ['', ''], - }, + axes, } } @@ -82,9 +80,12 @@ class CellEditorOverlay extends Component { handleSetRange(e) { const {min, max} = e.target.form + this.setState({ - yRanges: { - y: [min.value, max.value], + axes: { + y: { + bounds: [min.value, max.value], + }, }, }) e.preventDefault() @@ -108,7 +109,7 @@ class CellEditorOverlay extends Component { queriesWorkingDraft, cellWorkingType: type, cellWorkingName: name, - yRanges, + axes, } = this.state const {cell} = this.props @@ -125,7 +126,13 @@ class CellEditorOverlay extends Component { } }) - this.props.onSave({...cell, name, type, queries, yRanges}) + this.props.onSave({ + ...cell, + name, + type, + queries, + axes, + }) } handleSelectGraphType(graphType) { @@ -174,7 +181,7 @@ class CellEditorOverlay extends Component { cellWorkingType, isDisplayOptionsTabOpen, queriesWorkingDraft, - yRanges, + axes, } = this.state const queryActions = { @@ -205,7 +212,7 @@ class CellEditorOverlay extends Component { cellType={cellWorkingType} cellName={cellWorkingName} editQueryStatus={editQueryStatus} - yRanges={yRanges} + axes={axes} views={[]} />
    @@ -221,7 +228,7 @@ class CellEditorOverlay extends Component { selectedGraphType={cellWorkingType} onSelectGraphType={this.handleSelectGraphType} onSetRange={this.handleSetRange} - yRanges={yRanges} + axes={axes} /> :
    - +
    -const {array, func, shape, string} = PropTypes +const {func, shape, string} = PropTypes DisplayOptions.propTypes = { selectedGraphType: string.isRequired, onSelectGraphType: func.isRequired, onSetRange: func.isRequired, - yRanges: shape({ - y: array, - y2: array, - }).isRequired, + axes: shape({}).isRequired, } export default DisplayOptions diff --git a/ui/src/dashboards/components/Ranger.js b/ui/src/dashboards/components/Ranger.js index c97375688..c550fa393 100644 --- a/ui/src/dashboards/components/Ranger.js +++ b/ui/src/dashboards/components/Ranger.js @@ -1,29 +1,34 @@ import React, {PropTypes} from 'react' +import _ from 'lodash' -const Ranger = ({onSetRange, yRanges}) => +const Ranger = ({onSetRange, axes}) =>
    Y Axis Controls
    - +
    - + @@ -35,9 +40,10 @@ const {array, func, shape} = PropTypes Ranger.propTypes = { onSetRange: func.isRequired, - yRanges: shape({ - y: array, - y2: array, + axes: shape({ + y: shape({ + bounds: array, + }), }).isRequired, } diff --git a/ui/src/data_explorer/components/VisView.js b/ui/src/data_explorer/components/VisView.js index a32063cd5..036d554c1 100644 --- a/ui/src/data_explorer/components/VisView.js +++ b/ui/src/data_explorer/components/VisView.js @@ -1,23 +1,18 @@ import React, {PropTypes} from 'react' import Table from './Table' -import AutoRefresh from 'shared/components/AutoRefresh' -import LineGraph from 'shared/components/LineGraph' -import SingleStat from 'shared/components/SingleStat' -const RefreshingLineGraph = AutoRefresh(LineGraph) -const RefreshingSingleStat = AutoRefresh(SingleStat) +import RefreshingGraph from 'shared/components/RefreshingGraph' const VisView = ({ + axes, view, queries, - yRanges, cellType, templates, autoRefresh, heightPixels, editQueryStatus, activeQueryIndex, - isInDataExplorer, }) => { const activeQuery = queries[activeQueryIndex] const defaultQuery = queries[0] @@ -41,42 +36,23 @@ const VisView = ({ ) } - if (cellType === 'single-stat') { - return ( - - ) - } - - const displayOptions = { - stepPlot: cellType === 'line-stepplot', - stackedGraph: cellType === 'line-stacked', - } - return ( - ) } -const {arrayOf, bool, func, number, shape, string} = PropTypes +const {arrayOf, func, number, shape, string} = PropTypes VisView.propTypes = { view: string.isRequired, - yRanges: shape(), + axes: shape().isRequired, queries: arrayOf(shape()).isRequired, cellType: string, templates: arrayOf(shape()), @@ -84,7 +60,6 @@ VisView.propTypes = { heightPixels: number, editQueryStatus: func.isRequired, activeQueryIndex: number, - isInDataExplorer: bool, } export default VisView diff --git a/ui/src/data_explorer/components/Visualization.js b/ui/src/data_explorer/components/Visualization.js index c03c5a23f..472900904 100644 --- a/ui/src/data_explorer/components/Visualization.js +++ b/ui/src/data_explorer/components/Visualization.js @@ -6,7 +6,7 @@ import VisView from 'src/data_explorer/components/VisView' import {GRAPH, TABLE} from 'shared/constants' import _ from 'lodash' -const {arrayOf, bool, func, number, shape, string} = PropTypes +const {array, arrayOf, bool, func, number, shape, string} = PropTypes const META_QUERY_REGEX = /^show/i const Visualization = React.createClass({ @@ -26,7 +26,11 @@ const Visualization = React.createClass({ heightPixels: number, editQueryStatus: func.isRequired, views: arrayOf(string).isRequired, - yRanges: shape(), + axes: shape({ + y: shape({ + bounds: array, + }), + }), }, contextTypes: { @@ -77,9 +81,9 @@ const Visualization = React.createClass({ render() { const { + axes, views, height, - yRanges, cellType, cellName, timeRange, @@ -118,7 +122,7 @@ const Visualization = React.createClass({ > { text: string, }).isRequired ).isRequired, - yRanges: shape({ - y: arrayOf(string), - y2: arrayOf(string), + axes: shape({ + bounds: shape({ + y: array, + y2: array, + }), }), editQueryStatus: func, }, @@ -173,7 +176,8 @@ const AutoRefresh = ComposedComponent => { } if ( - this._noResultsForQuery(timeSeries) || !this.state.lastQuerySuccessful + this._noResultsForQuery(timeSeries) || + !this.state.lastQuerySuccessful ) { return this.renderNoResults() } diff --git a/ui/src/shared/components/Dygraph.js b/ui/src/shared/components/Dygraph.js index 07381d756..8b80312c4 100644 --- a/ui/src/shared/components/Dygraph.js +++ b/ui/src/shared/components/Dygraph.js @@ -54,7 +54,7 @@ export default class Dygraph extends Component { const timeSeries = this.getTimeSeries() // dygraphSeries is a legend label and its corresponding y-axis e.g. {legendLabel1: 'y', legendLabel2: 'y2'}; const { - ranges, + axes, dygraphSeries, ruleValues, overrideLineColors, @@ -71,6 +71,9 @@ export default class Dygraph extends Component { finalLineColors = LINE_COLORS } + const yAxis = _.get(axes, ['y', 'bounds'], [null, null]) + const y2Axis = _.get(axes, ['y2', 'bounds'], undefined) + const defaultOptions = { plugins: [ new Dygraphs.Plugins.Crosshair({ @@ -92,10 +95,10 @@ export default class Dygraph extends Component { series: dygraphSeries, axes: { y: { - valueRange: getRange(timeSeries, ranges && ranges.y, ruleValues), + valueRange: getRange(timeSeries, yAxis, ruleValues), }, y2: { - valueRange: getRange(timeSeries, ranges && ranges.y2), + valueRange: getRange(timeSeries, y2Axis), }, }, highlightSeriesOpts: { @@ -235,7 +238,7 @@ export default class Dygraph extends Component { componentDidUpdate() { const { labels, - ranges, + axes, options, dygraphSeries, ruleValues, @@ -249,16 +252,19 @@ export default class Dygraph extends Component { ) } + const y = _.get(axes, ['y', 'bounds'], [null, null]) + const y2 = _.get(axes, ['y2', 'bounds'], undefined) const timeSeries = this.getTimeSeries() + const updateOptions = { labels, file: timeSeries, axes: { y: { - valueRange: getRange(timeSeries, ranges && ranges.y, ruleValues), + valueRange: getRange(timeSeries, y, ruleValues), }, y2: { - valueRange: getRange(timeSeries, ranges && ranges.y2), + valueRange: getRange(timeSeries, y2), }, }, stepPlot: options.stepPlot, @@ -343,7 +349,7 @@ export default class Dygraph extends Component { isAscending={isAscending} onSnip={this.handleSnipLabel} onSort={this.handleSortLegend} - legendRef={el => this.legendRef = el} + legendRef={el => (this.legendRef = el)} onInputChange={this.handleLegendInputChange} onToggleFilter={this.handleToggleFilter} /> @@ -359,12 +365,16 @@ export default class Dygraph extends Component { } } -const {array, arrayOf, func, bool, shape, string} = PropTypes +const {array, bool, func, shape, string} = PropTypes Dygraph.propTypes = { - ranges: shape({ - y: arrayOf(string), - y2: arrayOf(string), + axes: shape({ + y: shape({ + bounds: array, + }), + y2: shape({ + bounds: array, + }), }), timeSeries: array.isRequired, labels: array.isRequired, diff --git a/ui/src/shared/components/LineGraph.js b/ui/src/shared/components/LineGraph.js index 3d6af1b4f..e92d4dfe8 100644 --- a/ui/src/shared/components/LineGraph.js +++ b/ui/src/shared/components/LineGraph.js @@ -15,9 +15,13 @@ export default React.createClass({ displayName: 'LineGraph', propTypes: { data: arrayOf(shape({}).isRequired).isRequired, - yRanges: shape({ - y: arrayOf(string), - y2: arrayOf(string), + axes: shape({ + y: shape({ + bounds: array, + }), + y2: shape({ + bounds: array, + }), }), title: string, isFetchingInitially: bool, @@ -67,7 +71,8 @@ export default React.createClass({ componentWillUpdate(nextProps) { const {data, activeQueryIndex} = this.props if ( - data !== nextProps.data || activeQueryIndex !== nextProps.activeQueryIndex + data !== nextProps.data || + activeQueryIndex !== nextProps.activeQueryIndex ) { this._timeSeries = timeSeriesToDygraph( nextProps.data, @@ -80,7 +85,7 @@ export default React.createClass({ render() { const { data, - yRanges, + axes, isFetchingInitially, isRefreshing, isGraphFilled, @@ -159,6 +164,7 @@ export default React.createClass({ > {isRefreshing ? this.renderSpinner() : null} - {roundedValue} + + {roundedValue} +
    : null} diff --git a/ui/src/shared/components/RefreshingGraph.js b/ui/src/shared/components/RefreshingGraph.js index 746f77c44..56e43857f 100644 --- a/ui/src/shared/components/RefreshingGraph.js +++ b/ui/src/shared/components/RefreshingGraph.js @@ -15,7 +15,7 @@ const RefreshingGraph = ({ type, queries, cellHeight, - yRanges, + axes, }) => { if (type === 'single-stat') { return ( @@ -43,7 +43,7 @@ const RefreshingGraph = ({ isBarGraph={type === 'bar'} displayOptions={displayOptions} synchronizer={synchronizer} - yRanges={yRanges} + axes={axes} /> ) } @@ -59,11 +59,8 @@ RefreshingGraph.propTypes = { synchronizer: func, type: string.isRequired, queries: arrayOf(shape()).isRequired, - cellHeight: number.isRequired, - yRanges: shape({ - y: arrayOf(string), - y2: arrayOf(string), - }), + cellHeight: number, + axes: shape(), } export default RefreshingGraph diff --git a/ui/src/shared/parsing/getRangeForDygraph.js b/ui/src/shared/parsing/getRangeForDygraph.js index 2f2561e1b..1e505718b 100644 --- a/ui/src/shared/parsing/getRangeForDygraph.js +++ b/ui/src/shared/parsing/getRangeForDygraph.js @@ -1,8 +1,12 @@ const PADDING_FACTOR = 0.1 const considerZero = (userNumber, number) => { - if (typeof userNumber === 'number') { - return userNumber + if (userNumber === '') { + return null + } + + if (userNumber) { + return +userNumber } return number From 833911e5169f86949d34a9e2d7b93a096a7159ba Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Mon, 24 Jul 2017 15:08:01 -0700 Subject: [PATCH 022/107] WIP add range to dashboard cell --- .../components/CellEditorOverlay.js | 20 ++++- ui/src/dashboards/components/Ranger.js | 74 ++++++++++--------- ui/src/shared/components/LayoutRenderer.js | 4 +- 3 files changed, 61 insertions(+), 37 deletions(-) diff --git a/ui/src/dashboards/components/CellEditorOverlay.js b/ui/src/dashboards/components/CellEditorOverlay.js index ae97b8d83..001aa5fa0 100644 --- a/ui/src/dashboards/components/CellEditorOverlay.js +++ b/ui/src/dashboards/components/CellEditorOverlay.js @@ -34,6 +34,7 @@ class CellEditorOverlay extends Component { this.handleSetActiveQueryIndex = ::this.handleSetActiveQueryIndex this.handleEditRawText = ::this.handleEditRawText this.handleSetRange = ::this.handleSetRange + this.normalizeAxes = ::this.normalizeAxes const {cell: {name, type, queries, axes}} = props @@ -81,6 +82,7 @@ class CellEditorOverlay extends Component { handleSetRange(e) { const {min, max} = e.target.form + // TODO: handle "" for min and max value this.setState({ axes: { y: { @@ -109,10 +111,10 @@ class CellEditorOverlay extends Component { queriesWorkingDraft, cellWorkingType: type, cellWorkingName: name, - axes, } = this.state const {cell} = this.props + const axes = this.normalizeAxes() const queries = queriesWorkingDraft.map(q => { const timeRange = q.range || {upper: null, lower: ':dashboardTime:'} @@ -135,6 +137,22 @@ class CellEditorOverlay extends Component { }) } + normalizeAxes() { + const axes = this.state.axes + const bounds = _.get(axes, ['y', 'bounds'], false) + if (!bounds && !bounds.length) { + return {...axes, y: {bounds: []}} + } + + const [min, max] = bounds + if (min === '' || max === '') { + // TODO: throw requirement error + return + } + + return {...axes, y: {bounds: [+min, +max]}} + } + handleSelectGraphType(graphType) { this.setState({cellWorkingType: graphType}) } diff --git a/ui/src/dashboards/components/Ranger.js b/ui/src/dashboards/components/Ranger.js index c550fa393..2db044e0c 100644 --- a/ui/src/dashboards/components/Ranger.js +++ b/ui/src/dashboards/components/Ranger.js @@ -1,40 +1,46 @@ import React, {PropTypes} from 'react' import _ from 'lodash' -const Ranger = ({onSetRange, axes}) => -
    -
    Y Axis Controls
    - -
    - - -
    -
    - - -
    - -
    +const Ranger = ({onSetRange, axes}) => { + const min = _.get(axes, ['y', 'bounds', '0'], '') + const max = _.get(axes, ['y', 'bounds', '1'], '') + + return ( +
    +
    Y Axis Controls
    +
    +
    + + +
    +
    + + +
    +
    +
    + ) +} const {array, func, shape} = PropTypes diff --git a/ui/src/shared/components/LayoutRenderer.js b/ui/src/shared/components/LayoutRenderer.js index bba4fbc82..5408f54b5 100644 --- a/ui/src/shared/components/LayoutRenderer.js +++ b/ui/src/shared/components/LayoutRenderer.js @@ -151,7 +151,7 @@ class LayoutRenderer extends Component { } = this.props return cells.map(cell => { - const {type, h, yRanges} = cell + const {type, h, axes} = cell return (
    @@ -175,7 +175,7 @@ class LayoutRenderer extends Component { type={type} queries={this.standardizeQueries(cell, source)} cellHeight={h} - yRanges={yRanges} + axes={axes} />}
    From 2662e1957ca760eaa195de137848d5cc17117601 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Thu, 27 Jul 2017 13:29:46 -0700 Subject: [PATCH 023/107] Give preference to queryConfig range --- ui/src/data_explorer/components/Visualization.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ui/src/data_explorer/components/Visualization.js b/ui/src/data_explorer/components/Visualization.js index 452c4f5a4..2b6b4d157 100644 --- a/ui/src/data_explorer/components/Visualization.js +++ b/ui/src/data_explorer/components/Visualization.js @@ -93,7 +93,8 @@ const Visualization = React.createClass({ const {view} = this.state const statements = queryConfigs.map(query => { - const text = query.rawText || buildInfluxQLQuery(timeRange, query) + const text = + query.rawText || buildInfluxQLQuery(query.range || timeRange, query) return {text, id: query.id} }) const queries = statements.filter(s => s.text !== null).map(s => { From 57b384710b081972a9510b758f042d019570beeb Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Thu, 27 Jul 2017 13:41:44 -0700 Subject: [PATCH 024/107] Update CHANGEOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bce5079bc..b03cbb441 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## v1.3.6.0 [unreleased] ### Bug Fixes +1. [#1798](https://github.com/influxdata/chronograf/pull/1798): Fix domain on CEO not updating when new time is entered into InfluxQL in the builder ### Features ### UI Improvements From e3b5ef646ec35a0190dc396a52c16631e9150e55 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Thu, 27 Jul 2017 14:38:57 -0700 Subject: [PATCH 025/107] Dont sync graphs if there is only one graph --- ui/src/dashboards/containers/DashboardPage.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ui/src/dashboards/containers/DashboardPage.js b/ui/src/dashboards/containers/DashboardPage.js index 82941d680..9172e6ef1 100644 --- a/ui/src/dashboards/containers/DashboardPage.js +++ b/ui/src/dashboards/containers/DashboardPage.js @@ -209,7 +209,11 @@ class DashboardPage extends Component { const dygraphs = [...this.state.dygraphs, dygraph] const {dashboards, params} = this.props const dashboard = dashboards.find(d => d.id === +params.dashboardID) - if (dashboard && dygraphs.length === dashboard.cells.length) { + if ( + dashboard && + dygraphs.length === dashboard.cells.length && + dashboard.cells.length > 1 + ) { Dygraph.synchronize(dygraphs, { selection: true, zoom: false, From 0958d2f3f9112793afcd9c844f81f7f2e0c8d6fe Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Thu, 27 Jul 2017 14:47:04 -0700 Subject: [PATCH 026/107] Update CHANGELOG --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3516dfe02..609fd4933 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ ## v1.3.6.0 [unreleased] ### Bug Fixes +1. [#1799](https://github.com/influxdata/chronograf/pull/1799): Fix console error spam from Dygraph.syncronize + ### Features ### UI Improvements 1. [#1796](https://github.com/influxdata/chronograf/pull/1796): Add spinner to indicate data is being written From 9e87035b7f9d8f1e78a77789d69ab4709a095f67 Mon Sep 17 00:00:00 2001 From: Jared Scheib Date: Thu, 27 Jul 2017 14:48:18 -0700 Subject: [PATCH 027/107] Update release date --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bce5079bc..acf262997 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ### Features ### UI Improvements -## v1.3.5.0 [2017-07-25] +## v1.3.5.0 [2017-07-27] ### Bug Fixes 1. [#1708](https://github.com/influxdata/chronograf/pull/1708): Fix z-index issue in dashboard cell context menu 1. [#1752](https://github.com/influxdata/chronograf/pull/1752): Clarify BoltPath server flag help text by making example the default path From e9355d8a617eee1f6a21809e39d227b6e0239ec3 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Thu, 27 Jul 2017 15:23:59 -0700 Subject: [PATCH 028/107] Embiggen the write data form --- ui/src/data_explorer/components/WriteDataBody.js | 2 +- ui/src/style/components/write-data-form.scss | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/src/data_explorer/components/WriteDataBody.js b/ui/src/data_explorer/components/WriteDataBody.js index 70b987a77..2cb59caab 100644 --- a/ui/src/data_explorer/components/WriteDataBody.js +++ b/ui/src/data_explorer/components/WriteDataBody.js @@ -33,7 +33,7 @@ const WriteDataBody = ({ ref={fileInput} accept="text/*, application/gzip" /> - {uploadContent diff --git a/ui/src/style/components/write-data-form.scss b/ui/src/style/components/write-data-form.scss index cb8ede605..f3b8db936 100644 --- a/ui/src/style/components/write-data-form.scss +++ b/ui/src/style/components/write-data-form.scss @@ -7,7 +7,7 @@ $write-data--max-width: 960px; $write-data--gutter: 30px; $write-data--margin: 18px; -$write-data--input-height: 120px; +$write-data--input-height: 80vh; $write-data--transition: opacity 0.4s ease; .write-data-form { From 5034b2546cfdd3e247501b637f281decc676c3e7 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Thu, 27 Jul 2017 15:26:20 -0700 Subject: [PATCH 029/107] Update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3516dfe02..c6c5cc784 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ### Features ### UI Improvements 1. [#1796](https://github.com/influxdata/chronograf/pull/1796): Add spinner to indicate data is being written +1. [#1800](https://github.com/influxdata/chronograf/pull/1796): Embiggen line protocol INSERT text area ## v1.3.5.0 [2017-07-25] ### Bug Fixes From 811ab58b2ef4a7dc2fdb30372b4e9fffd4dd1e2e Mon Sep 17 00:00:00 2001 From: Jared Scheib Date: Thu, 27 Jul 2017 16:35:18 -0700 Subject: [PATCH 030/107] Clarify changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9097df731..e1f00a248 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ### Features ### UI Improvements 1. [#1796](https://github.com/influxdata/chronograf/pull/1796): Add spinner to indicate data is being written -1. [#1800](https://github.com/influxdata/chronograf/pull/1796): Embiggen line protocol INSERT text area +1. [#1800](https://github.com/influxdata/chronograf/pull/1796): Embiggen text area for line protocol manual entry in Data Explorer's Write Data overlay ## v1.3.5.0 [2017-07-27] ### Bug Fixes From 99f045e397d7d78c7930dd074f9531a726722275 Mon Sep 17 00:00:00 2001 From: Jared Scheib Date: Thu, 27 Jul 2017 16:49:18 -0700 Subject: [PATCH 031/107] Clarify changelog, fix method name typo --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 609fd4933..635c79f9f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ ## v1.3.6.0 [unreleased] ### Bug Fixes -1. [#1799](https://github.com/influxdata/chronograf/pull/1799): Fix console error spam from Dygraph.syncronize +1. [#1799](https://github.com/influxdata/chronograf/pull/1799): Prevent console error spam from Dygraph.synchronize when a dashboard has only one graph ### Features ### UI Improvements From dc0aaaaf5dd498589f41a060dfe03758f29e8fbe Mon Sep 17 00:00:00 2001 From: Alex P Date: Thu, 27 Jul 2017 16:57:09 -0700 Subject: [PATCH 032/107] Prevent overlay from extending beyond the viewport Factored in the heights of all the neighboring elements --- ui/src/style/components/write-data-form.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/style/components/write-data-form.scss b/ui/src/style/components/write-data-form.scss index f3b8db936..5a76ce6d4 100644 --- a/ui/src/style/components/write-data-form.scss +++ b/ui/src/style/components/write-data-form.scss @@ -7,7 +7,7 @@ $write-data--max-width: 960px; $write-data--gutter: 30px; $write-data--margin: 18px; -$write-data--input-height: 80vh; +$write-data--input-height: calc(90vh - 48px - 60px - 36px); // Heights of everything but input height $write-data--transition: opacity 0.4s ease; .write-data-form { From f8b8cc7364ff3815676ad0bd6fc6749fd2ff91e1 Mon Sep 17 00:00:00 2001 From: Hunter Trujillo Date: Fri, 28 Jul 2017 18:20:22 -0600 Subject: [PATCH 033/107] Add universal hashed colors algorithm. Fix bar graph column overlap. Remove crosshairs on bar graphs. --- ui/src/shared/components/Dygraph.js | 48 +++++++++++++++++++++++------ ui/src/shared/graphs/helpers.js | 32 ++++++++++++------- 2 files changed, 60 insertions(+), 20 deletions(-) diff --git a/ui/src/shared/components/Dygraph.js b/ui/src/shared/components/Dygraph.js index cfdb06968..02e5ba101 100644 --- a/ui/src/shared/components/Dygraph.js +++ b/ui/src/shared/components/Dygraph.js @@ -10,6 +10,12 @@ import getRange from 'shared/parsing/getRangeForDygraph' import {LINE_COLORS, multiColumnBarPlotter} from 'src/shared/graphs/helpers' import DygraphLegend from 'src/shared/components/DygraphLegend' +const hasherino = (str, len) => + str + .split('') + .map(char => char.charCodeAt(0)) + .reduce((hash, code) => (hash + code) % len, 0) + export default class Dygraph extends Component { constructor(props) { super(props) @@ -65,18 +71,42 @@ export default class Dygraph extends Component { const graphRef = this.graphRef const legendRef = this.legendRef - let finalLineColors = overrideLineColors + const finalLineColors = [...(overrideLineColors || LINE_COLORS)] - if (finalLineColors === null) { - finalLineColors = LINE_COLORS + const hashColorDygraphSeries = {} + const {length} = finalLineColors + + let used = [] + + for (const seriesName in dygraphSeries) { + const series = dygraphSeries[seriesName] + let hashIndex = hasherino(seriesName, length) + + // Check to see if color is already being used + while (used.includes(hashIndex)) { + hashIndex = (hashIndex + 1) % length + } + + used.push(hashIndex) + + // Empty used array if all colors are used + if (used.length === length) { + used = [] + } + + const color = finalLineColors[hashIndex] + + hashColorDygraphSeries[seriesName] = {...series, color} } const defaultOptions = { - plugins: [ - new Dygraphs.Plugins.Crosshair({ - direction: 'vertical', - }), - ], + plugins: isBarGraph + ? [] + : [ + new Dygraphs.Plugins.Crosshair({ + direction: 'vertical', + }), + ], labelsSeparateLines: false, labelsKMB: true, rightGap: 0, @@ -89,7 +119,7 @@ export default class Dygraph extends Component { animatedZooms: true, hideOverlayOnMouseOut: false, colors: finalLineColors, - series: dygraphSeries, + series: hashColorDygraphSeries, axes: { y: { valueRange: getRange(timeSeries, ranges.y, ruleValues), diff --git a/ui/src/shared/graphs/helpers.js b/ui/src/shared/graphs/helpers.js index a0dc84372..2470fafc5 100644 --- a/ui/src/shared/graphs/helpers.js +++ b/ui/src/shared/graphs/helpers.js @@ -26,7 +26,7 @@ export const darkenColor = colorStr => { return `rgb(${color.r},${color.g},${color.b})` } -// Bar Graph code below is from http://dygraphs.com/tests/plotters.html +// Bar Graph code below is adapted from http://dygraphs.com/tests/plotters.html export const multiColumnBarPlotter = e => { // We need to handle all the series simultaneously. if (e.seriesIndex !== 0) { @@ -51,24 +51,32 @@ export const multiColumnBarPlotter = e => { } } - const barWidth = Math.floor(2.0 / 3 * minSep) + const barWidth = Math.max(Math.floor(2.0 / 3.0 * minSep), 1) const fillColors = [] const strokeColors = g.getColors() + + let selPointX + if (g.selPoints_ && g.selPoints_.length) { + selPointX = g.selPoints_[0].canvasx + } + for (let i = 0; i < strokeColors.length; i++) { fillColors.push(darkenColor(strokeColors[i])) } + ctx.lineWidth = 2 + for (let j = 0; j < sets.length; j++) { - ctx.fillStyle = fillColors[j] ctx.strokeStyle = strokeColors[j] for (let i = 0; i < sets[j].length; i++) { const p = sets[j][i] const centerX = p.canvasx + ctx.fillStyle = fillColors[j] const xLeft = sets.length === 1 - ? centerX - barWidth / 2 - : centerX - barWidth / 2 * (1 - j / (sets.length - 1)) + ? centerX - barWidth / 1 + : centerX - barWidth / 1 * (1 - j / sets.length) ctx.fillRect( xLeft, @@ -77,12 +85,14 @@ export const multiColumnBarPlotter = e => { yBottom - p.canvasy ) - ctx.strokeRect( - xLeft, - p.canvasy, - barWidth / sets.length, - yBottom - p.canvasy - ) + if (selPointX === centerX) { + ctx.strokeRect( + xLeft, + p.canvasy, + barWidth / sets.length, + yBottom - p.canvasy + ) + } } } } From 4ab18d586ba5b792efe35fcbc52e580358952195 Mon Sep 17 00:00:00 2001 From: Hunter Trujillo Date: Mon, 31 Jul 2017 11:57:51 -0600 Subject: [PATCH 034/107] Refactor hashing function to be simpler and remove hash de-duplication. Remove highlight circle on bar graphs. --- ui/src/shared/components/Dygraph.js | 22 +++------------------- 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/ui/src/shared/components/Dygraph.js b/ui/src/shared/components/Dygraph.js index 02e5ba101..ccd73ef39 100644 --- a/ui/src/shared/components/Dygraph.js +++ b/ui/src/shared/components/Dygraph.js @@ -76,26 +76,10 @@ export default class Dygraph extends Component { const hashColorDygraphSeries = {} const {length} = finalLineColors - let used = [] - for (const seriesName in dygraphSeries) { const series = dygraphSeries[seriesName] - let hashIndex = hasherino(seriesName, length) - - // Check to see if color is already being used - while (used.includes(hashIndex)) { - hashIndex = (hashIndex + 1) % length - } - - used.push(hashIndex) - - // Empty used array if all colors are used - if (used.length === length) { - used = [] - } - + const hashIndex = hasherino(seriesName, length) const color = finalLineColors[hashIndex] - hashColorDygraphSeries[seriesName] = {...series, color} } @@ -115,7 +99,7 @@ export default class Dygraph extends Component { fillGraph: isGraphFilled, axisLineWidth: 2, gridLineWidth: 1, - highlightCircleSize: 3, + highlightCircleSize: isBarGraph ? 0 : 3, animatedZooms: true, hideOverlayOnMouseOut: false, colors: finalLineColors, @@ -130,7 +114,7 @@ export default class Dygraph extends Component { }, highlightSeriesOpts: { strokeWidth: 2, - highlightCircleSize: 5, + highlightCircleSize: isBarGraph ? 0 : 5, }, legendFormatter: legend => { if (!legend.x) { From 91c3611ef2f341fe7877669e75e820f33cb7eae4 Mon Sep 17 00:00:00 2001 From: Hunter Trujillo Date: Mon, 31 Jul 2017 12:07:48 -0600 Subject: [PATCH 035/107] =?UTF-8?q?.=20.=20c=CC=8C=CD=90=CD=98=CC=82=CC=8B?= =?UTF-8?q?=CC=89=CD=9D=CC=A5=CD=88=CC=BA=CD=95=CC=AF=CC=A7H=CC=9A=CC=94?= =?UTF-8?q?=CD=9B=CC=8F=CD=A0=CC=84=CD=8D=CC=B3=CD=9F=CC=9F=CC=96=CC=A8?= =?UTF-8?q?=CC=B3=CC=B1=CD=96=CD=8E=CC=B8a=CD=9B=CC=8B=CD=8A=CC=BE=CD=9D?= =?UTF-8?q?=CC=81=CC=BB=CD=9F=CC=98=CC=AA=CC=9D=CC=A1=CC=A8=CC=A4=CC=BB?= =?UTF-8?q?=CC=B3N=CD=84=CC=95=CC=8E=CC=91=CC=8D=CD=8A=CC=95=CC=9A=CC=83?= =?UTF-8?q?=CD=80=CD=89=CD=9C=CD=87=CD=85=CC=A4=CD=94=CC=B7g=CD=83=CD=A0?= =?UTF-8?q?=CD=84=CD=8A=CD=9B=CD=88=CD=88=CC=AD=CC=99=CC=AC=CC=9C=CC=A2E?= =?UTF-8?q?=CC=BE=CC=81=CC=82=CD=98=CC=9B=CC=90=CD=8B=CC=86=CC=AB=CD=87?= =?UTF-8?q?=CD=9A=CC=98=CD=A2=CC=B9l=CD=90=CC=86=CD=98=CD=81=CD=83=CC=9B?= =?UTF-8?q?=CC=B2=CC=A7=CC=AE=CD=93=CD=85=CD=88=CC=BC=CD=89=CC=AE=CC=B4O?= =?UTF-8?q?=CC=8B=CC=BE=CC=92=CD=A0=CD=82=CD=97=CD=8B=CC=8E=CD=81=CC=9A?= =?UTF-8?q?=CC=A2=CC=A8=CC=A7=CD=95=CD=96=CC=B8g=CD=81=CC=8B=CD=8A=CD=8B?= =?UTF-8?q?=CC=8D=CC=A1=CD=A2=CD=99=CC=98=CC=96=CC=BA=CD=96=CD=9C=CD=89?= =?UTF-8?q?=CD=8E=CC=B5E=CC=89=CC=8D=CC=87=CC=84=CC=9A=CC=86=CC=B3=CC=A5?= =?UTF-8?q?=CC=B2=CD=87=CC=BB=20.=20.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d10faa318..eca666284 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ ### UI Improvements 1. [#1796](https://github.com/influxdata/chronograf/pull/1796): Add spinner to indicate data is being written 1. [#1800](https://github.com/influxdata/chronograf/pull/1796): Embiggen text area for line protocol manual entry in Data Explorer's Write Data overlay +1. [#1805](https://github.com/influxdata/chronograf/pull/1805): Bar graphs no longer overlap with each other, and bonus, series names are hashed so that graph colors should stay the same for the same series across charts ## v1.3.5.0 [2017-07-27] ### Bug Fixes From 0a042e2e0f074da1a18f32b2b2d657665318229a Mon Sep 17 00:00:00 2001 From: Tim Raymond Date: Wed, 26 Jul 2017 15:45:17 -0400 Subject: [PATCH 036/107] Convert Axis Bounds to []string Due to various limitations with the previous implementation of Bounds as a [2]int64{}, we've decided to change this to a []string{}. This will allow clients to store arbitrary data specifying a bound and interpret it as they wish. --- bolt/internal/internal.go | 22 ++++-- bolt/internal/internal.pb.go | 124 +++++++++++++++++---------------- bolt/internal/internal.proto | 3 +- bolt/internal/internal_test.go | 75 +++++++++++++++++++- chronograf.go | 3 +- server/cells_test.go | 10 +-- server/dashboards_test.go | 8 +-- 7 files changed, 165 insertions(+), 80 deletions(-) diff --git a/bolt/internal/internal.go b/bolt/internal/internal.go index b9400a489..cca08cdd1 100644 --- a/bolt/internal/internal.go +++ b/bolt/internal/internal.go @@ -2,6 +2,7 @@ package internal import ( "encoding/json" + "strconv" "github.com/gogo/protobuf/proto" "github.com/influxdata/chronograf" @@ -181,14 +182,14 @@ func MarshalDashboard(d chronograf.Dashboard) ([]byte, error) { axes := make(map[string]*Axis, len(c.Axes)) for a, r := range c.Axes { - // need to explicitly allocate a new array because r.Bounds is - // over-written and the resulting slices from previous iterations will - // point to later iteration's data. It is _not_ enough to simply re-slice - // r.Bounds + // we only marshal LegacyBounds for a data migration test. This should + // not be used by anything in production. axis := [2]int64{} - copy(axis[:], r.Bounds[:2]) + copy(axis[:], r.LegacyBounds[:2]) + axes[a] = &Axis{ - Bounds: axis[:], + Bounds: r.Bounds, + LegacyBounds: axis[:], } } @@ -268,7 +269,14 @@ func UnmarshalDashboard(data []byte, d *chronograf.Dashboard) error { axes := make(map[string]chronograf.Axis, len(c.Axes)) for a, r := range c.Axes { axis := chronograf.Axis{} - copy(axis.Bounds[:], r.Bounds[:2]) + // repair legacy bounds + for _, bound := range r.LegacyBounds { + axis.Bounds = append(axis.Bounds, strconv.FormatInt(bound, 10)) + } + + if len(r.Bounds) > 0 { + axis.Bounds = r.Bounds + } axes[a] = axis } diff --git a/bolt/internal/internal.pb.go b/bolt/internal/internal.pb.go index ccc0aedc9..c81dd610f 100644 --- a/bolt/internal/internal.pb.go +++ b/bolt/internal/internal.pb.go @@ -117,7 +117,8 @@ func (m *DashboardCell) GetAxes() map[string]*Axis { } type Axis struct { - Bounds []int64 `protobuf:"varint,1,rep,name=bounds" json:"bounds,omitempty"` + LegacyBounds []int64 `protobuf:"varint,1,rep,name=legacyBounds" json:"legacyBounds,omitempty"` + Bounds []string `protobuf:"bytes,2,rep,name=bounds" json:"bounds,omitempty"` } func (m *Axis) Reset() { *m = Axis{} } @@ -312,64 +313,65 @@ func init() { func init() { proto.RegisterFile("internal.proto", fileDescriptorInternal) } var fileDescriptorInternal = []byte{ - // 935 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xbc, 0x56, 0xdd, 0x8e, 0xdb, 0x44, - 0x14, 0xd6, 0xc4, 0x76, 0x12, 0x9f, 0x6d, 0x17, 0x34, 0xaa, 0xa8, 0x29, 0x12, 0x0a, 0x16, 0x48, - 0x01, 0x89, 0x05, 0xb5, 0x42, 0x42, 0xdc, 0x65, 0x37, 0xa8, 0x0a, 0xbb, 0x2d, 0xcb, 0x64, 0x77, - 0xb9, 0x42, 0xd5, 0x24, 0x39, 0xd9, 0xb5, 0xea, 0xd8, 0x66, 0x6c, 0x6f, 0xe2, 0x57, 0xe0, 0x8a, - 0x27, 0x40, 0x42, 0xe2, 0x8a, 0x4b, 0x5e, 0x80, 0x87, 0xe0, 0x85, 0xd0, 0x99, 0x19, 0xff, 0x84, - 0x6e, 0x51, 0xaf, 0x7a, 0x37, 0xdf, 0x39, 0x93, 0xef, 0x78, 0xbe, 0xf3, 0x9d, 0xa3, 0xc0, 0x61, - 0x94, 0x14, 0xa8, 0x12, 0x19, 0x1f, 0x65, 0x2a, 0x2d, 0x52, 0x3e, 0xac, 0x71, 0xf8, 0x4b, 0x0f, - 0xfa, 0xf3, 0xb4, 0x54, 0x4b, 0xe4, 0x87, 0xd0, 0x9b, 0x4d, 0x03, 0x36, 0x62, 0x63, 0x47, 0xf4, - 0x66, 0x53, 0xce, 0xc1, 0x7d, 0x2e, 0x37, 0x18, 0xf4, 0x46, 0x6c, 0xec, 0x0b, 0x7d, 0xa6, 0xd8, - 0x45, 0x95, 0x61, 0xe0, 0x98, 0x18, 0x9d, 0xf9, 0x23, 0x18, 0x5e, 0xe6, 0xc4, 0xb6, 0xc1, 0xc0, - 0xd5, 0xf1, 0x06, 0x53, 0xee, 0x5c, 0xe6, 0xf9, 0x36, 0x55, 0xab, 0xc0, 0x33, 0xb9, 0x1a, 0xf3, - 0x77, 0xc1, 0xb9, 0x14, 0x67, 0x41, 0x5f, 0x87, 0xe9, 0xc8, 0x03, 0x18, 0x4c, 0x71, 0x2d, 0xcb, - 0xb8, 0x08, 0x06, 0x23, 0x36, 0x1e, 0x8a, 0x1a, 0x12, 0xcf, 0x05, 0xc6, 0x78, 0xad, 0xe4, 0x3a, - 0x18, 0x1a, 0x9e, 0x1a, 0xf3, 0x23, 0xe0, 0xb3, 0x24, 0xc7, 0x65, 0xa9, 0x70, 0xfe, 0x32, 0xca, - 0xae, 0x50, 0x45, 0xeb, 0x2a, 0xf0, 0x35, 0xc1, 0x1d, 0x19, 0xaa, 0xf2, 0x0c, 0x0b, 0x49, 0xb5, - 0x41, 0x53, 0xd5, 0x30, 0xfc, 0x95, 0x81, 0x3f, 0x95, 0xf9, 0xcd, 0x22, 0x95, 0x6a, 0xf5, 0x46, - 0x7a, 0x7c, 0x0e, 0xde, 0x12, 0xe3, 0x38, 0x0f, 0x9c, 0x91, 0x33, 0x3e, 0x78, 0xfc, 0xf0, 0xa8, - 0x11, 0xba, 0xe1, 0x39, 0xc1, 0x38, 0x16, 0xe6, 0x16, 0xff, 0x12, 0xfc, 0x02, 0x37, 0x59, 0x2c, - 0x0b, 0xcc, 0x03, 0x57, 0xff, 0x84, 0xb7, 0x3f, 0xb9, 0xb0, 0x29, 0xd1, 0x5e, 0x0a, 0xff, 0xec, - 0xc1, 0xfd, 0x3d, 0x2a, 0x7e, 0x0f, 0xd8, 0x4e, 0x7f, 0x95, 0x27, 0xd8, 0x8e, 0x50, 0xa5, 0xbf, - 0xc8, 0x13, 0xac, 0x22, 0xb4, 0xd5, 0xbd, 0xf1, 0x04, 0xdb, 0x12, 0xba, 0xd1, 0x1d, 0xf1, 0x04, - 0xbb, 0xe1, 0x9f, 0xc2, 0xe0, 0xe7, 0x12, 0x55, 0x84, 0x79, 0xe0, 0xe9, 0xca, 0xef, 0xb4, 0x95, - 0x7f, 0x28, 0x51, 0x55, 0xa2, 0xce, 0xd3, 0x4b, 0x75, 0x37, 0x4d, 0x6b, 0xf4, 0x99, 0x62, 0x05, - 0x75, 0x7e, 0x60, 0x62, 0x74, 0xb6, 0x0a, 0x99, 0x7e, 0x90, 0x42, 0x5f, 0x81, 0x2b, 0x77, 0x98, - 0x07, 0xbe, 0xe6, 0xff, 0xe8, 0x35, 0x62, 0x1c, 0x4d, 0x76, 0x98, 0x7f, 0x9b, 0x14, 0xaa, 0x12, - 0xfa, 0xfa, 0xa3, 0xa7, 0xe0, 0x37, 0x21, 0x72, 0xc5, 0x4b, 0xac, 0xf4, 0x03, 0x7d, 0x41, 0x47, - 0xfe, 0x31, 0x78, 0xb7, 0x32, 0x2e, 0x8d, 0xf0, 0x07, 0x8f, 0x0f, 0x5b, 0xda, 0xc9, 0x2e, 0xca, - 0x85, 0x49, 0x7e, 0xd3, 0xfb, 0x9a, 0x85, 0x1f, 0x82, 0x4b, 0x21, 0xfe, 0x1e, 0xf4, 0x17, 0x69, - 0x99, 0xac, 0xf2, 0x80, 0x8d, 0x9c, 0xb1, 0x23, 0x2c, 0x0a, 0xff, 0x66, 0x64, 0x23, 0x23, 0x6d, - 0xa7, 0xbd, 0xe6, 0xe3, 0xdf, 0x87, 0x21, 0xc9, 0xfe, 0xe2, 0x56, 0x2a, 0xdb, 0xe2, 0x01, 0xe1, - 0x2b, 0xa9, 0xf8, 0x17, 0xd0, 0xd7, 0x45, 0xee, 0x68, 0x73, 0x4d, 0x77, 0x45, 0x79, 0x61, 0xaf, - 0x35, 0x62, 0xb9, 0x1d, 0xb1, 0x1e, 0x80, 0x17, 0xcb, 0x05, 0xc6, 0x76, 0x0e, 0x0c, 0x20, 0x03, - 0x91, 0xea, 0x95, 0xd6, 0xfa, 0x4e, 0x66, 0xd3, 0x1b, 0x73, 0x2b, 0xbc, 0x84, 0xfb, 0x7b, 0x15, - 0x9b, 0x4a, 0x6c, 0xbf, 0x52, 0x2b, 0x98, 0x6f, 0x05, 0xa2, 0x11, 0xca, 0x31, 0xc6, 0x65, 0x81, - 0x2b, 0x6d, 0x91, 0xa1, 0x68, 0x70, 0xf8, 0x3b, 0x6b, 0x79, 0x75, 0x3d, 0x1a, 0x92, 0x65, 0xba, - 0xd9, 0xc8, 0x64, 0x65, 0xa9, 0x6b, 0x48, 0xba, 0xad, 0x16, 0x96, 0xba, 0xb7, 0x5a, 0x10, 0x56, - 0x99, 0x5d, 0x08, 0x3d, 0x95, 0xf1, 0x11, 0x1c, 0x6c, 0x50, 0xe6, 0xa5, 0xc2, 0x0d, 0x26, 0x85, - 0x95, 0xa0, 0x1b, 0xe2, 0x0f, 0x61, 0x50, 0xc8, 0xeb, 0x17, 0xd4, 0x66, 0xa3, 0x45, 0xbf, 0x90, - 0xd7, 0xa7, 0x58, 0xf1, 0x0f, 0xc0, 0x5f, 0x47, 0x18, 0xaf, 0x74, 0xca, 0x98, 0x6f, 0xa8, 0x03, - 0xa7, 0x58, 0x85, 0x7f, 0x30, 0xe8, 0xcf, 0x51, 0xdd, 0xa2, 0x7a, 0xa3, 0xc9, 0xec, 0x6e, 0x25, - 0xe7, 0x7f, 0xb6, 0x92, 0x7b, 0xf7, 0x56, 0xf2, 0xda, 0xad, 0xf4, 0x00, 0xbc, 0xb9, 0x5a, 0xce, - 0xa6, 0xfa, 0x8b, 0x1c, 0x61, 0x00, 0x79, 0x6c, 0xb2, 0x2c, 0xa2, 0x5b, 0xb4, 0xab, 0xca, 0xa2, - 0xf0, 0x37, 0x06, 0xfd, 0x33, 0x59, 0xa5, 0x65, 0xf1, 0x8a, 0xc3, 0x46, 0x70, 0x30, 0xc9, 0xb2, - 0x38, 0x5a, 0xca, 0x22, 0x4a, 0x13, 0xfb, 0xb5, 0xdd, 0x10, 0xdd, 0x78, 0xd6, 0xd1, 0xce, 0x7c, - 0x77, 0x37, 0x44, 0xc3, 0x70, 0xa2, 0x17, 0x8e, 0xd9, 0x1e, 0x9d, 0x61, 0x30, 0x7b, 0x46, 0x27, - 0xe9, 0x81, 0x93, 0xb2, 0x48, 0xd7, 0x71, 0xba, 0xd5, 0x2f, 0x19, 0x8a, 0x06, 0x87, 0xff, 0x30, - 0x70, 0xdf, 0xd6, 0x22, 0xb9, 0x07, 0x2c, 0xb2, 0x8d, 0x64, 0x51, 0xb3, 0x56, 0x06, 0x9d, 0xb5, - 0x12, 0xc0, 0xa0, 0x52, 0x32, 0xb9, 0xc6, 0x3c, 0x18, 0xea, 0x59, 0xad, 0xa1, 0xce, 0xe8, 0x19, - 0x31, 0xfb, 0xc4, 0x17, 0x35, 0x6c, 0x3c, 0x0f, 0xad, 0xe7, 0xc3, 0xbf, 0x18, 0x78, 0x8d, 0x73, - 0x4f, 0xf6, 0x9d, 0x7b, 0xd2, 0x3a, 0x77, 0x7a, 0x5c, 0x3b, 0x77, 0x7a, 0x4c, 0x58, 0x9c, 0xd7, - 0xce, 0x15, 0xe7, 0xa4, 0xda, 0x53, 0x95, 0x96, 0xd9, 0x71, 0x65, 0xe4, 0xf5, 0x45, 0x83, 0xa9, - 0xdd, 0x3f, 0xde, 0xa0, 0xb2, 0x6f, 0xf6, 0x85, 0x45, 0x64, 0x8e, 0x33, 0x3d, 0xd5, 0xe6, 0x95, - 0x06, 0xf0, 0x4f, 0xc0, 0x13, 0xf4, 0x0a, 0xfd, 0xd4, 0x3d, 0x81, 0x74, 0x58, 0x98, 0x6c, 0xf8, - 0xc4, 0x5e, 0x23, 0x96, 0xcb, 0x2c, 0x43, 0x65, 0x3d, 0x6d, 0x80, 0xe6, 0x4e, 0xb7, 0x68, 0xd6, - 0x91, 0x23, 0x0c, 0x08, 0x7f, 0x02, 0x7f, 0x12, 0xa3, 0x2a, 0x44, 0x19, 0xbf, 0xba, 0xc4, 0x38, - 0xb8, 0xdf, 0xcd, 0xbf, 0x7f, 0x5e, 0x4f, 0x02, 0x9d, 0x5b, 0xff, 0x3a, 0xff, 0xf1, 0xef, 0xa9, - 0xcc, 0xe4, 0x6c, 0xaa, 0x1b, 0xeb, 0x08, 0x8b, 0xc2, 0xcf, 0xc0, 0xa5, 0x39, 0xe9, 0x30, 0xbb, - 0xaf, 0x9b, 0xb1, 0x45, 0x5f, 0xff, 0x9b, 0x78, 0xf2, 0x6f, 0x00, 0x00, 0x00, 0xff, 0xff, 0xa1, - 0xf4, 0x45, 0x04, 0x5f, 0x08, 0x00, 0x00, + // 950 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xbc, 0x56, 0xcd, 0x8e, 0xe3, 0x44, + 0x10, 0x56, 0xc7, 0x76, 0x12, 0xd7, 0xcc, 0x0e, 0xa8, 0xb5, 0x62, 0xcd, 0x72, 0x09, 0x16, 0x48, + 0x01, 0x89, 0x01, 0xed, 0x0a, 0x09, 0x71, 0x4b, 0x26, 0x68, 0x15, 0x66, 0x76, 0x19, 0x3a, 0x33, + 0xc3, 0x09, 0xad, 0x3a, 0x49, 0x25, 0x63, 0xad, 0x13, 0x9b, 0xb6, 0x3d, 0x89, 0x5f, 0x81, 0x13, + 0x4f, 0x80, 0x84, 0xc4, 0x89, 0x23, 0x2f, 0xc0, 0x43, 0xf0, 0x42, 0xa8, 0xba, 0xdb, 0x3f, 0x61, + 0x67, 0xd1, 0x9e, 0xb8, 0xf5, 0x57, 0xd5, 0xf9, 0xda, 0xf5, 0xd5, 0x57, 0xa5, 0xc0, 0x49, 0xb4, + 0xcd, 0x51, 0x6d, 0x65, 0x7c, 0x9a, 0xaa, 0x24, 0x4f, 0x78, 0xbf, 0xc2, 0xe1, 0xcf, 0x1d, 0xe8, + 0xce, 0x92, 0x42, 0x2d, 0x90, 0x9f, 0x40, 0x67, 0x3a, 0x09, 0xd8, 0x80, 0x0d, 0x1d, 0xd1, 0x99, + 0x4e, 0x38, 0x07, 0xf7, 0x85, 0xdc, 0x60, 0xd0, 0x19, 0xb0, 0xa1, 0x2f, 0xf4, 0x99, 0x62, 0x57, + 0x65, 0x8a, 0x81, 0x63, 0x62, 0x74, 0xe6, 0x8f, 0xa1, 0x7f, 0x9d, 0x11, 0xdb, 0x06, 0x03, 0x57, + 0xc7, 0x6b, 0x4c, 0xb9, 0x4b, 0x99, 0x65, 0xbb, 0x44, 0x2d, 0x03, 0xcf, 0xe4, 0x2a, 0xcc, 0xdf, + 0x05, 0xe7, 0x5a, 0x5c, 0x04, 0x5d, 0x1d, 0xa6, 0x23, 0x0f, 0xa0, 0x37, 0xc1, 0x95, 0x2c, 0xe2, + 0x3c, 0xe8, 0x0d, 0xd8, 0xb0, 0x2f, 0x2a, 0x48, 0x3c, 0x57, 0x18, 0xe3, 0x5a, 0xc9, 0x55, 0xd0, + 0x37, 0x3c, 0x15, 0xe6, 0xa7, 0xc0, 0xa7, 0xdb, 0x0c, 0x17, 0x85, 0xc2, 0xd9, 0xab, 0x28, 0xbd, + 0x41, 0x15, 0xad, 0xca, 0xc0, 0xd7, 0x04, 0xf7, 0x64, 0xe8, 0x95, 0xe7, 0x98, 0x4b, 0x7a, 0x1b, + 0x34, 0x55, 0x05, 0xc3, 0x5f, 0x18, 0xf8, 0x13, 0x99, 0xdd, 0xce, 0x13, 0xa9, 0x96, 0x6f, 0xa5, + 0xc7, 0x67, 0xe0, 0x2d, 0x30, 0x8e, 0xb3, 0xc0, 0x19, 0x38, 0xc3, 0xa3, 0x27, 0x8f, 0x4e, 0x6b, + 0xa1, 0x6b, 0x9e, 0x33, 0x8c, 0x63, 0x61, 0x6e, 0xf1, 0x2f, 0xc0, 0xcf, 0x71, 0x93, 0xc6, 0x32, + 0xc7, 0x2c, 0x70, 0xf5, 0x4f, 0x78, 0xf3, 0x93, 0x2b, 0x9b, 0x12, 0xcd, 0xa5, 0xf0, 0x8f, 0x0e, + 0x3c, 0x38, 0xa0, 0xe2, 0xc7, 0xc0, 0xf6, 0xfa, 0xab, 0x3c, 0xc1, 0xf6, 0x84, 0x4a, 0xfd, 0x45, + 0x9e, 0x60, 0x25, 0xa1, 0x9d, 0xee, 0x8d, 0x27, 0xd8, 0x8e, 0xd0, 0xad, 0xee, 0x88, 0x27, 0xd8, + 0x2d, 0xff, 0x04, 0x7a, 0x3f, 0x15, 0xa8, 0x22, 0xcc, 0x02, 0x4f, 0xbf, 0xfc, 0x4e, 0xf3, 0xf2, + 0xf7, 0x05, 0xaa, 0x52, 0x54, 0x79, 0xaa, 0x54, 0x77, 0xd3, 0xb4, 0x46, 0x9f, 0x29, 0x96, 0x53, + 0xe7, 0x7b, 0x26, 0x46, 0x67, 0xab, 0x90, 0xe9, 0x07, 0x29, 0xf4, 0x25, 0xb8, 0x72, 0x8f, 0x59, + 0xe0, 0x6b, 0xfe, 0x0f, 0xdf, 0x20, 0xc6, 0xe9, 0x68, 0x8f, 0xd9, 0x37, 0xdb, 0x5c, 0x95, 0x42, + 0x5f, 0x7f, 0xfc, 0x0c, 0xfc, 0x3a, 0x44, 0xae, 0x78, 0x85, 0xa5, 0x2e, 0xd0, 0x17, 0x74, 0xe4, + 0x1f, 0x81, 0x77, 0x27, 0xe3, 0xc2, 0x08, 0x7f, 0xf4, 0xe4, 0xa4, 0xa1, 0x1d, 0xed, 0xa3, 0x4c, + 0x98, 0xe4, 0xd7, 0x9d, 0xaf, 0x58, 0x38, 0x06, 0x97, 0x42, 0x3c, 0x84, 0xe3, 0x18, 0xd7, 0x72, + 0x51, 0x8e, 0x93, 0x62, 0xbb, 0xcc, 0x02, 0x36, 0x70, 0x86, 0x8e, 0x38, 0x88, 0xf1, 0xf7, 0xa0, + 0x3b, 0x37, 0xd9, 0xce, 0xc0, 0x19, 0xfa, 0xc2, 0xa2, 0xf0, 0x2f, 0x46, 0x56, 0x33, 0xf2, 0xb7, + 0x2c, 0x60, 0x0a, 0x7c, 0x1f, 0xfa, 0xd4, 0x9a, 0x97, 0x77, 0x52, 0x59, 0x1b, 0xf4, 0x08, 0xdf, + 0x48, 0xc5, 0x3f, 0x87, 0xae, 0xfe, 0x90, 0x7b, 0xac, 0x50, 0xd1, 0xdd, 0x50, 0x5e, 0xd8, 0x6b, + 0xb5, 0xa0, 0x6e, 0x4b, 0xd0, 0x87, 0xe0, 0xc5, 0x72, 0x8e, 0xb1, 0x9d, 0x15, 0x03, 0xc8, 0x64, + 0xd4, 0x99, 0x52, 0xf7, 0xe3, 0x5e, 0x66, 0xd3, 0x3f, 0x73, 0x2b, 0xbc, 0x86, 0x07, 0x07, 0x2f, + 0xd6, 0x2f, 0xb1, 0xc3, 0x97, 0x1a, 0x51, 0x7d, 0x2b, 0x22, 0x8d, 0x59, 0x86, 0x31, 0x2e, 0x72, + 0x5c, 0x6a, 0x1b, 0xf5, 0x45, 0x8d, 0xc3, 0xdf, 0x58, 0xc3, 0xab, 0xdf, 0xa3, 0x41, 0x5a, 0x24, + 0x9b, 0x8d, 0xdc, 0x2e, 0x2d, 0x75, 0x05, 0x49, 0xb7, 0xe5, 0xdc, 0x52, 0x77, 0x96, 0x73, 0xc2, + 0x2a, 0xb5, 0x4b, 0xa3, 0xa3, 0x52, 0x3e, 0x80, 0xa3, 0x0d, 0xca, 0xac, 0x50, 0xb8, 0xc1, 0x6d, + 0x6e, 0x25, 0x68, 0x87, 0xf8, 0x23, 0xe8, 0xe5, 0x72, 0xfd, 0x92, 0xac, 0x60, 0xb4, 0xe8, 0xe6, + 0x72, 0x7d, 0x8e, 0x25, 0xff, 0x00, 0xfc, 0x55, 0x84, 0xf1, 0x52, 0xa7, 0x8c, 0x41, 0xfb, 0x3a, + 0x70, 0x8e, 0x65, 0xf8, 0x3b, 0x83, 0xee, 0x0c, 0xd5, 0x1d, 0xaa, 0xb7, 0x9a, 0xde, 0xf6, 0xe6, + 0x72, 0xfe, 0x63, 0x73, 0xb9, 0xf7, 0x6f, 0x2e, 0xaf, 0xd9, 0x5c, 0x0f, 0xc1, 0x9b, 0xa9, 0xc5, + 0x74, 0xa2, 0xbf, 0xc8, 0x11, 0x06, 0x90, 0xc7, 0x46, 0x8b, 0x3c, 0xba, 0x43, 0xbb, 0xce, 0x2c, + 0x0a, 0x7f, 0x65, 0xd0, 0xbd, 0x90, 0x65, 0x52, 0xe4, 0xaf, 0x39, 0x6c, 0x00, 0x47, 0xa3, 0x34, + 0x8d, 0xa3, 0x85, 0xcc, 0xa3, 0x64, 0x6b, 0xbf, 0xb6, 0x1d, 0xa2, 0x1b, 0xcf, 0x5b, 0xda, 0x99, + 0xef, 0x6e, 0x87, 0x68, 0x60, 0xce, 0xf4, 0x52, 0x32, 0x1b, 0xa6, 0x35, 0x30, 0x66, 0x17, 0xe9, + 0x24, 0x15, 0x38, 0x2a, 0xf2, 0x64, 0x15, 0x27, 0x3b, 0x5d, 0x49, 0x5f, 0xd4, 0x38, 0xfc, 0x9b, + 0x81, 0xfb, 0x7f, 0x2d, 0x9b, 0x63, 0x60, 0x91, 0x6d, 0x24, 0x8b, 0xea, 0xd5, 0xd3, 0x6b, 0xad, + 0x9e, 0x00, 0x7a, 0xa5, 0x92, 0xdb, 0x35, 0x66, 0x41, 0x5f, 0x4f, 0x72, 0x05, 0x75, 0x46, 0xcf, + 0x88, 0xd9, 0x39, 0xbe, 0xa8, 0x60, 0xed, 0x79, 0x68, 0x3c, 0x1f, 0xfe, 0xc9, 0xc0, 0xab, 0x9d, + 0x7b, 0x76, 0xe8, 0xdc, 0xb3, 0xc6, 0xb9, 0x93, 0x71, 0xe5, 0xdc, 0xc9, 0x98, 0xb0, 0xb8, 0xac, + 0x9c, 0x2b, 0x2e, 0x49, 0xb5, 0x67, 0x2a, 0x29, 0xd2, 0x71, 0x69, 0xe4, 0xf5, 0x45, 0x8d, 0xa9, + 0xdd, 0x3f, 0xdc, 0xa2, 0xb2, 0x35, 0xfb, 0xc2, 0x22, 0x32, 0xc7, 0x85, 0x9e, 0x6a, 0x53, 0xa5, + 0x01, 0xfc, 0x63, 0xf0, 0x04, 0x55, 0xa1, 0x4b, 0x3d, 0x10, 0x48, 0x87, 0x85, 0xc9, 0x86, 0x4f, + 0xed, 0x35, 0x62, 0xb9, 0x4e, 0x53, 0x54, 0xd6, 0xd3, 0x06, 0x68, 0xee, 0x64, 0x87, 0x66, 0x1d, + 0x39, 0xc2, 0x80, 0xf0, 0x47, 0xf0, 0x47, 0x31, 0xaa, 0x5c, 0x14, 0xf1, 0xeb, 0x4b, 0x8c, 0x83, + 0xfb, 0xed, 0xec, 0xbb, 0x17, 0xd5, 0x24, 0xd0, 0xb9, 0xf1, 0xaf, 0xf3, 0x2f, 0xff, 0x9e, 0xcb, + 0x54, 0x4e, 0x27, 0xba, 0xb1, 0x8e, 0xb0, 0x28, 0xfc, 0x14, 0x5c, 0x9a, 0x93, 0x16, 0xb3, 0xfb, + 0xa6, 0x19, 0x9b, 0x77, 0xf5, 0x3f, 0x8e, 0xa7, 0xff, 0x04, 0x00, 0x00, 0xff, 0xff, 0x33, 0xaf, + 0xb8, 0xb8, 0x83, 0x08, 0x00, 0x00, } diff --git a/bolt/internal/internal.proto b/bolt/internal/internal.proto index 2b5981363..18a2a7746 100644 --- a/bolt/internal/internal.proto +++ b/bolt/internal/internal.proto @@ -34,7 +34,8 @@ message DashboardCell { } message Axis { - repeated int64 bounds = 1; // bounds are an ordered 2-tuple consisting of lower and upper axis extents, respectively + repeated int64 legacyBounds = 1; // legacyBounds are an ordered 2-tuple consisting of lower and upper axis extents, respectively + repeated string bounds = 2; // bounds are an arbitrary list of client-defined bounds. } message Template { diff --git a/bolt/internal/internal_test.go b/bolt/internal/internal_test.go index 5c0a3bfd9..7eb86ac30 100644 --- a/bolt/internal/internal_test.go +++ b/bolt/internal/internal_test.go @@ -128,7 +128,7 @@ func Test_MarshalDashboard(t *testing.T) { }, Axes: map[string]chronograf.Axis{ "y": chronograf.Axis{ - Bounds: [2]int64{0, 100}, + Bounds: []string{"0", "3", "1-7", "foo"}, }, }, Type: "line", @@ -147,3 +147,76 @@ func Test_MarshalDashboard(t *testing.T) { t.Fatalf("Dashboard protobuf copy error: diff follows:\n%s", cmp.Diff(dashboard, actual)) } } + +func Test_MarshalDashboard_WithLegacyBounds(t *testing.T) { + dashboard := chronograf.Dashboard{ + ID: 1, + Cells: []chronograf.DashboardCell{ + { + ID: "9b5367de-c552-4322-a9e8-7f384cbd235c", + X: 0, + Y: 0, + W: 4, + H: 4, + Name: "Super awesome query", + Queries: []chronograf.DashboardQuery{ + { + Command: "select * from cpu", + Label: "CPU Utilization", + Range: &chronograf.Range{ + Upper: int64(100), + }, + }, + }, + Axes: map[string]chronograf.Axis{ + "y": chronograf.Axis{ + LegacyBounds: [2]int64{0, 5}, + }, + }, + Type: "line", + }, + }, + Templates: []chronograf.Template{}, + Name: "Dashboard", + } + + expected := chronograf.Dashboard{ + ID: 1, + Cells: []chronograf.DashboardCell{ + { + ID: "9b5367de-c552-4322-a9e8-7f384cbd235c", + X: 0, + Y: 0, + W: 4, + H: 4, + Name: "Super awesome query", + Queries: []chronograf.DashboardQuery{ + { + Command: "select * from cpu", + Label: "CPU Utilization", + Range: &chronograf.Range{ + Upper: int64(100), + }, + }, + }, + Axes: map[string]chronograf.Axis{ + "y": chronograf.Axis{ + Bounds: []string{"0", "5"}, + }, + }, + Type: "line", + }, + }, + Templates: []chronograf.Template{}, + Name: "Dashboard", + } + + var actual chronograf.Dashboard + if buf, err := internal.MarshalDashboard(dashboard); err != nil { + t.Fatal("Error marshaling dashboard: err", err) + } else if err := internal.UnmarshalDashboard(buf, &actual); err != nil { + t.Fatal("Error unmarshaling dashboard: err:", err) + } else if !cmp.Equal(expected, actual) { + t.Fatalf("Dashboard protobuf copy error: diff follows:\n%s", cmp.Diff(expected, actual)) + } +} diff --git a/chronograf.go b/chronograf.go index 8a62763ab..b8dcd4537 100644 --- a/chronograf.go +++ b/chronograf.go @@ -568,7 +568,8 @@ type Dashboard struct { // Axis represents the visible extents of a visualization type Axis struct { - Bounds [2]int64 `json:"bounds"` // bounds are an ordered 2-tuple consisting of lower and upper axis extents, respectively + Bounds []string `json:"bounds"` // bounds are an arbitrary list of client-defined strings that specify the viewport for a cell + LegacyBounds [2]int64 `json:"-"` // legacy bounds are for testing a migration from an earlier version of axis } // DashboardCell holds visual and query information for a cell diff --git a/server/cells_test.go b/server/cells_test.go index be694b3df..a3ae00e9c 100644 --- a/server/cells_test.go +++ b/server/cells_test.go @@ -20,13 +20,13 @@ func Test_Cells_CorrectAxis(t *testing.T) { &chronograf.DashboardCell{ Axes: map[string]chronograf.Axis{ "x": chronograf.Axis{ - Bounds: [2]int64{0, 100}, + Bounds: []string{"0", "100"}, }, "y": chronograf.Axis{ - Bounds: [2]int64{0, 100}, + Bounds: []string{"0", "100"}, }, "y2": chronograf.Axis{ - Bounds: [2]int64{0, 100}, + Bounds: []string{"0", "100"}, }, }, }, @@ -37,10 +37,10 @@ func Test_Cells_CorrectAxis(t *testing.T) { &chronograf.DashboardCell{ Axes: map[string]chronograf.Axis{ "axis of evil": chronograf.Axis{ - Bounds: [2]int64{666, 666}, + Bounds: []string{"666", "666"}, }, "axis of awesome": chronograf.Axis{ - Bounds: [2]int64{1337, 31337}, + Bounds: []string{"1337", "31337"}, }, }, }, diff --git a/server/dashboards_test.go b/server/dashboards_test.go index b5116913c..5c9208345 100644 --- a/server/dashboards_test.go +++ b/server/dashboards_test.go @@ -222,10 +222,10 @@ func Test_newDashboardResponse(t *testing.T) { }, Axes: map[string]chronograf.Axis{ "x": chronograf.Axis{ - Bounds: [2]int64{0, 100}, + Bounds: []string{"0", "100"}, }, "y": chronograf.Axis{ - Bounds: [2]int64{2, 95}, + Bounds: []string{"2", "95"}, }, }, }, @@ -268,10 +268,10 @@ func Test_newDashboardResponse(t *testing.T) { }, Axes: map[string]chronograf.Axis{ "x": chronograf.Axis{ - Bounds: [2]int64{0, 100}, + Bounds: []string{"0", "100"}, }, "y": chronograf.Axis{ - Bounds: [2]int64{2, 95}, + Bounds: []string{"2", "95"}, }, }, }, From 8bd622c49149b21a7ee4f45a161aad0901b3f3f7 Mon Sep 17 00:00:00 2001 From: Tim Raymond Date: Thu, 27 Jul 2017 15:57:04 -0400 Subject: [PATCH 037/107] Enforce presence of "x", "y", and "y2" axes Certain aspects of the frontend requires the presence of these three axes, so part of the contract established is that the backend will always provide them. Since we centralize creation of dashboardCellResponses, this is where these axes are added to all cell responses. Additionally, because there was previously no coverage over the dashboard cells endpoints, a test has been added to cover the DashboardCells method of Service. --- mocks/dashboards.go | 37 ++++++++++ mocks/logger.go | 9 +++ server/cells.go | 14 ++++ server/cells_test.go | 137 ++++++++++++++++++++++++++++++++++++++ server/dashboards_test.go | 6 ++ 5 files changed, 203 insertions(+) create mode 100644 mocks/dashboards.go diff --git a/mocks/dashboards.go b/mocks/dashboards.go new file mode 100644 index 000000000..a038f468b --- /dev/null +++ b/mocks/dashboards.go @@ -0,0 +1,37 @@ +package mocks + +import ( + "context" + + "github.com/influxdata/chronograf" +) + +var _ chronograf.DashboardsStore = &DashboardsStore{} + +type DashboardsStore struct { + AddF func(ctx context.Context, newDashboard chronograf.Dashboard) (chronograf.Dashboard, error) + AllF func(ctx context.Context) ([]chronograf.Dashboard, error) + DeleteF func(ctx context.Context, target chronograf.Dashboard) error + GetF func(ctx context.Context, id chronograf.DashboardID) (chronograf.Dashboard, error) + UpdateF func(ctx context.Context, target chronograf.Dashboard) error +} + +func (d *DashboardsStore) Add(ctx context.Context, newDashboard chronograf.Dashboard) (chronograf.Dashboard, error) { + return d.AddF(ctx, newDashboard) +} + +func (d *DashboardsStore) All(ctx context.Context) ([]chronograf.Dashboard, error) { + return d.AllF(ctx) +} + +func (d *DashboardsStore) Delete(ctx context.Context, target chronograf.Dashboard) error { + return d.DeleteF(ctx, target) +} + +func (d *DashboardsStore) Get(ctx context.Context, id chronograf.DashboardID) (chronograf.Dashboard, error) { + return d.GetF(ctx, id) +} + +func (d *DashboardsStore) Update(ctx context.Context, target chronograf.Dashboard) error { + return d.UpdateF(ctx, target) +} diff --git a/mocks/logger.go b/mocks/logger.go index 46c7bae9f..f2926e09a 100644 --- a/mocks/logger.go +++ b/mocks/logger.go @@ -3,6 +3,7 @@ package mocks import ( "fmt" "io" + "testing" "github.com/influxdata/chronograf" ) @@ -72,3 +73,11 @@ func (tl *TestLogger) stringifyArg(arg interface{}) []byte { return []byte("UNKNOWN") } } + +// Dump dumps out logs into a given testing.T's logs +func (tl *TestLogger) Dump(t *testing.T) { + t.Log("== Dumping Test Logs ==") + for _, msg := range tl.Messages { + t.Logf("lvl: %s, msg: %s", msg.Level, msg.Body) + } +} diff --git a/server/cells.go b/server/cells.go index f60437c91..a16cb702d 100644 --- a/server/cells.go +++ b/server/cells.go @@ -33,6 +33,20 @@ func newCellResponses(dID chronograf.DashboardID, dcells []chronograf.DashboardC if len(cell.Queries) == 0 { cell.Queries = make([]chronograf.DashboardQuery, 0) } + + // ensure x, y, and y2 axes always returned + labels := []string{"x", "y", "y2"} + if cell.Axes == nil { + cell.Axes = make(map[string]chronograf.Axis, len(labels)) + } + + for _, lbl := range labels { + _, found := cell.Axes[lbl] + if !found { + cell.Axes[lbl] = chronograf.Axis{} + } + } + cells[i] = dashboardCellResponse{ DashboardCell: cell, Links: dashboardCellLinks{ diff --git a/server/cells_test.go b/server/cells_test.go index a3ae00e9c..1be5981f9 100644 --- a/server/cells_test.go +++ b/server/cells_test.go @@ -1,9 +1,18 @@ package server_test import ( + "context" + "encoding/json" + "net/http" + "net/http/httptest" + "net/url" + "strings" "testing" + "github.com/bouk/httprouter" + "github.com/google/go-cmp/cmp" "github.com/influxdata/chronograf" + "github.com/influxdata/chronograf/mocks" "github.com/influxdata/chronograf/server" ) @@ -58,3 +67,131 @@ func Test_Cells_CorrectAxis(t *testing.T) { }) } } + +func Test_Service_DashboardCells(t *testing.T) { + cellsTests := []struct { + name string + reqURL *url.URL + ctxParams map[string]string + mockResponse []chronograf.DashboardCell + expected []chronograf.DashboardCell + expectedCode int + }{ + { + "happy path", + &url.URL{ + Path: "/chronograf/v1/dashboards/1/cells", + }, + map[string]string{ + "id": "1", + }, + []chronograf.DashboardCell{}, + []chronograf.DashboardCell{}, + http.StatusOK, + }, + { + "cell axes should always be \"x\", \"y\", and \"y2\"", + &url.URL{ + Path: "/chronograf/v1/dashboards/1/cells", + }, + map[string]string{ + "id": "1", + }, + []chronograf.DashboardCell{ + { + ID: "3899be5a-f6eb-4347-b949-de2f4fbea859", + X: 0, + Y: 0, + W: 4, + H: 4, + Name: "CPU", + Queries: []chronograf.DashboardQuery{}, + Axes: map[string]chronograf.Axis{}, + }, + }, + []chronograf.DashboardCell{ + { + ID: "3899be5a-f6eb-4347-b949-de2f4fbea859", + X: 0, + Y: 0, + W: 4, + H: 4, + Name: "CPU", + Queries: []chronograf.DashboardQuery{}, + Axes: map[string]chronograf.Axis{ + "x": chronograf.Axis{}, + "y": chronograf.Axis{}, + "y2": chronograf.Axis{}, + }, + }, + }, + http.StatusOK, + }, + } + + for _, test := range cellsTests { + t.Run(test.name, func(t *testing.T) { + t.Parallel() + + // setup context with params + ctx := context.Background() + params := httprouter.Params{} + for k, v := range test.ctxParams { + params = append(params, httprouter.Param{k, v}) + } + ctx = httprouter.WithParams(ctx, params) + + // setup response recorder and request + rr := httptest.NewRecorder() + req := httptest.NewRequest("GET", test.reqURL.RequestURI(), strings.NewReader("")).WithContext(ctx) + + // setup mock DashboardCells store and logger + tlog := &mocks.TestLogger{} + svc := &server.Service{ + DashboardsStore: &mocks.DashboardsStore{ + GetF: func(ctx context.Context, id chronograf.DashboardID) (chronograf.Dashboard, error) { + return chronograf.Dashboard{ + ID: chronograf.DashboardID(1), + Cells: test.mockResponse, + Templates: []chronograf.Template{}, + Name: "empty dashboard", + }, nil + }, + }, + Logger: tlog, + } + + // invoke DashboardCell handler + svc.DashboardCells(rr, req) + + // setup frame to decode response into + respFrame := []struct { + chronograf.DashboardCell + Links json.RawMessage `json:"links"` // ignore links + }{} + + // decode response + resp := rr.Result() + + if resp.StatusCode != test.expectedCode { + tlog.Dump(t) + t.Fatalf("%q - Status codes do not match. Want %d (%s), Got %d (%s)", test.name, test.expectedCode, http.StatusText(test.expectedCode), resp.StatusCode, http.StatusText(resp.StatusCode)) + } + + if err := json.NewDecoder(resp.Body).Decode(&respFrame); err != nil { + t.Fatalf("%q - Error unmarshaling response body: err: %s", test.name, err) + } + + // extract actual + actual := []chronograf.DashboardCell{} + for _, rsp := range respFrame { + actual = append(actual, rsp.DashboardCell) + } + + // compare actual and expected + if !cmp.Equal(actual, test.expected) { + t.Fatalf("%q - Dashboard Cells do not match: diff: %s", test.name, cmp.Diff(actual, test.expected)) + } + }) + } +} diff --git a/server/dashboards_test.go b/server/dashboards_test.go index 5c9208345..d328addad 100644 --- a/server/dashboards_test.go +++ b/server/dashboards_test.go @@ -273,6 +273,7 @@ func Test_newDashboardResponse(t *testing.T) { "y": chronograf.Axis{ Bounds: []string{"2", "95"}, }, + "y2": chronograf.Axis{}, }, }, }, @@ -284,6 +285,11 @@ func Test_newDashboardResponse(t *testing.T) { ID: "b", W: 4, H: 4, + Axes: map[string]chronograf.Axis{ + "x": chronograf.Axis{}, + "y": chronograf.Axis{}, + "y2": chronograf.Axis{}, + }, Queries: []chronograf.DashboardQuery{ { Command: "SELECT winning_horses from grays_sports_alamanc where time > now() - 15m", From e5331dc5369d3b0cb1908608ae25e6fa3f1362a8 Mon Sep 17 00:00:00 2001 From: Tim Raymond Date: Thu, 27 Jul 2017 16:03:24 -0400 Subject: [PATCH 038/107] Ensure cell bounds come back as empty array The contract with the frontend states that bounds should come back as an empty array instead of null when there are no bounds present. We must explicitly specify []string{} for this to happen. --- server/cells.go | 4 +++- server/cells_test.go | 12 +++++++++--- server/dashboards_test.go | 16 ++++++++++++---- 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/server/cells.go b/server/cells.go index a16cb702d..6c7859df4 100644 --- a/server/cells.go +++ b/server/cells.go @@ -43,7 +43,9 @@ func newCellResponses(dID chronograf.DashboardID, dcells []chronograf.DashboardC for _, lbl := range labels { _, found := cell.Axes[lbl] if !found { - cell.Axes[lbl] = chronograf.Axis{} + cell.Axes[lbl] = chronograf.Axis{ + Bounds: []string{}, + } } } diff --git a/server/cells_test.go b/server/cells_test.go index 1be5981f9..5ca5dce5e 100644 --- a/server/cells_test.go +++ b/server/cells_test.go @@ -119,9 +119,15 @@ func Test_Service_DashboardCells(t *testing.T) { Name: "CPU", Queries: []chronograf.DashboardQuery{}, Axes: map[string]chronograf.Axis{ - "x": chronograf.Axis{}, - "y": chronograf.Axis{}, - "y2": chronograf.Axis{}, + "x": chronograf.Axis{ + Bounds: []string{}, + }, + "y": chronograf.Axis{ + Bounds: []string{}, + }, + "y2": chronograf.Axis{ + Bounds: []string{}, + }, }, }, }, diff --git a/server/dashboards_test.go b/server/dashboards_test.go index d328addad..d659099dc 100644 --- a/server/dashboards_test.go +++ b/server/dashboards_test.go @@ -273,7 +273,9 @@ func Test_newDashboardResponse(t *testing.T) { "y": chronograf.Axis{ Bounds: []string{"2", "95"}, }, - "y2": chronograf.Axis{}, + "y2": chronograf.Axis{ + Bounds: []string{}, + }, }, }, }, @@ -286,9 +288,15 @@ func Test_newDashboardResponse(t *testing.T) { W: 4, H: 4, Axes: map[string]chronograf.Axis{ - "x": chronograf.Axis{}, - "y": chronograf.Axis{}, - "y2": chronograf.Axis{}, + "x": chronograf.Axis{ + Bounds: []string{}, + }, + "y": chronograf.Axis{ + Bounds: []string{}, + }, + "y2": chronograf.Axis{ + Bounds: []string{}, + }, }, Queries: []chronograf.DashboardQuery{ { From 25059c0591b70c1a3b4c8c1acd679f02b2914d92 Mon Sep 17 00:00:00 2001 From: Tim Raymond Date: Fri, 28 Jul 2017 11:05:25 -0400 Subject: [PATCH 039/107] Fix data races in dashboard response construction Dashboard responses had data races because multiple goroutines were reading and modifying dashboards before sending them out on the wire. This patch introduces immutability in the construction of the response, so that each goroutine is working with its own set of dashboardResponse structs. --- server/cells.go | 27 +++++++++++++++++---------- server/dashboards.go | 31 +++++++++++++++++-------------- server/dashboards_test.go | 2 +- 3 files changed, 35 insertions(+), 25 deletions(-) diff --git a/server/cells.go b/server/cells.go index 6c7859df4..29c0bd57a 100644 --- a/server/cells.go +++ b/server/cells.go @@ -30,27 +30,34 @@ func newCellResponses(dID chronograf.DashboardID, dcells []chronograf.DashboardC base := "/chronograf/v1/dashboards" cells := make([]dashboardCellResponse, len(dcells)) for i, cell := range dcells { - if len(cell.Queries) == 0 { - cell.Queries = make([]chronograf.DashboardQuery, 0) - } + newCell := chronograf.DashboardCell{} + + newCell.Queries = make([]chronograf.DashboardQuery, len(cell.Queries)) + copy(newCell.Queries, cell.Queries) // ensure x, y, and y2 axes always returned labels := []string{"x", "y", "y2"} - if cell.Axes == nil { - cell.Axes = make(map[string]chronograf.Axis, len(labels)) - } + newCell.Axes = make(map[string]chronograf.Axis, len(labels)) + + newCell.X = cell.X + newCell.Y = cell.Y + newCell.W = cell.W + newCell.H = cell.H + newCell.Name = cell.Name + newCell.ID = cell.ID for _, lbl := range labels { - _, found := cell.Axes[lbl] - if !found { - cell.Axes[lbl] = chronograf.Axis{ + if axis, found := cell.Axes[lbl]; !found { + newCell.Axes[lbl] = chronograf.Axis{ Bounds: []string{}, } + } else { + newCell.Axes[lbl] = axis } } cells[i] = dashboardCellResponse{ - DashboardCell: cell, + DashboardCell: newCell, Links: dashboardCellLinks{ Self: fmt.Sprintf("%s/%d/cells/%s", base, dID, cell.ID), }, diff --git a/server/dashboards.go b/server/dashboards.go index ee9bb8a60..df307a8b4 100644 --- a/server/dashboards.go +++ b/server/dashboards.go @@ -28,20 +28,19 @@ type getDashboardsResponse struct { func newDashboardResponse(d chronograf.Dashboard) *dashboardResponse { base := "/chronograf/v1/dashboards" - DashboardDefaults(&d) - AddQueryConfigs(&d) - cells := newCellResponses(d.ID, d.Cells) - templates := newTemplateResponses(d.ID, d.Templates) + dd := AddQueryConfigs(DashboardDefaults(d)) + cells := newCellResponses(dd.ID, dd.Cells) + templates := newTemplateResponses(dd.ID, dd.Templates) return &dashboardResponse{ - ID: d.ID, - Name: d.Name, + ID: dd.ID, + Name: dd.Name, Cells: cells, Templates: templates, Links: dashboardLinks{ - Self: fmt.Sprintf("%s/%d", base, d.ID), - Cells: fmt.Sprintf("%s/%d/cells", base, d.ID), - Templates: fmt.Sprintf("%s/%d/templates", base, d.ID), + Self: fmt.Sprintf("%s/%d", base, dd.ID), + Cells: fmt.Sprintf("%s/%d/cells", base, dd.ID), + Templates: fmt.Sprintf("%s/%d/templates", base, dd.ID), }, } } @@ -229,24 +228,28 @@ func ValidDashboardRequest(d *chronograf.Dashboard) error { return err } } - DashboardDefaults(d) + (*d) = DashboardDefaults(*d) return nil } // DashboardDefaults updates the dashboard with the default values // if none are specified -func DashboardDefaults(d *chronograf.Dashboard) { +func DashboardDefaults(d chronograf.Dashboard) (newDash chronograf.Dashboard) { + newDash.Cells = make([]chronograf.DashboardCell, len(d.Cells)) for i, c := range d.Cells { CorrectWidthHeight(&c) - d.Cells[i] = c + newDash.Cells[i] = c } + return } // AddQueryConfigs updates all the celsl in the dashboard to have query config // objects corresponding to their influxql queries. -func AddQueryConfigs(d *chronograf.Dashboard) { +func AddQueryConfigs(d chronograf.Dashboard) (newDash chronograf.Dashboard) { + newDash.Cells = make([]chronograf.DashboardCell, len(d.Cells)) for i, c := range d.Cells { AddQueryConfig(&c) - d.Cells[i] = c + newDash.Cells[i] = c } + return } diff --git a/server/dashboards_test.go b/server/dashboards_test.go index d659099dc..f7cbaa747 100644 --- a/server/dashboards_test.go +++ b/server/dashboards_test.go @@ -128,7 +128,7 @@ func TestDashboardDefaults(t *testing.T) { }, } for _, tt := range tests { - if DashboardDefaults(&tt.d); !reflect.DeepEqual(tt.d, tt.want) { + if actual := DashboardDefaults(tt.d); !reflect.DeepEqual(actual, tt.want) { t.Errorf("%q. DashboardDefaults() = %v, want %v", tt.name, tt.d, tt.want) } } From 2df26802142dd626fe0461c00676d29332277960 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Mon, 31 Jul 2017 14:00:54 -0700 Subject: [PATCH 040/107] Remove axes normalizer With the `bounds` returning from the backend and the values saved as strings there's no longer a need to normalize cell state. --- .../components/CellEditorOverlay.js | 20 +------------------ 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/ui/src/dashboards/components/CellEditorOverlay.js b/ui/src/dashboards/components/CellEditorOverlay.js index 001aa5fa0..ae97b8d83 100644 --- a/ui/src/dashboards/components/CellEditorOverlay.js +++ b/ui/src/dashboards/components/CellEditorOverlay.js @@ -34,7 +34,6 @@ class CellEditorOverlay extends Component { this.handleSetActiveQueryIndex = ::this.handleSetActiveQueryIndex this.handleEditRawText = ::this.handleEditRawText this.handleSetRange = ::this.handleSetRange - this.normalizeAxes = ::this.normalizeAxes const {cell: {name, type, queries, axes}} = props @@ -82,7 +81,6 @@ class CellEditorOverlay extends Component { handleSetRange(e) { const {min, max} = e.target.form - // TODO: handle "" for min and max value this.setState({ axes: { y: { @@ -111,10 +109,10 @@ class CellEditorOverlay extends Component { queriesWorkingDraft, cellWorkingType: type, cellWorkingName: name, + axes, } = this.state const {cell} = this.props - const axes = this.normalizeAxes() const queries = queriesWorkingDraft.map(q => { const timeRange = q.range || {upper: null, lower: ':dashboardTime:'} @@ -137,22 +135,6 @@ class CellEditorOverlay extends Component { }) } - normalizeAxes() { - const axes = this.state.axes - const bounds = _.get(axes, ['y', 'bounds'], false) - if (!bounds && !bounds.length) { - return {...axes, y: {bounds: []}} - } - - const [min, max] = bounds - if (min === '' || max === '') { - // TODO: throw requirement error - return - } - - return {...axes, y: {bounds: [+min, +max]}} - } - handleSelectGraphType(graphType) { this.setState({cellWorkingType: graphType}) } From d08eab72148dee1f48175582b97e9bf0561baf4c Mon Sep 17 00:00:00 2001 From: Tim Raymond Date: Mon, 31 Jul 2017 17:24:43 -0400 Subject: [PATCH 041/107] Copy missing properties from Dashboards When creating new dashboards to set defaults, not all properties of the dashboard were being copied. This ensures that they are so that zero values are not used for things like the ID and Name. --- server/dashboards.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/server/dashboards.go b/server/dashboards.go index df307a8b4..62e6aac1c 100644 --- a/server/dashboards.go +++ b/server/dashboards.go @@ -235,7 +235,11 @@ func ValidDashboardRequest(d *chronograf.Dashboard) error { // DashboardDefaults updates the dashboard with the default values // if none are specified func DashboardDefaults(d chronograf.Dashboard) (newDash chronograf.Dashboard) { + newDash.ID = d.ID + newDash.Templates = d.Templates + newDash.Name = d.Name newDash.Cells = make([]chronograf.DashboardCell, len(d.Cells)) + for i, c := range d.Cells { CorrectWidthHeight(&c) newDash.Cells[i] = c @@ -246,7 +250,11 @@ func DashboardDefaults(d chronograf.Dashboard) (newDash chronograf.Dashboard) { // AddQueryConfigs updates all the celsl in the dashboard to have query config // objects corresponding to their influxql queries. func AddQueryConfigs(d chronograf.Dashboard) (newDash chronograf.Dashboard) { + newDash.ID = d.ID + newDash.Templates = d.Templates + newDash.Name = d.Name newDash.Cells = make([]chronograf.DashboardCell, len(d.Cells)) + for i, c := range d.Cells { AddQueryConfig(&c) newDash.Cells[i] = c From a51f0a175fa9b4804707e14f439c6e2ca30e5379 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Tue, 1 Aug 2017 09:10:08 -0700 Subject: [PATCH 042/107] Save y range for user --- ui/src/dashboards/components/CellEditorOverlay.js | 9 ++------- ui/src/data_explorer/components/Visualization.js | 4 ++-- ui/src/shared/parsing/getRangeForDygraph.js | 8 ++++++-- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/ui/src/dashboards/components/CellEditorOverlay.js b/ui/src/dashboards/components/CellEditorOverlay.js index ae97b8d83..595642a60 100644 --- a/ui/src/dashboards/components/CellEditorOverlay.js +++ b/ui/src/dashboards/components/CellEditorOverlay.js @@ -80,14 +80,9 @@ class CellEditorOverlay extends Component { handleSetRange(e) { const {min, max} = e.target.form + const {axes} = this.state - this.setState({ - axes: { - y: { - bounds: [min.value, max.value], - }, - }, - }) + this.setState({axes: {...axes, y: {bounds: [min.value, max.value]}}}) e.preventDefault() } diff --git a/ui/src/data_explorer/components/Visualization.js b/ui/src/data_explorer/components/Visualization.js index 472900904..66c34664d 100644 --- a/ui/src/data_explorer/components/Visualization.js +++ b/ui/src/data_explorer/components/Visualization.js @@ -6,7 +6,7 @@ import VisView from 'src/data_explorer/components/VisView' import {GRAPH, TABLE} from 'shared/constants' import _ from 'lodash' -const {array, arrayOf, bool, func, number, shape, string} = PropTypes +const {arrayOf, bool, func, number, shape, string} = PropTypes const META_QUERY_REGEX = /^show/i const Visualization = React.createClass({ @@ -28,7 +28,7 @@ const Visualization = React.createClass({ views: arrayOf(string).isRequired, axes: shape({ y: shape({ - bounds: array, + bounds: arrayOf(string), }), }), }, diff --git a/ui/src/shared/parsing/getRangeForDygraph.js b/ui/src/shared/parsing/getRangeForDygraph.js index 1e505718b..43eec356d 100644 --- a/ui/src/shared/parsing/getRangeForDygraph.js +++ b/ui/src/shared/parsing/getRangeForDygraph.js @@ -19,6 +19,11 @@ const getRange = ( ) => { const {value, rangeValue, operator} = ruleValues + if (userSelectedRange.length) { + const [userMin, userMax] = userSelectedRange + return [considerZero(userMin), considerZero(userMax)] + } + const subtractPadding = val => +val - Math.abs(val * PADDING_FACTOR) const addPadding = val => +val + Math.abs(val * PADDING_FACTOR) @@ -65,10 +70,9 @@ const getRange = ( return [null, null] } - const [userMin, userMax] = userSelectedRange const [min, max] = range - return [considerZero(userMin, min), considerZero(userMax, max)] + return [min, max] } export default getRange From 6460e416d729a3d0523a54b97b0dea5f136e5973 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Tue, 1 Aug 2017 09:42:37 -0700 Subject: [PATCH 043/107] Shorten text --- ui/src/dashboards/components/Ranger.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/dashboards/components/Ranger.js b/ui/src/dashboards/components/Ranger.js index 2db044e0c..d39caa5b8 100644 --- a/ui/src/dashboards/components/Ranger.js +++ b/ui/src/dashboards/components/Ranger.js @@ -25,7 +25,7 @@ const Ranger = ({onSetRange, axes}) => {
    Date: Tue, 1 Aug 2017 11:02:59 -0700 Subject: [PATCH 044/107] Tweak styles --- ui/src/dashboards/components/Ranger.js | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/ui/src/dashboards/components/Ranger.js b/ui/src/dashboards/components/Ranger.js index d39caa5b8..7dbc79bc7 100644 --- a/ui/src/dashboards/components/Ranger.js +++ b/ui/src/dashboards/components/Ranger.js @@ -9,10 +9,8 @@ const Ranger = ({onSetRange, axes}) => {
    Y Axis Controls
    -
    - +
    + { placeholder="auto" />
    -
    - +
    + Date: Tue, 1 Aug 2017 11:31:56 -0700 Subject: [PATCH 045/107] Add UI scaffolding for more axis controls --- ui/src/dashboards/components/Ranger.js | 43 ++++++++++++++++++- .../style/components/ceo-display-options.scss | 21 +++++---- 2 files changed, 54 insertions(+), 10 deletions(-) diff --git a/ui/src/dashboards/components/Ranger.js b/ui/src/dashboards/components/Ranger.js index 7dbc79bc7..1e94ac92a 100644 --- a/ui/src/dashboards/components/Ranger.js +++ b/ui/src/dashboards/components/Ranger.js @@ -8,7 +8,34 @@ const Ranger = ({onSetRange, axes}) => { return (
    Y Axis Controls
    - + +
    + + +
    +
    + + +
    +
    + + +
    { placeholder="auto" />
    +
    + +
      +
    • Base 10
    • +
    • Base 2
    • +
    +
    +
    + +
      +
    • Linear
    • +
    • Logarithmic
    • +
    +
    ) diff --git a/ui/src/style/components/ceo-display-options.scss b/ui/src/style/components/ceo-display-options.scss index 62a9a6eac..635c79dde 100644 --- a/ui/src/style/components/ceo-display-options.scss +++ b/ui/src/style/components/ceo-display-options.scss @@ -30,15 +30,6 @@ color: $g11-sidewalk; @include no-user-select(); } -.display-options--row { - display: flex; - align-items: center; - flex-wrap: nowrap; - margin-bottom: 8px; - - label { margin: 0; } - input { flex: 1 0 0; } -} .viz-type-selector { @@ -138,3 +129,15 @@ .viz-type-selector--graphic-fill.graphic-fill-b, .viz-type-selector--graphic-fill.graphic-fill-c {opacity: 0.18;} } + + + +.display-options--cell .form-group .nav.nav-tablist { + display: flex; + width: 100%; + + > li { + flex: 1 0 0; + justify-content: center; + } +} From 737e4a8e0277c2f84b10b6f1fb0f5275ed6211ff Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Tue, 1 Aug 2017 13:14:31 -0700 Subject: [PATCH 046/107] Fix test and user submitted values check --- ui/spec/shared/parsing/getRangeForDygraphSpec.js | 4 ++-- ui/src/shared/parsing/getRangeForDygraph.js | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ui/spec/shared/parsing/getRangeForDygraphSpec.js b/ui/spec/shared/parsing/getRangeForDygraphSpec.js index 2cec89bcf..d7840444f 100644 --- a/ui/spec/shared/parsing/getRangeForDygraphSpec.js +++ b/ui/spec/shared/parsing/getRangeForDygraphSpec.js @@ -17,10 +17,10 @@ describe.only('getRangeForDygraphSpec', () => { it('does not get range when a range is provided', () => { const timeSeries = [[date, min], [date, max], [date, mid]] - const providedRange = [0, 4] + const providedRange = ['0', '4'] const actual = getRange(timeSeries, providedRange) - expect(actual).to.deep.equal(providedRange) + expect(actual).to.deep.equal([0, 4]) }) it('gets the range for multiple timeSeries', () => { diff --git a/ui/src/shared/parsing/getRangeForDygraph.js b/ui/src/shared/parsing/getRangeForDygraph.js index 43eec356d..8d7696dbd 100644 --- a/ui/src/shared/parsing/getRangeForDygraph.js +++ b/ui/src/shared/parsing/getRangeForDygraph.js @@ -18,9 +18,9 @@ const getRange = ( ruleValues = {value: null, rangeValue: null} ) => { const {value, rangeValue, operator} = ruleValues + const [userMin, userMax] = userSelectedRange - if (userSelectedRange.length) { - const [userMin, userMax] = userSelectedRange + if (userMin && userMax) { return [considerZero(userMin), considerZero(userMax)] } @@ -72,7 +72,7 @@ const getRange = ( const [min, max] = range - return [min, max] + return [considerZero(userMin, min), considerZero(userMax, max)] } export default getRange From 6865b90e33c88b3ef998e65e702a8bf3d7dabe72 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Tue, 1 Aug 2017 13:24:52 -0700 Subject: [PATCH 047/107] Comment out undone features --- ui/src/dashboards/components/Ranger.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/ui/src/dashboards/components/Ranger.js b/ui/src/dashboards/components/Ranger.js index 1e94ac92a..e97fec0ca 100644 --- a/ui/src/dashboards/components/Ranger.js +++ b/ui/src/dashboards/components/Ranger.js @@ -1,6 +1,7 @@ import React, {PropTypes} from 'react' import _ from 'lodash' +// TODO: add logic for for Prefix, Suffix, Scale, and Multiplier const Ranger = ({onSetRange, axes}) => { const min = _.get(axes, ['y', 'bounds', '0'], '') const max = _.get(axes, ['y', 'bounds', '1'], '') @@ -9,7 +10,7 @@ const Ranger = ({onSetRange, axes}) => {
    Y Axis Controls
    -
    + {/*
    { name="suffix" id="suffix" /> -
    +
    */}
    { placeholder="auto" />
    -
    + {/*
    • Base 10
    • @@ -73,7 +74,7 @@ const Ranger = ({onSetRange, axes}) => {
    • Linear
    • Logarithmic
    -
    +
    */}
    ) From b5ac54c719b16effbd1e33ffa844351b72e566ce Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Tue, 1 Aug 2017 14:12:40 -0700 Subject: [PATCH 048/107] Update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7613754c1..f8780fc09 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ ### Features 1. [#1717](https://github.com/influxdata/chronograf/pull/1717): View server generated TICKscripts 1. [#1681](https://github.com/influxdata/chronograf/pull/1681): Add the ability to select Custom Time Ranges in the Hostpages, Data Explorer, and Dashboards. +1. [#1714](https://github.com/influxdata/chronograf/pull/1714): Add ability for users to set y-axis bounds ### UI Improvements From 240de08849bdde2bddc41ed45081b7415a9b5120 Mon Sep 17 00:00:00 2001 From: Alex P Date: Tue, 1 Aug 2017 14:38:12 -0700 Subject: [PATCH 049/107] Fine tune UI for axis controls --- ui/src/dashboards/components/Ranger.js | 47 +++++++++++++------------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/ui/src/dashboards/components/Ranger.js b/ui/src/dashboards/components/Ranger.js index 1e94ac92a..9f3bdb83e 100644 --- a/ui/src/dashboards/components/Ranger.js +++ b/ui/src/dashboards/components/Ranger.js @@ -9,17 +9,8 @@ const Ranger = ({onSetRange, axes}) => {
    Y Axis Controls
    -
    - - -
    -
    - +
    + { id="label" />
    -
    - - -
    { />
    - + + +
    +
    + + +
    +
    +
      -
    • Base 10
    • -
    • Base 2
    • +
    • K/M/B
    • +
    • K/M/G
    @@ -74,6 +74,7 @@ const Ranger = ({onSetRange, axes}) => {
  • Logarithmic
+
) From 12ab2430188e0d5da6a074397808eca89a6a7235 Mon Sep 17 00:00:00 2001 From: Jared Scheib Date: Tue, 1 Aug 2017 16:10:14 -0700 Subject: [PATCH 050/107] Improve error message on JSON Feed GET fail --- ui/src/status/actions/index.js | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/ui/src/status/actions/index.js b/ui/src/status/actions/index.js index d985035b3..c64594a67 100644 --- a/ui/src/status/actions/index.js +++ b/ui/src/status/actions/index.js @@ -7,8 +7,6 @@ import {errorThrown} from 'shared/actions/errors' import * as actionTypes from 'src/status/constants/actionTypes' -import {HTTP_NOT_FOUND} from 'shared/constants' - const fetchJSONFeedRequested = () => ({ type: actionTypes.FETCH_JSON_FEED_REQUESTED, }) @@ -45,15 +43,11 @@ export const fetchJSONFeedAsync = url => async dispatch => { } catch (error) { console.error(error) dispatch(fetchJSONFeedFailed()) - if (error.status === HTTP_NOT_FOUND) { - dispatch( - errorThrown( - error, - `Failed to fetch News Feed. JSON Feed at '${url}' returned 404 (Not Found)` - ) + dispatch( + errorThrown( + error, + `Failed to fetch JSON Feed for News Feed from '${url}'` ) - } else { - dispatch(errorThrown(error, 'Failed to fetch NewsFeed')) - } + ) } } From 544b35fba85e17d86968d96782c50ac024742885 Mon Sep 17 00:00:00 2001 From: Jared Scheib Date: Tue, 1 Aug 2017 16:16:04 -0700 Subject: [PATCH 051/107] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d10faa318..ac734b262 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ ### UI Improvements 1. [#1796](https://github.com/influxdata/chronograf/pull/1796): Add spinner to indicate data is being written 1. [#1800](https://github.com/influxdata/chronograf/pull/1796): Embiggen text area for line protocol manual entry in Data Explorer's Write Data overlay +1. [#1812](https://github.com/influxdata/chronograf/pull/1812): Improve error message when request for Status Page News Feed fails ## v1.3.5.0 [2017-07-27] ### Bug Fixes From ff0bad562990880606471204513081950564ec5d Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Tue, 1 Aug 2017 16:16:24 -0700 Subject: [PATCH 052/107] Make linter happy --- ui/src/dashboards/components/Ranger.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/ui/src/dashboards/components/Ranger.js b/ui/src/dashboards/components/Ranger.js index 33f8f27a8..09ce13400 100644 --- a/ui/src/dashboards/components/Ranger.js +++ b/ui/src/dashboards/components/Ranger.js @@ -10,7 +10,7 @@ const Ranger = ({onSetRange, axes}) => {
Y Axis Controls
- {/*
+ {/*
{ name="label" id="label" /> -
*/} +
*/}
{ placeholder="auto" />
- {/*
+ {/*
{
  • Linear
  • Logarithmic
  • -
    */} - +
    */}
    ) From 25709ea923f5059f28283175601c3996c84e7111 Mon Sep 17 00:00:00 2001 From: Jared Scheib Date: Tue, 1 Aug 2017 16:17:11 -0700 Subject: [PATCH 053/107] Fix link in changelog update for #1800 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d10faa318..48c7e6109 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ ### Features ### UI Improvements 1. [#1796](https://github.com/influxdata/chronograf/pull/1796): Add spinner to indicate data is being written -1. [#1800](https://github.com/influxdata/chronograf/pull/1796): Embiggen text area for line protocol manual entry in Data Explorer's Write Data overlay +1. [#1800](https://github.com/influxdata/chronograf/pull/1800): Embiggen text area for line protocol manual entry in Data Explorer's Write Data overlay ## v1.3.5.0 [2017-07-27] ### Bug Fixes From aa5f710b5a70d0ece6b348bb4d0580d6a7ed2519 Mon Sep 17 00:00:00 2001 From: Alex P Date: Tue, 1 Aug 2017 19:46:11 -0700 Subject: [PATCH 054/107] WIP "one or any" component --- ui/src/dashboards/components/Ranger.js | 29 ++++--- ui/src/style/chronograf.scss | 1 + ui/src/style/components/one-or-any.scss | 104 ++++++++++++++++++++++++ 3 files changed, 123 insertions(+), 11 deletions(-) create mode 100644 ui/src/style/components/one-or-any.scss diff --git a/ui/src/dashboards/components/Ranger.js b/ui/src/dashboards/components/Ranger.js index 09ce13400..70902d5f4 100644 --- a/ui/src/dashboards/components/Ranger.js +++ b/ui/src/dashboards/components/Ranger.js @@ -20,19 +20,26 @@ const Ranger = ({onSetRange, axes}) => { />
    */}
    - - + +
    +
    auto
    +
    +
    +
    + +
    - + div { + margin: 0 10px; + z-index: 3; + width: 28px; + height: 8px; + border-radius: 4px; + background-color: $g6-smoke; + position: relative; + + // Dot + &:after { + content: ''; + position: absolute; + top: 50%; + left: 50%; + width: 14px; + height: 14px; + border-radius: 50%; + background-color: $c-pool; + transition: + background-color 0.25s ease, + transform 0.25s ease; + transform: translate(-100%,-50%); + } + } + + // Background Gradients + &:before, &:after { + content: ''; + display: block; + position: absolute; + width: 100%; + height: 100%; + transition: opacity 0.25s ease; + } + // Left + &:before { + z-index: 2; + @include gradient-h($g2-kevlar,$g3-castle); + opacity: 1; + } + // Right + &:after { + @include gradient-h($g3-castle,$g2-kevlar); + z-index: 1; + } + + &:hover { + cursor: pointer; + > div:after {background-color: $c-laser;} + } +} +// Customize form input +.one-or-any > input.form-control { + border-radius: 0 4px 4px 0; + font-family: $code-font; +} +// Toggled state +.one-or-any.toggled { + .one-or-any--switch > div:after {transform: translate(0%,-50%);} + // Fade out left, fade in right + .one-or-any--switch:before {opacity: 0;} + // Make auto look disabled + .one-or-any--auto { + background-color: $g3-castle; + color: $g8-storm; + font-style: italic; + } +} From 58c23ad14fd265feaf8323b41fa8ebb52aa138c9 Mon Sep 17 00:00:00 2001 From: Alex P Date: Tue, 1 Aug 2017 19:51:20 -0700 Subject: [PATCH 055/107] Rename classes and element --- ui/src/dashboards/components/Ranger.js | 4 +- ui/src/style/components/one-or-any.scss | 60 ++++++++++++------------- 2 files changed, 31 insertions(+), 33 deletions(-) diff --git a/ui/src/dashboards/components/Ranger.js b/ui/src/dashboards/components/Ranger.js index 70902d5f4..b755494ee 100644 --- a/ui/src/dashboards/components/Ranger.js +++ b/ui/src/dashboards/components/Ranger.js @@ -23,8 +23,8 @@ const Ranger = ({onSetRange, axes}) => {
    auto
    -
    -
    +
    +
    div { - margin: 0 10px; - z-index: 3; - width: 28px; - height: 8px; - border-radius: 4px; - background-color: $g6-smoke; - position: relative; - - // Dot - &:after { - content: ''; - position: absolute; - top: 50%; - left: 50%; - width: 14px; - height: 14px; - border-radius: 50%; - background-color: $c-pool; - transition: - background-color 0.25s ease, - transform 0.25s ease; - transform: translate(-100%,-50%); - } - } - // Background Gradients &:before, &:after { content: ''; @@ -82,7 +55,32 @@ &:hover { cursor: pointer; - > div:after {background-color: $c-laser;} + .one-or-any--groove-knob:after {background-color: $c-laser;} + } +} +.one-or-any--groove-knob { + margin: 0 10px; + z-index: 3; + width: 28px; + height: 8px; + border-radius: 4px; + background-color: $g6-smoke; + position: relative; + + // Knob + &:after { + content: ''; + position: absolute; + top: 50%; + left: 50%; + width: 14px; + height: 14px; + border-radius: 50%; + background-color: $c-pool; + transition: + background-color 0.25s ease, + transform 0.25s ease; + transform: translate(-100%,-50%); } } // Customize form input @@ -92,9 +90,9 @@ } // Toggled state .one-or-any.toggled { - .one-or-any--switch > div:after {transform: translate(0%,-50%);} + .one-or-any--groove-knob:after {transform: translate(0%,-50%);} // Fade out left, fade in right - .one-or-any--switch:before {opacity: 0;} + .one-or-any--toggle:before {opacity: 0;} // Make auto look disabled .one-or-any--auto { background-color: $g3-castle; From 2e15eed1451a743c5a47ddbc91bd9c3cc999cb10 Mon Sep 17 00:00:00 2001 From: Alex Paxton Date: Tue, 1 Aug 2017 20:42:29 -0700 Subject: [PATCH 056/107] Wire up GrooveKnob to be fully functional Signed-off-by: Jared Scheib --- ui/src/shared/components/GrooveKnob.js | 83 +++++++++++++++++++++++++ ui/src/style/components/one-or-any.scss | 8 +-- 2 files changed, 87 insertions(+), 4 deletions(-) create mode 100644 ui/src/shared/components/GrooveKnob.js diff --git a/ui/src/shared/components/GrooveKnob.js b/ui/src/shared/components/GrooveKnob.js new file mode 100644 index 000000000..134e2d71a --- /dev/null +++ b/ui/src/shared/components/GrooveKnob.js @@ -0,0 +1,83 @@ +import React, {Component, PropTypes} from 'react' +import classnames from 'classnames' + +class GrooveKnob extends Component { + constructor(props) { + super(props) + + const {leftValue, rightValue} = props + + this.state = { + leftValue, + rightValue, + } + + this.handleChangeRightValue = ::this.handleChangeRightValue + this.handleToggleLeftValue = ::this.handleToggleLeftValue + this.handleSetValues = ::this.handleSetValues + } + + handleChangeRightValue(newValue) { + this.setState({rightValue: newValue}, this.handleSetValues) + } + + handleToggleLeftValue() { + const {leftValue} = this.state + + this.setState({leftValue: !leftValue}, this.handleSetValues) + } + + handleSetValues() { + const {onSetValues} = this.props + const {leftValue, rightValue} = this.state + + onSetValues({leftValue, rightValue}) + } + + render() { + const {leftLabel, rightLabel} = this.props + const {leftValue: useLeftValue, rightValue} = this.state + + return ( +
    +
    {leftLabel}
    +
    +
    +
    + this.handleChangeRightValue(e.target.value)} + placeholder={rightLabel} + disabled={useLeftValue} + /> +
    + ) + } +} + +GrooveKnob.defaultProps = { + leftLabel: 'auto', + leftValue: true, + rightLabel: 'Custom Value', + rightValue: null, +} +const {bool, func, number, string} = PropTypes + +GrooveKnob.propTypes = { + leftLabel: string, + leftValue: bool, + rightLabel: string, + rightValue: number, + onSetValues: func.isRequired, +} + +export default GrooveKnob diff --git a/ui/src/style/components/one-or-any.scss b/ui/src/style/components/one-or-any.scss index 7b23d3bc7..6580d5cbd 100644 --- a/ui/src/style/components/one-or-any.scss +++ b/ui/src/style/components/one-or-any.scss @@ -24,7 +24,7 @@ background-color 0.25s ease, color 0.25s ease; } -.one-or-any--toggle { +.one-or-any--switch { display: flex; align-items: center; border: 2px solid $g5-pepper; @@ -88,11 +88,11 @@ border-radius: 0 4px 4px 0; font-family: $code-font; } -// Toggled state -.one-or-any.toggled { +// When using right value +.one-or-any.use-right-value { .one-or-any--groove-knob:after {transform: translate(0%,-50%);} // Fade out left, fade in right - .one-or-any--toggle:before {opacity: 0;} + .one-or-any--switch:before {opacity: 0;} // Make auto look disabled .one-or-any--auto { background-color: $g3-castle; From d07573339867b3a1ac8abe217743f51773da12ff Mon Sep 17 00:00:00 2001 From: Alex Paxton Date: Tue, 1 Aug 2017 20:43:45 -0700 Subject: [PATCH 057/107] WIP Pass values from GrooveKnob to parent Signed-off-by: Jared Scheib --- ui/src/dashboards/components/Ranger.js | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/ui/src/dashboards/components/Ranger.js b/ui/src/dashboards/components/Ranger.js index b755494ee..70cd9aeb9 100644 --- a/ui/src/dashboards/components/Ranger.js +++ b/ui/src/dashboards/components/Ranger.js @@ -1,6 +1,8 @@ import React, {PropTypes} from 'react' import _ from 'lodash' +import GrooveKnob from 'shared/components/GrooveKnob' + // TODO: add logic for for Prefix, Suffix, Scale, and Multiplier const Ranger = ({onSetRange, axes}) => { const min = _.get(axes, ['y', 'bounds', '0'], '') @@ -21,22 +23,13 @@ const Ranger = ({onSetRange, axes}) => {
    */}
    -
    -
    auto
    -
    -
    -
    - -
    + { + console.log(leftValue, rightValue) + // onSetRange() + }} + rightValue={Number(min)} + />
    From d6322ae8f102971ba4b48e37d42e4d333fabd6e0 Mon Sep 17 00:00:00 2001 From: Tim Raymond Date: Wed, 2 Aug 2017 10:58:01 -0400 Subject: [PATCH 058/107] Return nothing from previously set bounds Previously, if bounds were not set, the default value that would be returned was ["0", "0"], which is incorrect. This now returns [] when there was nothing set for a particular axis. --- bolt/internal/internal.go | 18 ++++---- bolt/internal/internal_test.go | 75 +++++++++++++++++++++++++++++++++- 2 files changed, 82 insertions(+), 11 deletions(-) diff --git a/bolt/internal/internal.go b/bolt/internal/internal.go index cca08cdd1..4b64a0362 100644 --- a/bolt/internal/internal.go +++ b/bolt/internal/internal.go @@ -2,7 +2,6 @@ package internal import ( "encoding/json" - "strconv" "github.com/gogo/protobuf/proto" "github.com/influxdata/chronograf" @@ -268,16 +267,15 @@ func UnmarshalDashboard(data []byte, d *chronograf.Dashboard) error { axes := make(map[string]chronograf.Axis, len(c.Axes)) for a, r := range c.Axes { - axis := chronograf.Axis{} - // repair legacy bounds - for _, bound := range r.LegacyBounds { - axis.Bounds = append(axis.Bounds, strconv.FormatInt(bound, 10)) + if r.Bounds != nil { + axes[a] = chronograf.Axis{ + Bounds: r.Bounds, + } + } else { + axes[a] = chronograf.Axis{ + Bounds: []string{}, + } } - - if len(r.Bounds) > 0 { - axis.Bounds = r.Bounds - } - axes[a] = axis } cells[i] = chronograf.DashboardCell{ diff --git a/bolt/internal/internal_test.go b/bolt/internal/internal_test.go index 7eb86ac30..82abcbad2 100644 --- a/bolt/internal/internal_test.go +++ b/bolt/internal/internal_test.go @@ -201,7 +201,80 @@ func Test_MarshalDashboard_WithLegacyBounds(t *testing.T) { }, Axes: map[string]chronograf.Axis{ "y": chronograf.Axis{ - Bounds: []string{"0", "5"}, + Bounds: []string{}, + }, + }, + Type: "line", + }, + }, + Templates: []chronograf.Template{}, + Name: "Dashboard", + } + + var actual chronograf.Dashboard + if buf, err := internal.MarshalDashboard(dashboard); err != nil { + t.Fatal("Error marshaling dashboard: err", err) + } else if err := internal.UnmarshalDashboard(buf, &actual); err != nil { + t.Fatal("Error unmarshaling dashboard: err:", err) + } else if !cmp.Equal(expected, actual) { + t.Fatalf("Dashboard protobuf copy error: diff follows:\n%s", cmp.Diff(expected, actual)) + } +} + +func Test_MarshalDashboard_WithNoLegacyBounds(t *testing.T) { + dashboard := chronograf.Dashboard{ + ID: 1, + Cells: []chronograf.DashboardCell{ + { + ID: "9b5367de-c552-4322-a9e8-7f384cbd235c", + X: 0, + Y: 0, + W: 4, + H: 4, + Name: "Super awesome query", + Queries: []chronograf.DashboardQuery{ + { + Command: "select * from cpu", + Label: "CPU Utilization", + Range: &chronograf.Range{ + Upper: int64(100), + }, + }, + }, + Axes: map[string]chronograf.Axis{ + "y": chronograf.Axis{ + LegacyBounds: [2]int64{}, + }, + }, + Type: "line", + }, + }, + Templates: []chronograf.Template{}, + Name: "Dashboard", + } + + expected := chronograf.Dashboard{ + ID: 1, + Cells: []chronograf.DashboardCell{ + { + ID: "9b5367de-c552-4322-a9e8-7f384cbd235c", + X: 0, + Y: 0, + W: 4, + H: 4, + Name: "Super awesome query", + Queries: []chronograf.DashboardQuery{ + { + Command: "select * from cpu", + Label: "CPU Utilization", + Range: &chronograf.Range{ + Upper: int64(100), + }, + }, + }, + Axes: map[string]chronograf.Axis{ + "y": chronograf.Axis{ + Bounds: []string{}, }, }, Type: "line", From 89c0d84a8dac58bb8a3e306a24a3f5ef6dddbb1a Mon Sep 17 00:00:00 2001 From: Tim Raymond Date: Wed, 2 Aug 2017 11:12:47 -0400 Subject: [PATCH 059/107] Add Label to DashboardCell Axis It's useful for the frontend to be able to specify a label on a particular axis. This adds a property to Axis to facilitate that. --- bolt/internal/internal.go | 2 + bolt/internal/internal.pb.go | 123 +++++++++++++++++---------------- bolt/internal/internal.proto | 3 +- bolt/internal/internal_test.go | 1 + chronograf.go | 5 +- server/dashboards_test.go | 2 + 6 files changed, 72 insertions(+), 64 deletions(-) diff --git a/bolt/internal/internal.go b/bolt/internal/internal.go index 4b64a0362..9869f8f1f 100644 --- a/bolt/internal/internal.go +++ b/bolt/internal/internal.go @@ -189,6 +189,7 @@ func MarshalDashboard(d chronograf.Dashboard) ([]byte, error) { axes[a] = &Axis{ Bounds: r.Bounds, LegacyBounds: axis[:], + Label: r.Label, } } @@ -270,6 +271,7 @@ func UnmarshalDashboard(data []byte, d *chronograf.Dashboard) error { if r.Bounds != nil { axes[a] = chronograf.Axis{ Bounds: r.Bounds, + Label: r.Label, } } else { axes[a] = chronograf.Axis{ diff --git a/bolt/internal/internal.pb.go b/bolt/internal/internal.pb.go index c81dd610f..59de1a321 100644 --- a/bolt/internal/internal.pb.go +++ b/bolt/internal/internal.pb.go @@ -119,6 +119,7 @@ func (m *DashboardCell) GetAxes() map[string]*Axis { type Axis struct { LegacyBounds []int64 `protobuf:"varint,1,rep,name=legacyBounds" json:"legacyBounds,omitempty"` Bounds []string `protobuf:"bytes,2,rep,name=bounds" json:"bounds,omitempty"` + Label string `protobuf:"bytes,3,opt,name=label,proto3" json:"label,omitempty"` } func (m *Axis) Reset() { *m = Axis{} } @@ -313,65 +314,65 @@ func init() { func init() { proto.RegisterFile("internal.proto", fileDescriptorInternal) } var fileDescriptorInternal = []byte{ - // 950 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xbc, 0x56, 0xcd, 0x8e, 0xe3, 0x44, - 0x10, 0x56, 0xc7, 0x76, 0x12, 0xd7, 0xcc, 0x0e, 0xa8, 0xb5, 0x62, 0xcd, 0x72, 0x09, 0x16, 0x48, - 0x01, 0x89, 0x01, 0xed, 0x0a, 0x09, 0x71, 0x4b, 0x26, 0x68, 0x15, 0x66, 0x76, 0x19, 0x3a, 0x33, - 0xc3, 0x09, 0xad, 0x3a, 0x49, 0x25, 0x63, 0xad, 0x13, 0x9b, 0xb6, 0x3d, 0x89, 0x5f, 0x81, 0x13, - 0x4f, 0x80, 0x84, 0xc4, 0x89, 0x23, 0x2f, 0xc0, 0x43, 0xf0, 0x42, 0xa8, 0xba, 0xdb, 0x3f, 0x61, - 0x67, 0xd1, 0x9e, 0xb8, 0xf5, 0x57, 0xd5, 0xf9, 0xda, 0xf5, 0xd5, 0x57, 0xa5, 0xc0, 0x49, 0xb4, - 0xcd, 0x51, 0x6d, 0x65, 0x7c, 0x9a, 0xaa, 0x24, 0x4f, 0x78, 0xbf, 0xc2, 0xe1, 0xcf, 0x1d, 0xe8, - 0xce, 0x92, 0x42, 0x2d, 0x90, 0x9f, 0x40, 0x67, 0x3a, 0x09, 0xd8, 0x80, 0x0d, 0x1d, 0xd1, 0x99, - 0x4e, 0x38, 0x07, 0xf7, 0x85, 0xdc, 0x60, 0xd0, 0x19, 0xb0, 0xa1, 0x2f, 0xf4, 0x99, 0x62, 0x57, - 0x65, 0x8a, 0x81, 0x63, 0x62, 0x74, 0xe6, 0x8f, 0xa1, 0x7f, 0x9d, 0x11, 0xdb, 0x06, 0x03, 0x57, - 0xc7, 0x6b, 0x4c, 0xb9, 0x4b, 0x99, 0x65, 0xbb, 0x44, 0x2d, 0x03, 0xcf, 0xe4, 0x2a, 0xcc, 0xdf, - 0x05, 0xe7, 0x5a, 0x5c, 0x04, 0x5d, 0x1d, 0xa6, 0x23, 0x0f, 0xa0, 0x37, 0xc1, 0x95, 0x2c, 0xe2, - 0x3c, 0xe8, 0x0d, 0xd8, 0xb0, 0x2f, 0x2a, 0x48, 0x3c, 0x57, 0x18, 0xe3, 0x5a, 0xc9, 0x55, 0xd0, - 0x37, 0x3c, 0x15, 0xe6, 0xa7, 0xc0, 0xa7, 0xdb, 0x0c, 0x17, 0x85, 0xc2, 0xd9, 0xab, 0x28, 0xbd, - 0x41, 0x15, 0xad, 0xca, 0xc0, 0xd7, 0x04, 0xf7, 0x64, 0xe8, 0x95, 0xe7, 0x98, 0x4b, 0x7a, 0x1b, - 0x34, 0x55, 0x05, 0xc3, 0x5f, 0x18, 0xf8, 0x13, 0x99, 0xdd, 0xce, 0x13, 0xa9, 0x96, 0x6f, 0xa5, - 0xc7, 0x67, 0xe0, 0x2d, 0x30, 0x8e, 0xb3, 0xc0, 0x19, 0x38, 0xc3, 0xa3, 0x27, 0x8f, 0x4e, 0x6b, - 0xa1, 0x6b, 0x9e, 0x33, 0x8c, 0x63, 0x61, 0x6e, 0xf1, 0x2f, 0xc0, 0xcf, 0x71, 0x93, 0xc6, 0x32, - 0xc7, 0x2c, 0x70, 0xf5, 0x4f, 0x78, 0xf3, 0x93, 0x2b, 0x9b, 0x12, 0xcd, 0xa5, 0xf0, 0x8f, 0x0e, - 0x3c, 0x38, 0xa0, 0xe2, 0xc7, 0xc0, 0xf6, 0xfa, 0xab, 0x3c, 0xc1, 0xf6, 0x84, 0x4a, 0xfd, 0x45, - 0x9e, 0x60, 0x25, 0xa1, 0x9d, 0xee, 0x8d, 0x27, 0xd8, 0x8e, 0xd0, 0xad, 0xee, 0x88, 0x27, 0xd8, - 0x2d, 0xff, 0x04, 0x7a, 0x3f, 0x15, 0xa8, 0x22, 0xcc, 0x02, 0x4f, 0xbf, 0xfc, 0x4e, 0xf3, 0xf2, - 0xf7, 0x05, 0xaa, 0x52, 0x54, 0x79, 0xaa, 0x54, 0x77, 0xd3, 0xb4, 0x46, 0x9f, 0x29, 0x96, 0x53, - 0xe7, 0x7b, 0x26, 0x46, 0x67, 0xab, 0x90, 0xe9, 0x07, 0x29, 0xf4, 0x25, 0xb8, 0x72, 0x8f, 0x59, - 0xe0, 0x6b, 0xfe, 0x0f, 0xdf, 0x20, 0xc6, 0xe9, 0x68, 0x8f, 0xd9, 0x37, 0xdb, 0x5c, 0x95, 0x42, - 0x5f, 0x7f, 0xfc, 0x0c, 0xfc, 0x3a, 0x44, 0xae, 0x78, 0x85, 0xa5, 0x2e, 0xd0, 0x17, 0x74, 0xe4, - 0x1f, 0x81, 0x77, 0x27, 0xe3, 0xc2, 0x08, 0x7f, 0xf4, 0xe4, 0xa4, 0xa1, 0x1d, 0xed, 0xa3, 0x4c, - 0x98, 0xe4, 0xd7, 0x9d, 0xaf, 0x58, 0x38, 0x06, 0x97, 0x42, 0x3c, 0x84, 0xe3, 0x18, 0xd7, 0x72, - 0x51, 0x8e, 0x93, 0x62, 0xbb, 0xcc, 0x02, 0x36, 0x70, 0x86, 0x8e, 0x38, 0x88, 0xf1, 0xf7, 0xa0, - 0x3b, 0x37, 0xd9, 0xce, 0xc0, 0x19, 0xfa, 0xc2, 0xa2, 0xf0, 0x2f, 0x46, 0x56, 0x33, 0xf2, 0xb7, - 0x2c, 0x60, 0x0a, 0x7c, 0x1f, 0xfa, 0xd4, 0x9a, 0x97, 0x77, 0x52, 0x59, 0x1b, 0xf4, 0x08, 0xdf, - 0x48, 0xc5, 0x3f, 0x87, 0xae, 0xfe, 0x90, 0x7b, 0xac, 0x50, 0xd1, 0xdd, 0x50, 0x5e, 0xd8, 0x6b, - 0xb5, 0xa0, 0x6e, 0x4b, 0xd0, 0x87, 0xe0, 0xc5, 0x72, 0x8e, 0xb1, 0x9d, 0x15, 0x03, 0xc8, 0x64, - 0xd4, 0x99, 0x52, 0xf7, 0xe3, 0x5e, 0x66, 0xd3, 0x3f, 0x73, 0x2b, 0xbc, 0x86, 0x07, 0x07, 0x2f, - 0xd6, 0x2f, 0xb1, 0xc3, 0x97, 0x1a, 0x51, 0x7d, 0x2b, 0x22, 0x8d, 0x59, 0x86, 0x31, 0x2e, 0x72, - 0x5c, 0x6a, 0x1b, 0xf5, 0x45, 0x8d, 0xc3, 0xdf, 0x58, 0xc3, 0xab, 0xdf, 0xa3, 0x41, 0x5a, 0x24, - 0x9b, 0x8d, 0xdc, 0x2e, 0x2d, 0x75, 0x05, 0x49, 0xb7, 0xe5, 0xdc, 0x52, 0x77, 0x96, 0x73, 0xc2, - 0x2a, 0xb5, 0x4b, 0xa3, 0xa3, 0x52, 0x3e, 0x80, 0xa3, 0x0d, 0xca, 0xac, 0x50, 0xb8, 0xc1, 0x6d, - 0x6e, 0x25, 0x68, 0x87, 0xf8, 0x23, 0xe8, 0xe5, 0x72, 0xfd, 0x92, 0xac, 0x60, 0xb4, 0xe8, 0xe6, - 0x72, 0x7d, 0x8e, 0x25, 0xff, 0x00, 0xfc, 0x55, 0x84, 0xf1, 0x52, 0xa7, 0x8c, 0x41, 0xfb, 0x3a, - 0x70, 0x8e, 0x65, 0xf8, 0x3b, 0x83, 0xee, 0x0c, 0xd5, 0x1d, 0xaa, 0xb7, 0x9a, 0xde, 0xf6, 0xe6, - 0x72, 0xfe, 0x63, 0x73, 0xb9, 0xf7, 0x6f, 0x2e, 0xaf, 0xd9, 0x5c, 0x0f, 0xc1, 0x9b, 0xa9, 0xc5, - 0x74, 0xa2, 0xbf, 0xc8, 0x11, 0x06, 0x90, 0xc7, 0x46, 0x8b, 0x3c, 0xba, 0x43, 0xbb, 0xce, 0x2c, - 0x0a, 0x7f, 0x65, 0xd0, 0xbd, 0x90, 0x65, 0x52, 0xe4, 0xaf, 0x39, 0x6c, 0x00, 0x47, 0xa3, 0x34, - 0x8d, 0xa3, 0x85, 0xcc, 0xa3, 0x64, 0x6b, 0xbf, 0xb6, 0x1d, 0xa2, 0x1b, 0xcf, 0x5b, 0xda, 0x99, - 0xef, 0x6e, 0x87, 0x68, 0x60, 0xce, 0xf4, 0x52, 0x32, 0x1b, 0xa6, 0x35, 0x30, 0x66, 0x17, 0xe9, - 0x24, 0x15, 0x38, 0x2a, 0xf2, 0x64, 0x15, 0x27, 0x3b, 0x5d, 0x49, 0x5f, 0xd4, 0x38, 0xfc, 0x9b, - 0x81, 0xfb, 0x7f, 0x2d, 0x9b, 0x63, 0x60, 0x91, 0x6d, 0x24, 0x8b, 0xea, 0xd5, 0xd3, 0x6b, 0xad, - 0x9e, 0x00, 0x7a, 0xa5, 0x92, 0xdb, 0x35, 0x66, 0x41, 0x5f, 0x4f, 0x72, 0x05, 0x75, 0x46, 0xcf, - 0x88, 0xd9, 0x39, 0xbe, 0xa8, 0x60, 0xed, 0x79, 0x68, 0x3c, 0x1f, 0xfe, 0xc9, 0xc0, 0xab, 0x9d, - 0x7b, 0x76, 0xe8, 0xdc, 0xb3, 0xc6, 0xb9, 0x93, 0x71, 0xe5, 0xdc, 0xc9, 0x98, 0xb0, 0xb8, 0xac, - 0x9c, 0x2b, 0x2e, 0x49, 0xb5, 0x67, 0x2a, 0x29, 0xd2, 0x71, 0x69, 0xe4, 0xf5, 0x45, 0x8d, 0xa9, - 0xdd, 0x3f, 0xdc, 0xa2, 0xb2, 0x35, 0xfb, 0xc2, 0x22, 0x32, 0xc7, 0x85, 0x9e, 0x6a, 0x53, 0xa5, - 0x01, 0xfc, 0x63, 0xf0, 0x04, 0x55, 0xa1, 0x4b, 0x3d, 0x10, 0x48, 0x87, 0x85, 0xc9, 0x86, 0x4f, - 0xed, 0x35, 0x62, 0xb9, 0x4e, 0x53, 0x54, 0xd6, 0xd3, 0x06, 0x68, 0xee, 0x64, 0x87, 0x66, 0x1d, - 0x39, 0xc2, 0x80, 0xf0, 0x47, 0xf0, 0x47, 0x31, 0xaa, 0x5c, 0x14, 0xf1, 0xeb, 0x4b, 0x8c, 0x83, - 0xfb, 0xed, 0xec, 0xbb, 0x17, 0xd5, 0x24, 0xd0, 0xb9, 0xf1, 0xaf, 0xf3, 0x2f, 0xff, 0x9e, 0xcb, - 0x54, 0x4e, 0x27, 0xba, 0xb1, 0x8e, 0xb0, 0x28, 0xfc, 0x14, 0x5c, 0x9a, 0x93, 0x16, 0xb3, 0xfb, - 0xa6, 0x19, 0x9b, 0x77, 0xf5, 0x3f, 0x8e, 0xa7, 0xff, 0x04, 0x00, 0x00, 0xff, 0xff, 0x33, 0xaf, - 0xb8, 0xb8, 0x83, 0x08, 0x00, 0x00, + // 957 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xbc, 0x56, 0xdd, 0x6e, 0xe3, 0x44, + 0x14, 0xd6, 0xc4, 0x76, 0x12, 0x9f, 0x76, 0x0b, 0x1a, 0xad, 0x58, 0xb3, 0xdc, 0x04, 0x0b, 0xa4, + 0x80, 0x44, 0x41, 0xbb, 0x42, 0x42, 0xdc, 0xa5, 0x0d, 0x5a, 0x85, 0x76, 0x97, 0x32, 0x69, 0x0b, + 0x37, 0x68, 0x35, 0x49, 0x4e, 0x52, 0x6b, 0x9d, 0xd8, 0x8c, 0xed, 0x26, 0x7e, 0x05, 0xae, 0x78, + 0x02, 0x24, 0x24, 0xae, 0xb8, 0xe4, 0x05, 0x78, 0x08, 0x5e, 0x08, 0x9d, 0x99, 0xf1, 0x4f, 0xd8, + 0x2e, 0xea, 0xd5, 0xde, 0xcd, 0x77, 0xce, 0xf8, 0x9b, 0x99, 0xef, 0x7c, 0xe7, 0xc8, 0x70, 0x14, + 0x6d, 0x72, 0x54, 0x1b, 0x19, 0x1f, 0xa7, 0x2a, 0xc9, 0x13, 0xde, 0xaf, 0x70, 0xf8, 0x4b, 0x07, + 0xba, 0xd3, 0xa4, 0x50, 0x73, 0xe4, 0x47, 0xd0, 0x99, 0x8c, 0x03, 0x36, 0x60, 0x43, 0x47, 0x74, + 0x26, 0x63, 0xce, 0xc1, 0x7d, 0x21, 0xd7, 0x18, 0x74, 0x06, 0x6c, 0xe8, 0x0b, 0xbd, 0xa6, 0xd8, + 0x65, 0x99, 0x62, 0xe0, 0x98, 0x18, 0xad, 0xf9, 0x63, 0xe8, 0x5f, 0x65, 0xc4, 0xb6, 0xc6, 0xc0, + 0xd5, 0xf1, 0x1a, 0x53, 0xee, 0x42, 0x66, 0xd9, 0x36, 0x51, 0x8b, 0xc0, 0x33, 0xb9, 0x0a, 0xf3, + 0x77, 0xc1, 0xb9, 0x12, 0xe7, 0x41, 0x57, 0x87, 0x69, 0xc9, 0x03, 0xe8, 0x8d, 0x71, 0x29, 0x8b, + 0x38, 0x0f, 0x7a, 0x03, 0x36, 0xec, 0x8b, 0x0a, 0x12, 0xcf, 0x25, 0xc6, 0xb8, 0x52, 0x72, 0x19, + 0xf4, 0x0d, 0x4f, 0x85, 0xf9, 0x31, 0xf0, 0xc9, 0x26, 0xc3, 0x79, 0xa1, 0x70, 0xfa, 0x2a, 0x4a, + 0xaf, 0x51, 0x45, 0xcb, 0x32, 0xf0, 0x35, 0xc1, 0x1d, 0x19, 0x3a, 0xe5, 0x39, 0xe6, 0x92, 0xce, + 0x06, 0x4d, 0x55, 0xc1, 0xf0, 0x57, 0x06, 0xfe, 0x58, 0x66, 0x37, 0xb3, 0x44, 0xaa, 0xc5, 0xbd, + 0xf4, 0xf8, 0x0c, 0xbc, 0x39, 0xc6, 0x71, 0x16, 0x38, 0x03, 0x67, 0x78, 0xf0, 0xe4, 0xd1, 0x71, + 0x2d, 0x74, 0xcd, 0x73, 0x8a, 0x71, 0x2c, 0xcc, 0x2e, 0xfe, 0x05, 0xf8, 0x39, 0xae, 0xd3, 0x58, + 0xe6, 0x98, 0x05, 0xae, 0xfe, 0x84, 0x37, 0x9f, 0x5c, 0xda, 0x94, 0x68, 0x36, 0x85, 0x7f, 0x76, + 0xe0, 0xc1, 0x1e, 0x15, 0x3f, 0x04, 0xb6, 0xd3, 0xb7, 0xf2, 0x04, 0xdb, 0x11, 0x2a, 0xf5, 0x8d, + 0x3c, 0xc1, 0x4a, 0x42, 0x5b, 0x5d, 0x1b, 0x4f, 0xb0, 0x2d, 0xa1, 0x1b, 0x5d, 0x11, 0x4f, 0xb0, + 0x1b, 0xfe, 0x09, 0xf4, 0x7e, 0x2e, 0x50, 0x45, 0x98, 0x05, 0x9e, 0x3e, 0xf9, 0x9d, 0xe6, 0xe4, + 0xef, 0x0b, 0x54, 0xa5, 0xa8, 0xf2, 0xf4, 0x52, 0x5d, 0x4d, 0x53, 0x1a, 0xbd, 0xa6, 0x58, 0x4e, + 0x95, 0xef, 0x99, 0x18, 0xad, 0xad, 0x42, 0xa6, 0x1e, 0xa4, 0xd0, 0x97, 0xe0, 0xca, 0x1d, 0x66, + 0x81, 0xaf, 0xf9, 0x3f, 0x7c, 0x83, 0x18, 0xc7, 0xa3, 0x1d, 0x66, 0xdf, 0x6c, 0x72, 0x55, 0x0a, + 0xbd, 0xfd, 0xf1, 0x33, 0xf0, 0xeb, 0x10, 0xb9, 0xe2, 0x15, 0x96, 0xfa, 0x81, 0xbe, 0xa0, 0x25, + 0xff, 0x08, 0xbc, 0x5b, 0x19, 0x17, 0x46, 0xf8, 0x83, 0x27, 0x47, 0x0d, 0xed, 0x68, 0x17, 0x65, + 0xc2, 0x24, 0xbf, 0xee, 0x7c, 0xc5, 0xc2, 0x1f, 0xc1, 0xa5, 0x10, 0x0f, 0xe1, 0x30, 0xc6, 0x95, + 0x9c, 0x97, 0x27, 0x49, 0xb1, 0x59, 0x64, 0x01, 0x1b, 0x38, 0x43, 0x47, 0xec, 0xc5, 0xf8, 0x7b, + 0xd0, 0x9d, 0x99, 0x6c, 0x67, 0xe0, 0x0c, 0x7d, 0x61, 0x11, 0x7f, 0x08, 0x5e, 0x2c, 0x67, 0x18, + 0x5b, 0x8b, 0x1b, 0x10, 0xfe, 0xcd, 0xc8, 0x80, 0xa6, 0x28, 0x2d, 0x63, 0x98, 0x67, 0xbf, 0x0f, + 0x7d, 0x2a, 0xd8, 0xcb, 0x5b, 0xa9, 0xac, 0x39, 0x7a, 0x84, 0xaf, 0xa5, 0xe2, 0x9f, 0x43, 0x57, + 0x5f, 0xef, 0x0e, 0x83, 0x54, 0x74, 0xd7, 0x94, 0x17, 0x76, 0x5b, 0x2d, 0xb3, 0xdb, 0x92, 0xb9, + 0xbe, 0x92, 0xd7, 0xba, 0x12, 0x59, 0x8f, 0xea, 0x55, 0xea, 0x2a, 0xdd, 0xc9, 0x6c, 0xaa, 0x6a, + 0x76, 0x85, 0x57, 0xf0, 0x60, 0xef, 0xc4, 0xfa, 0x24, 0xb6, 0x7f, 0x52, 0x23, 0xb5, 0x6f, 0xa5, + 0xa5, 0xe6, 0xcb, 0x30, 0xc6, 0x79, 0x8e, 0x0b, 0xad, 0x4a, 0x5f, 0xd4, 0x38, 0xfc, 0x9d, 0x35, + 0xbc, 0xfa, 0x3c, 0x6a, 0xaf, 0x79, 0xb2, 0x5e, 0xcb, 0xcd, 0xc2, 0x52, 0x57, 0x90, 0x74, 0x5b, + 0xcc, 0x2c, 0x75, 0x67, 0x31, 0x23, 0xac, 0x52, 0xab, 0x73, 0x47, 0xa5, 0x7c, 0x00, 0x07, 0x6b, + 0x94, 0x59, 0xa1, 0x70, 0x8d, 0x9b, 0xdc, 0x4a, 0xd0, 0x0e, 0xf1, 0x47, 0xd0, 0xcb, 0xe5, 0xea, + 0x25, 0x19, 0xc4, 0x68, 0xd1, 0xcd, 0xe5, 0xea, 0x0c, 0x4b, 0xfe, 0x01, 0xf8, 0xcb, 0x08, 0xe3, + 0x85, 0x4e, 0x19, 0xdb, 0xf6, 0x75, 0xe0, 0x0c, 0xcb, 0xf0, 0x0f, 0x06, 0xdd, 0x29, 0xaa, 0x5b, + 0x54, 0xf7, 0xea, 0xe9, 0xf6, 0x3c, 0x73, 0xfe, 0x67, 0x9e, 0xb9, 0x77, 0xcf, 0x33, 0xaf, 0x99, + 0x67, 0x0f, 0xc1, 0x9b, 0xaa, 0xf9, 0x64, 0xac, 0x6f, 0xe4, 0x08, 0x03, 0xc8, 0x79, 0xa3, 0x79, + 0x1e, 0xdd, 0xa2, 0x1d, 0x72, 0x16, 0x85, 0xbf, 0x31, 0xe8, 0x9e, 0xcb, 0x32, 0x29, 0xf2, 0xd7, + 0x1c, 0x36, 0x80, 0x83, 0x51, 0x9a, 0xc6, 0xd1, 0x5c, 0xe6, 0x51, 0xb2, 0xb1, 0xb7, 0x6d, 0x87, + 0x68, 0xc7, 0xf3, 0x96, 0x76, 0xe6, 0xde, 0xed, 0x10, 0xb5, 0xd1, 0xa9, 0x1e, 0x55, 0x66, 0xee, + 0xb4, 0xda, 0xc8, 0x4c, 0x28, 0x9d, 0xa4, 0x07, 0x8e, 0x8a, 0x3c, 0x59, 0xc6, 0xc9, 0x56, 0xbf, + 0xa4, 0x2f, 0x6a, 0x1c, 0xfe, 0xc3, 0xc0, 0x7d, 0x5b, 0x23, 0xe8, 0x10, 0x58, 0x64, 0x0b, 0xc9, + 0xa2, 0x7a, 0x20, 0xf5, 0x5a, 0x03, 0x29, 0x80, 0x5e, 0xa9, 0xe4, 0x66, 0x85, 0x59, 0xd0, 0xd7, + 0xfd, 0x5d, 0x41, 0x9d, 0xd1, 0x3d, 0x62, 0x26, 0x91, 0x2f, 0x2a, 0x58, 0x7b, 0x1e, 0x1a, 0xcf, + 0x87, 0x7f, 0x31, 0xf0, 0x6a, 0xe7, 0x9e, 0xee, 0x3b, 0xf7, 0xb4, 0x71, 0xee, 0xf8, 0xa4, 0x72, + 0xee, 0xf8, 0x84, 0xb0, 0xb8, 0xa8, 0x9c, 0x2b, 0x2e, 0x48, 0xb5, 0x67, 0x2a, 0x29, 0xd2, 0x93, + 0xd2, 0xc8, 0xeb, 0x8b, 0x1a, 0x53, 0xb9, 0x7f, 0xb8, 0x41, 0x65, 0xdf, 0xec, 0x0b, 0x8b, 0xc8, + 0x1c, 0xe7, 0xba, 0xab, 0xcd, 0x2b, 0x0d, 0xe0, 0x1f, 0x83, 0x27, 0xe8, 0x15, 0xfa, 0xa9, 0x7b, + 0x02, 0xe9, 0xb0, 0x30, 0xd9, 0xf0, 0xa9, 0xdd, 0x46, 0x2c, 0x57, 0x69, 0x8a, 0xca, 0x7a, 0xda, + 0x00, 0xcd, 0x9d, 0x6c, 0xd1, 0x8c, 0x23, 0x47, 0x18, 0x10, 0xfe, 0x04, 0xfe, 0x28, 0x46, 0x95, + 0x8b, 0x22, 0x7e, 0x7d, 0x88, 0x71, 0x70, 0xbf, 0x9d, 0x7e, 0xf7, 0xa2, 0xea, 0x04, 0x5a, 0x37, + 0xfe, 0x75, 0xfe, 0xe3, 0xdf, 0x33, 0x99, 0xca, 0xc9, 0x58, 0x17, 0xd6, 0x11, 0x16, 0x85, 0x9f, + 0x82, 0x4b, 0x7d, 0xd2, 0x62, 0x76, 0xdf, 0xd4, 0x63, 0xb3, 0xae, 0xfe, 0x0f, 0x79, 0xfa, 0x6f, + 0x00, 0x00, 0x00, 0xff, 0xff, 0x0d, 0xf5, 0x54, 0xe9, 0x99, 0x08, 0x00, 0x00, } diff --git a/bolt/internal/internal.proto b/bolt/internal/internal.proto index 18a2a7746..ec979b6c9 100644 --- a/bolt/internal/internal.proto +++ b/bolt/internal/internal.proto @@ -35,7 +35,8 @@ message DashboardCell { message Axis { repeated int64 legacyBounds = 1; // legacyBounds are an ordered 2-tuple consisting of lower and upper axis extents, respectively - repeated string bounds = 2; // bounds are an arbitrary list of client-defined bounds. + repeated string bounds = 2; // bounds are an arbitrary list of client-defined bounds. + string label = 3; // label is a description of this axis } message Template { diff --git a/bolt/internal/internal_test.go b/bolt/internal/internal_test.go index 82abcbad2..a2952148a 100644 --- a/bolt/internal/internal_test.go +++ b/bolt/internal/internal_test.go @@ -129,6 +129,7 @@ func Test_MarshalDashboard(t *testing.T) { Axes: map[string]chronograf.Axis{ "y": chronograf.Axis{ Bounds: []string{"0", "3", "1-7", "foo"}, + Label: "foo", }, }, Type: "line", diff --git a/chronograf.go b/chronograf.go index b8dcd4537..2b69c578c 100644 --- a/chronograf.go +++ b/chronograf.go @@ -568,8 +568,9 @@ type Dashboard struct { // Axis represents the visible extents of a visualization type Axis struct { - Bounds []string `json:"bounds"` // bounds are an arbitrary list of client-defined strings that specify the viewport for a cell - LegacyBounds [2]int64 `json:"-"` // legacy bounds are for testing a migration from an earlier version of axis + Bounds []string `json:"bounds"` // bounds are an arbitrary list of client-defined strings that specify the viewport for a cell + LegacyBounds [2]int64 `json:"-"` // legacy bounds are for testing a migration from an earlier version of axis + Label string `json:"label,omitempty"` // label is a description of this Axis } // DashboardCell holds visual and query information for a cell diff --git a/server/dashboards_test.go b/server/dashboards_test.go index f7cbaa747..ff740ba00 100644 --- a/server/dashboards_test.go +++ b/server/dashboards_test.go @@ -226,6 +226,7 @@ func Test_newDashboardResponse(t *testing.T) { }, "y": chronograf.Axis{ Bounds: []string{"2", "95"}, + Label: "foo", }, }, }, @@ -272,6 +273,7 @@ func Test_newDashboardResponse(t *testing.T) { }, "y": chronograf.Axis{ Bounds: []string{"2", "95"}, + Label: "foo", }, "y2": chronograf.Axis{ Bounds: []string{}, From 0bb49d9c4cd989584dbb363c0ed2220a108b4472 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Wed, 2 Aug 2017 08:45:09 -0700 Subject: [PATCH 060/107] Prettier --- ui/src/dashboards/components/GraphTypeSelector.js | 4 +++- ui/src/dashboards/components/Ranger.js | 6 +++--- ui/src/dashboards/containers/DashboardPage.js | 3 ++- ui/src/shared/components/GrooveKnob.js | 4 +++- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/ui/src/dashboards/components/GraphTypeSelector.js b/ui/src/dashboards/components/GraphTypeSelector.js index 15561447a..92ff5313e 100644 --- a/ui/src/dashboards/components/GraphTypeSelector.js +++ b/ui/src/dashboards/components/GraphTypeSelector.js @@ -15,7 +15,9 @@ const GraphTypeSelector = ({selectedGraphType, onSelectGraphType}) => >
    onSelectGraphType(graphType.type)}> {graphType.graphic} -

    {graphType.menuOption}

    +

    + {graphType.menuOption} +

    )} diff --git a/ui/src/dashboards/components/Ranger.js b/ui/src/dashboards/components/Ranger.js index 70cd9aeb9..53d055471 100644 --- a/ui/src/dashboards/components/Ranger.js +++ b/ui/src/dashboards/components/Ranger.js @@ -22,17 +22,17 @@ const Ranger = ({onSetRange, axes}) => { />
    */}
    - + { console.log(leftValue, rightValue) // onSetRange() }} - rightValue={Number(min)} + rightValue={min} />
    - + -
    {leftLabel}
    +
    + {leftLabel} +
    Date: Wed, 2 Aug 2017 09:38:15 -0700 Subject: [PATCH 061/107] Revert "WIP Pass values from GrooveKnob to parent" This reverts commit d07573339867b3a1ac8abe217743f51773da12ff. --- ui/src/dashboards/components/Ranger.js | 27 ++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/ui/src/dashboards/components/Ranger.js b/ui/src/dashboards/components/Ranger.js index 53d055471..19c3fbd0c 100644 --- a/ui/src/dashboards/components/Ranger.js +++ b/ui/src/dashboards/components/Ranger.js @@ -1,8 +1,6 @@ import React, {PropTypes} from 'react' import _ from 'lodash' -import GrooveKnob from 'shared/components/GrooveKnob' - // TODO: add logic for for Prefix, Suffix, Scale, and Multiplier const Ranger = ({onSetRange, axes}) => { const min = _.get(axes, ['y', 'bounds', '0'], '') @@ -22,14 +20,23 @@ const Ranger = ({onSetRange, axes}) => { />
    */}
    - - { - console.log(leftValue, rightValue) - // onSetRange() - }} - rightValue={min} - /> + +
    +
    auto
    +
    +
    +
    + +
    From 04fe3672561d473be8ec291ea5dccbbbf92f406b Mon Sep 17 00:00:00 2001 From: Jared Scheib Date: Wed, 2 Aug 2017 09:39:55 -0700 Subject: [PATCH 062/107] Revert "Wire up GrooveKnob to be fully functional" This reverts commit 2e15eed1451a743c5a47ddbc91bd9c3cc999cb10. --- ui/src/style/components/one-or-any.scss | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ui/src/style/components/one-or-any.scss b/ui/src/style/components/one-or-any.scss index 6580d5cbd..7b23d3bc7 100644 --- a/ui/src/style/components/one-or-any.scss +++ b/ui/src/style/components/one-or-any.scss @@ -24,7 +24,7 @@ background-color 0.25s ease, color 0.25s ease; } -.one-or-any--switch { +.one-or-any--toggle { display: flex; align-items: center; border: 2px solid $g5-pepper; @@ -88,11 +88,11 @@ border-radius: 0 4px 4px 0; font-family: $code-font; } -// When using right value -.one-or-any.use-right-value { +// Toggled state +.one-or-any.toggled { .one-or-any--groove-knob:after {transform: translate(0%,-50%);} // Fade out left, fade in right - .one-or-any--switch:before {opacity: 0;} + .one-or-any--toggle:before {opacity: 0;} // Make auto look disabled .one-or-any--auto { background-color: $g3-castle; From 488fb2b5fae2b812010c718edb9ed38f11a094fb Mon Sep 17 00:00:00 2001 From: Jared Scheib Date: Wed, 2 Aug 2017 09:40:22 -0700 Subject: [PATCH 063/107] Revert "Rename classes and element" This reverts commit 58c23ad14fd265feaf8323b41fa8ebb52aa138c9. --- ui/src/dashboards/components/Ranger.js | 4 +- ui/src/style/components/one-or-any.scss | 60 +++++++++++++------------ 2 files changed, 33 insertions(+), 31 deletions(-) diff --git a/ui/src/dashboards/components/Ranger.js b/ui/src/dashboards/components/Ranger.js index 19c3fbd0c..b5615c6b8 100644 --- a/ui/src/dashboards/components/Ranger.js +++ b/ui/src/dashboards/components/Ranger.js @@ -23,8 +23,8 @@ const Ranger = ({onSetRange, axes}) => {
    auto
    -
    -
    +
    +
    div { + margin: 0 10px; + z-index: 3; + width: 28px; + height: 8px; + border-radius: 4px; + background-color: $g6-smoke; + position: relative; + + // Dot + &:after { + content: ''; + position: absolute; + top: 50%; + left: 50%; + width: 14px; + height: 14px; + border-radius: 50%; + background-color: $c-pool; + transition: + background-color 0.25s ease, + transform 0.25s ease; + transform: translate(-100%,-50%); + } + } + // Background Gradients &:before, &:after { content: ''; @@ -55,32 +82,7 @@ &:hover { cursor: pointer; - .one-or-any--groove-knob:after {background-color: $c-laser;} - } -} -.one-or-any--groove-knob { - margin: 0 10px; - z-index: 3; - width: 28px; - height: 8px; - border-radius: 4px; - background-color: $g6-smoke; - position: relative; - - // Knob - &:after { - content: ''; - position: absolute; - top: 50%; - left: 50%; - width: 14px; - height: 14px; - border-radius: 50%; - background-color: $c-pool; - transition: - background-color 0.25s ease, - transform 0.25s ease; - transform: translate(-100%,-50%); + > div:after {background-color: $c-laser;} } } // Customize form input @@ -90,9 +92,9 @@ } // Toggled state .one-or-any.toggled { - .one-or-any--groove-knob:after {transform: translate(0%,-50%);} + .one-or-any--switch > div:after {transform: translate(0%,-50%);} // Fade out left, fade in right - .one-or-any--toggle:before {opacity: 0;} + .one-or-any--switch:before {opacity: 0;} // Make auto look disabled .one-or-any--auto { background-color: $g3-castle; From d1b31c384e9c10a5e58375b72a2876b7e7dcc567 Mon Sep 17 00:00:00 2001 From: Jared Scheib Date: Wed, 2 Aug 2017 09:40:31 -0700 Subject: [PATCH 064/107] Revert "WIP "one or any" component" This reverts commit aa5f710b5a70d0ece6b348bb4d0580d6a7ed2519. --- ui/src/dashboards/components/Ranger.js | 27 +++--- ui/src/style/chronograf.scss | 1 - ui/src/style/components/one-or-any.scss | 104 ------------------------ 3 files changed, 10 insertions(+), 122 deletions(-) delete mode 100644 ui/src/style/components/one-or-any.scss diff --git a/ui/src/dashboards/components/Ranger.js b/ui/src/dashboards/components/Ranger.js index b5615c6b8..09ce13400 100644 --- a/ui/src/dashboards/components/Ranger.js +++ b/ui/src/dashboards/components/Ranger.js @@ -20,23 +20,16 @@ const Ranger = ({onSetRange, axes}) => { />
    */}
    - -
    -
    auto
    -
    -
    -
    - -
    + +
    diff --git a/ui/src/style/chronograf.scss b/ui/src/style/chronograf.scss index c6547693c..55094c8cf 100644 --- a/ui/src/style/chronograf.scss +++ b/ui/src/style/chronograf.scss @@ -36,7 +36,6 @@ @import 'components/graph'; @import 'components/input-tag-list'; @import 'components/newsfeed'; -@import 'components/one-or-any'; @import 'components/page-header-dropdown'; @import 'components/page-header-editable'; @import 'components/page-spinner'; diff --git a/ui/src/style/components/one-or-any.scss b/ui/src/style/components/one-or-any.scss deleted file mode 100644 index 82a941e05..000000000 --- a/ui/src/style/components/one-or-any.scss +++ /dev/null @@ -1,104 +0,0 @@ -/* - One or Any Input - ------------------------------------------------------------------------------ - User can toggle between a single value or any value -*/ -.one-or-any { - display: flex; - align-items: stretch; - flex-wrap: nowrap; -} -.one-or-any--auto { - border: 2px solid $g5-pepper; - background-color: $g2-kevlar; - color: $c-pool; - font-family: $code-font; - padding: 0 11px; - border-radius: 4px 0 0 4px; - line-height: 24px; - font-size: 13px; - font-weight: 500; - cursor: default; - @include no-user-select(); - transition: - background-color 0.25s ease, - color 0.25s ease; -} -.one-or-any--switch { - display: flex; - align-items: center; - border: 2px solid $g5-pepper; - border-left: 0; - border-right: 0; - position: relative; - - // Tray - > div { - margin: 0 10px; - z-index: 3; - width: 28px; - height: 8px; - border-radius: 4px; - background-color: $g6-smoke; - position: relative; - - // Dot - &:after { - content: ''; - position: absolute; - top: 50%; - left: 50%; - width: 14px; - height: 14px; - border-radius: 50%; - background-color: $c-pool; - transition: - background-color 0.25s ease, - transform 0.25s ease; - transform: translate(-100%,-50%); - } - } - - // Background Gradients - &:before, &:after { - content: ''; - display: block; - position: absolute; - width: 100%; - height: 100%; - transition: opacity 0.25s ease; - } - // Left - &:before { - z-index: 2; - @include gradient-h($g2-kevlar,$g3-castle); - opacity: 1; - } - // Right - &:after { - @include gradient-h($g3-castle,$g2-kevlar); - z-index: 1; - } - - &:hover { - cursor: pointer; - > div:after {background-color: $c-laser;} - } -} -// Customize form input -.one-or-any > input.form-control { - border-radius: 0 4px 4px 0; - font-family: $code-font; -} -// Toggled state -.one-or-any.toggled { - .one-or-any--switch > div:after {transform: translate(0%,-50%);} - // Fade out left, fade in right - .one-or-any--switch:before {opacity: 0;} - // Make auto look disabled - .one-or-any--auto { - background-color: $g3-castle; - color: $g8-storm; - font-style: italic; - } -} From 80ad15eefb7f5c6b7de679991aecfaac87ca931b Mon Sep 17 00:00:00 2001 From: Jared Scheib Date: Wed, 2 Aug 2017 09:41:35 -0700 Subject: [PATCH 065/107] Revert GrooveKnob addition --- ui/src/shared/components/GrooveKnob.js | 85 -------------------------- 1 file changed, 85 deletions(-) delete mode 100644 ui/src/shared/components/GrooveKnob.js diff --git a/ui/src/shared/components/GrooveKnob.js b/ui/src/shared/components/GrooveKnob.js deleted file mode 100644 index 890c7d9a6..000000000 --- a/ui/src/shared/components/GrooveKnob.js +++ /dev/null @@ -1,85 +0,0 @@ -import React, {Component, PropTypes} from 'react' -import classnames from 'classnames' - -class GrooveKnob extends Component { - constructor(props) { - super(props) - - const {leftValue, rightValue} = props - - this.state = { - leftValue, - rightValue, - } - - this.handleChangeRightValue = ::this.handleChangeRightValue - this.handleToggleLeftValue = ::this.handleToggleLeftValue - this.handleSetValues = ::this.handleSetValues - } - - handleChangeRightValue(newValue) { - this.setState({rightValue: newValue}, this.handleSetValues) - } - - handleToggleLeftValue() { - const {leftValue} = this.state - - this.setState({leftValue: !leftValue}, this.handleSetValues) - } - - handleSetValues() { - const {onSetValues} = this.props - const {leftValue, rightValue} = this.state - - onSetValues({leftValue, rightValue}) - } - - render() { - const {leftLabel, rightLabel} = this.props - const {leftValue: useLeftValue, rightValue} = this.state - - return ( -
    -
    - {leftLabel} -
    -
    -
    -
    - this.handleChangeRightValue(e.target.value)} - placeholder={rightLabel} - disabled={useLeftValue} - /> -
    - ) - } -} - -GrooveKnob.defaultProps = { - leftLabel: 'auto', - leftValue: true, - rightLabel: 'Custom Value', - rightValue: null, -} -const {bool, func, number, string} = PropTypes - -GrooveKnob.propTypes = { - leftLabel: string, - leftValue: bool, - rightLabel: string, - rightValue: number, - onSetValues: func.isRequired, -} - -export default GrooveKnob From b4a4fd6c9cc4717a0781edc646803ba367eb133a Mon Sep 17 00:00:00 2001 From: Tim Raymond Date: Wed, 2 Aug 2017 13:07:27 -0400 Subject: [PATCH 066/107] Remove omitempty from Axis Label If the key is always present in the response, it's easier for the front end to perform tests on contents of its key. --- chronograf.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/chronograf.go b/chronograf.go index 336dc2d0c..319065bef 100644 --- a/chronograf.go +++ b/chronograf.go @@ -569,9 +569,9 @@ type Dashboard struct { // Axis represents the visible extents of a visualization type Axis struct { - Bounds []string `json:"bounds"` // bounds are an arbitrary list of client-defined strings that specify the viewport for a cell - LegacyBounds [2]int64 `json:"-"` // legacy bounds are for testing a migration from an earlier version of axis - Label string `json:"label,omitempty"` // label is a description of this Axis + Bounds []string `json:"bounds"` // bounds are an arbitrary list of client-defined strings that specify the viewport for a cell + LegacyBounds [2]int64 `json:"-"` // legacy bounds are for testing a migration from an earlier version of axis + Label string `json:"label"` // label is a description of this Axis } // DashboardCell holds visual and query information for a cell From 441d3036b9a46e1e4cb5b638c16d83c2dfaecd6b Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Wed, 2 Aug 2017 12:47:57 -0700 Subject: [PATCH 067/107] Fix props error --- ui/src/data_explorer/components/VisView.js | 2 +- ui/src/data_explorer/components/Visualization.js | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/ui/src/data_explorer/components/VisView.js b/ui/src/data_explorer/components/VisView.js index 036d554c1..9e717c6f1 100644 --- a/ui/src/data_explorer/components/VisView.js +++ b/ui/src/data_explorer/components/VisView.js @@ -52,7 +52,7 @@ const {arrayOf, func, number, shape, string} = PropTypes VisView.propTypes = { view: string.isRequired, - axes: shape().isRequired, + axes: shape(), queries: arrayOf(shape()).isRequired, cellType: string, templates: arrayOf(shape()), diff --git a/ui/src/data_explorer/components/Visualization.js b/ui/src/data_explorer/components/Visualization.js index 26c212b39..7466ff69a 100644 --- a/ui/src/data_explorer/components/Visualization.js +++ b/ui/src/data_explorer/components/Visualization.js @@ -53,6 +53,7 @@ const Visualization = React.createClass({ getDefaultProps() { return { cellName: '', + cellType: '', } }, From 42009c2081dd60e1d841fd1f2c328aef7dc8c116 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Wed, 2 Aug 2017 14:08:07 -0700 Subject: [PATCH 068/107] Add ability for users to specify labels --- .../components/CellEditorOverlay.js | 29 ++++++++++++++++--- .../dashboards/components/DisplayOptions.js | 4 ++- ui/src/dashboards/components/Ranger.js | 17 +++++++---- ui/src/shared/components/Dygraph.js | 4 +++ ui/src/shared/components/LineGraph.js | 6 ++-- 5 files changed, 45 insertions(+), 15 deletions(-) diff --git a/ui/src/dashboards/components/CellEditorOverlay.js b/ui/src/dashboards/components/CellEditorOverlay.js index 595642a60..525995320 100644 --- a/ui/src/dashboards/components/CellEditorOverlay.js +++ b/ui/src/dashboards/components/CellEditorOverlay.js @@ -23,17 +23,15 @@ class CellEditorOverlay extends Component { super(props) this.queryStateReducer = ::this.queryStateReducer - this.handleAddQuery = ::this.handleAddQuery this.handleDeleteQuery = ::this.handleDeleteQuery - this.handleSaveCell = ::this.handleSaveCell - this.handleSelectGraphType = ::this.handleSelectGraphType this.handleSelectDisplayOptions = ::this.handleSelectDisplayOptions this.handleSetActiveQueryIndex = ::this.handleSetActiveQueryIndex this.handleEditRawText = ::this.handleEditRawText this.handleSetRange = ::this.handleSetRange + this.handleSetLabel = ::this.handleSetLabel const {cell: {name, type, queries, axes}} = props @@ -47,10 +45,24 @@ class CellEditorOverlay extends Component { queriesWorkingDraft, activeQueryIndex: 0, isDisplayOptionsTabOpen: false, - axes, + axes: this.setDefaultLabels(axes, queries), } } + setDefaultLabels(axes, queries) { + if (!queries.length) { + return axes + } + + if (axes.y.label) { + return axes + } + + const q = queries[0].queryConfig + const label = q.rawText ? '' : `${q.measurement}.${q.fields[0].field}` + return {...axes, y: {...axes.y, label}} + } + componentWillReceiveProps(nextProps) { const {status, queryID} = this.props.queryStatus const nextStatus = nextProps.queryStatus @@ -86,6 +98,14 @@ class CellEditorOverlay extends Component { e.preventDefault() } + handleSetLabel(e) { + const {label} = e.target.form + const {axes} = this.state + + this.setState({axes: {...axes, y: {label: label.value}}}) + e.preventDefault() + } + handleAddQuery(options) { const newQuery = Object.assign({}, defaultQueryConfig(uuid.v4()), options) const nextQueries = this.state.queriesWorkingDraft.concat(newQuery) @@ -223,6 +243,7 @@ class CellEditorOverlay extends Component { selectedGraphType={cellWorkingType} onSelectGraphType={this.handleSelectGraphType} onSetRange={this.handleSetRange} + onSetLabel={this.handleSetLabel} axes={axes} /> : @@ -14,7 +15,7 @@ const DisplayOptions = ({ selectedGraphType={selectedGraphType} onSelectGraphType={onSelectGraphType} /> - +
    const {func, shape, string} = PropTypes @@ -23,6 +24,7 @@ DisplayOptions.propTypes = { selectedGraphType: string.isRequired, onSelectGraphType: func.isRequired, onSetRange: func.isRequired, + onSetLabel: func.isRequired, axes: shape({}).isRequired, } diff --git a/ui/src/dashboards/components/Ranger.js b/ui/src/dashboards/components/Ranger.js index 09ce13400..93353f826 100644 --- a/ui/src/dashboards/components/Ranger.js +++ b/ui/src/dashboards/components/Ranger.js @@ -2,23 +2,26 @@ import React, {PropTypes} from 'react' import _ from 'lodash' // TODO: add logic for for Prefix, Suffix, Scale, and Multiplier -const Ranger = ({onSetRange, axes}) => { +const Ranger = ({onSetRange, onSetLabel, axes}) => { const min = _.get(axes, ['y', 'bounds', '0'], '') const max = _.get(axes, ['y', 'bounds', '1'], '') + const label = _.get(axes, ['y', 'label'], '') return (
    Y Axis Controls
    - {/*
    - +
    + -
    */} +
    { ) } -const {array, func, shape} = PropTypes +const {arrayOf, func, shape, string} = PropTypes Ranger.propTypes = { onSetRange: func.isRequired, + onSetLabel: func.isRequired, axes: shape({ y: shape({ - bounds: array, + bounds: arrayOf(string), + label: string, }), }).isRequired, } diff --git a/ui/src/shared/components/Dygraph.js b/ui/src/shared/components/Dygraph.js index 8b80312c4..9d597ef23 100644 --- a/ui/src/shared/components/Dygraph.js +++ b/ui/src/shared/components/Dygraph.js @@ -73,6 +73,7 @@ export default class Dygraph extends Component { const yAxis = _.get(axes, ['y', 'bounds'], [null, null]) const y2Axis = _.get(axes, ['y2', 'bounds'], undefined) + const ylabel = _.get(axes, ['y', 'label'], '') const defaultOptions = { plugins: [ @@ -93,6 +94,7 @@ export default class Dygraph extends Component { hideOverlayOnMouseOut: false, colors: finalLineColors, series: dygraphSeries, + ylabel, axes: { y: { valueRange: getRange(timeSeries, yAxis, ruleValues), @@ -254,11 +256,13 @@ export default class Dygraph extends Component { const y = _.get(axes, ['y', 'bounds'], [null, null]) const y2 = _.get(axes, ['y2', 'bounds'], undefined) + const ylabel = _.get(axes, ['y', 'label'], '') const timeSeries = this.getTimeSeries() const updateOptions = { labels, file: timeSeries, + ylabel, axes: { y: { valueRange: getRange(timeSeries, y, ruleValues), diff --git a/ui/src/shared/components/LineGraph.js b/ui/src/shared/components/LineGraph.js index e92d4dfe8..dcf93fab1 100644 --- a/ui/src/shared/components/LineGraph.js +++ b/ui/src/shared/components/LineGraph.js @@ -2,7 +2,6 @@ import React, {PropTypes} from 'react' import Dygraph from 'shared/components/Dygraph' import classnames from 'classnames' import shallowCompare from 'react-addons-shallow-compare' -import _ from 'lodash' import timeSeriesToDygraph from 'utils/timeSeriesToDygraph' import lastValues from 'shared/parsing/lastValues' @@ -18,9 +17,11 @@ export default React.createClass({ axes: shape({ y: shape({ bounds: array, + label: string, }), y2: shape({ bounds: array, + label: string, }), }), title: string, @@ -93,7 +94,6 @@ export default React.createClass({ overrideLineColors, title, underlayCallback, - queries, showSingleStat, displayOptions, ruleValues, @@ -124,8 +124,6 @@ export default React.createClass({ axisLabelWidth: 60, drawAxesAtZero: true, underlayCallback, - ylabel: _.get(queries, ['0', 'label'], ''), - y2label: _.get(queries, ['1', 'label'], ''), ...displayOptions, } From 6b3306ed3c2928899990731ece8170906ab4e751 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Wed, 2 Aug 2017 14:09:08 -0700 Subject: [PATCH 069/107] Stop adding labels to queries --- ui/src/dashboards/components/CellEditorOverlay.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/ui/src/dashboards/components/CellEditorOverlay.js b/ui/src/dashboards/components/CellEditorOverlay.js index 525995320..325837e8d 100644 --- a/ui/src/dashboards/components/CellEditorOverlay.js +++ b/ui/src/dashboards/components/CellEditorOverlay.js @@ -132,12 +132,10 @@ class CellEditorOverlay extends Component { const queries = queriesWorkingDraft.map(q => { const timeRange = q.range || {upper: null, lower: ':dashboardTime:'} const query = q.rawText || buildInfluxQLQuery(timeRange, q) - const label = q.rawText ? '' : `${q.measurement}.${q.fields[0].field}` return { queryConfig: q, query, - label, } }) From b7c420d57d612e9f628d1cbba5368ce6389d7909 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Wed, 2 Aug 2017 14:15:00 -0700 Subject: [PATCH 070/107] Remove TODO --- ui/src/dashboards/actions/index.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ui/src/dashboards/actions/index.js b/ui/src/dashboards/actions/index.js index d128589af..205d017cb 100644 --- a/ui/src/dashboards/actions/index.js +++ b/ui/src/dashboards/actions/index.js @@ -184,8 +184,7 @@ export const putDashboardByID = dashboardID => async (dispatch, getState) => { export const updateDashboardCell = (dashboard, cell) => async dispatch => { try { const {data} = await updateDashboardCellAJAX(cell) - // TODO: remove yRanges when server persists the ranges - dispatch(syncDashboardCell(dashboard, {...data, yRanges: cell.yRanges})) + dispatch(syncDashboardCell(dashboard, data)) } catch (error) { console.error(error) dispatch(errorThrown(error)) From 98a0124b0c4ef5650e86c621926acadff2407711 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Wed, 2 Aug 2017 14:16:14 -0700 Subject: [PATCH 071/107] Update style logic to use axes labels --- ui/src/shared/components/LineGraph.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ui/src/shared/components/LineGraph.js b/ui/src/shared/components/LineGraph.js index dcf93fab1..cbeeac511 100644 --- a/ui/src/shared/components/LineGraph.js +++ b/ui/src/shared/components/LineGraph.js @@ -153,10 +153,12 @@ export default React.createClass({ roundedValue = Math.round(+lastValue * precision) / precision } + const isLabelSet = !!axes.y.label || !!axes.y2.label + return (
    From f7743e3d2bc6287e0a319ec749f7f07ea2ed0320 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Wed, 2 Aug 2017 14:26:05 -0700 Subject: [PATCH 072/107] Cleanup classname --- ui/src/shared/components/LineGraph.js | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/ui/src/shared/components/LineGraph.js b/ui/src/shared/components/LineGraph.js index cbeeac511..65257c3e6 100644 --- a/ui/src/shared/components/LineGraph.js +++ b/ui/src/shared/components/LineGraph.js @@ -153,15 +153,11 @@ export default React.createClass({ roundedValue = Math.round(+lastValue * precision) / precision } - const isLabelSet = !!axes.y.label || !!axes.y2.label + const isLabelSet = + !!axes.y.label || !!axes.y2.label ? 'graph--hasYLabel' : '' return ( -
    +
    {isRefreshing ? this.renderSpinner() : null} Date: Wed, 2 Aug 2017 15:07:54 -0700 Subject: [PATCH 073/107] Move building of label to dygraph component --- .../components/CellEditorOverlay.js | 4 +++- ui/src/shared/components/Dygraph.js | 23 +++++++++++++++---- ui/src/shared/components/LineGraph.js | 10 +++++--- ui/src/shared/presenters/index.js | 6 +++++ 4 files changed, 34 insertions(+), 9 deletions(-) diff --git a/ui/src/dashboards/components/CellEditorOverlay.js b/ui/src/dashboards/components/CellEditorOverlay.js index 325837e8d..584ce2118 100644 --- a/ui/src/dashboards/components/CellEditorOverlay.js +++ b/ui/src/dashboards/components/CellEditorOverlay.js @@ -17,6 +17,7 @@ import {getQueryConfig} from 'shared/apis' import {removeUnselectedTemplateValues} from 'src/dashboards/constants' import {OVERLAY_TECHNOLOGY} from 'shared/constants/classNames' import {MINIMUM_HEIGHTS, INITIAL_HEIGHTS} from 'src/data_explorer/constants' +import {buildYLabel} from 'shared/presenters' class CellEditorOverlay extends Component { constructor(props) { @@ -59,7 +60,8 @@ class CellEditorOverlay extends Component { } const q = queries[0].queryConfig - const label = q.rawText ? '' : `${q.measurement}.${q.fields[0].field}` + const label = buildYLabel(q) + return {...axes, y: {...axes.y, label}} } diff --git a/ui/src/shared/components/Dygraph.js b/ui/src/shared/components/Dygraph.js index 9d597ef23..7690884e6 100644 --- a/ui/src/shared/components/Dygraph.js +++ b/ui/src/shared/components/Dygraph.js @@ -9,6 +9,7 @@ import getRange from 'shared/parsing/getRangeForDygraph' import {LINE_COLORS, multiColumnBarPlotter} from 'src/shared/graphs/helpers' import DygraphLegend from 'src/shared/components/DygraphLegend' +import {buildYLabel} from 'shared/presenters' export default class Dygraph extends Component { constructor(props) { @@ -35,6 +36,7 @@ export default class Dygraph extends Component { this.handleHideLegend = ::this.handleHideLegend this.handleToggleFilter = ::this.handleToggleFilter this.visibility = ::this.visibility + this.getLabel = ::this.getLabel } static defaultProps = { @@ -50,6 +52,18 @@ export default class Dygraph extends Component { return timeSeries.length ? timeSeries : [[0]] } + getLabel(axis) { + const {axes, queries} = this.props + const label = _.get(axes, [axis, 'label'], '') + const queryConfig = _.get(queries, ['0', 'queryConfig'], false) + + if (label || !queryConfig) { + return label + } + + return buildYLabel(queryConfig) + } + componentDidMount() { const timeSeries = this.getTimeSeries() // dygraphSeries is a legend label and its corresponding y-axis e.g. {legendLabel1: 'y', legendLabel2: 'y2'}; @@ -73,7 +87,6 @@ export default class Dygraph extends Component { const yAxis = _.get(axes, ['y', 'bounds'], [null, null]) const y2Axis = _.get(axes, ['y2', 'bounds'], undefined) - const ylabel = _.get(axes, ['y', 'label'], '') const defaultOptions = { plugins: [ @@ -94,7 +107,7 @@ export default class Dygraph extends Component { hideOverlayOnMouseOut: false, colors: finalLineColors, series: dygraphSeries, - ylabel, + ylabel: this.getLabel('y'), axes: { y: { valueRange: getRange(timeSeries, yAxis, ruleValues), @@ -256,13 +269,12 @@ export default class Dygraph extends Component { const y = _.get(axes, ['y', 'bounds'], [null, null]) const y2 = _.get(axes, ['y2', 'bounds'], undefined) - const ylabel = _.get(axes, ['y', 'label'], '') const timeSeries = this.getTimeSeries() const updateOptions = { labels, file: timeSeries, - ylabel, + ylabel: this.getLabel('y'), axes: { y: { valueRange: getRange(timeSeries, y, ruleValues), @@ -369,7 +381,7 @@ export default class Dygraph extends Component { } } -const {array, bool, func, shape, string} = PropTypes +const {array, arrayOf, bool, func, shape, string} = PropTypes Dygraph.propTypes = { axes: shape({ @@ -380,6 +392,7 @@ Dygraph.propTypes = { bounds: array, }), }), + queries: arrayOf(shape), timeSeries: array.isRequired, labels: array.isRequired, options: shape({}), diff --git a/ui/src/shared/components/LineGraph.js b/ui/src/shared/components/LineGraph.js index 65257c3e6..af81ee88b 100644 --- a/ui/src/shared/components/LineGraph.js +++ b/ui/src/shared/components/LineGraph.js @@ -93,6 +93,7 @@ export default React.createClass({ isBarGraph, overrideLineColors, title, + queries, underlayCallback, showSingleStat, displayOptions, @@ -156,15 +157,18 @@ export default React.createClass({ const isLabelSet = !!axes.y.label || !!axes.y2.label ? 'graph--hasYLabel' : '' + const lineColors = showSingleStat + ? singleStatLineColors + : overrideLineColors + return (
    {isRefreshing ? this.renderSpinner() : null} { + return queryConfig.rawText + ? '' + : `${queryConfig.measurement}.${queryConfig.fields[0].field}` +} From bf7fca8994e8424b01ac612669e88e90a8529562 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Wed, 2 Aug 2017 15:42:25 -0700 Subject: [PATCH 074/107] Determine if label present via the DOM --- ui/src/shared/components/Dygraph.js | 3 +++ ui/src/shared/components/LineGraph.js | 26 ++++++++++++++++++++++---- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/ui/src/shared/components/Dygraph.js b/ui/src/shared/components/Dygraph.js index 7690884e6..0ddf8bf74 100644 --- a/ui/src/shared/components/Dygraph.js +++ b/ui/src/shared/components/Dygraph.js @@ -43,6 +43,7 @@ export default class Dygraph extends Component { containerStyle: {}, isGraphFilled: true, overrideLineColors: null, + dygraphRef: () => {}, } getTimeSeries() { @@ -372,6 +373,7 @@ export default class Dygraph extends Component {
    { this.graphRef = r + this.props.dygraphRef(r) }} style={this.props.containerStyle} className="dygraph-child-container" @@ -411,4 +413,5 @@ Dygraph.propTypes = { }), synchronizer: func, setResolution: func, + dygraphRef: func, } diff --git a/ui/src/shared/components/LineGraph.js b/ui/src/shared/components/LineGraph.js index af81ee88b..1ada791d1 100644 --- a/ui/src/shared/components/LineGraph.js +++ b/ui/src/shared/components/LineGraph.js @@ -154,19 +154,17 @@ export default React.createClass({ roundedValue = Math.round(+lastValue * precision) / precision } - const isLabelSet = - !!axes.y.label || !!axes.y2.label ? 'graph--hasYLabel' : '' - const lineColors = showSingleStat ? singleStatLineColors : overrideLineColors return ( -
    +
    {isRefreshing ? this.renderSpinner() : null} From f2014bbe0ba5e2fa6f78470248aff213634217b6 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Wed, 2 Aug 2017 15:50:29 -0700 Subject: [PATCH 075/107] Fix clobbering of other axes fields --- ui/src/dashboards/components/CellEditorOverlay.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/dashboards/components/CellEditorOverlay.js b/ui/src/dashboards/components/CellEditorOverlay.js index 584ce2118..c1a1cfe36 100644 --- a/ui/src/dashboards/components/CellEditorOverlay.js +++ b/ui/src/dashboards/components/CellEditorOverlay.js @@ -104,7 +104,7 @@ class CellEditorOverlay extends Component { const {label} = e.target.form const {axes} = this.state - this.setState({axes: {...axes, y: {label: label.value}}}) + this.setState({axes: {...axes, y: {...axes.y, label: label.value}}}) e.preventDefault() } From c2e38c1ea2f691c1e75407a74eea68d3af44423e Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Wed, 2 Aug 2017 16:08:58 -0700 Subject: [PATCH 076/107] Default to having a Ylabel class --- ui/src/shared/components/LineGraph.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/shared/components/LineGraph.js b/ui/src/shared/components/LineGraph.js index 1ada791d1..3bcd69115 100644 --- a/ui/src/shared/components/LineGraph.js +++ b/ui/src/shared/components/LineGraph.js @@ -199,7 +199,7 @@ export default React.createClass({ const dygraph = this.dygraphRef if (!dygraph) { - return '' + return 'graph--hasYLabel' } const label = dygraph.querySelector('.dygraph-ylabel') From 2bf409d95b27750bdbd231e0cf1aafbdbba95c32 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Wed, 2 Aug 2017 16:11:25 -0700 Subject: [PATCH 077/107] Prevent clobbering of label when bounds are set --- ui/src/dashboards/components/CellEditorOverlay.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ui/src/dashboards/components/CellEditorOverlay.js b/ui/src/dashboards/components/CellEditorOverlay.js index c1a1cfe36..48859abe0 100644 --- a/ui/src/dashboards/components/CellEditorOverlay.js +++ b/ui/src/dashboards/components/CellEditorOverlay.js @@ -96,7 +96,9 @@ class CellEditorOverlay extends Component { const {min, max} = e.target.form const {axes} = this.state - this.setState({axes: {...axes, y: {bounds: [min.value, max.value]}}}) + this.setState({ + axes: {...axes, y: {...axes.y, bounds: [min.value, max.value]}}, + }) e.preventDefault() } From 2abb70a1b8aa50a5b217c505a53b718f98781d41 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Wed, 2 Aug 2017 16:13:48 -0700 Subject: [PATCH 078/107] Add placeholder to label --- ui/src/dashboards/components/Ranger.js | 1 + 1 file changed, 1 insertion(+) diff --git a/ui/src/dashboards/components/Ranger.js b/ui/src/dashboards/components/Ranger.js index 93353f826..bad00dfba 100644 --- a/ui/src/dashboards/components/Ranger.js +++ b/ui/src/dashboards/components/Ranger.js @@ -20,6 +20,7 @@ const Ranger = ({onSetRange, onSetLabel, axes}) => { id="label" value={label} onChange={onSetLabel} + placeholder="auto" />
    From 923d2bf5e291f76cc83c5e6f3b7e3d3de98e8949 Mon Sep 17 00:00:00 2001 From: Jared Scheib Date: Wed, 2 Aug 2017 17:05:24 -0700 Subject: [PATCH 079/107] Fix and add second item for changelog update --- CHANGELOG.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 24e76292f..1b03ba238 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ 1. [#1799](https://github.com/influxdata/chronograf/pull/1799): Prevent console error spam from Dygraph.synchronize when a dashboard has only one graph ### Features +1. [#1714](https://github.com/influxdata/chronograf/pull/1714): Add ability to edit a dashboard graph's y-axis bounds +1. [#1714](https://github.com/influxdata/chronograf/pull/1714): Add ability to edit a dashboard graph's y-axis label + ### UI Improvements 1. [#1796](https://github.com/influxdata/chronograf/pull/1796): Add spinner to indicate data is being written 1. [#1800](https://github.com/influxdata/chronograf/pull/1800): Embiggen text area for line protocol manual entry in Data Explorer's Write Data overlay @@ -22,9 +25,6 @@ ### Features 1. [#1717](https://github.com/influxdata/chronograf/pull/1717): View server generated TICKscripts -1. [#1681](https://github.com/influxdata/chronograf/pull/1681): Add the ability to select Custom Time Ranges in the Hostpages, Data Explorer, and Dashboards. -1. [#1714](https://github.com/influxdata/chronograf/pull/1714): Add ability for users to set y-axis bounds - 1. [#1681](https://github.com/influxdata/chronograf/pull/1681): Add the ability to select Custom Time Ranges in the Hostpages, Data Explorer, and Dashboards 1. [#1752](https://github.com/influxdata/chronograf/pull/1752): Clarify BoltPath server flag help text by making example the default path 1. [#1738](https://github.com/influxdata/chronograf/pull/1738): Add shared secret JWT authorization to InfluxDB From b49142e344c9b131bccd52f7781abcd6a096766b Mon Sep 17 00:00:00 2001 From: Jared Scheib Date: Wed, 2 Aug 2017 18:08:52 -0700 Subject: [PATCH 080/107] Add line spacing, reorder import --- ui/src/dashboards/components/CellEditorOverlay.js | 3 ++- ui/src/dashboards/components/GraphTypeSelector.js | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/ui/src/dashboards/components/CellEditorOverlay.js b/ui/src/dashboards/components/CellEditorOverlay.js index 48859abe0..2c1ae67a0 100644 --- a/ui/src/dashboards/components/CellEditorOverlay.js +++ b/ui/src/dashboards/components/CellEditorOverlay.js @@ -8,16 +8,17 @@ import QueryMaker from 'src/data_explorer/components/QueryMaker' import Visualization from 'src/data_explorer/components/Visualization' import OverlayControls from 'src/dashboards/components/OverlayControls' import DisplayOptions from 'src/dashboards/components/DisplayOptions' + import * as queryModifiers from 'src/utils/queryTransitions' import defaultQueryConfig from 'src/utils/defaultQueryConfig' import buildInfluxQLQuery from 'utils/influxql' import {getQueryConfig} from 'shared/apis' +import {buildYLabel} from 'shared/presenters' import {removeUnselectedTemplateValues} from 'src/dashboards/constants' import {OVERLAY_TECHNOLOGY} from 'shared/constants/classNames' import {MINIMUM_HEIGHTS, INITIAL_HEIGHTS} from 'src/data_explorer/constants' -import {buildYLabel} from 'shared/presenters' class CellEditorOverlay extends Component { constructor(props) { diff --git a/ui/src/dashboards/components/GraphTypeSelector.js b/ui/src/dashboards/components/GraphTypeSelector.js index 92ff5313e..ee23f444a 100644 --- a/ui/src/dashboards/components/GraphTypeSelector.js +++ b/ui/src/dashboards/components/GraphTypeSelector.js @@ -1,5 +1,6 @@ import React, {PropTypes} from 'react' import classnames from 'classnames' + import {graphTypes} from 'src/dashboards/graphics/graph' const GraphTypeSelector = ({selectedGraphType, onSelectGraphType}) => From 744b1f876d2a2079fc959fdafdcad982f5026788 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Thu, 3 Aug 2017 09:40:24 -0700 Subject: [PATCH 081/107] Test all things --- ui/spec/shared/parsing/getRangeForDygraphSpec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/spec/shared/parsing/getRangeForDygraphSpec.js b/ui/spec/shared/parsing/getRangeForDygraphSpec.js index d7840444f..c44d1a4a9 100644 --- a/ui/spec/shared/parsing/getRangeForDygraphSpec.js +++ b/ui/spec/shared/parsing/getRangeForDygraphSpec.js @@ -6,7 +6,7 @@ const mid = 10 const min = 5 const kapacitor = {value: null, rangeValue: null, operator: null} -describe.only('getRangeForDygraphSpec', () => { +describe('getRangeForDygraphSpec', () => { it('gets the range for one timeSeries', () => { const timeSeries = [[date, min], [date, mid], [date, max]] const actual = getRange(timeSeries) From 16cac2a5fe39ee2390a86a38c58484f9ba987285 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Thu, 3 Aug 2017 12:16:26 -0700 Subject: [PATCH 082/107] Add user instructions for how to use display options --- ui/src/dashboards/components/Ranger.js | 3 +++ ui/src/shared/parsing/getRangeForDygraph.js | 7 +++++-- ui/src/style/components/ceo-display-options.scss | 14 ++++++++++++-- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/ui/src/dashboards/components/Ranger.js b/ui/src/dashboards/components/Ranger.js index bad00dfba..4ab4c0ea3 100644 --- a/ui/src/dashboards/components/Ranger.js +++ b/ui/src/dashboards/components/Ranger.js @@ -47,6 +47,9 @@ const Ranger = ({onSetRange, onSetLabel, axes}) => { placeholder="auto" />
    +

    + Values left blank will be set automatically +

    {/*
    Date: Thu, 3 Aug 2017 13:43:46 -0700 Subject: [PATCH 083/107] Rename ranger to AxesOptions --- ui/src/dashboards/components/{Ranger.js => AxesOptions.js} | 6 +++--- ui/src/dashboards/components/DisplayOptions.js | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) rename ui/src/dashboards/components/{Ranger.js => AxesOptions.js} (96%) diff --git a/ui/src/dashboards/components/Ranger.js b/ui/src/dashboards/components/AxesOptions.js similarity index 96% rename from ui/src/dashboards/components/Ranger.js rename to ui/src/dashboards/components/AxesOptions.js index 4ab4c0ea3..d8ae22d59 100644 --- a/ui/src/dashboards/components/Ranger.js +++ b/ui/src/dashboards/components/AxesOptions.js @@ -2,7 +2,7 @@ import React, {PropTypes} from 'react' import _ from 'lodash' // TODO: add logic for for Prefix, Suffix, Scale, and Multiplier -const Ranger = ({onSetRange, onSetLabel, axes}) => { +const AxesOptions = ({onSetRange, onSetLabel, axes}) => { const min = _.get(axes, ['y', 'bounds', '0'], '') const max = _.get(axes, ['y', 'bounds', '1'], '') const label = _.get(axes, ['y', 'label'], '') @@ -89,7 +89,7 @@ const Ranger = ({onSetRange, onSetLabel, axes}) => { const {arrayOf, func, shape, string} = PropTypes -Ranger.propTypes = { +AxesOptions.propTypes = { onSetRange: func.isRequired, onSetLabel: func.isRequired, axes: shape({ @@ -100,4 +100,4 @@ Ranger.propTypes = { }).isRequired, } -export default Ranger +export default AxesOptions diff --git a/ui/src/dashboards/components/DisplayOptions.js b/ui/src/dashboards/components/DisplayOptions.js index 8d9e57e82..693ad4da7 100644 --- a/ui/src/dashboards/components/DisplayOptions.js +++ b/ui/src/dashboards/components/DisplayOptions.js @@ -1,7 +1,7 @@ import React, {PropTypes} from 'react' import GraphTypeSelector from 'src/dashboards/components/GraphTypeSelector' -import Ranger from 'src/dashboards/components/Ranger' +import AxesOptions from 'src/dashboards/components/AxesOptions' const DisplayOptions = ({ selectedGraphType, @@ -15,7 +15,7 @@ const DisplayOptions = ({ selectedGraphType={selectedGraphType} onSelectGraphType={onSelectGraphType} /> - +
    const {func, shape, string} = PropTypes From 3ce6a2c6efc92bbe39ea3d7ab91ba1364f99c543 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Thu, 3 Aug 2017 14:28:58 -0700 Subject: [PATCH 084/107] Add logic to handle ranges that are submitted as equal --- ui/spec/shared/parsing/getRangeForDygraphSpec.js | 8 ++++++++ ui/src/shared/parsing/getRangeForDygraph.js | 15 ++++++++------- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/ui/spec/shared/parsing/getRangeForDygraphSpec.js b/ui/spec/shared/parsing/getRangeForDygraphSpec.js index c44d1a4a9..1c3c7d80e 100644 --- a/ui/spec/shared/parsing/getRangeForDygraphSpec.js +++ b/ui/spec/shared/parsing/getRangeForDygraphSpec.js @@ -23,6 +23,14 @@ describe('getRangeForDygraphSpec', () => { expect(actual).to.deep.equal([0, 4]) }) + it('does not use the user submitted range if they are equal', () => { + const timeSeries = [[date, min], [date, max], [date, mid]] + const providedRange = ['0', '0'] + const actual = getRange(timeSeries, providedRange) + + expect(actual).to.deep.equal([min, max]) + }) + it('gets the range for multiple timeSeries', () => { const timeSeries = [[date, null, min], [date, max, mid], [date, null, mid]] const actual = getRange(timeSeries) diff --git a/ui/src/shared/parsing/getRangeForDygraph.js b/ui/src/shared/parsing/getRangeForDygraph.js index bb874b358..7a19fa439 100644 --- a/ui/src/shared/parsing/getRangeForDygraph.js +++ b/ui/src/shared/parsing/getRangeForDygraph.js @@ -1,6 +1,6 @@ const PADDING_FACTOR = 0.1 -const considerZero = (userNumber, number) => { +const considerEmpty = (userNumber, number) => { if (userNumber === '') { return null } @@ -20,10 +20,6 @@ const getRange = ( const {value, rangeValue, operator} = ruleValues const [userMin, userMax] = userSelectedRange - if (userMin && userMax) { - return [considerZero(userMin), considerZero(userMax)] - } - const subtractPadding = val => +val - Math.abs(val * PADDING_FACTOR) const addPadding = val => +val + Math.abs(val * PADDING_FACTOR) @@ -65,8 +61,9 @@ const getRange = ( [null, null] ) - // If time series is such that min and max are equal use Dygraph defaults const [min, max] = range + + // If time series is such that min and max are equal use Dygraph defaults if (min === max) { return [null, null] } @@ -75,7 +72,11 @@ const getRange = ( return [min, max] } - return [considerZero(userMin, min), considerZero(userMax, max)] + if (userMin && userMax) { + return [considerEmpty(userMin), considerEmpty(userMax)] + } + + return [considerEmpty(userMin, min), considerEmpty(userMax, max)] } export default getRange From 07de1760d173c3c918cad983920fa55033aea712 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Thu, 3 Aug 2017 14:50:44 -0700 Subject: [PATCH 085/107] Pass queryConfig down to Dygraphs so it can make label decisions --- ui/src/data_explorer/components/Visualization.js | 5 +++-- ui/src/shared/components/Dygraph.js | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/ui/src/data_explorer/components/Visualization.js b/ui/src/data_explorer/components/Visualization.js index 7466ff69a..b33b99a7b 100644 --- a/ui/src/data_explorer/components/Visualization.js +++ b/ui/src/data_explorer/components/Visualization.js @@ -102,10 +102,11 @@ const Visualization = React.createClass({ const statements = queryConfigs.map(query => { const text = query.rawText || buildInfluxQLQuery(query.range || timeRange, query) - return {text, id: query.id} + return {text, id: query.id, queryConfig: query} }) + const queries = statements.filter(s => s.text !== null).map(s => { - return {host: [proxy], text: s.text, id: s.id} + return {host: [proxy], text: s.text, id: s.id, queryConfig: s.queryConfig} }) return ( diff --git a/ui/src/shared/components/Dygraph.js b/ui/src/shared/components/Dygraph.js index 0ddf8bf74..697a2ae88 100644 --- a/ui/src/shared/components/Dygraph.js +++ b/ui/src/shared/components/Dygraph.js @@ -271,11 +271,12 @@ export default class Dygraph extends Component { const y = _.get(axes, ['y', 'bounds'], [null, null]) const y2 = _.get(axes, ['y2', 'bounds'], undefined) const timeSeries = this.getTimeSeries() + const ylabel = this.getLabel('y') const updateOptions = { labels, file: timeSeries, - ylabel: this.getLabel('y'), + ylabel, axes: { y: { valueRange: getRange(timeSeries, y, ruleValues), From 8285dcdc645f483d1548e884eeca6e40f942325f Mon Sep 17 00:00:00 2001 From: Jared Scheib Date: Thu, 3 Aug 2017 14:50:41 -0700 Subject: [PATCH 086/107] Use UUID for AlertsTable element keys to prevent overlap --- ui/src/alerts/components/AlertsTable.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/ui/src/alerts/components/AlertsTable.js b/ui/src/alerts/components/AlertsTable.js index f5dfce893..fba3e2109 100644 --- a/ui/src/alerts/components/AlertsTable.js +++ b/ui/src/alerts/components/AlertsTable.js @@ -1,7 +1,9 @@ import React, {Component, PropTypes} from 'react' + import _ from 'lodash' import classnames from 'classnames' import {Link} from 'react-router' +import uuid from 'node-uuid' import FancyScrollbar from 'shared/components/FancyScrollbar' @@ -130,10 +132,7 @@ class AlertsTable extends Component { > {alerts.map(({name, level, time, host, value}) => { return ( -
    +
    Date: Thu, 3 Aug 2017 15:03:47 -0700 Subject: [PATCH 087/107] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 97ea199c4..f0650e4ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ### Bug Fixes 1. [#1798](https://github.com/influxdata/chronograf/pull/1798): Fix domain not updating in visualizations when changing time range manually 1. [#1799](https://github.com/influxdata/chronograf/pull/1799): Prevent console error spam from Dygraph.synchronize when a dashboard has only one graph +1. [#1813](https://github.com/influxdata/chronograf/pull/1813): Guarantee UUID for each Alert Table key to prevent dropping items when keys overlap ### Features ### UI Improvements From 795833ebff5fcdde7294879473c4ead31e38ff6c Mon Sep 17 00:00:00 2001 From: Jared Scheib Date: Thu, 3 Aug 2017 15:14:22 -0700 Subject: [PATCH 088/107] Clarify and specify var and func names --- .../components/CellEditorOverlay.js | 23 ++++++++++--------- .../dashboards/components/OverlayControls.js | 8 +++---- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/ui/src/dashboards/components/CellEditorOverlay.js b/ui/src/dashboards/components/CellEditorOverlay.js index 2c1ae67a0..19a5c00c3 100644 --- a/ui/src/dashboards/components/CellEditorOverlay.js +++ b/ui/src/dashboards/components/CellEditorOverlay.js @@ -29,10 +29,11 @@ class CellEditorOverlay extends Component { this.handleDeleteQuery = ::this.handleDeleteQuery this.handleSaveCell = ::this.handleSaveCell this.handleSelectGraphType = ::this.handleSelectGraphType - this.handleSelectDisplayOptions = ::this.handleSelectDisplayOptions + this.handleMakeDisplayOptionsTabActive = ::this + .handleMakeDisplayOptionsTabActive this.handleSetActiveQueryIndex = ::this.handleSetActiveQueryIndex this.handleEditRawText = ::this.handleEditRawText - this.handleSetRange = ::this.handleSetRange + this.handleSetYAxisBounds = ::this.handleSetYAxisBounds this.handleSetLabel = ::this.handleSetLabel const {cell: {name, type, queries, axes}} = props @@ -46,7 +47,7 @@ class CellEditorOverlay extends Component { cellWorkingType: type, queriesWorkingDraft, activeQueryIndex: 0, - isDisplayOptionsTabOpen: false, + isDisplayOptionsTabActive: false, axes: this.setDefaultLabels(axes, queries), } } @@ -93,7 +94,7 @@ class CellEditorOverlay extends Component { } } - handleSetRange(e) { + handleSetYAxisBounds(e) { const {min, max} = e.target.form const {axes} = this.state @@ -157,9 +158,9 @@ class CellEditorOverlay extends Component { this.setState({cellWorkingType: graphType}) } - handleSelectDisplayOptions(isDisplayOptionsTabOpen) { + handleMakeDisplayOptionsTabActive(isDisplayOptionsTabActive) { return () => { - this.setState({isDisplayOptionsTabOpen}) + this.setState({isDisplayOptionsTabActive}) } } @@ -197,7 +198,7 @@ class CellEditorOverlay extends Component { activeQueryIndex, cellWorkingName, cellWorkingType, - isDisplayOptionsTabOpen, + isDisplayOptionsTabActive, queriesWorkingDraft, axes, } = this.state @@ -235,17 +236,17 @@ class CellEditorOverlay extends Component { />
    - {isDisplayOptionsTabOpen + {isDisplayOptionsTabActive ? diff --git a/ui/src/dashboards/components/OverlayControls.js b/ui/src/dashboards/components/OverlayControls.js index 7eb510c1f..15bc0d53d 100644 --- a/ui/src/dashboards/components/OverlayControls.js +++ b/ui/src/dashboards/components/OverlayControls.js @@ -6,7 +6,7 @@ import ConfirmButtons from 'shared/components/ConfirmButtons' const OverlayControls = ({ onCancel, onSave, - isDisplayOptionsTabOpen, + isDisplayOptionsTabActive, onSelectDisplayOptions, isSavable, }) => @@ -16,7 +16,7 @@ const OverlayControls = ({
  • @@ -25,7 +25,7 @@ const OverlayControls = ({
  • @@ -46,7 +46,7 @@ const {func, bool} = PropTypes OverlayControls.propTypes = { onCancel: func.isRequired, onSave: func.isRequired, - isDisplayOptionsTabOpen: bool.isRequired, + isDisplayOptionsTabActive: bool.isRequired, onSelectDisplayOptions: func.isRequired, isSavable: bool, } From 7da35312bdf904c39cef9c31faac7da3038ddd35 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Thu, 3 Aug 2017 17:22:35 -0700 Subject: [PATCH 089/107] Update function names for fun and profit --- ui/src/dashboards/components/CellEditorOverlay.js | 7 +++---- ui/src/dashboards/components/OverlayControls.js | 8 ++++---- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/ui/src/dashboards/components/CellEditorOverlay.js b/ui/src/dashboards/components/CellEditorOverlay.js index 19a5c00c3..57ce0d349 100644 --- a/ui/src/dashboards/components/CellEditorOverlay.js +++ b/ui/src/dashboards/components/CellEditorOverlay.js @@ -29,8 +29,7 @@ class CellEditorOverlay extends Component { this.handleDeleteQuery = ::this.handleDeleteQuery this.handleSaveCell = ::this.handleSaveCell this.handleSelectGraphType = ::this.handleSelectGraphType - this.handleMakeDisplayOptionsTabActive = ::this - .handleMakeDisplayOptionsTabActive + this.handleClickDisplayOptionsTab = ::this.handleClickDisplayOptionsTab this.handleSetActiveQueryIndex = ::this.handleSetActiveQueryIndex this.handleEditRawText = ::this.handleEditRawText this.handleSetYAxisBounds = ::this.handleSetYAxisBounds @@ -158,7 +157,7 @@ class CellEditorOverlay extends Component { this.setState({cellWorkingType: graphType}) } - handleMakeDisplayOptionsTabActive(isDisplayOptionsTabActive) { + handleClickDisplayOptionsTab(isDisplayOptionsTabActive) { return () => { this.setState({isDisplayOptionsTabActive}) } @@ -237,7 +236,7 @@ class CellEditorOverlay extends Component {
    @@ -18,7 +18,7 @@ const OverlayControls = ({ className={classnames({ active: !isDisplayOptionsTabActive, })} - onClick={onSelectDisplayOptions(false)} + onClick={onClickDisplayOptions(false)} > Queries
  • @@ -27,7 +27,7 @@ const OverlayControls = ({ className={classnames({ active: isDisplayOptionsTabActive, })} - onClick={onSelectDisplayOptions(true)} + onClick={onClickDisplayOptions(true)} > Display Options @@ -47,7 +47,7 @@ OverlayControls.propTypes = { onCancel: func.isRequired, onSave: func.isRequired, isDisplayOptionsTabActive: bool.isRequired, - onSelectDisplayOptions: func.isRequired, + onClickDisplayOptions: func.isRequired, isSavable: bool, } From 9cf77684590acbb4d3ab1c3a39a96141c608e2aa Mon Sep 17 00:00:00 2001 From: Hunter Trujillo Date: Mon, 7 Aug 2017 08:31:11 -0700 Subject: [PATCH 090/107] Remove inner modulo from hasherino. --- ui/src/shared/components/Dygraph.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/shared/components/Dygraph.js b/ui/src/shared/components/Dygraph.js index ccd73ef39..d47d8226a 100644 --- a/ui/src/shared/components/Dygraph.js +++ b/ui/src/shared/components/Dygraph.js @@ -14,7 +14,7 @@ const hasherino = (str, len) => str .split('') .map(char => char.charCodeAt(0)) - .reduce((hash, code) => (hash + code) % len, 0) + .reduce((hash, code) => hash + code, 0) % len export default class Dygraph extends Component { constructor(props) { From 78f345ad644c5b0350ab427cd70e816ff503a917 Mon Sep 17 00:00:00 2001 From: Hunter Trujillo Date: Mon, 7 Aug 2017 09:06:55 -0700 Subject: [PATCH 091/107] Add graphics comments. Fix redundant math. --- ui/src/shared/graphs/helpers.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/ui/src/shared/graphs/helpers.js b/ui/src/shared/graphs/helpers.js index 2470fafc5..33786c8b5 100644 --- a/ui/src/shared/graphs/helpers.js +++ b/ui/src/shared/graphs/helpers.js @@ -51,7 +51,9 @@ export const multiColumnBarPlotter = e => { } } - const barWidth = Math.max(Math.floor(2.0 / 3.0 * minSep), 1) + // calculate bar width using some graphics math while + // ensuring a bar is never smaller than one px, so it is always rendered + const barWidth = Math.max(Math.floor(2.0 / 3.0 * minSep), 1.0) const fillColors = [] const strokeColors = g.getColors() @@ -75,8 +77,8 @@ export const multiColumnBarPlotter = e => { ctx.fillStyle = fillColors[j] const xLeft = sets.length === 1 - ? centerX - barWidth / 1 - : centerX - barWidth / 1 * (1 - j / sets.length) + ? centerX - barWidth + : centerX - barWidth * (1 - j / sets.length) ctx.fillRect( xLeft, @@ -85,6 +87,7 @@ export const multiColumnBarPlotter = e => { yBottom - p.canvasy ) + // hover highlighting if (selPointX === centerX) { ctx.strokeRect( xLeft, From 46162b634348bbeaf826a8f0b4c64a03d5861869 Mon Sep 17 00:00:00 2001 From: Hunter Trujillo Date: Mon, 7 Aug 2017 09:12:14 -0700 Subject: [PATCH 092/107] update changelog update --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eca666284..3265649d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,8 @@ ### UI Improvements 1. [#1796](https://github.com/influxdata/chronograf/pull/1796): Add spinner to indicate data is being written 1. [#1800](https://github.com/influxdata/chronograf/pull/1796): Embiggen text area for line protocol manual entry in Data Explorer's Write Data overlay -1. [#1805](https://github.com/influxdata/chronograf/pull/1805): Bar graphs no longer overlap with each other, and bonus, series names are hashed so that graph colors should stay the same for the same series across charts +1. [#1805](https://github.com/influxdata/chronograf/pull/1805): Fix bar graphs overlapping +1. [#1805](https://github.com/influxdata/chronograf/pull/1805): Add series names hashing so that graph colors should stay the same for the same series across charts ## v1.3.5.0 [2017-07-27] ### Bug Fixes From 39ca84d6913b7e0431cae7fb160c43a757778a0c Mon Sep 17 00:00:00 2001 From: Nathan L Smith Date: Mon, 7 Aug 2017 19:28:23 +0000 Subject: [PATCH 093/107] Change some remaining npm mentions to yarn While `npm` is still installed as part of the build process, most of the scripts and documentation should be using yarn. Change `npm` to `yarn` where appropriate. Signed-off-by: Nathan L Smith --- CONTRIBUTING.md | 8 ++++---- Makefile | 8 ++++---- ui/README.md | 2 +- ui/karma.conf.js | 2 +- ui/package.json | 6 +++--- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 27f84f45c..f5090bb7c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -21,7 +21,7 @@ We really like to receive feature requests, as it helps us prioritize our work. Contributing to the source code ------------------------------- -Chronograf is built using Go for its API backend and serving the front-end assets. The front-end visualization is built with React and uses NPM for package management. The assumption is that all your Go development are done in `$GOPATH/src`. `GOPATH` can be any directory under which Chronograf and all its dependencies will be cloned. For full details on the project structure, follow along below. +Chronograf is built using Go for its API backend and serving the front-end assets. The front-end visualization is built with React and uses Yarn for package management. The assumption is that all your Go development are done in `$GOPATH/src`. `GOPATH` can be any directory under which Chronograf and all its dependencies will be cloned. For full details on the project structure, follow along below. Submitting a pull request ------------------------- @@ -43,9 +43,9 @@ Signing the CLA If you are going to be contributing back to Chronograf please take a second to sign our CLA, which can be found [on our website](https://influxdata.com/community/cla/). -Installing NPM +Installing Yarn -------------- -You'll need to install NPM to manage the JavaScript modules that the front-end uses. This varies depending on what platform you're developing on, but you should be able to find an installer on [the NPM downloads page](https://nodejs.org/en/download/). +You'll need to install Yarn to manage the JavaScript modules that the front-end uses. This varies depending on what platform you're developing on, but you should be able to find an installer on [the Yarn installation page](https://yarnpkg.com/en/docs/install). Installing Go ------------- @@ -105,7 +105,7 @@ Retaining the directory structure `$GOPATH/src/github.com/influxdata` is necessa Build and Test -------------- -Make sure you have `go` and `npm` installed and the project structure as shown above. We provide a `Makefile` to get up and running quickly, so all you'll need to do is run the following: +Make sure you have `go` and `yarn` installed and the project structure as shown above. We provide a `Makefile` to get up and running quickly, so all you'll need to do is run the following: ```bash cd $GOPATH/src/github.com/influxdata/chronograf diff --git a/Makefile b/Makefile index 25c9df79b..a4de270cc 100644 --- a/Makefile +++ b/Makefile @@ -60,11 +60,11 @@ canned/bin_gen.go: canned/*.json go generate -x ./canned .jssrc: $(UISOURCES) - cd ui && npm run build + cd ui && yarn run build @touch .jssrc .dev-jssrc: $(UISOURCES) - cd ui && npm run build:dev + cd ui && yarn run build:dev @touch .dev-jssrc dep: .jsdep .godep @@ -98,7 +98,7 @@ gotestrace: go test -race `go list ./... | grep -v /vendor/` jstest: - cd ui && npm test + cd ui && yarn test run: ${BINARY} ./chronograf @@ -108,7 +108,7 @@ run-dev: chronogiraffe clean: if [ -f ${BINARY} ] ; then rm ${BINARY} ; fi - cd ui && npm run clean + cd ui && yarn run clean cd ui && rm -rf node_modules rm -f dist/dist_gen.go canned/bin_gen.go server/swagger_gen.go @rm -f .godep .jsdep .jssrc .dev-jssrc .bindata diff --git a/ui/README.md b/ui/README.md index 997412b20..24f34aec5 100644 --- a/ui/README.md +++ b/ui/README.md @@ -33,4 +33,4 @@ yarn upgrade packageName ``` ## Testing -Tests can be run via command line with `npm test`, from within the `/ui` directory. For more detailed reporting, use `npm test -- --reporters=verbose`. +Tests can be run via command line with `yarn test`, from within the `/ui` directory. For more detailed reporting, use `yarn test -- --reporters=verbose`. diff --git a/ui/karma.conf.js b/ui/karma.conf.js index a8a4f693f..dad28ee4e 100644 --- a/ui/karma.conf.js +++ b/ui/karma.conf.js @@ -16,7 +16,7 @@ module.exports = function(config) { 'spec/index.js': ['webpack', 'sourcemap'], }, // For more detailed reporting on tests, you can add 'verbose' and/or 'progress'. - // This can also be done via the command line with `npm test -- --reporters=verbose`. + // This can also be done via the command line with `yarn test -- --reporters=verbose`. reporters: ['dots'], webpack: { devtool: 'inline-source-map', diff --git a/ui/package.json b/ui/package.json index b98dc2cec..9b5467b36 100644 --- a/ui/package.json +++ b/ui/package.json @@ -9,13 +9,13 @@ "url": "github:influxdata/chronograf" }, "scripts": { - "build": "npm run clean && env NODE_ENV=production node_modules/webpack/bin/webpack.js -p --config ./webpack/prodConfig.js", + "build": "yarn run clean && env NODE_ENV=production node_modules/webpack/bin/webpack.js -p --config ./webpack/prodConfig.js", "build:dev": "node_modules/webpack/bin/webpack.js --config ./webpack/devConfig.js", "start": "node_modules/webpack/bin/webpack.js -w --config ./webpack/devConfig.js", "lint": "node_modules/eslint/bin/eslint.js src/", "test": "karma start", - "test:lint": "npm run lint; npm run test", - "test:dev": "nodemon --exec npm run test:lint", + "test:lint": "yarn run lint; yarn run test", + "test:dev": "nodemon --exec yarn run test:lint", "clean": "rm -rf build", "storybook": "node ./storybook", "prettier": "prettier --single-quote --trailing-comma es5 --bracket-spacing false --semi false --write \"{src,spec}/**/*.js\"; eslint src --fix" From 03d3277feb38985b6d705183705b8ae3b6fc3c59 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Mon, 7 Aug 2017 12:56:22 -0700 Subject: [PATCH 094/107] Add hashing logic to componentDidUpdate If we dont run the hashing logic during component update series added after component mount wont necessarily line up with their hashed colors. --- ui/src/shared/components/Dygraph.js | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/ui/src/shared/components/Dygraph.js b/ui/src/shared/components/Dygraph.js index 165fad10c..e90fa28fd 100644 --- a/ui/src/shared/components/Dygraph.js +++ b/ui/src/shared/components/Dygraph.js @@ -272,6 +272,7 @@ export default class Dygraph extends Component { dygraphSeries, ruleValues, isBarGraph, + overrideLineColors, } = this.props const dygraph = this.dygraph @@ -285,6 +286,17 @@ export default class Dygraph extends Component { const y2 = _.get(axes, ['y2', 'bounds'], undefined) const timeSeries = this.getTimeSeries() const ylabel = this.getLabel('y') + const finalLineColors = [...(overrideLineColors || LINE_COLORS)] + + const hashColorDygraphSeries = {} + const {length} = finalLineColors + + for (const seriesName in dygraphSeries) { + const series = dygraphSeries[seriesName] + const hashIndex = hasherino(seriesName, length) + const color = finalLineColors[hashIndex] + hashColorDygraphSeries[seriesName] = {...series, color} + } const updateOptions = { labels, @@ -301,7 +313,8 @@ export default class Dygraph extends Component { stepPlot: options.stepPlot, stackedGraph: options.stackedGraph, underlayCallback: options.underlayCallback, - series: dygraphSeries, + colors: finalLineColors, + series: hashColorDygraphSeries, plotter: isBarGraph ? multiColumnBarPlotter : null, visibility: this.visibility(), } From 7a62088acbf998784872fd274ab7968cd11809be Mon Sep 17 00:00:00 2001 From: Alex P Date: Tue, 8 Aug 2017 10:53:34 -0700 Subject: [PATCH 095/107] Prepare for release --- CHANGELOG.md | 18 ++++++++++++------ ui/package.json | 2 +- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b2be3e6c8..b0f53981b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,14 @@ -## v1.3.6.0 [unreleased] +## v1.3.7.0 [unreleased] +### Bug Fixes + +### Features + +### UI Improvements + +## v1.3.6.0 [2017-08-08] ### Bug Fixes 1. [#1798](https://github.com/influxdata/chronograf/pull/1798): Fix domain not updating in visualizations when changing time range manually -1. [#1799](https://github.com/influxdata/chronograf/pull/1799): Prevent console error spam from Dygraph.synchronize when a dashboard has only one graph +1. [#1799](https://github.com/influxdata/chronograf/pull/1799): Prevent console error spam from Dygraph -- synchronize when a dashboard has only one graph 1. [#1813](https://github.com/influxdata/chronograf/pull/1813): Guarantee UUID for each Alert Table key to prevent dropping items when keys overlap ### Features @@ -9,11 +16,10 @@ 1. [#1714](https://github.com/influxdata/chronograf/pull/1714): Add ability to edit a dashboard graph's y-axis label ### UI Improvements -1. [#1796](https://github.com/influxdata/chronograf/pull/1796): Add spinner to indicate data is being written -1. [#1800](https://github.com/influxdata/chronograf/pull/1796): Embiggen text area for line protocol manual entry in Data Explorer's Write Data overlay +1. [#1796](https://github.com/influxdata/chronograf/pull/1796): Add spinner write data modal to indicate data is being written 1. [#1805](https://github.com/influxdata/chronograf/pull/1805): Fix bar graphs overlapping -1. [#1805](https://github.com/influxdata/chronograf/pull/1805): Add series names hashing so that graph colors should stay the same for the same series across charts -1. [#1800](https://github.com/influxdata/chronograf/pull/1800): Embiggen text area for line protocol manual entry in Data Explorer's Write Data overlay +1. [#1805](https://github.com/influxdata/chronograf/pull/1805): Assign a series consistent coloring when it appear in multiple cells +1. [#1800](https://github.com/influxdata/chronograf/pull/1800): Increase size of line protocol manual entry in Data Explorer's Write Data overlay 1. [#1812](https://github.com/influxdata/chronograf/pull/1812): Improve error message when request for Status Page News Feed fails ## v1.3.5.0 [2017-07-27] diff --git a/ui/package.json b/ui/package.json index b98dc2cec..19304204b 100644 --- a/ui/package.json +++ b/ui/package.json @@ -1,6 +1,6 @@ { "name": "chronograf-ui", - "version": "1.3.5-0", + "version": "1.3.6-0", "private": false, "license": "AGPL-3.0", "description": "", From 9139ce754f0f5cccdd163572c8ae0478eb96aef3 Mon Sep 17 00:00:00 2001 From: Alex P Date: Tue, 8 Aug 2017 11:57:23 -0700 Subject: [PATCH 096/107] Revert "Prepare for release" This reverts commit 7a62088acbf998784872fd274ab7968cd11809be. --- CHANGELOG.md | 18 ++++++------------ ui/package.json | 2 +- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b0f53981b..b2be3e6c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,14 +1,7 @@ -## v1.3.7.0 [unreleased] -### Bug Fixes - -### Features - -### UI Improvements - -## v1.3.6.0 [2017-08-08] +## v1.3.6.0 [unreleased] ### Bug Fixes 1. [#1798](https://github.com/influxdata/chronograf/pull/1798): Fix domain not updating in visualizations when changing time range manually -1. [#1799](https://github.com/influxdata/chronograf/pull/1799): Prevent console error spam from Dygraph -- synchronize when a dashboard has only one graph +1. [#1799](https://github.com/influxdata/chronograf/pull/1799): Prevent console error spam from Dygraph.synchronize when a dashboard has only one graph 1. [#1813](https://github.com/influxdata/chronograf/pull/1813): Guarantee UUID for each Alert Table key to prevent dropping items when keys overlap ### Features @@ -16,10 +9,11 @@ 1. [#1714](https://github.com/influxdata/chronograf/pull/1714): Add ability to edit a dashboard graph's y-axis label ### UI Improvements -1. [#1796](https://github.com/influxdata/chronograf/pull/1796): Add spinner write data modal to indicate data is being written +1. [#1796](https://github.com/influxdata/chronograf/pull/1796): Add spinner to indicate data is being written +1. [#1800](https://github.com/influxdata/chronograf/pull/1796): Embiggen text area for line protocol manual entry in Data Explorer's Write Data overlay 1. [#1805](https://github.com/influxdata/chronograf/pull/1805): Fix bar graphs overlapping -1. [#1805](https://github.com/influxdata/chronograf/pull/1805): Assign a series consistent coloring when it appear in multiple cells -1. [#1800](https://github.com/influxdata/chronograf/pull/1800): Increase size of line protocol manual entry in Data Explorer's Write Data overlay +1. [#1805](https://github.com/influxdata/chronograf/pull/1805): Add series names hashing so that graph colors should stay the same for the same series across charts +1. [#1800](https://github.com/influxdata/chronograf/pull/1800): Embiggen text area for line protocol manual entry in Data Explorer's Write Data overlay 1. [#1812](https://github.com/influxdata/chronograf/pull/1812): Improve error message when request for Status Page News Feed fails ## v1.3.5.0 [2017-07-27] diff --git a/ui/package.json b/ui/package.json index 19304204b..b98dc2cec 100644 --- a/ui/package.json +++ b/ui/package.json @@ -1,6 +1,6 @@ { "name": "chronograf-ui", - "version": "1.3.6-0", + "version": "1.3.5-0", "private": false, "license": "AGPL-3.0", "description": "", From 8ebbeda94170daf47804215c6d18504761e75012 Mon Sep 17 00:00:00 2001 From: Alex P Date: Tue, 8 Aug 2017 10:53:34 -0700 Subject: [PATCH 097/107] Update changelog and package.json for 1.3.6.x release --- CHANGELOG.md | 18 ++++++++++++------ ui/package.json | 2 +- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b2be3e6c8..9c266631d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,14 @@ -## v1.3.6.0 [unreleased] +## v1.3.7.0 [unreleased] +### Bug Fixes + +### Features + +### UI Improvements + +## v1.3.6.0 [2017-08-08] ### Bug Fixes 1. [#1798](https://github.com/influxdata/chronograf/pull/1798): Fix domain not updating in visualizations when changing time range manually -1. [#1799](https://github.com/influxdata/chronograf/pull/1799): Prevent console error spam from Dygraph.synchronize when a dashboard has only one graph +1. [#1799](https://github.com/influxdata/chronograf/pull/1799): Prevent console error spam from Dygraph's synchronize method when a dashboard has only one graph 1. [#1813](https://github.com/influxdata/chronograf/pull/1813): Guarantee UUID for each Alert Table key to prevent dropping items when keys overlap ### Features @@ -9,11 +16,10 @@ 1. [#1714](https://github.com/influxdata/chronograf/pull/1714): Add ability to edit a dashboard graph's y-axis label ### UI Improvements -1. [#1796](https://github.com/influxdata/chronograf/pull/1796): Add spinner to indicate data is being written -1. [#1800](https://github.com/influxdata/chronograf/pull/1796): Embiggen text area for line protocol manual entry in Data Explorer's Write Data overlay +1. [#1796](https://github.com/influxdata/chronograf/pull/1796): Add spinner write data modal to indicate data is being written 1. [#1805](https://github.com/influxdata/chronograf/pull/1805): Fix bar graphs overlapping -1. [#1805](https://github.com/influxdata/chronograf/pull/1805): Add series names hashing so that graph colors should stay the same for the same series across charts -1. [#1800](https://github.com/influxdata/chronograf/pull/1800): Embiggen text area for line protocol manual entry in Data Explorer's Write Data overlay +1. [#1805](https://github.com/influxdata/chronograf/pull/1805): Assign a series consistent coloring when it appears in multiple cells +1. [#1800](https://github.com/influxdata/chronograf/pull/1800): Increase size of line protocol manual entry in Data Explorer's Write Data overlay 1. [#1812](https://github.com/influxdata/chronograf/pull/1812): Improve error message when request for Status Page News Feed fails ## v1.3.5.0 [2017-07-27] diff --git a/ui/package.json b/ui/package.json index b98dc2cec..19304204b 100644 --- a/ui/package.json +++ b/ui/package.json @@ -1,6 +1,6 @@ { "name": "chronograf-ui", - "version": "1.3.5-0", + "version": "1.3.6-0", "private": false, "license": "AGPL-3.0", "description": "", From 9c73920c88c90810813e46e1520318761d4a5674 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Tue, 8 Aug 2017 16:33:08 -0700 Subject: [PATCH 098/107] Change height of table dynamically according to resizer --- ui/src/data_explorer/components/Table.js | 11 +++++----- ui/src/data_explorer/components/VisView.js | 4 +++- .../data_explorer/components/Visualization.js | 3 +++ ui/src/shared/components/ResizeContainer.js | 22 ++++++++++++++----- 4 files changed, 28 insertions(+), 12 deletions(-) diff --git a/ui/src/data_explorer/components/Table.js b/ui/src/data_explorer/components/Table.js index 4f13a53e5..72538180f 100644 --- a/ui/src/data_explorer/components/Table.js +++ b/ui/src/data_explorer/components/Table.js @@ -12,7 +12,6 @@ import {Table, Column, Cell} from 'fixed-data-table' const {arrayOf, bool, func, number, oneOfType, shape, string} = PropTypes -const defaultTableHeight = 1000 const emptySeries = {columns: [], values: []} const CustomCell = React.createClass({ @@ -64,7 +63,7 @@ const ChronoTable = React.createClass({ getDefaultProps() { return { - height: defaultTableHeight, + height: 500, } }, @@ -139,11 +138,11 @@ const ChronoTable = React.createClass({ const maximumTabsCount = 11 // adjust height to proper value by subtracting the heights of the UI around it // tab height, graph-container vertical padding, graph-heading height, multitable-header height - const stylePixelOffset = 136 - const rowHeight = 34 - const defaultColumnWidth = 200 - const headerHeight = 30 const minWidth = 70 + const rowHeight = 34 + const headerHeight = 30 + const stylePixelOffset = 130 + const defaultColumnWidth = 200 const styleAdjustedHeight = height - stylePixelOffset const width = columns && columns.length > 1 ? defaultColumnWidth : containerWidth diff --git a/ui/src/data_explorer/components/VisView.js b/ui/src/data_explorer/components/VisView.js index 9e717c6f1..186240529 100644 --- a/ui/src/data_explorer/components/VisView.js +++ b/ui/src/data_explorer/components/VisView.js @@ -13,6 +13,7 @@ const VisView = ({ heightPixels, editQueryStatus, activeQueryIndex, + resizerBottomHeight, }) => { const activeQuery = queries[activeQueryIndex] const defaultQuery = queries[0] @@ -30,7 +31,7 @@ const VisView = ({ return ( ) @@ -60,6 +61,7 @@ VisView.propTypes = { heightPixels: number, editQueryStatus: func.isRequired, activeQueryIndex: number, + resizerBottomHeight: number, } export default VisView diff --git a/ui/src/data_explorer/components/Visualization.js b/ui/src/data_explorer/components/Visualization.js index b33b99a7b..e9cb86a4a 100644 --- a/ui/src/data_explorer/components/Visualization.js +++ b/ui/src/data_explorer/components/Visualization.js @@ -31,6 +31,7 @@ const Visualization = React.createClass({ bounds: arrayOf(string), }), }), + resizerBottomHeight: number, }, contextTypes: { @@ -95,6 +96,7 @@ const Visualization = React.createClass({ editQueryStatus, activeQueryIndex, isInDataExplorer, + resizerBottomHeight, } = this.props const {source: {links: {proxy}}} = this.context const {view} = this.state @@ -134,6 +136,7 @@ const Visualization = React.createClass({ editQueryStatus={editQueryStatus} activeQueryIndex={activeQueryIndex} isInDataExplorer={isInDataExplorer} + resizerBottomHeight={resizerBottomHeight} /> diff --git a/ui/src/shared/components/ResizeContainer.js b/ui/src/shared/components/ResizeContainer.js index cb3fb19ba..a8baab69e 100644 --- a/ui/src/shared/components/ResizeContainer.js +++ b/ui/src/shared/components/ResizeContainer.js @@ -31,6 +31,12 @@ class ResizeContainer extends Component { initialBottomHeight: defaultInitialBottomHeight, } + componentDidMount() { + this.setState({ + bottomHeightPixels: this.bottom.getBoundingClientRect().height, + }) + } + handleStartDrag() { this.setState({isDragging: true}) } @@ -51,7 +57,7 @@ class ResizeContainer extends Component { const {minTopHeight, minBottomHeight} = this.props const oneHundred = 100 const containerHeight = parseInt( - getComputedStyle(this.refs.resizeContainer).height, + getComputedStyle(this.resizeContainer).height, 10 ) // verticalOffset moves the resize handle as many pixels as the page-heading is taking up. @@ -85,11 +91,12 @@ class ResizeContainer extends Component { this.setState({ topHeight: `${newTopPanelPercent}%`, bottomHeight: `${newBottomPanelPercent}%`, + bottomHeightPixels, }) } render() { - const {topHeight, bottomHeight, isDragging} = this.state + const {bottomHeightPixels, topHeight, bottomHeight, isDragging} = this.state const {containerClass, children} = this.props if (React.Children.count(children) > maximumNumChildren) { @@ -107,10 +114,12 @@ class ResizeContainer extends Component { onMouseLeave={this.handleMouseLeave} onMouseUp={this.handleStopDrag} onMouseMove={this.handleDrag} - ref="resizeContainer" + ref={r => (this.resizeContainer = r)} >
    - {React.cloneElement(children[0])} + {React.cloneElement(children[0], { + resizerBottomHeight: bottomHeightPixels, + })}
    (this.bottom = r)} > - {React.cloneElement(children[1])} + {React.cloneElement(children[1], { + resizerBottomHeight: bottomHeightPixels, + })} ) From 45851e1cfa21df5755cfc75842cb6ecaa38f7100 Mon Sep 17 00:00:00 2001 From: Alex P Date: Tue, 8 Aug 2017 17:12:00 -0700 Subject: [PATCH 099/107] Make lower half of CEO flush to the viewport --- .../style/components/ceo-display-options.scss | 4 +--- ui/src/style/pages/overlay-technology.scss | 17 +++++++++-------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/ui/src/style/components/ceo-display-options.scss b/ui/src/style/components/ceo-display-options.scss index d558f53c3..1e74b592e 100644 --- a/ui/src/style/components/ceo-display-options.scss +++ b/ui/src/style/components/ceo-display-options.scss @@ -4,11 +4,9 @@ */ .display-options { height: 100%; - margin: 0 60px; display: flex; background-color: $g2-kevlar; - padding: 0 8px 8px 8px; - border-radius: 0 0 4px 4px; + padding: 0 18px 8px 18px; flex-wrap: nowrap; align-items: stretch; } diff --git a/ui/src/style/pages/overlay-technology.scss b/ui/src/style/pages/overlay-technology.scss index 706256249..bb02ff605 100644 --- a/ui/src/style/pages/overlay-technology.scss +++ b/ui/src/style/pages/overlay-technology.scss @@ -3,7 +3,7 @@ ------------------------------------------------------ */ -$overlay-controls-height: 50px; +$overlay-controls-height: 60px; $overlay-controls-bg: $g2-kevlar; $overlay-z: 100; @@ -33,17 +33,16 @@ $overlay-z: 100; } .overlay-controls { - padding: 0 16px; + padding: 0 18px; position: relative; display: flex; align-items: center; justify-content: space-between; flex: 0 0 $overlay-controls-height; - width: calc(100% - #{($page-wrapper-padding * 2)}); - left: $page-wrapper-padding; + width: 100%; + left: 0; border: 0; background-color: $g2-kevlar; - border-radius: $radius $radius 0 0; } .overlay-controls--right { display: flex; @@ -74,7 +73,9 @@ $overlay-z: 100; flex-direction: column; align-items: stretch; height: 100%; - padding: 16px 0; +} +.overlay-technology--editor .query-maker--empty { + margin-bottom: 8px; } .overlay-controls .confirm-buttons { margin-left: 32px; @@ -86,8 +87,8 @@ $overlay-z: 100; } .overlay-technology .query-maker { flex: 1 0 0; - padding: 0 8px; - border-radius: 0 0 $radius $radius; + padding: 0 18px; + margin: 0; background-color: $g2-kevlar; } .overlay-technology .query-maker--tabs { From 8d4141aaf7dcc75c667a15fee7b61dce15f8ce9f Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Wed, 9 Aug 2017 11:29:25 -0700 Subject: [PATCH 100/107] Pixel perfect --- ui/src/data_explorer/components/Table.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/data_explorer/components/Table.js b/ui/src/data_explorer/components/Table.js index 72538180f..b2dbb5574 100644 --- a/ui/src/data_explorer/components/Table.js +++ b/ui/src/data_explorer/components/Table.js @@ -141,7 +141,7 @@ const ChronoTable = React.createClass({ const minWidth = 70 const rowHeight = 34 const headerHeight = 30 - const stylePixelOffset = 130 + const stylePixelOffset = 125 const defaultColumnWidth = 200 const styleAdjustedHeight = height - stylePixelOffset const width = From a9105166e0061a5e77b353ec02e99ae41451d2cc Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Wed, 9 Aug 2017 12:35:06 -0700 Subject: [PATCH 101/107] Update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c266631d..9e0e1cb1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## v1.3.7.0 [unreleased] ### Bug Fixes +1. [#1845](https://github.com/influxdata/chronograf/pull/1845): Fix no-scroll bar appearing in the Data Explorer table ### Features From 5e48567d6d7ac00df9b10ab8379df6154991865f Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Wed, 9 Aug 2017 13:23:08 -0700 Subject: [PATCH 102/107] Pass editQueryStatus action creator --- ui/src/data_explorer/components/VisView.js | 1 + ui/src/shared/components/RefreshingGraph.js | 15 +++++++++------ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/ui/src/data_explorer/components/VisView.js b/ui/src/data_explorer/components/VisView.js index 9e717c6f1..89eb84956 100644 --- a/ui/src/data_explorer/components/VisView.js +++ b/ui/src/data_explorer/components/VisView.js @@ -44,6 +44,7 @@ const VisView = ({ templates={templates} cellHeight={heightPixels} autoRefresh={autoRefresh} + editQueryStatus={editQueryStatus} /> ) } diff --git a/ui/src/shared/components/RefreshingGraph.js b/ui/src/shared/components/RefreshingGraph.js index 56e43857f..e70ff5b5f 100644 --- a/ui/src/shared/components/RefreshingGraph.js +++ b/ui/src/shared/components/RefreshingGraph.js @@ -8,14 +8,15 @@ const RefreshingLineGraph = AutoRefresh(LineGraph) const RefreshingSingleStat = AutoRefresh(SingleStat) const RefreshingGraph = ({ - timeRange, - autoRefresh, - templates, - synchronizer, + axes, type, queries, + templates, + timeRange, cellHeight, - axes, + autoRefresh, + synchronizer, + editQueryStatus, }) => { if (type === 'single-stat') { return ( @@ -43,6 +44,7 @@ const RefreshingGraph = ({ isBarGraph={type === 'bar'} displayOptions={displayOptions} synchronizer={synchronizer} + editQueryStatus={editQueryStatus} axes={axes} /> ) @@ -58,9 +60,10 @@ RefreshingGraph.propTypes = { templates: arrayOf(shape()), synchronizer: func, type: string.isRequired, - queries: arrayOf(shape()).isRequired, cellHeight: number, axes: shape(), + queries: arrayOf(shape()).isRequired, + editQueryStatus: func, } export default RefreshingGraph From 456504b293d3a27f4c802ef232230af6305d720c Mon Sep 17 00:00:00 2001 From: Alex P Date: Wed, 9 Aug 2017 13:42:56 -0700 Subject: [PATCH 103/107] Updoot changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c266631d..9b4ee2a2a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Features ### UI Improvements +1. [#1846](https://github.com/influxdata/chronograf/pull/1846): Increase screen real estate of Query Maker in the Cell Editor Overlay ## v1.3.6.0 [2017-08-08] ### Bug Fixes From 812de33be00ce615a0a0dd98f94af4643d8e9d4b Mon Sep 17 00:00:00 2001 From: Jared Scheib Date: Fri, 11 Aug 2017 11:54:16 -0700 Subject: [PATCH 104/107] Add 'type' to new-sources server flag example --- server/server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/server.go b/server/server.go index eff14cbf3..d51d8148c 100644 --- a/server/server.go +++ b/server/server.go @@ -50,7 +50,7 @@ type Server struct { KapacitorUsername string `long:"kapacitor-username" description:"Username of your Kapacitor instance" env:"KAPACITOR_USERNAME"` KapacitorPassword string `long:"kapacitor-password" description:"Password of your Kapacitor instance" env:"KAPACITOR_PASSWORD"` - NewSources string `long:"new-sources" description:"Config for adding a new InfluxDB source and Kapacitor server, in JSON as an array of objects, and surrounded by single quotes. E.g. --new-sources='[{\"influxdb\":{\"name\":\"Influx 1\",\"username\":\"user1\",\"password\":\"pass1\",\"url\":\"http://localhost:8086\",\"metaUrl\":\"http://metaurl.com\",\"insecureSkipVerify\":false,\"default\":true,\"telegraf\":\"telegraf\",\"sharedSecret\":\"hunter2\"},\"kapacitor\":{\"name\":\"Kapa 1\",\"url\":\"http://localhost:9092\",\"active\":true}}]'" env:"NEW_SOURCES" hidden:"true"` + NewSources string `long:"new-sources" description:"Config for adding a new InfluxDB source and Kapacitor server, in JSON as an array of objects, and surrounded by single quotes. E.g. --new-sources='[{\"influxdb\":{\"name\":\"Influx 1\",\"username\":\"user1\",\"password\":\"pass1\",\"url\":\"http://localhost:8086\",\"metaUrl\":\"http://metaurl.com\",\"type\":\"influx-enterprise\",\"insecureSkipVerify\":false,\"default\":true,\"telegraf\":\"telegraf\",\"sharedSecret\":\"cubeapples\"},\"kapacitor\":{\"name\":\"Kapa 1\",\"url\":\"http://localhost:9092\",\"active\":true}}]'" env:"NEW_SOURCES" hidden:"true"` Develop bool `short:"d" long:"develop" description:"Run server in develop mode."` BoltPath string `short:"b" long:"bolt-path" description:"Full path to boltDB file (e.g. './chronograf-v1.db')" env:"BOLT_PATH" default:"chronograf-v1.db"` From 7012674cc3c80486025d65371351fa22d891acb7 Mon Sep 17 00:00:00 2001 From: Jared Scheib Date: Fri, 11 Aug 2017 13:34:26 -0700 Subject: [PATCH 105/107] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ebaa9862..a4e832729 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ 1. [#1845](https://github.com/influxdata/chronograf/pull/1845): Fix no-scroll bar appearing in the Data Explorer table ### Features +1. [#1863](https://github.com/influxdata/chronograf/pull/1863): Improve 'new-sources' server flag example by adding 'type' key ### UI Improvements 1. [#1846](https://github.com/influxdata/chronograf/pull/1846): Increase screen real estate of Query Maker in the Cell Editor Overlay From 45e7bd866247a16eff487dbc122fd1464ed54283 Mon Sep 17 00:00:00 2001 From: Tim Raymond Date: Mon, 14 Aug 2017 13:13:39 -0400 Subject: [PATCH 106/107] Fix missing cell type Because we are now creating new instances of dashboards when we create a response, it's critical to copy every element of Dashboards from the previous to the new instance. We were not previously copying the Type field of cells, so this was defaulting to the empty string zero value. This patch adds "Type" to the tests and ensures that it's properly copied --- server/cells.go | 1 + server/cells_test.go | 2 ++ 2 files changed, 3 insertions(+) diff --git a/server/cells.go b/server/cells.go index 29c0bd57a..29d78b000 100644 --- a/server/cells.go +++ b/server/cells.go @@ -45,6 +45,7 @@ func newCellResponses(dID chronograf.DashboardID, dcells []chronograf.DashboardC newCell.H = cell.H newCell.Name = cell.Name newCell.ID = cell.ID + newCell.Type = cell.Type for _, lbl := range labels { if axis, found := cell.Axes[lbl]; !found { diff --git a/server/cells_test.go b/server/cells_test.go index 5ca5dce5e..c0ade6f5f 100644 --- a/server/cells_test.go +++ b/server/cells_test.go @@ -105,6 +105,7 @@ func Test_Service_DashboardCells(t *testing.T) { W: 4, H: 4, Name: "CPU", + Type: "bar", Queries: []chronograf.DashboardQuery{}, Axes: map[string]chronograf.Axis{}, }, @@ -117,6 +118,7 @@ func Test_Service_DashboardCells(t *testing.T) { W: 4, H: 4, Name: "CPU", + Type: "bar", Queries: []chronograf.DashboardQuery{}, Axes: map[string]chronograf.Axis{ "x": chronograf.Axis{ From 8575df56150dc07bf042d895bd17644ca3da55d1 Mon Sep 17 00:00:00 2001 From: Tim Raymond Date: Mon, 14 Aug 2017 14:03:40 -0400 Subject: [PATCH 107/107] Update CHANGELOG w/ fix for single stat --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ebaa9862..16a52aee3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ## v1.3.7.0 [unreleased] ### Bug Fixes 1. [#1845](https://github.com/influxdata/chronograf/pull/1845): Fix no-scroll bar appearing in the Data Explorer table +1. [#1866](https://github.com/influxdata/chronograf/pull/1866): Fix missing cell type (and consequently single-stat) ### Features