Merge branch 'master' into style/table-thresholds-polish

pull/10616/head
Alex P 2018-03-22 14:17:26 -07:00
commit 0a8ac81c02
28 changed files with 729 additions and 671 deletions

View File

@ -265,16 +265,18 @@ func MarshalDashboard(d chronograf.Dashboard) ([]byte, error) {
}
}
sortBy := &TableColumn{
sortBy := &RenamableField{
InternalName: c.TableOptions.SortBy.InternalName,
DisplayName: c.TableOptions.SortBy.DisplayName,
Visible: c.TableOptions.SortBy.Visible,
}
columnNames := make([]*TableColumn, len(c.TableOptions.ColumnNames))
for i, column := range c.TableOptions.ColumnNames {
columnNames[i] = &TableColumn{
InternalName: column.InternalName,
DisplayName: column.DisplayName,
fieldNames := make([]*RenamableField, len(c.TableOptions.FieldNames))
for i, field := range c.TableOptions.FieldNames {
fieldNames[i] = &RenamableField{
InternalName: field.InternalName,
DisplayName: field.DisplayName,
Visible: field.Visible,
}
}
@ -283,7 +285,7 @@ func MarshalDashboard(d chronograf.Dashboard) ([]byte, error) {
VerticalTimeAxis: c.TableOptions.VerticalTimeAxis,
SortBy: sortBy,
Wrapping: c.TableOptions.Wrapping,
ColumnNames: columnNames,
FieldNames: fieldNames,
FixFirstColumn: c.TableOptions.FixFirstColumn,
}
@ -431,20 +433,22 @@ func UnmarshalDashboard(data []byte, d *chronograf.Dashboard) error {
tableOptions := chronograf.TableOptions{}
if c.TableOptions != nil {
sortBy := chronograf.TableColumn{}
sortBy := chronograf.RenamableField{}
if c.TableOptions.SortBy != nil {
sortBy.InternalName = c.TableOptions.SortBy.InternalName
sortBy.DisplayName = c.TableOptions.SortBy.DisplayName
sortBy.Visible = c.TableOptions.SortBy.Visible
}
tableOptions.SortBy = sortBy
columnNames := make([]chronograf.TableColumn, len(c.TableOptions.ColumnNames))
for i, column := range c.TableOptions.ColumnNames {
columnNames[i] = chronograf.TableColumn{}
columnNames[i].InternalName = column.InternalName
columnNames[i].DisplayName = column.DisplayName
fieldNames := make([]chronograf.RenamableField, len(c.TableOptions.FieldNames))
for i, field := range c.TableOptions.FieldNames {
fieldNames[i] = chronograf.RenamableField{}
fieldNames[i].InternalName = field.InternalName
fieldNames[i].DisplayName = field.DisplayName
fieldNames[i].Visible = field.Visible
}
tableOptions.ColumnNames = columnNames
tableOptions.FieldNames = fieldNames
tableOptions.TimeFormat = c.TableOptions.TimeFormat
tableOptions.VerticalTimeAxis = c.TableOptions.VerticalTimeAxis
tableOptions.Wrapping = c.TableOptions.Wrapping

View File

@ -12,7 +12,7 @@ It has these top-level messages:
Dashboard
DashboardCell
TableOptions
TableColumn
RenamableField
Color
Legend
Axis
@ -316,12 +316,12 @@ func (m *DashboardCell) GetTableOptions() *TableOptions {
}
type TableOptions struct {
TimeFormat string `protobuf:"bytes,1,opt,name=timeFormat,proto3" json:"timeFormat,omitempty"`
VerticalTimeAxis bool `protobuf:"varint,2,opt,name=verticalTimeAxis,proto3" json:"verticalTimeAxis,omitempty"`
SortBy *TableColumn `protobuf:"bytes,3,opt,name=sortBy" json:"sortBy,omitempty"`
Wrapping string `protobuf:"bytes,4,opt,name=wrapping,proto3" json:"wrapping,omitempty"`
ColumnNames []*TableColumn `protobuf:"bytes,5,rep,name=columnNames" json:"columnNames,omitempty"`
FixFirstColumn bool `protobuf:"varint,6,opt,name=fixFirstColumn,proto3" json:"fixFirstColumn,omitempty"`
TimeFormat string `protobuf:"bytes,1,opt,name=timeFormat,proto3" json:"timeFormat,omitempty"`
VerticalTimeAxis bool `protobuf:"varint,2,opt,name=verticalTimeAxis,proto3" json:"verticalTimeAxis,omitempty"`
SortBy *RenamableField `protobuf:"bytes,3,opt,name=sortBy" json:"sortBy,omitempty"`
Wrapping string `protobuf:"bytes,4,opt,name=wrapping,proto3" json:"wrapping,omitempty"`
FieldNames []*RenamableField `protobuf:"bytes,5,rep,name=fieldNames" json:"fieldNames,omitempty"`
FixFirstColumn bool `protobuf:"varint,6,opt,name=fixFirstColumn,proto3" json:"fixFirstColumn,omitempty"`
}
func (m *TableOptions) Reset() { *m = TableOptions{} }
@ -343,7 +343,7 @@ func (m *TableOptions) GetVerticalTimeAxis() bool {
return false
}
func (m *TableOptions) GetSortBy() *TableColumn {
func (m *TableOptions) GetSortBy() *RenamableField {
if m != nil {
return m.SortBy
}
@ -357,9 +357,9 @@ func (m *TableOptions) GetWrapping() string {
return ""
}
func (m *TableOptions) GetColumnNames() []*TableColumn {
func (m *TableOptions) GetFieldNames() []*RenamableField {
if m != nil {
return m.ColumnNames
return m.FieldNames
}
return nil
}
@ -371,30 +371,38 @@ func (m *TableOptions) GetFixFirstColumn() bool {
return false
}
type TableColumn struct {
type RenamableField struct {
InternalName string `protobuf:"bytes,1,opt,name=internalName,proto3" json:"internalName,omitempty"`
DisplayName string `protobuf:"bytes,2,opt,name=displayName,proto3" json:"displayName,omitempty"`
Visible bool `protobuf:"varint,3,opt,name=visible,proto3" json:"visible,omitempty"`
}
func (m *TableColumn) Reset() { *m = TableColumn{} }
func (m *TableColumn) String() string { return proto.CompactTextString(m) }
func (*TableColumn) ProtoMessage() {}
func (*TableColumn) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{4} }
func (m *RenamableField) Reset() { *m = RenamableField{} }
func (m *RenamableField) String() string { return proto.CompactTextString(m) }
func (*RenamableField) ProtoMessage() {}
func (*RenamableField) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{4} }
func (m *TableColumn) GetInternalName() string {
func (m *RenamableField) GetInternalName() string {
if m != nil {
return m.InternalName
}
return ""
}
func (m *TableColumn) GetDisplayName() string {
func (m *RenamableField) GetDisplayName() string {
if m != nil {
return m.DisplayName
}
return ""
}
func (m *RenamableField) GetVisible() bool {
if m != nil {
return m.Visible
}
return false
}
type Color struct {
ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"`
Type string `protobuf:"bytes,2,opt,name=Type,proto3" json:"Type,omitempty"`
@ -1296,7 +1304,7 @@ func init() {
proto.RegisterType((*Dashboard)(nil), "internal.Dashboard")
proto.RegisterType((*DashboardCell)(nil), "internal.DashboardCell")
proto.RegisterType((*TableOptions)(nil), "internal.TableOptions")
proto.RegisterType((*TableColumn)(nil), "internal.TableColumn")
proto.RegisterType((*RenamableField)(nil), "internal.RenamableField")
proto.RegisterType((*Color)(nil), "internal.Color")
proto.RegisterType((*Legend)(nil), "internal.Legend")
proto.RegisterType((*Axis)(nil), "internal.Axis")
@ -1322,104 +1330,105 @@ func init() {
func init() { proto.RegisterFile("internal.proto", fileDescriptorInternal) }
var fileDescriptorInternal = []byte{
// 1573 bytes of a gzipped FileDescriptorProto
// 1586 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x57, 0x5f, 0x8f, 0xdb, 0x44,
0x10, 0x97, 0x93, 0x38, 0x89, 0x27, 0xd7, 0xe3, 0x64, 0x8e, 0xd6, 0x14, 0x09, 0x05, 0x8b, 0x3f,
0xe1, 0x4f, 0x0f, 0x94, 0x0a, 0x81, 0x2a, 0xa8, 0x94, 0xbb, 0xd0, 0x72, 0xf4, 0xda, 0xbb, 0x6e,
0xee, 0x8e, 0x27, 0x54, 0x6d, 0x92, 0x4d, 0x62, 0xd5, 0xb1, 0xcd, 0x7a, 0x7d, 0x17, 0xf3, 0x01,
0xf8, 0x18, 0x48, 0x48, 0xf0, 0x05, 0x10, 0x2f, 0x3c, 0xf1, 0xce, 0x07, 0xe1, 0x2b, 0xc0, 0x23,
0x9a, 0xdd, 0xb5, 0xb3, 0xb9, 0xa4, 0x55, 0x91, 0x10, 0x6f, 0xfb, 0x9b, 0x19, 0xcf, 0xce, 0xce,
0xcc, 0x6f, 0x76, 0x0d, 0xdb, 0x41, 0x24, 0x18, 0x8f, 0x68, 0xb8, 0x97, 0xf0, 0x58, 0xc4, 0x6e,
0xb3, 0xc0, 0xfe, 0x9f, 0x15, 0xa8, 0x0f, 0xe2, 0x8c, 0x8f, 0x98, 0xbb, 0x0d, 0x95, 0xc3, 0xbe,
0x67, 0xb5, 0xad, 0x4e, 0x95, 0x54, 0x0e, 0xfb, 0xae, 0x0b, 0xb5, 0x47, 0x74, 0xce, 0xbc, 0x4a,
0xdb, 0xea, 0x38, 0x44, 0xae, 0x51, 0x76, 0x9a, 0x27, 0xcc, 0xab, 0x2a, 0x19, 0xae, 0xdd, 0x9b,
0xd0, 0x3c, 0x4b, 0xd1, 0xdb, 0x9c, 0x79, 0x35, 0x29, 0x2f, 0x31, 0xea, 0x4e, 0x68, 0x9a, 0x5e,
0xc6, 0x7c, 0xec, 0xd9, 0x4a, 0x57, 0x60, 0x77, 0x07, 0xaa, 0x67, 0xe4, 0xc8, 0xab, 0x4b, 0x31,
0x2e, 0x5d, 0x0f, 0x1a, 0x7d, 0x36, 0xa1, 0x59, 0x28, 0xbc, 0x46, 0xdb, 0xea, 0x34, 0x49, 0x01,
0xd1, 0xcf, 0x29, 0x0b, 0xd9, 0x94, 0xd3, 0x89, 0xd7, 0x54, 0x7e, 0x0a, 0xec, 0xee, 0x81, 0x7b,
0x18, 0xa5, 0x6c, 0x94, 0x71, 0x36, 0x78, 0x1a, 0x24, 0xe7, 0x8c, 0x07, 0x93, 0xdc, 0x73, 0xa4,
0x83, 0x0d, 0x1a, 0xdc, 0xe5, 0x21, 0x13, 0x14, 0xf7, 0x06, 0xe9, 0xaa, 0x80, 0xae, 0x0f, 0x5b,
0x83, 0x19, 0xe5, 0x6c, 0x3c, 0x60, 0x23, 0xce, 0x84, 0xd7, 0x92, 0xea, 0x15, 0x19, 0xda, 0x1c,
0xf3, 0x29, 0x8d, 0x82, 0xef, 0xa8, 0x08, 0xe2, 0xc8, 0xdb, 0x52, 0x36, 0xa6, 0x0c, 0xb3, 0x44,
0xe2, 0x90, 0x79, 0xd7, 0x54, 0x96, 0x70, 0xed, 0xff, 0x6a, 0x81, 0xd3, 0xa7, 0xe9, 0x6c, 0x18,
0x53, 0x3e, 0x7e, 0xa1, 0x5c, 0xdf, 0x02, 0x7b, 0xc4, 0xc2, 0x30, 0xf5, 0xaa, 0xed, 0x6a, 0xa7,
0xd5, 0xbd, 0xb1, 0x57, 0x16, 0xb1, 0xf4, 0x73, 0xc0, 0xc2, 0x90, 0x28, 0x2b, 0xf7, 0x23, 0x70,
0x04, 0x9b, 0x27, 0x21, 0x15, 0x2c, 0xf5, 0x6a, 0xf2, 0x13, 0x77, 0xf9, 0xc9, 0xa9, 0x56, 0x91,
0xa5, 0xd1, 0xda, 0x51, 0xec, 0xf5, 0xa3, 0xf8, 0xbf, 0x55, 0xe1, 0xda, 0xca, 0x76, 0xee, 0x16,
0x58, 0x0b, 0x19, 0xb9, 0x4d, 0xac, 0x05, 0xa2, 0x5c, 0x46, 0x6d, 0x13, 0x2b, 0x47, 0x74, 0x29,
0x7b, 0xc3, 0x26, 0xd6, 0x25, 0xa2, 0x99, 0xec, 0x08, 0x9b, 0x58, 0x33, 0xf7, 0x5d, 0x68, 0x7c,
0x9b, 0x31, 0x1e, 0xb0, 0xd4, 0xb3, 0x65, 0x74, 0x2f, 0x2d, 0xa3, 0x7b, 0x9c, 0x31, 0x9e, 0x93,
0x42, 0x8f, 0xd9, 0x90, 0xdd, 0xa4, 0x5a, 0x43, 0xae, 0x51, 0x26, 0xb0, 0xf3, 0x1a, 0x4a, 0x86,
0x6b, 0x9d, 0x45, 0xd5, 0x0f, 0x98, 0xc5, 0x8f, 0xa1, 0x46, 0x17, 0x2c, 0xf5, 0x1c, 0xe9, 0xff,
0x8d, 0x67, 0x24, 0x6c, 0xaf, 0xb7, 0x60, 0xe9, 0x17, 0x91, 0xe0, 0x39, 0x91, 0xe6, 0xee, 0x3b,
0x50, 0x1f, 0xc5, 0x61, 0xcc, 0x53, 0x0f, 0xae, 0x06, 0x76, 0x80, 0x72, 0xa2, 0xd5, 0x6e, 0x07,
0xea, 0x21, 0x9b, 0xb2, 0x68, 0x2c, 0x3b, 0xa3, 0xd5, 0xdd, 0x59, 0x1a, 0x1e, 0x49, 0x39, 0xd1,
0x7a, 0xf7, 0x0e, 0x6c, 0x09, 0x3a, 0x0c, 0xd9, 0x71, 0x82, 0x59, 0x4c, 0x65, 0x97, 0xb4, 0xba,
0xd7, 0x8d, 0x7a, 0x18, 0x5a, 0xb2, 0x62, 0x7b, 0xf3, 0x3e, 0x38, 0x65, 0x84, 0x48, 0x92, 0xa7,
0x2c, 0x97, 0xf9, 0x76, 0x08, 0x2e, 0xdd, 0x37, 0xc1, 0xbe, 0xa0, 0x61, 0xa6, 0x7a, 0xa5, 0xd5,
0xdd, 0x5e, 0xfa, 0xec, 0x2d, 0x82, 0x94, 0x28, 0xe5, 0x9d, 0xca, 0xa7, 0x96, 0xff, 0x7d, 0x05,
0xb6, 0xcc, 0x7d, 0xdc, 0xd7, 0x01, 0x44, 0x30, 0x67, 0xf7, 0x62, 0x3e, 0xa7, 0x42, 0xfb, 0x34,
0x24, 0xee, 0x7b, 0xb0, 0x73, 0xc1, 0xb8, 0x08, 0x46, 0x34, 0x3c, 0x0d, 0xe6, 0x0c, 0xfd, 0xc9,
0x5d, 0x9a, 0x64, 0x4d, 0xee, 0xde, 0x82, 0x7a, 0x1a, 0x73, 0xb1, 0x9f, 0xcb, 0x7a, 0xb7, 0xba,
0xaf, 0x5c, 0x39, 0xdb, 0x41, 0x1c, 0x66, 0xf3, 0x88, 0x68, 0x23, 0x24, 0xf0, 0x25, 0xa7, 0x49,
0x12, 0x44, 0xd3, 0x62, 0x48, 0x14, 0xd8, 0xfd, 0x04, 0x5a, 0x23, 0x69, 0x8d, 0x6d, 0x5f, 0x74,
0xc7, 0x33, 0xfc, 0x99, 0x96, 0xee, 0xdb, 0xb0, 0x3d, 0x09, 0x16, 0xf7, 0x02, 0x9e, 0x0a, 0xa5,
0x96, 0x1d, 0xd3, 0x24, 0x57, 0xa4, 0xfe, 0x00, 0x5a, 0x86, 0x0f, 0xec, 0xfb, 0xc2, 0xb7, 0x24,
0x9d, 0x4a, 0xc4, 0x8a, 0xcc, 0x6d, 0x43, 0x6b, 0x1c, 0xa4, 0x49, 0x48, 0x73, 0x83, 0x97, 0xa6,
0xc8, 0x9f, 0x82, 0x2d, 0xbb, 0xc3, 0xe0, 0xb2, 0x53, 0x70, 0x59, 0xce, 0xc8, 0x8a, 0x31, 0x23,
0x77, 0xa0, 0xfa, 0x25, 0x5b, 0xe8, 0xb1, 0x89, 0xcb, 0x92, 0xf1, 0x35, 0x83, 0xf1, 0xbb, 0x60,
0x9f, 0xcb, 0xd2, 0x2a, 0x26, 0x2a, 0xe0, 0xdf, 0x85, 0xba, 0xea, 0xae, 0xd2, 0xb3, 0x65, 0x78,
0x6e, 0x43, 0xeb, 0x98, 0x07, 0x2c, 0x12, 0x8a, 0xc3, 0x3a, 0x50, 0x43, 0xe4, 0xff, 0x62, 0x41,
0x4d, 0x96, 0xcc, 0x87, 0xad, 0x90, 0x4d, 0xe9, 0x28, 0xdf, 0x8f, 0xb3, 0x68, 0x9c, 0x7a, 0x56,
0xbb, 0xda, 0xa9, 0x92, 0x15, 0x99, 0x7b, 0x1d, 0xea, 0x43, 0xa5, 0xad, 0xb4, 0xab, 0x1d, 0x87,
0x68, 0x84, 0xa1, 0x85, 0x74, 0xc8, 0x42, 0x7d, 0x04, 0x05, 0xd0, 0x3a, 0xe1, 0x6c, 0x12, 0x2c,
0xf4, 0x31, 0x34, 0x42, 0x79, 0x9a, 0x4d, 0x50, 0xae, 0x4e, 0xa2, 0x11, 0x1e, 0x60, 0x48, 0xd3,
0x92, 0xd8, 0xb8, 0x46, 0xcf, 0xe9, 0x88, 0x86, 0x05, 0xb3, 0x15, 0xf0, 0x7f, 0xb7, 0x70, 0xe2,
0xab, 0x49, 0xb5, 0x96, 0xe1, 0x57, 0xa1, 0x89, 0x53, 0xec, 0xc9, 0x05, 0xe5, 0xfa, 0xc0, 0x0d,
0xc4, 0xe7, 0x94, 0xbb, 0x1f, 0x42, 0x5d, 0x12, 0x60, 0xc3, 0xd4, 0x2c, 0xdc, 0xc9, 0xac, 0x12,
0x6d, 0x56, 0xce, 0x95, 0x9a, 0x31, 0x57, 0xca, 0xc3, 0xda, 0xe6, 0x61, 0x6f, 0x81, 0x8d, 0x03,
0x2a, 0x97, 0xd1, 0x6f, 0xf4, 0xac, 0xc6, 0x98, 0xb2, 0xf2, 0xcf, 0xe0, 0xda, 0xca, 0x8e, 0xe5,
0x4e, 0xd6, 0xea, 0x4e, 0x4b, 0x32, 0x3b, 0x9a, 0xbc, 0x48, 0x96, 0x94, 0x85, 0x6c, 0x24, 0xd8,
0x58, 0xe6, 0xbb, 0x49, 0x4a, 0xec, 0xff, 0x68, 0x2d, 0xfd, 0xca, 0xfd, 0xf0, 0x3e, 0x1b, 0xc5,
0xf3, 0x39, 0x8d, 0xc6, 0xda, 0x75, 0x01, 0x31, 0x6f, 0xe3, 0xa1, 0x76, 0x5d, 0x19, 0x0f, 0x11,
0xf3, 0x44, 0x57, 0xb0, 0xc2, 0x13, 0xec, 0x9d, 0x39, 0xa3, 0x69, 0xc6, 0xd9, 0x9c, 0x45, 0x42,
0xa7, 0xc0, 0x14, 0xb9, 0x37, 0xa0, 0x21, 0xe8, 0xf4, 0x09, 0x8e, 0x20, 0x5d, 0x49, 0x41, 0xa7,
0x0f, 0x58, 0xee, 0xbe, 0x06, 0xce, 0x24, 0x60, 0xe1, 0x58, 0xaa, 0x54, 0x39, 0x9b, 0x52, 0xf0,
0x80, 0xe5, 0xfe, 0xdf, 0x16, 0xd4, 0x07, 0x8c, 0x5f, 0x30, 0xfe, 0x42, 0x17, 0x9d, 0xf9, 0x80,
0xa8, 0x3e, 0xe7, 0x01, 0x51, 0xdb, 0xfc, 0x80, 0xb0, 0x97, 0x0f, 0x88, 0x5d, 0xb0, 0x07, 0x7c,
0x74, 0xd8, 0x97, 0x11, 0x55, 0x89, 0x02, 0xd8, 0x8d, 0xbd, 0x91, 0x08, 0x2e, 0x98, 0x7e, 0x55,
0x68, 0xb4, 0x76, 0xff, 0x35, 0x37, 0x5c, 0xe5, 0xff, 0xf2, 0x71, 0xe1, 0xff, 0x60, 0x41, 0xfd,
0x88, 0xe6, 0x71, 0x26, 0xd6, 0xba, 0xb6, 0x0d, 0xad, 0x5e, 0x92, 0x84, 0xc1, 0x68, 0x85, 0xa9,
0x86, 0x08, 0x2d, 0x1e, 0x1a, 0xf5, 0x50, 0xb9, 0x30, 0x45, 0x38, 0xfc, 0x0f, 0xe4, 0x9b, 0x40,
0x5d, 0xf0, 0xc6, 0xf0, 0x57, 0x4f, 0x01, 0xa9, 0xc4, 0xa4, 0xf5, 0x32, 0x11, 0x4f, 0xc2, 0xf8,
0x52, 0x66, 0xa7, 0x49, 0x4a, 0xec, 0xff, 0x51, 0x81, 0xda, 0xff, 0x75, 0x8f, 0x6f, 0x81, 0x15,
0xe8, 0xe6, 0xb0, 0x82, 0xf2, 0x56, 0x6f, 0x18, 0xb7, 0xba, 0x07, 0x8d, 0x9c, 0xd3, 0x68, 0xca,
0x52, 0xaf, 0x29, 0xa7, 0x51, 0x01, 0xa5, 0x46, 0xf2, 0x4e, 0x5d, 0xe7, 0x0e, 0x29, 0x60, 0xc9,
0x23, 0x30, 0x78, 0xf4, 0x81, 0xbe, 0xf9, 0x5b, 0x32, 0x22, 0x6f, 0x35, 0x2d, 0x57, 0x2f, 0xfc,
0xff, 0xee, 0x86, 0xfd, 0xcb, 0x02, 0xbb, 0x24, 0xe1, 0xc1, 0x2a, 0x09, 0x0f, 0x96, 0x24, 0xec,
0xef, 0x17, 0x24, 0xec, 0xef, 0x23, 0x26, 0x27, 0x05, 0x09, 0xc9, 0x09, 0x16, 0xeb, 0x3e, 0x8f,
0xb3, 0x64, 0x3f, 0x57, 0x55, 0x75, 0x48, 0x89, 0xb1, 0x73, 0xbf, 0x9e, 0x31, 0xae, 0x53, 0xed,
0x10, 0x8d, 0xb0, 0xcf, 0x8f, 0xe4, 0x80, 0x52, 0xc9, 0x55, 0xc0, 0x7d, 0x0b, 0x6c, 0x82, 0xc9,
0x93, 0x19, 0x5e, 0xa9, 0x8b, 0x14, 0x13, 0xa5, 0x45, 0xa7, 0xea, 0xc5, 0xaf, 0x1b, 0xbe, 0x78,
0xff, 0xbf, 0x0f, 0xf5, 0xc1, 0x2c, 0x98, 0x88, 0xe2, 0xfd, 0xf4, 0xb2, 0x31, 0xe0, 0x82, 0x39,
0x93, 0x3a, 0xa2, 0x4d, 0xfc, 0xc7, 0xe0, 0x94, 0xc2, 0x65, 0x38, 0x96, 0x19, 0x8e, 0x0b, 0xb5,
0xb3, 0x28, 0x10, 0x05, 0xd5, 0x71, 0x8d, 0x87, 0x7d, 0x9c, 0xd1, 0x48, 0x04, 0x22, 0x2f, 0xa8,
0x5e, 0x60, 0xff, 0xb6, 0x0e, 0x1f, 0xdd, 0x9d, 0x25, 0x09, 0xe3, 0x7a, 0x6c, 0x28, 0x20, 0x37,
0x89, 0x2f, 0x99, 0x9a, 0xf8, 0x55, 0xa2, 0x80, 0xff, 0x0d, 0x38, 0xbd, 0x90, 0x71, 0x41, 0xb2,
0x90, 0x6d, 0xba, 0x89, 0xbf, 0x1a, 0x1c, 0x3f, 0x2a, 0x22, 0xc0, 0xf5, 0x72, 0x44, 0x54, 0xaf,
0x8c, 0x88, 0x07, 0x34, 0xa1, 0x87, 0x7d, 0xd9, 0xe7, 0x55, 0xa2, 0x91, 0xff, 0x93, 0x05, 0x35,
0x9c, 0x45, 0x86, 0xeb, 0xda, 0xf3, 0xe6, 0xd8, 0x09, 0x8f, 0x2f, 0x82, 0x31, 0xe3, 0xc5, 0xe1,
0x0a, 0x2c, 0x93, 0x3e, 0x9a, 0xb1, 0xf2, 0xc2, 0xd7, 0x08, 0x7b, 0x0d, 0x7f, 0x0f, 0x0a, 0x2e,
0x19, 0xbd, 0x86, 0x62, 0xa2, 0x94, 0xf8, 0x70, 0x1b, 0x64, 0x09, 0xe3, 0xbd, 0xf1, 0x3c, 0x28,
0x1e, 0x39, 0x86, 0xc4, 0xbf, 0xab, 0x7e, 0x38, 0xd6, 0x26, 0x9a, 0xb5, 0xf9, 0xe7, 0xe4, 0x6a,
0xe4, 0xfe, 0xcf, 0x16, 0x34, 0x1e, 0xea, 0xd7, 0x98, 0x79, 0x0a, 0xeb, 0x99, 0xa7, 0xa8, 0xac,
0x9c, 0xa2, 0x0b, 0xbb, 0x85, 0xcd, 0xca, 0xfe, 0x2a, 0x0b, 0x1b, 0x75, 0x3a, 0xa3, 0xb5, 0xb2,
0x58, 0x2f, 0xf2, 0x37, 0x72, 0xba, 0x6a, 0xb3, 0xa9, 0xe0, 0x6b, 0x55, 0x69, 0x43, 0x4b, 0xff,
0x45, 0xca, 0x7f, 0x32, 0x3d, 0x54, 0x0d, 0x91, 0xdf, 0x85, 0xfa, 0x41, 0x1c, 0x4d, 0x82, 0xa9,
0xdb, 0x81, 0x5a, 0x2f, 0x13, 0x33, 0xe9, 0xb1, 0xd5, 0xdd, 0x35, 0x88, 0x9f, 0x89, 0x99, 0xb2,
0x21, 0xd2, 0xc2, 0xff, 0x0c, 0x60, 0x29, 0xc3, 0x5b, 0x62, 0x59, 0x8d, 0x47, 0xec, 0x12, 0x5b,
0x26, 0x95, 0x5e, 0x9a, 0x64, 0x83, 0xc6, 0xff, 0x1c, 0x9c, 0xfd, 0x2c, 0x08, 0xc7, 0x87, 0xd1,
0x24, 0xc6, 0xd1, 0x71, 0xce, 0x78, 0xba, 0xac, 0x57, 0x01, 0x31, 0xdd, 0x38, 0x45, 0x4a, 0x0e,
0x69, 0x34, 0xac, 0xcb, 0xbf, 0xf8, 0xdb, 0xff, 0x04, 0x00, 0x00, 0xff, 0xff, 0xc4, 0x7e, 0x5e,
0x7e, 0xd7, 0x0f, 0x00, 0x00,
0x10, 0x97, 0x93, 0x38, 0x89, 0x27, 0xd7, 0xe3, 0x64, 0x4e, 0xad, 0x29, 0x12, 0x0a, 0x16, 0x7f,
0xc2, 0x9f, 0x1e, 0x55, 0x2a, 0xa4, 0xaa, 0x82, 0x4a, 0xb9, 0x0b, 0x2d, 0x47, 0xaf, 0xbd, 0xeb,
0xe6, 0xee, 0x78, 0x42, 0xd5, 0x26, 0x99, 0x24, 0x56, 0x1d, 0xdb, 0xac, 0xed, 0xbb, 0x98, 0x8f,
0xc0, 0x87, 0x40, 0x42, 0x82, 0x2f, 0x80, 0x78, 0xe1, 0x89, 0x77, 0x3e, 0x08, 0x5f, 0x01, 0x1e,
0xd1, 0xec, 0xae, 0x1d, 0xe7, 0x92, 0x56, 0x45, 0x42, 0xbc, 0xed, 0x6f, 0x66, 0x3c, 0xbb, 0xf3,
0x7f, 0x0c, 0xdb, 0x5e, 0x90, 0xa0, 0x08, 0xb8, 0xbf, 0x17, 0x89, 0x30, 0x09, 0xed, 0x66, 0x8e,
0xdd, 0x3f, 0x2b, 0x50, 0x1f, 0x84, 0xa9, 0x18, 0xa1, 0xbd, 0x0d, 0x95, 0xc3, 0xbe, 0x63, 0xb4,
0x8d, 0x4e, 0x95, 0x55, 0x0e, 0xfb, 0xb6, 0x0d, 0xb5, 0x27, 0x7c, 0x8e, 0x4e, 0xa5, 0x6d, 0x74,
0x2c, 0x26, 0xcf, 0x44, 0x3b, 0xcd, 0x22, 0x74, 0xaa, 0x8a, 0x46, 0x67, 0xfb, 0x26, 0x34, 0xcf,
0x62, 0xd2, 0x36, 0x47, 0xa7, 0x26, 0xe9, 0x05, 0x26, 0xde, 0x09, 0x8f, 0xe3, 0xcb, 0x50, 0x8c,
0x1d, 0x53, 0xf1, 0x72, 0x6c, 0xef, 0x40, 0xf5, 0x8c, 0x1d, 0x39, 0x75, 0x49, 0xa6, 0xa3, 0xed,
0x40, 0xa3, 0x8f, 0x13, 0x9e, 0xfa, 0x89, 0xd3, 0x68, 0x1b, 0x9d, 0x26, 0xcb, 0x21, 0xe9, 0x39,
0x45, 0x1f, 0xa7, 0x82, 0x4f, 0x9c, 0xa6, 0xd2, 0x93, 0x63, 0x7b, 0x0f, 0xec, 0xc3, 0x20, 0xc6,
0x51, 0x2a, 0x70, 0xf0, 0xdc, 0x8b, 0xce, 0x51, 0x78, 0x93, 0xcc, 0xb1, 0xa4, 0x82, 0x0d, 0x1c,
0xba, 0xe5, 0x31, 0x26, 0x9c, 0xee, 0x06, 0xa9, 0x2a, 0x87, 0xb6, 0x0b, 0x5b, 0x83, 0x19, 0x17,
0x38, 0x1e, 0xe0, 0x48, 0x60, 0xe2, 0xb4, 0x24, 0x7b, 0x85, 0x46, 0x32, 0xc7, 0x62, 0xca, 0x03,
0xef, 0x3b, 0x9e, 0x78, 0x61, 0xe0, 0x6c, 0x29, 0x99, 0x32, 0x8d, 0xbc, 0xc4, 0x42, 0x1f, 0x9d,
0x6b, 0xca, 0x4b, 0x74, 0x76, 0x7f, 0x35, 0xc0, 0xea, 0xf3, 0x78, 0x36, 0x0c, 0xb9, 0x18, 0xbf,
0x92, 0xaf, 0x6f, 0x81, 0x39, 0x42, 0xdf, 0x8f, 0x9d, 0x6a, 0xbb, 0xda, 0x69, 0x75, 0x6f, 0xec,
0x15, 0x41, 0x2c, 0xf4, 0x1c, 0xa0, 0xef, 0x33, 0x25, 0x65, 0xdf, 0x06, 0x2b, 0xc1, 0x79, 0xe4,
0xf3, 0x04, 0x63, 0xa7, 0x26, 0x3f, 0xb1, 0x97, 0x9f, 0x9c, 0x6a, 0x16, 0x5b, 0x0a, 0xad, 0x99,
0x62, 0xae, 0x9b, 0xe2, 0xfe, 0x56, 0x85, 0x6b, 0x2b, 0xd7, 0xd9, 0x5b, 0x60, 0x2c, 0xe4, 0xcb,
0x4d, 0x66, 0x2c, 0x08, 0x65, 0xf2, 0xd5, 0x26, 0x33, 0x32, 0x42, 0x97, 0x32, 0x37, 0x4c, 0x66,
0x5c, 0x12, 0x9a, 0xc9, 0x8c, 0x30, 0x99, 0x31, 0xb3, 0x3f, 0x80, 0xc6, 0xb7, 0x29, 0x0a, 0x0f,
0x63, 0xc7, 0x94, 0xaf, 0x7b, 0x6d, 0xf9, 0xba, 0xa7, 0x29, 0x8a, 0x8c, 0xe5, 0x7c, 0xf2, 0x86,
0xcc, 0x26, 0x95, 0x1a, 0xf2, 0x4c, 0xb4, 0x84, 0x32, 0xaf, 0xa1, 0x68, 0x74, 0xd6, 0x5e, 0x54,
0xf9, 0x40, 0x5e, 0xfc, 0x14, 0x6a, 0x7c, 0x81, 0xb1, 0x63, 0x49, 0xfd, 0x6f, 0xbf, 0xc0, 0x61,
0x7b, 0xbd, 0x05, 0xc6, 0x5f, 0x04, 0x89, 0xc8, 0x98, 0x14, 0xb7, 0xdf, 0x87, 0xfa, 0x28, 0xf4,
0x43, 0x11, 0x3b, 0x70, 0xf5, 0x61, 0x07, 0x44, 0x67, 0x9a, 0x6d, 0x77, 0xa0, 0xee, 0xe3, 0x14,
0x83, 0xb1, 0xcc, 0x8c, 0x56, 0x77, 0x67, 0x29, 0x78, 0x24, 0xe9, 0x4c, 0xf3, 0xed, 0x7b, 0xb0,
0x95, 0xf0, 0xa1, 0x8f, 0xc7, 0x11, 0x79, 0x31, 0x96, 0x59, 0xd2, 0xea, 0x5e, 0x2f, 0xc5, 0xa3,
0xc4, 0x65, 0x2b, 0xb2, 0x37, 0x1f, 0x82, 0x55, 0xbc, 0x90, 0x8a, 0xe4, 0x39, 0x66, 0xd2, 0xdf,
0x16, 0xa3, 0xa3, 0xfd, 0x0e, 0x98, 0x17, 0xdc, 0x4f, 0x55, 0xae, 0xb4, 0xba, 0xdb, 0x4b, 0x9d,
0xbd, 0x85, 0x17, 0x33, 0xc5, 0xbc, 0x57, 0xb9, 0x6b, 0xb8, 0xdf, 0x57, 0x60, 0xab, 0x7c, 0x8f,
0xfd, 0x16, 0x40, 0xe2, 0xcd, 0xf1, 0x41, 0x28, 0xe6, 0x3c, 0xd1, 0x3a, 0x4b, 0x14, 0xfb, 0x43,
0xd8, 0xb9, 0x40, 0x91, 0x78, 0x23, 0xee, 0x9f, 0x7a, 0x73, 0x24, 0x7d, 0xf2, 0x96, 0x26, 0x5b,
0xa3, 0xdb, 0xb7, 0xa1, 0x1e, 0x87, 0x22, 0xd9, 0xcf, 0x64, 0xbc, 0x5b, 0x5d, 0x67, 0xf9, 0x0e,
0x86, 0x01, 0x9f, 0xd3, 0xbd, 0x0f, 0x3c, 0xf4, 0xc7, 0x4c, 0xcb, 0x51, 0x0d, 0x5f, 0x0a, 0x1e,
0x45, 0x5e, 0x30, 0xcd, 0xfb, 0x44, 0x8e, 0xed, 0xbb, 0x00, 0x13, 0x12, 0xa6, 0xc4, 0xcf, 0xf3,
0xe3, 0xc5, 0x1a, 0x4b, 0xb2, 0xf6, 0x7b, 0xb0, 0x3d, 0xf1, 0x16, 0x0f, 0x3c, 0x11, 0x27, 0x07,
0xa1, 0x9f, 0xce, 0x03, 0x99, 0x35, 0x4d, 0x76, 0x85, 0xea, 0x46, 0xb0, 0xbd, 0xaa, 0x85, 0xd2,
0x3f, 0xbf, 0x40, 0xd6, 0x9e, 0xf2, 0xc7, 0x0a, 0xcd, 0x6e, 0x43, 0x6b, 0xec, 0xc5, 0x91, 0xcf,
0xb3, 0x52, 0x79, 0x96, 0x49, 0xd4, 0x4d, 0x2e, 0xbc, 0xd8, 0x1b, 0xfa, 0xaa, 0x29, 0x36, 0x59,
0x0e, 0xdd, 0x29, 0x98, 0x32, 0x7d, 0x4a, 0xc5, 0x6e, 0xe5, 0xc5, 0x2e, 0x9b, 0x68, 0xa5, 0xd4,
0x44, 0x77, 0xa0, 0xfa, 0x25, 0x2e, 0x74, 0x5f, 0xa5, 0x63, 0xd1, 0x12, 0x6a, 0xa5, 0x96, 0xb0,
0x0b, 0xe6, 0xb9, 0x8c, 0xbd, 0x2a, 0x55, 0x05, 0xdc, 0xfb, 0x50, 0x57, 0xe9, 0x57, 0x68, 0x36,
0x4a, 0x9a, 0xdb, 0xd0, 0x3a, 0x16, 0x1e, 0x06, 0x89, 0x2a, 0x72, 0x6d, 0x42, 0x89, 0xe4, 0xfe,
0x62, 0x40, 0x4d, 0xc6, 0xd4, 0x85, 0x2d, 0x1f, 0xa7, 0x7c, 0x94, 0xed, 0x87, 0x69, 0x30, 0x8e,
0x1d, 0xa3, 0x5d, 0xed, 0x54, 0xd9, 0x0a, 0xcd, 0xbe, 0x0e, 0xf5, 0xa1, 0xe2, 0x56, 0xda, 0xd5,
0x8e, 0xc5, 0x34, 0xa2, 0xa7, 0xf9, 0x7c, 0x88, 0xbe, 0x36, 0x41, 0x01, 0x92, 0x8e, 0x04, 0x4e,
0xbc, 0x85, 0x36, 0x43, 0x23, 0xa2, 0xc7, 0xe9, 0x84, 0xe8, 0xca, 0x12, 0x8d, 0xc8, 0x80, 0x21,
0x8f, 0x8b, 0xca, 0xa7, 0x33, 0x69, 0x8e, 0x47, 0xdc, 0xcf, 0x4b, 0x5f, 0x01, 0xf7, 0x77, 0x83,
0x46, 0x82, 0x6a, 0x65, 0x6b, 0x1e, 0x7e, 0x03, 0x9a, 0xd4, 0xe6, 0x9e, 0x5d, 0x70, 0xa1, 0x0d,
0x6e, 0x10, 0x3e, 0xe7, 0xc2, 0xfe, 0x04, 0xea, 0xb2, 0x42, 0x36, 0xb4, 0xd5, 0x5c, 0x9d, 0xf4,
0x2a, 0xd3, 0x62, 0x45, 0xe3, 0xa9, 0x95, 0x1a, 0x4f, 0x61, 0xac, 0x59, 0x36, 0xf6, 0x16, 0x98,
0xd4, 0xc1, 0x32, 0xf9, 0xfa, 0x8d, 0x9a, 0x55, 0x9f, 0x53, 0x52, 0xee, 0x19, 0x5c, 0x5b, 0xb9,
0xb1, 0xb8, 0xc9, 0x58, 0xbd, 0x69, 0x59, 0xed, 0x96, 0xae, 0x6e, 0x2a, 0xa5, 0x18, 0x7d, 0x1c,
0x25, 0x38, 0xd6, 0x59, 0x57, 0x60, 0xf7, 0x47, 0x63, 0xa9, 0x57, 0xde, 0x47, 0x29, 0x3a, 0x0a,
0xe7, 0x73, 0x1e, 0x8c, 0xb5, 0xea, 0x1c, 0x92, 0xdf, 0xc6, 0x43, 0xad, 0xba, 0x32, 0x1e, 0x12,
0x16, 0x91, 0x8e, 0x60, 0x45, 0x44, 0x94, 0x3b, 0x73, 0xe4, 0x71, 0x2a, 0x70, 0x8e, 0x41, 0xa2,
0x5d, 0x50, 0x26, 0xd9, 0x37, 0xa0, 0x91, 0xf0, 0xe9, 0x33, 0xea, 0x51, 0x3a, 0x92, 0x09, 0x9f,
0x3e, 0xc2, 0xcc, 0x7e, 0x13, 0x2c, 0x59, 0xa5, 0x92, 0xa5, 0xc2, 0xd9, 0x94, 0x84, 0x47, 0x98,
0xb9, 0x7f, 0x1b, 0x50, 0x1f, 0xa0, 0xb8, 0x40, 0xf1, 0x4a, 0x93, 0xb0, 0xbc, 0x61, 0x54, 0x5f,
0xb2, 0x61, 0xd4, 0x36, 0x6f, 0x18, 0xe6, 0x72, 0xc3, 0xd8, 0x05, 0x73, 0x20, 0x46, 0x87, 0x7d,
0xf9, 0xa2, 0x2a, 0x53, 0x80, 0xb2, 0xb1, 0x37, 0x4a, 0xbc, 0x0b, 0xd4, 0x6b, 0x87, 0x46, 0x6b,
0x03, 0xb2, 0xb9, 0x61, 0xd6, 0xff, 0xcb, 0xed, 0xc3, 0xfd, 0xc1, 0x80, 0xfa, 0x11, 0xcf, 0xc2,
0x34, 0x59, 0xcb, 0xda, 0x36, 0xb4, 0x7a, 0x51, 0xe4, 0x7b, 0xa3, 0x95, 0x4a, 0x2d, 0x91, 0x48,
0xe2, 0x71, 0x29, 0x1e, 0xca, 0x17, 0x65, 0x12, 0x4d, 0x87, 0x03, 0xb9, 0x34, 0xa8, 0x0d, 0xa0,
0x34, 0x1d, 0xd4, 0xae, 0x20, 0x99, 0xe4, 0xb4, 0x5e, 0x9a, 0x84, 0x13, 0x3f, 0xbc, 0x94, 0xde,
0x69, 0xb2, 0x02, 0xbb, 0x7f, 0x54, 0xa0, 0xf6, 0x7f, 0x0d, 0xfa, 0x2d, 0x30, 0x3c, 0x9d, 0x1c,
0x86, 0x57, 0x8c, 0xfd, 0x46, 0x69, 0xec, 0x3b, 0xd0, 0xc8, 0x04, 0x0f, 0xa6, 0x18, 0x3b, 0x4d,
0xd9, 0x8d, 0x72, 0x28, 0x39, 0xb2, 0xee, 0xd4, 0xbc, 0xb7, 0x58, 0x0e, 0x8b, 0x3a, 0x82, 0x52,
0x1d, 0x7d, 0xac, 0x57, 0x83, 0xd6, 0xd5, 0xd1, 0xb2, 0x69, 0x23, 0xf8, 0xef, 0x46, 0xf0, 0x5f,
0x06, 0x98, 0x45, 0x11, 0x1e, 0xac, 0x16, 0xe1, 0xc1, 0xb2, 0x08, 0xfb, 0xfb, 0x79, 0x11, 0xf6,
0xf7, 0x09, 0xb3, 0x93, 0xbc, 0x08, 0xd9, 0x09, 0x05, 0xeb, 0xa1, 0x08, 0xd3, 0x68, 0x3f, 0x53,
0x51, 0xb5, 0x58, 0x81, 0x29, 0x73, 0xbf, 0x9e, 0xa1, 0xd0, 0xae, 0xb6, 0x98, 0x46, 0x94, 0xe7,
0x47, 0xb2, 0x41, 0x29, 0xe7, 0x2a, 0x60, 0xbf, 0x0b, 0x26, 0x23, 0xe7, 0x49, 0x0f, 0xaf, 0xc4,
0x45, 0x92, 0x99, 0xe2, 0x92, 0x52, 0xf5, 0x4b, 0xa0, 0x13, 0x3e, 0xff, 0x41, 0xf8, 0x08, 0xea,
0x83, 0x99, 0x37, 0x49, 0xf2, 0x05, 0xeb, 0xf5, 0x52, 0x83, 0xf3, 0xe6, 0x28, 0x79, 0x4c, 0x8b,
0xb8, 0x4f, 0xc1, 0x2a, 0x88, 0xcb, 0xe7, 0x18, 0xe5, 0xe7, 0xd8, 0x50, 0x3b, 0x0b, 0xbc, 0x24,
0x2f, 0x75, 0x3a, 0x93, 0xb1, 0x4f, 0x53, 0x1e, 0x24, 0x5e, 0x92, 0xe5, 0xa5, 0x9e, 0x63, 0xf7,
0x8e, 0x7e, 0x3e, 0xa9, 0x3b, 0x8b, 0x22, 0x14, 0xba, 0x6d, 0x28, 0x20, 0x2f, 0x09, 0x2f, 0x51,
0x75, 0xfc, 0x2a, 0x53, 0xc0, 0xfd, 0x06, 0xac, 0x9e, 0x8f, 0x22, 0x61, 0xa9, 0x8f, 0x9b, 0x26,
0xf1, 0x57, 0x83, 0xe3, 0x27, 0xf9, 0x0b, 0xe8, 0xbc, 0x6c, 0x11, 0xd5, 0x2b, 0x2d, 0xe2, 0x11,
0x8f, 0xf8, 0x61, 0x5f, 0xe6, 0x79, 0x95, 0x69, 0xe4, 0xfe, 0x64, 0x40, 0x8d, 0x7a, 0x51, 0x49,
0x75, 0xed, 0x65, 0x7d, 0xec, 0x44, 0x84, 0x17, 0xde, 0x18, 0x45, 0x6e, 0x5c, 0x8e, 0xa5, 0xd3,
0x47, 0x33, 0x2c, 0x06, 0xbe, 0x46, 0x94, 0x6b, 0xf4, 0xff, 0x90, 0xd7, 0x52, 0x29, 0xd7, 0x88,
0xcc, 0x14, 0x93, 0x36, 0xbb, 0x41, 0x1a, 0xa1, 0xe8, 0x8d, 0xe7, 0x5e, 0xbe, 0x01, 0x95, 0x28,
0xee, 0x7d, 0xf5, 0x47, 0xb2, 0xd6, 0xd1, 0x8c, 0xcd, 0x7f, 0x2f, 0x57, 0x5f, 0xee, 0xfe, 0x6c,
0x40, 0xe3, 0xb1, 0xde, 0xd5, 0xca, 0x56, 0x18, 0x2f, 0xb4, 0xa2, 0xb2, 0x62, 0x45, 0x17, 0x76,
0x73, 0x99, 0x95, 0xfb, 0x95, 0x17, 0x36, 0xf2, 0xb4, 0x47, 0x6b, 0x45, 0xb0, 0x5e, 0xe5, 0x77,
0xe5, 0x74, 0x55, 0x66, 0x53, 0xc0, 0xd7, 0xa2, 0xd2, 0x86, 0x96, 0xfe, 0xcd, 0x94, 0x3f, 0x6d,
0xba, 0xa9, 0x96, 0x48, 0x6e, 0x17, 0xea, 0x07, 0x61, 0x30, 0xf1, 0xa6, 0x76, 0x07, 0x6a, 0xbd,
0x34, 0x99, 0x49, 0x8d, 0xad, 0xee, 0x6e, 0xa9, 0xf0, 0xd3, 0x64, 0xa6, 0x64, 0x98, 0x94, 0x70,
0x3f, 0x03, 0x58, 0xd2, 0x68, 0x4a, 0x2c, 0xa3, 0xf1, 0x04, 0x2f, 0x29, 0x65, 0x62, 0xa9, 0xa5,
0xc9, 0x36, 0x70, 0xdc, 0xcf, 0xc1, 0xda, 0x4f, 0x3d, 0x7f, 0x7c, 0x18, 0x4c, 0x42, 0x6a, 0x1d,
0xe7, 0x28, 0xe2, 0x65, 0xbc, 0x72, 0x48, 0xee, 0xa6, 0x2e, 0x52, 0xd4, 0x90, 0x46, 0xc3, 0xba,
0xfc, 0xcd, 0xbf, 0xf3, 0x4f, 0x00, 0x00, 0x00, 0xff, 0xff, 0xda, 0x7c, 0x0d, 0xab, 0xf8, 0x0f,
0x00, 0x00,
}

View File

@ -43,15 +43,16 @@ message DashboardCell {
message TableOptions {
string timeFormat = 1; // format for time
bool verticalTimeAxis = 2; // time axis should be a column not row
TableColumn sortBy = 3; // which column should a table be sorted by
RenamableField sortBy = 3; // which column should a table be sorted by
string wrapping = 4; // option for text wrapping
repeated TableColumn columnNames = 5; // names and renames for columns
repeated RenamableField fieldNames = 5; // names and renames for column/row fields
bool fixFirstColumn = 6; // first column should be fixed/frozen
}
message TableColumn {
message RenamableField {
string internalName = 1; // name of column
string displayName = 2; // what column is renamed to
bool visible = 3; // Represents whether RenamableField is visible
}
message Color {

View File

@ -195,8 +195,8 @@ func Test_MarshalDashboard(t *testing.T) {
},
},
TableOptions: chronograf.TableOptions{
TimeFormat: "",
ColumnNames: []chronograf.TableColumn{},
TimeFormat: "",
FieldNames: []chronograf.RenamableField{},
},
},
},
@ -318,8 +318,8 @@ func Test_MarshalDashboard_WithLegacyBounds(t *testing.T) {
Orientation: "bottom",
},
TableOptions: chronograf.TableOptions{
TimeFormat: "MM:DD:YYYY",
ColumnNames: []chronograf.TableColumn{},
TimeFormat: "MM:DD:YYYY",
FieldNames: []chronograf.RenamableField{},
},
Type: "line",
},
@ -434,8 +434,8 @@ func Test_MarshalDashboard_WithEmptyLegacyBounds(t *testing.T) {
},
},
TableOptions: chronograf.TableOptions{
TimeFormat: "MM:DD:YYYY",
ColumnNames: []chronograf.TableColumn{},
TimeFormat: "MM:DD:YYYY",
FieldNames: []chronograf.RenamableField{},
},
Type: "line",
},
@ -474,7 +474,7 @@ func Test_MarshalDashboard_WithEmptyCellType(t *testing.T) {
Axes: map[string]chronograf.Axis{},
CellColors: []chronograf.CellColor{},
TableOptions: chronograf.TableOptions{
ColumnNames: []chronograf.TableColumn{},
FieldNames: []chronograf.RenamableField{},
},
},
},

View File

@ -560,20 +560,21 @@ type DashboardCell struct {
TableOptions TableOptions `json:"tableOptions,omitempty"`
}
// TableColumn is a column in a DashboardCell of type Table
type TableColumn struct {
// RenamableField is a column/row field in a DashboardCell of type Table
type RenamableField struct {
InternalName string `json:"internalName"`
DisplayName string `json:"displayName"`
Visible bool `json:"visible"`
}
// TableOptions is a type of options for a DashboardCell with type Table
type TableOptions struct {
TimeFormat string `json:"timeFormat"`
VerticalTimeAxis bool `json:"verticalTimeAxis"`
SortBy TableColumn `json:"sortBy"`
Wrapping string `json:"wrapping"`
ColumnNames []TableColumn `json:"columnNames"`
FixFirstColumn bool `json:"fixFirstColumn"`
TimeFormat string `json:"timeFormat"`
VerticalTimeAxis bool `json:"verticalTimeAxis"`
SortBy RenamableField `json:"sortBy"`
Wrapping string `json:"wrapping"`
FieldNames []RenamableField `json:"fieldNames"`
FixFirstColumn bool `json:"fixFirstColumn"`
}
// DashboardsStore is the storage and retrieval of dashboards

View File

@ -548,9 +548,11 @@ func TestServer(t *testing.T) {
"verticalTimeAxis": false,
"sortBy":{
"internalName": "",
"displayName": ""},
"displayName": "",
"visible": false
},
"wrapping": "",
"columnNames": null,
"fieldNames": null,
"fixFirstColumn": false
},
"links": {
@ -797,10 +799,11 @@ func TestServer(t *testing.T) {
"verticalTimeAxis":false,
"sortBy":{
"internalName":"",
"displayName":""
"displayName":"",
"visible":false
},
"wrapping":"",
"columnNames":null,
"fieldNames":null,
"fixFirstColumn":false
},
"legend":{

View File

@ -532,7 +532,7 @@ func TestService_ReplaceDashboardCell(t *testing.T) {
}
}
`))),
want: `{"i":"3c5c4102-fa40-4585-a8f9-917c77e37192","x":0,"y":0,"w":4,"h":4,"name":"Untitled Cell","queries":[{"query":"SELECT mean(\"usage_user\") AS \"mean_usage_user\" FROM \"telegraf\".\"autogen\".\"cpu\" WHERE time \u003e :dashboardTime: AND \"cpu\"=:cpu: GROUP BY :interval: FILL(null)","queryConfig":{"id":"3cd3eaa4-a4b8-44b3-b69e-0c7bf6b91d9e","database":"telegraf","measurement":"cpu","retentionPolicy":"autogen","fields":[{"value":"mean","type":"func","alias":"mean_usage_user","args":[{"value":"usage_user","type":"field","alias":""}]}],"tags":{"cpu":["ChristohersMBP2.lan"]},"groupBy":{"time":"2s","tags":[]},"areTagsAccepted":true,"fill":"null","rawText":"SELECT mean(\"usage_user\") AS \"mean_usage_user\" FROM \"telegraf\".\"autogen\".\"cpu\" WHERE time \u003e :dashboardTime: AND \"cpu\"=:cpu: GROUP BY :interval: FILL(null)","range":{"upper":"","lower":"now() - 15m"},"shifts":[]},"source":""}],"axes":{"x":{"bounds":["",""],"label":"","prefix":"","suffix":"","base":"","scale":""},"y":{"bounds":["",""],"label":"","prefix":"","suffix":"","base":"","scale":""},"y2":{"bounds":["",""],"label":"","prefix":"","suffix":"","base":"","scale":""}},"type":"line","colors":[{"id":"0","type":"min","hex":"#00C9FF","name":"laser","value":"0"},{"id":"1","type":"max","hex":"#9394FF","name":"comet","value":"100"}],"legend":{},"tableOptions":{"timeFormat":"","verticalTimeAxis":false,"sortBy":{"internalName":"","displayName":""},"wrapping":"","columnNames":null,"fixFirstColumn":false},"links":{"self":"/chronograf/v1/dashboards/1/cells/3c5c4102-fa40-4585-a8f9-917c77e37192"}}
want: `{"i":"3c5c4102-fa40-4585-a8f9-917c77e37192","x":0,"y":0,"w":4,"h":4,"name":"Untitled Cell","queries":[{"query":"SELECT mean(\"usage_user\") AS \"mean_usage_user\" FROM \"telegraf\".\"autogen\".\"cpu\" WHERE time \u003e :dashboardTime: AND \"cpu\"=:cpu: GROUP BY :interval: FILL(null)","queryConfig":{"id":"3cd3eaa4-a4b8-44b3-b69e-0c7bf6b91d9e","database":"telegraf","measurement":"cpu","retentionPolicy":"autogen","fields":[{"value":"mean","type":"func","alias":"mean_usage_user","args":[{"value":"usage_user","type":"field","alias":""}]}],"tags":{"cpu":["ChristohersMBP2.lan"]},"groupBy":{"time":"2s","tags":[]},"areTagsAccepted":true,"fill":"null","rawText":"SELECT mean(\"usage_user\") AS \"mean_usage_user\" FROM \"telegraf\".\"autogen\".\"cpu\" WHERE time \u003e :dashboardTime: AND \"cpu\"=:cpu: GROUP BY :interval: FILL(null)","range":{"upper":"","lower":"now() - 15m"},"shifts":[]},"source":""}],"axes":{"x":{"bounds":["",""],"label":"","prefix":"","suffix":"","base":"","scale":""},"y":{"bounds":["",""],"label":"","prefix":"","suffix":"","base":"","scale":""},"y2":{"bounds":["",""],"label":"","prefix":"","suffix":"","base":"","scale":""}},"type":"line","colors":[{"id":"0","type":"min","hex":"#00C9FF","name":"laser","value":"0"},{"id":"1","type":"max","hex":"#9394FF","name":"comet","value":"100"}],"legend":{},"tableOptions":{"timeFormat":"","verticalTimeAxis":false,"sortBy":{"internalName":"","displayName":"","visible":false},"wrapping":"","fieldNames":null,"fixFirstColumn":false},"links":{"self":"/chronograf/v1/dashboards/1/cells/3c5c4102-fa40-4585-a8f9-917c77e37192"}}
`,
},
{

View File

@ -48,6 +48,7 @@ class CellEditorOverlay extends Component {
activeQueryIndex: 0,
isDisplayOptionsTabActive: false,
staticLegend: IS_STATIC_LEGEND(legend),
dataLabels: [],
}
}
@ -259,6 +260,10 @@ class CellEditorOverlay extends Component {
this.overlayRef.focus()
}
setDataLabels = dataLabels => {
this.setState({dataLabels})
}
render() {
const {
onCancel,
@ -273,6 +278,7 @@ class CellEditorOverlay extends Component {
isDisplayOptionsTabActive,
queriesWorkingDraft,
staticLegend,
dataLabels,
} = this.state
const queryActions = {
@ -305,6 +311,7 @@ class CellEditorOverlay extends Component {
queryConfigs={queriesWorkingDraft}
editQueryStatus={editQueryStatus}
staticLegend={staticLegend}
setDataLabels={this.setDataLabels}
/>
<CEOBottom>
<OverlayControls
@ -324,6 +331,7 @@ class CellEditorOverlay extends Component {
onToggleStaticLegend={this.handleToggleStaticLegend}
staticLegend={staticLegend}
onResetFocus={this.handleResetFocus}
dataLabels={dataLabels}
/>
) : (
<QueryMaker

View File

@ -42,7 +42,7 @@ class DisplayOptions extends Component {
staticLegend,
onToggleStaticLegend,
onResetFocus,
queryConfigs,
dataLabels,
} = this.props
switch (type) {
case 'gauge':
@ -51,10 +51,7 @@ class DisplayOptions extends Component {
return <SingleStatOptions onResetFocus={onResetFocus} />
case 'table':
return (
<TableOptions
onResetFocus={onResetFocus}
queryConfigs={queryConfigs}
/>
<TableOptions onResetFocus={onResetFocus} dataLabels={dataLabels} />
)
default:
return (
@ -93,6 +90,7 @@ DisplayOptions.propTypes = {
onToggleStaticLegend: func.isRequired,
staticLegend: bool,
onResetFocus: func.isRequired,
dataLabels: arrayOf(string),
}
const mapStateToProps = ({cellEditorOverlay: {cell, cell: {axes}}}) => ({

View File

@ -1,46 +0,0 @@
import React, {PureComponent} from 'react'
import InputClickToEdit from 'src/shared/components/InputClickToEdit'
interface Column {
internalName: string
displayName: string
}
interface Props {
internalName: string
displayName: string
onColumnRename: (column: Column) => void
}
class GraphOptionsCustomizableColumn extends PureComponent<Props, {}> {
constructor(props) {
super(props)
this.handleColumnRename = this.handleColumnRename.bind(this)
}
public handleColumnRename(rename) {
const {onColumnRename, internalName} = this.props
onColumnRename({internalName, displayName: rename})
}
public render() {
const {internalName, displayName} = this.props
return (
<div className="column-controls--section">
<div className="column-controls--label">{internalName}</div>
<InputClickToEdit
value={displayName}
wrapperClass="column-controls-input"
onBlur={this.handleColumnRename}
placeholder="Rename..."
appearAsNormalInput={true}
/>
</div>
)
}
}
export default GraphOptionsCustomizableColumn

View File

@ -0,0 +1,65 @@
import React, {PureComponent} from 'react'
import InputClickToEdit from 'src/shared/components/InputClickToEdit'
interface Field {
internalName: string
displayName: string
visible: boolean
}
interface Props {
internalName: string
displayName: string
visible: boolean
onFieldUpdate: (field: Field) => void
}
class GraphOptionsCustomizableField extends PureComponent<Props, {}> {
constructor(props) {
super(props)
this.handleFieldRename = this.handleFieldRename.bind(this)
this.handleToggleVisible = this.handleToggleVisible.bind(this)
}
public handleFieldRename(rename: string) {
const {onFieldUpdate, internalName, visible} = this.props
onFieldUpdate({internalName, displayName: rename, visible})
}
public handleToggleVisible() {
const {onFieldUpdate, internalName, displayName, visible} = this.props
onFieldUpdate({internalName, displayName, visible: !visible})
}
public render() {
const {internalName, displayName, visible} = this.props
return (
<div className="field-controls--section">
<div
className={
visible ? 'field-controls--label' : 'field-controls--label-hidden'
}
>
<span
className={visible ? 'icon eye-open' : 'icon eye-closed'}
onClick={this.handleToggleVisible}
/>
{internalName}
</div>
<InputClickToEdit
value={displayName}
wrapperClass="field-controls-input"
onBlur={this.handleFieldRename}
placeholder="Rename..."
appearAsNormalInput={true}
disabled={!visible}
/>
</div>
)
}
}
export default GraphOptionsCustomizableField

View File

@ -1,37 +0,0 @@
import React, {SFC} from 'react'
import GraphOptionsCustomizableColumn from 'src/dashboards/components/GraphOptionsCustomizableColumn'
import uuid from 'uuid'
interface Column {
internalName: string
displayName: string
}
interface Props {
columns: Column[]
onColumnRename: (column: Column) => void
}
const GraphOptionsCustomizeColumns: SFC<Props> = ({
columns,
onColumnRename,
}) => {
return (
<div className="graph-options-group">
<label className="form-label">Customize Columns</label>
{columns.map(col => {
return (
<GraphOptionsCustomizableColumn
key={uuid.v4()}
internalName={col.internalName}
displayName={col.displayName}
onColumnRename={onColumnRename}
/>
)
})}
</div>
)
}
export default GraphOptionsCustomizeColumns

View File

@ -0,0 +1,38 @@
import React, {SFC} from 'react'
import GraphOptionsCustomizableField from 'src/dashboards/components/GraphOptionsCustomizableField'
import uuid from 'uuid'
interface Field {
internalName: string
displayName: string
visible: boolean
}
interface Props {
fields: Field[]
onFieldUpdate: (field: Field) => void
}
const GraphOptionsCustomizeFields: SFC<Props> = ({fields, onFieldUpdate}) => {
return (
<div className="graph-options-group">
<label className="form-label">Customize Fields</label>
<div className="field-controls--group">
{fields.map(field => {
return (
<GraphOptionsCustomizableField
key={uuid.v4()}
internalName={field.internalName}
displayName={field.displayName}
visible={field.visible}
onFieldUpdate={onFieldUpdate}
/>
)
})}
</div>
</div>
)
}
export default GraphOptionsCustomizeFields

View File

@ -23,10 +23,9 @@ const GraphOptionsSortBy = ({
selected,
}: Props) => {
const selectedValue = selected.displayName || selected.internalName
return (
<div className="form-group col-xs-6">
<label>Sort By</label>
<label>Default Sort By</label>
<Dropdown
items={sortByOptions}
selected={selectedValue}

View File

@ -35,18 +35,22 @@ class GraphOptionsTimeFormat extends PureComponent<Props, State> {
return this.props.onTimeFormatChange
}
public handleChangeFormat = format => {
this.onTimeFormatChange(format)
this.setState({format})
}
public handleChooseFormat = (formatOption: TimeFormatOptions) => {
if (formatOption.text === TIME_FORMAT_CUSTOM) {
this.setState({customFormat: true})
} else {
this.setState({format: formatOption.text, customFormat: false})
this.onTimeFormatChange(formatOption.text)
this.setState({format: formatOption.text, customFormat: false})
}
}
public render() {
const {format, customFormat} = this.state
const {onTimeFormatChange} = this.props
const tipContent =
'For information on formatting, see http://momentjs.com/docs/#/parsing/string-format/'
@ -57,7 +61,7 @@ class GraphOptionsTimeFormat extends PureComponent<Props, State> {
<div className="form-group col-xs-12">
<label>
Time Format
{customFormat && (
{showCustom && (
<QuestionMarkTooltip
tipID="Time Axis Format"
tipContent={tipContent}
@ -75,10 +79,10 @@ class GraphOptionsTimeFormat extends PureComponent<Props, State> {
{showCustom && (
<div className="column-controls--section">
<InputClickToEdit
wrapperClass="column-controls-input "
wrapperClass="field-controls-input "
value={format}
onBlur={onTimeFormatChange}
onChange={onTimeFormatChange}
onBlur={this.handleChangeFormat}
onChange={this.handleChangeFormat}
placeholder="Enter custom format..."
appearAsNormalInput={true}
/>

View File

@ -2,54 +2,45 @@ import React, {PureComponent} from 'react'
import {connect} from 'react-redux'
import {bindActionCreators} from 'redux'
import _ from 'lodash'
import GraphOptionsCustomizeColumns from 'src/dashboards/components/GraphOptionsCustomizeColumns'
import GraphOptionsCustomizeFields from 'src/dashboards/components/GraphOptionsCustomizeFields'
import GraphOptionsFixFirstColumn from 'src/dashboards/components/GraphOptionsFixFirstColumn'
import GraphOptionsSortBy from 'src/dashboards/components/GraphOptionsSortBy'
import GraphOptionsTextWrapping from 'src/dashboards/components/GraphOptionsTextWrapping'
import GraphOptionsTimeAxis from 'src/dashboards/components/GraphOptionsTimeAxis'
import GraphOptionsTimeFormat from 'src/dashboards/components/GraphOptionsTimeFormat'
import FancyScrollbar from 'src/shared/components/FancyScrollbar'
import _ from 'lodash'
import ThresholdsList from 'src/shared/components/ThresholdsList'
import ThresholdsListTypeToggle from 'src/shared/components/ThresholdsListTypeToggle'
import {updateTableOptions} from 'src/dashboards/actions/cellEditorOverlay'
import {TIME_COLUMN_DEFAULT} from 'src/shared/constants/tableGraph'
import {TIME_FIELD_DEFAULT} from 'src/shared/constants/tableGraph'
interface Option {
text: string
key: string
}
interface TableColumn {
interface RenamableField {
internalName: string
displayName: string
visible: boolean
}
interface Options {
timeFormat: string
verticalTimeAxis: boolean
sortBy: TableColumn
wrapping: string
columnNames: TableColumn[]
sortBy: RenamableField
fieldNames: RenamableField[]
fixFirstColumn: boolean
}
interface QueryConfig {
measurement: string
fields: Array<{
alias: string
value: string
}>
}
interface Props {
queryConfigs: QueryConfig[]
handleUpdateTableOptions: (options: Options) => void
tableOptions: Options
onResetFocus: () => void
dataLabels: string[]
}
export class TableOptions extends PureComponent<Props, {}> {
@ -57,47 +48,33 @@ export class TableOptions extends PureComponent<Props, {}> {
super(props)
}
get columnNames() {
const {tableOptions: {columnNames}} = this.props
return columnNames || []
get fieldNames() {
const {tableOptions: {fieldNames}} = this.props
return fieldNames || []
}
get timeColumn() {
return (
this.columnNames.find(c => c.internalName === 'time') ||
TIME_COLUMN_DEFAULT
this.fieldNames.find(f => f.internalName === 'time') || TIME_FIELD_DEFAULT
)
}
get computedColumnNames() {
const {queryConfigs} = this.props
get computedFieldNames() {
const {dataLabels} = this.props
const queryFields = _.flatten(
queryConfigs.map(({measurement, fields}) => {
return fields.map(({alias}) => {
const internalName = `${measurement}.${alias}`
const existing = this.columnNames.find(
c => c.internalName === internalName
)
return existing || {internalName, displayName: ''}
})
})
)
return [this.timeColumn, ...queryFields]
}
public componentWillMount() {
const {handleUpdateTableOptions, tableOptions} = this.props
handleUpdateTableOptions({
...tableOptions,
columnNames: this.computedColumnNames,
return dataLabels.map(label => {
const existing = this.fieldNames.find(f => f.internalName === label)
return existing || {internalName: label, displayName: '', visible: true}
})
}
public handleChooseSortBy = (option: Option) => {
const {tableOptions, handleUpdateTableOptions} = this.props
const sortBy = {displayName: option.text, internalName: option.key}
const sortBy = {
displayName: option.text === option.key ? '' : option.text,
internalName: option.key,
visible: true,
}
handleUpdateTableOptions({...tableOptions, sortBy})
}
@ -118,32 +95,55 @@ export class TableOptions extends PureComponent<Props, {}> {
handleUpdateTableOptions({...tableOptions, fixFirstColumn})
}
public handleColumnRename = column => {
public handleFieldUpdate = field => {
const {handleUpdateTableOptions, tableOptions} = this.props
const {columnNames} = tableOptions
const updatedColumns = columnNames.map(
op => (op.internalName === column.internalName ? column : op)
const {fieldNames, sortBy} = tableOptions
const updatedFields = fieldNames.map(
f => (f.internalName === field.internalName ? field : f)
)
handleUpdateTableOptions({...tableOptions, columnNames: updatedColumns})
const updatedSortBy =
sortBy.internalName === field.internalName
? {...sortBy, displayName: field.displayName}
: sortBy
handleUpdateTableOptions({
...tableOptions,
fieldNames: updatedFields,
sortBy: updatedSortBy,
})
}
public componentWillMount() {
const {handleUpdateTableOptions, tableOptions} = this.props
handleUpdateTableOptions({
...tableOptions,
fieldNames: this.computedFieldNames,
})
}
public shouldComponentUpdate(nextProps) {
const {tableOptions, dataLabels} = this.props
const tableOptionsDifferent = !_.isEqual(
tableOptions,
nextProps.tableOptions
)
const dataLabelsDifferent = !_.isEqual(dataLabels, nextProps.dataLabels)
return tableOptionsDifferent || dataLabelsDifferent
}
public handleToggleTextWrapping = () => {}
public render() {
const {
tableOptions: {
timeFormat,
columnNames: columns,
verticalTimeAxis,
fixFirstColumn,
},
tableOptions: {timeFormat, fieldNames, verticalTimeAxis, fixFirstColumn},
onResetFocus,
tableOptions,
} = this.props
const tableSortByOptions = this.computedColumnNames.map(col => ({
key: col.internalName,
text: col.displayName || col.internalName,
const tableSortByOptions = this.computedFieldNames.map(field => ({
key: field.internalName,
text: field.displayName || field.internalName,
}))
return (
@ -163,22 +163,18 @@ export class TableOptions extends PureComponent<Props, {}> {
onToggleVerticalTimeAxis={this.handleToggleVerticalTimeAxis}
/>
<GraphOptionsSortBy
selected={tableOptions.sortBy || TIME_COLUMN_DEFAULT}
selected={tableOptions.sortBy || TIME_FIELD_DEFAULT}
sortByOptions={tableSortByOptions}
onChooseSortBy={this.handleChooseSortBy}
/>
<GraphOptionsTextWrapping
thresholdsListType="background"
onToggleTextWrapping={this.handleToggleTextWrapping}
/>
<GraphOptionsFixFirstColumn
fixed={fixFirstColumn}
onToggleFixFirstColumn={this.handleToggleFixFirstColumn}
/>
</div>
<GraphOptionsCustomizeColumns
columns={columns}
onColumnRename={this.handleColumnRename}
<GraphOptionsCustomizeFields
fields={fieldNames}
onFieldUpdate={this.handleFieldUpdate}
/>
<ThresholdsList showListHeading={true} onResetFocus={onResetFocus} />
<div className="form-group-wrapper graph-options-group">

View File

@ -22,6 +22,7 @@ const DashVisualization = (
staticLegend,
thresholdsListColors,
tableOptions,
setDataLabels,
},
{source: {links: {proxy}}}
) => {
@ -42,6 +43,7 @@ const DashVisualization = (
editQueryStatus={editQueryStatus}
resizerTopHeight={resizerTopHeight}
staticLegend={staticLegend}
setDataLabels={setDataLabels}
/>
</div>
</div>
@ -86,6 +88,7 @@ DashVisualization.propTypes = {
}).isRequired
),
staticLegend: bool,
setDataLabels: func,
}
DashVisualization.contextTypes = {

View File

@ -23,6 +23,7 @@ const RefreshingGraph = ({
cellID,
queries,
tableOptions,
setDataLabels,
templates,
timeRange,
cellHeight,
@ -98,6 +99,7 @@ const RefreshingGraph = ({
hoverTime={hoverTime}
onSetHoverTime={onSetHoverTime}
inView={inView}
setDataLabels={setDataLabels}
/>
)
}
@ -164,6 +166,7 @@ RefreshingGraph.propTypes = {
cellID: string,
inView: bool,
tableOptions: shape({}),
setDataLabels: func,
}
RefreshingGraph.defaultProps = {

View File

@ -2,91 +2,130 @@ import React, {Component} from 'react'
import PropTypes from 'prop-types'
import _ from 'lodash'
import classnames from 'classnames'
import isEmpty from 'lodash/isEmpty'
import {MultiGrid, ColumnSizer} from 'react-virtualized'
import moment from 'moment'
import {timeSeriesToTableGraph} from 'src/utils/timeSeriesToDygraph'
import {
NULL_COLUMN_INDEX,
NULL_ROW_INDEX,
NULL_ARRAY_INDEX,
NULL_HOVER_TIME,
TIME_FORMAT_DEFAULT,
TIME_COLUMN_DEFAULT,
TIME_FIELD_DEFAULT,
ASCENDING,
DESCENDING,
FIX_FIRST_COLUMN_DEFAULT,
VERTICAL_TIME_AXIS_DEFAULT,
calculateTimeColumnWidth,
} from 'src/shared/constants/tableGraph'
const DEFAULT_SORT = ASCENDING
import {generateThresholdsListHexs} from 'shared/constants/colorOperations'
const filterInvisibleColumns = (data, fieldNames) => {
const visibility = {}
const filteredData = data.map((row, i) => {
return row.filter((col, j) => {
if (i === 0) {
const foundField = fieldNames.find(field => field.internalName === col)
visibility[j] = foundField ? foundField.visible : true
}
return visibility[j]
})
})
return filteredData[0].length ? filteredData : [[]]
}
const processData = (
data,
sortFieldName,
direction,
verticalTimeAxis,
fieldNames
) => {
const sortIndex = _.indexOf(data[0], sortFieldName)
const sortedData = [
data[0],
..._.orderBy(_.drop(data, 1), sortIndex, [direction]),
]
const filteredData = filterInvisibleColumns(sortedData, fieldNames)
const processedData = verticalTimeAxis ? filteredData : _.unzip(filteredData)
return {processedData}
}
class TableGraph extends Component {
constructor(props) {
super(props)
this.state = {
data: [[]],
unzippedData: [[]],
hoveredColumnIndex: NULL_COLUMN_INDEX,
hoveredRowIndex: NULL_ROW_INDEX,
sortByColumnIndex: NULL_COLUMN_INDEX,
clickToSortFieldIndex: NULL_COLUMN_INDEX,
clicktoSortDirection: DEFAULT_SORT,
timeColumnWidth: calculateTimeColumnWidth(props.tableOptions.timeFormat),
processedData: [[]],
hoveredColumnIndex: NULL_ARRAY_INDEX,
hoveredRowIndex: NULL_ARRAY_INDEX,
sortField: '',
sortDirection: DEFAULT_SORT,
}
}
componentWillReceiveProps(nextProps) {
const {data} = timeSeriesToTableGraph(nextProps.data)
const {labels, data} = timeSeriesToTableGraph(nextProps.data)
if (_.isEmpty(data[0])) {
return
}
const {sortField, sortDirection} = this.state
const {
clickToSortFieldIndex,
clicktoSortDirection,
sortByColumnIndex,
} = this.state
const {tableOptions: {sortBy: {internalName}, timeFormat}} = nextProps
tableOptions: {
sortBy: {internalName},
fieldNames,
verticalTimeAxis,
timeFormat,
},
setDataLabels,
} = nextProps
if (timeFormat !== this.props.tableOptions.timeFormat) {
this.setState({
timeColumnWidth: calculateTimeColumnWidth(timeFormat),
})
}
if (
sortByColumnIndex === NULL_COLUMN_INDEX ||
_.get(this.props, ['tableOptions', 'sortBy', 'internalName'], '') !==
internalName
) {
const newSortByColumnIndex = _.indexOf(data[0], internalName)
const sortedData = [
data[0],
..._.orderBy(_.drop(data, 1), newSortByColumnIndex, [DEFAULT_SORT]),
]
this.setState({
data: sortedData,
unzippedData: _.unzip(sortedData),
sortByColumnIndex: newSortByColumnIndex,
clickToSortFieldIndex: NULL_COLUMN_INDEX,
clicktoSortDirection: DEFAULT_SORT,
})
return
if (setDataLabels) {
setDataLabels(labels)
}
const clicked = clickToSortFieldIndex !== NULL_COLUMN_INDEX
const sortIndex = clicked ? clickToSortFieldIndex : sortByColumnIndex
const direction = clicked ? clicktoSortDirection : DEFAULT_SORT
const sortedData = [
data[0],
..._.orderBy(_.drop(data, 1), sortIndex, [direction]),
]
let direction, sortFieldName
if (
_.isEmpty(sortField) ||
_.get(this.props, ['tableOptions', 'sortBy', 'internalName'], '') !==
_.get(nextProps, ['tableOptions', 'sortBy', 'internalName'], '')
) {
direction = DEFAULT_SORT
sortFieldName = internalName
} else {
direction = sortDirection
sortFieldName = sortField
}
const {processedData} = processData(
data,
sortFieldName,
direction,
verticalTimeAxis,
fieldNames
)
this.setState({
data: sortedData,
unzippedData: _.unzip(sortedData),
data,
processedData,
sortField: sortFieldName,
sortDirection: direction,
})
}
calcHoverTimeIndex = (data, hoverTime, verticalTimeAxis) => {
if (isEmpty(data) || hoverTime === NULL_HOVER_TIME) {
if (_.isEmpty(data) || hoverTime === NULL_HOVER_TIME) {
return undefined
}
if (verticalTimeAxis) {
@ -99,11 +138,14 @@ class TableGraph extends Component {
handleHover = (columnIndex, rowIndex) => () => {
const {onSetHoverTime, tableOptions: {verticalTimeAxis}} = this.props
const data = verticalTimeAxis ? this.state.data : this.state.unzippedData
const {data} = this.state
if (rowIndex === 0 && verticalTimeAxis) {
return
}
if (onSetHoverTime) {
const hoverTime = verticalTimeAxis
? data[rowIndex][0]
: data[0][columnIndex]
: data[columnIndex][0]
onSetHoverTime(hoverTime.toString())
}
this.setState({
@ -116,42 +158,37 @@ class TableGraph extends Component {
if (this.props.onSetHoverTime) {
this.props.onSetHoverTime(NULL_HOVER_TIME)
this.setState({
hoveredColumnIndex: NULL_COLUMN_INDEX,
hoveredRowIndex: NULL_ROW_INDEX,
hoveredColumnIndex: NULL_ARRAY_INDEX,
hoveredRowIndex: NULL_ARRAY_INDEX,
})
}
}
handleClickFieldName = (columnIndex, rowIndex) => () => {
handleClickFieldName = fieldName => () => {
const {tableOptions} = this.props
const {clickToSortFieldIndex, clicktoSortDirection, data} = this.state
const {data, sortField, sortDirection} = this.state
const verticalTimeAxis = _.get(tableOptions, 'verticalTimeAxis', true)
const newIndex = verticalTimeAxis ? columnIndex : rowIndex
const fieldNames = _.get(tableOptions, 'fieldNames', [TIME_FIELD_DEFAULT])
if (clickToSortFieldIndex === newIndex) {
const direction =
clicktoSortDirection === ASCENDING ? DESCENDING : ASCENDING
const sortedData = [
data[0],
..._.orderBy(_.drop(data, 1), clickToSortFieldIndex, [direction]),
]
this.setState({
data: sortedData,
unzippedData: _.unzip(sortedData),
clicktoSortDirection: direction,
})
return
let direction
if (fieldName === sortField) {
direction = sortDirection === ASCENDING ? DESCENDING : ASCENDING
} else {
direction = DEFAULT_SORT
}
const sortedData = [
data[0],
..._.orderBy(_.drop(data, 1), clickToSortFieldIndex, [DEFAULT_SORT]),
]
const {processedData} = processData(
data,
fieldName,
direction,
verticalTimeAxis,
fieldNames
)
this.setState({
data: sortedData,
unzippedData: _.unzip(sortedData),
clickToSortFieldIndex: newIndex,
clicktoSortDirection: DEFAULT_SORT,
processedData,
sortField: fieldName,
sortDirection: direction,
})
}
@ -159,9 +196,8 @@ class TableGraph extends Component {
const {index} = column
const {tableOptions, tableOptions: {verticalTimeAxis}} = this.props
const {timeColumnWidth} = this.state
const columnNames = _.get(tableOptions, 'columnNames', [
TIME_COLUMN_DEFAULT,
])
const columnNames = _.get(tableOptions, 'columnNames', [TIME_FIELD_DEFAULT])
return verticalTimeAxis && columnNames[index].internalName === 'time'
? timeColumnWidth
@ -169,28 +205,31 @@ class TableGraph extends Component {
}
cellRenderer = ({columnIndex, rowIndex, key, parent, style}) => {
const {hoveredColumnIndex, hoveredRowIndex} = this.state
const {hoveredColumnIndex, hoveredRowIndex, processedData} = this.state
const {tableOptions, colors} = this.props
const verticalTimeAxis = _.get(tableOptions, 'verticalTimeAxis', true)
const data = verticalTimeAxis ? this.state.data : this.state.unzippedData
const timeFormat = _.get(tableOptions, 'timeFormat', TIME_FORMAT_DEFAULT)
const columnNames = _.get(tableOptions, 'columnNames', [
TIME_COLUMN_DEFAULT,
])
const fixFirstColumn = _.get(
tableOptions,
'fixFirstColumn',
FIX_FIRST_COLUMN_DEFAULT
const {
timeFormat = TIME_FORMAT_DEFAULT,
verticalTimeAxis = VERTICAL_TIME_AXIS_DEFAULT,
fixFirstColumn = FIX_FIRST_COLUMN_DEFAULT,
fieldNames = [TIME_FIELD_DEFAULT],
} = tableOptions
const cellData = processedData[rowIndex][columnIndex]
const timeField = fieldNames.find(
field => field.internalName === TIME_FIELD_DEFAULT.internalName
)
const visibleTime = _.get(timeField, 'visible', true)
const isFixedRow = rowIndex === 0 && columnIndex > 0
const isFixedColumn = fixFirstColumn && rowIndex > 0 && columnIndex === 0
const isTimeData = verticalTimeAxis
? rowIndex > 0 && columnIndex === 0
: isFixedRow
const isTimeData =
visibleTime &&
(verticalTimeAxis ? rowIndex > 0 && columnIndex === 0 : isFixedRow)
const isFieldName = verticalTimeAxis ? rowIndex === 0 : columnIndex === 0
const isFixedCorner = rowIndex === 0 && columnIndex === 0
const dataIsNumerical = _.isNumber(data[rowIndex][columnIndex])
const dataIsNumerical = _.isNumber(cellData)
const isHighlightedRow =
rowIndex === parent.props.scrollToRow ||
(rowIndex === hoveredRowIndex && hoveredRowIndex !== 0)
@ -203,7 +242,7 @@ class TableGraph extends Component {
if (!isFixedRow && !isFixedColumn && !isFixedCorner) {
const {bgColor, textColor} = generateThresholdsListHexs({
colors,
lastValue: data[rowIndex][columnIndex],
lastValue: cellData,
cellType: 'table',
})
@ -224,25 +263,21 @@ class TableGraph extends Component {
'table-graph-cell__isFieldName': isFieldName,
})
const cellData = data[rowIndex][columnIndex]
const foundColumn = columnNames.find(
column => column.internalName === cellData
)
const columnName =
foundColumn && (foundColumn.displayName || foundColumn.internalName)
const foundField =
isFieldName && fieldNames.find(field => field.internalName === cellData)
const fieldName =
foundField && (foundField.displayName || foundField.internalName)
const cellContents = isTimeData
? `${moment(cellData).format(timeFormat)}`
: columnName || `${cellData}`
: fieldName || `${cellData}`
return (
<div
key={key}
style={cellStyle}
className={cellClass}
onClick={
isFieldName ? this.handleClickFieldName(columnIndex, rowIndex) : null
}
onClick={isFieldName ? this.handleClickFieldName(cellData) : null}
onMouseOver={this.handleHover(columnIndex, rowIndex)}
title={cellContents}
>
@ -253,33 +288,41 @@ class TableGraph extends Component {
render() {
const {
sortByColumnIndex,
clickToSortFieldIndex,
clicktoSortDirection,
hoveredColumnIndex,
hoveredRowIndex,
sortField,
sortDirection,
processedData,
data,
} = this.state
const {hoverTime, tableOptions, colors} = this.props
const verticalTimeAxis = _.get(tableOptions, 'verticalTimeAxis', true)
const data = verticalTimeAxis ? this.state.data : this.state.unzippedData
const {
verticalTimeAxis = VERTICAL_TIME_AXIS_DEFAULT,
fixFirstColumn = FIX_FIRST_COLUMN_DEFAULT,
} = tableOptions
const columnCount = _.get(processedData, ['0', 'length'], 0)
const rowCount = columnCount === 0 ? 0 : processedData.length
const columnCount = _.get(data, ['0', 'length'], 0)
const rowCount = data.length
const COLUMN_MIN_WIDTH = 98
const COLUMN_MAX_WIDTH = 1000
const ROW_HEIGHT = 30
const fixedColumnCount = fixFirstColumn && columnCount > 1 ? 1 : undefined
const tableWidth = _.get(this, ['gridContainer', 'clientWidth'], 0)
const tableHeight = _.get(this, ['gridContainer', 'clientHeight'], 0)
const hoverTimeIndex =
hoveredRowIndex === NULL_ROW_INDEX
hoveredRowIndex === NULL_ARRAY_INDEX
? this.calcHoverTimeIndex(data, hoverTime, verticalTimeAxis)
: hoveredRowIndex
const fixedColumnCount = tableOptions.fixFirstColumn ? 1 : undefined
const hoveringThisTable = hoveredColumnIndex !== NULL_COLUMN_INDEX
const scrollToRow =
!hoveringThisTable && verticalTimeAxis ? hoverTimeIndex : undefined
const hoveringThisTable = hoveredColumnIndex !== NULL_ARRAY_INDEX
const scrollToColumn =
!hoveringThisTable && !verticalTimeAxis ? hoverTimeIndex : undefined
const scrollToRow =
!hoveringThisTable && verticalTimeAxis ? hoverTimeIndex : undefined
return (
<div
@ -287,14 +330,14 @@ class TableGraph extends Component {
ref={gridContainer => (this.gridContainer = gridContainer)}
onMouseOut={this.handleMouseOut}
>
{!isEmpty(data) && (
{rowCount > 0 &&
<ColumnSizer
columnCount={columnCount}
columnMaxWidth={COLUMN_MAX_WIDTH}
columnMinWidth={COLUMN_MIN_WIDTH}
width={tableWidth}
>
{({getColumnWidth, registerChild}) => (
{({getColumnWidth, registerChild}) =>
<MultiGrid
ref={registerChild}
columnCount={columnCount}
@ -307,41 +350,47 @@ class TableGraph extends Component {
fixedRowCount={1}
enableFixedColumnScroll={true}
enableFixedRowScroll={true}
timeFormat={
tableOptions ? tableOptions.timeFormat : TIME_FORMAT_DEFAULT
}
columnNames={
tableOptions
? tableOptions.columnNames
: [TIME_COLUMN_DEFAULT]
}
scrollToRow={scrollToRow}
scrollToColumn={scrollToColumn}
verticalTimeAxis={verticalTimeAxis}
sortByColumnIndex={sortByColumnIndex}
clickToSortFieldIndex={clickToSortFieldIndex}
clicktoSortDirection={clicktoSortDirection}
sortField={sortField}
sortDirection={sortDirection}
cellRenderer={this.cellRenderer}
hoveredColumnIndex={hoveredColumnIndex}
hoveredRowIndex={hoveredRowIndex}
hoverTime={hoverTime}
colors={colors}
tableOptions={tableOptions}
classNameBottomRightGrid="table-graph--scroll-window"
/>
)}
</ColumnSizer>
)}
/>}
</ColumnSizer>}
</div>
)
}
}
const {arrayOf, number, shape, string, func} = PropTypes
const {arrayOf, bool, number, shape, string, func} = PropTypes
TableGraph.propTypes = {
cellHeight: number,
data: arrayOf(shape()),
tableOptions: shape({}),
tableOptions: shape({
timeFormat: string.isRequired,
verticalTimeAxis: bool.isRequired,
sortBy: shape({
internalName: string.isRequired,
displayName: string.isRequired,
visible: bool.isRequired,
}).isRequired,
wrapping: string.isRequired,
fieldNames: arrayOf(
shape({
internalName: string.isRequired,
displayName: string.isRequired,
visible: bool.isRequired,
})
).isRequired,
fixFirstColumn: bool,
}),
hoverTime: string,
onSetHoverTime: func,
colors: arrayOf(
@ -353,6 +402,7 @@ TableGraph.propTypes = {
value: string.isRequired,
}).isRequired
),
setDataLabels: func,
}
export default TableGraph

View File

@ -3,17 +3,23 @@ import _ from 'lodash'
export const NULL_COLUMN_INDEX = -1
export const NULL_ROW_INDEX = -1
export const NULL_ARRAY_INDEX = -1
export const NULL_HOVER_TIME = '0'
export const TIME_FORMAT_DEFAULT = 'MM/DD/YYYY HH:mm:ss.ss'
export const TIME_FORMAT_CUSTOM = 'Custom'
export const TIME_COLUMN_DEFAULT = {internalName: 'time', displayName: ''}
export const TIME_FIELD_DEFAULT = {
internalName: 'time',
displayName: '',
visible: true,
}
export const ASCENDING = 'asc'
export const DESCENDING = 'desc'
export const FIX_FIRST_COLUMN_DEFAULT = true
export const VERTICAL_TIME_AXIS_DEFAULT = true
export const CELL_HORIZONTAL_PADDING = 18
@ -30,11 +36,11 @@ export const FORMAT_OPTIONS = [
]
export const DEFAULT_TABLE_OPTIONS = {
verticalTimeAxis: true,
verticalTimeAxis: VERTICAL_TIME_AXIS_DEFAULT,
timeFormat: TIME_FORMAT_DEFAULT,
sortBy: TIME_COLUMN_DEFAULT,
sortBy: TIME_FIELD_DEFAULT,
wrapping: 'truncate',
columnNames: [TIME_COLUMN_DEFAULT],
fieldNames: [TIME_FIELD_DEFAULT],
fixFirstColumn: FIX_FIRST_COLUMN_DEFAULT,
}

View File

@ -13,8 +13,6 @@ export const THRESHOLD_TYPE_TEXT = 'text'
export const THRESHOLD_TYPE_BG = 'background'
export const THRESHOLD_TYPE_BASE = 'base'
export const TIME_FORMAT_DEFAULT = 'MM/DD/YYYY HH:mm:ss.ss'
export const THRESHOLD_COLORS = [
{
hex: '#BF3D5E',
@ -121,16 +119,6 @@ export const DEFAULT_THRESHOLDS_LIST_COLORS = [
},
]
export const DEFAULT_TABLE_COLORS = [
{
type: THRESHOLD_TYPE_BG,
hex: THRESHOLD_COLORS[18].hex,
id: THRESHOLD_TYPE_BASE,
name: THRESHOLD_COLORS[18].name,
value: 0,
},
]
export const validateThresholdsListColors = (colors, type) => {
if (!colors || colors.length === 0) {
return DEFAULT_THRESHOLDS_LIST_COLORS

View File

@ -207,7 +207,7 @@ $graph-type--gutter: 4px;
}
.gauge-controls--section,
.column-controls--section {
.field-controls--section {
width: 100%;
display: flex;
flex-wrap: nowrap;
@ -256,18 +256,34 @@ button.btn.btn-primary.btn-sm.gauge-controls--add-threshold {
flex: 1 0 0;
}
.column-controls--label {
.field-controls--group {
max-height: 235px;
overflow-y: scroll;
}
.field-controls--label, .field-controls--label-hidden {
@extend %gauge-controls-label-styles;
color: $g16-pearl;
background-color: $g4-onyx;
flex: 2 0 0;
> span {
padding-right: 5px;
&:hover {
cursor: pointer;
}
}
}
.column-controls-input {
.field-controls--label-hidden {
color: $g0-obsidian;
}
.field-controls-input {
flex: 1 0 0;
display: flex;
align-items: center;
}
/*
Cell Editor Overlay - Single-Stat Controls
------------------------------------------------------------------------------

View File

@ -178,8 +178,10 @@ export const timeSeriesToTableGraph = raw => {
const tableData = map(sortedTimeSeries, ({time, values}) => [time, ...values])
const data = tableData.length ? [labels, ...tableData] : [[]]
return {data}
return {
labels,
data,
}
}
export default timeSeriesToDygraph

View File

@ -1,62 +0,0 @@
import React from 'react'
import GraphOptionsCustomizableColumn from 'src/dashboards/components/GraphOptionsCustomizableColumn'
import InputClickToEdit from 'src/shared/components/InputClickToEdit'
import {shallow} from 'enzyme'
const setup = (override = {}) => {
const props = {
displayName: '',
internalName: '',
onColumnRename: () => {},
...override,
}
const wrapper = shallow(<GraphOptionsCustomizableColumn {...props} />)
const instance = wrapper.instance() as GraphOptionsCustomizableColumn
return {wrapper, props, instance}
}
describe('Dashboards.Components.GraphOptionsCustomizableColumn', () => {
describe('rendering', () => {
it('displays both label div and InputClickToEdit', () => {
const {wrapper} = setup()
const label = wrapper.find('div').last()
const input = wrapper.find(InputClickToEdit)
expect(label.exists()).toBe(true)
expect(input.exists()).toBe(true)
})
describe('when there is an internalName', () => {
it('displays the value', () => {
const internalName = 'test'
const {wrapper} = setup({internalName})
const label = wrapper.find('div').last()
expect(label.exists()).toBe(true)
expect(label.children().contains(internalName)).toBe(true)
})
})
})
describe('instance methods', () => {
describe('#handleColumnRename', () => {
it('calls onColumnRename once', () => {
const onColumnRename = jest.fn()
const internalName = 'test'
const {instance} = setup({onColumnRename, internalName})
const rename = 'TEST'
instance.handleColumnRename(rename)
expect(onColumnRename).toHaveBeenCalledTimes(1)
expect(onColumnRename).toHaveBeenCalledWith({
displayName: rename,
internalName,
})
})
})
})
})

View File

@ -0,0 +1,101 @@
import {shallow} from 'enzyme'
import React from 'react'
import GraphOptionsCustomizableField from 'src/dashboards/components/GraphOptionsCustomizableField'
import InputClickToEdit from 'src/shared/components/InputClickToEdit'
const setup = (override = {}) => {
const props = {
displayName: '',
internalName: '',
onFieldUpdate: () => {},
visible: true,
...override,
}
const wrapper = shallow(<GraphOptionsCustomizableField {...props} />)
const instance = wrapper.instance() as GraphOptionsCustomizableField
return {wrapper, props, instance}
}
describe('Dashboards.Components.GraphOptionsCustomizableField', () => {
describe('rendering', () => {
it('displays both label div and InputClickToEdit', () => {
const {wrapper} = setup()
const label = wrapper.find('div').last()
const input = wrapper.find(InputClickToEdit)
const icon = wrapper.find('span')
expect(label.exists()).toBe(true)
expect(icon.exists()).toBe(true)
expect(input.exists()).toBe(true)
})
describe('when there is an internalName', () => {
it('displays the value', () => {
const internalName = 'test'
const {wrapper} = setup({internalName})
const label = wrapper.find('div').last()
const icon = wrapper.find('span')
expect(label.exists()).toBe(true)
expect(label.children().contains(internalName)).toBe(true)
expect(icon.exists()).toBe(true)
})
})
describe('when visible is false', () => {
it('displays disabled inputClickToEdit', () => {
const visible = false
const {wrapper} = setup({visible})
const input = wrapper.find(InputClickToEdit)
expect(input.prop('disabled')).toBe(!visible)
})
})
})
describe('instance methods', () => {
describe('#handleFieldUpdate', () => {
it('calls onFieldUpdate once with internalName, new name, and visible', () => {
const onFieldUpdate = jest.fn()
const internalName = 'test'
const {instance, props: {visible}} = setup({
internalName,
onFieldUpdate,
})
const rename = 'TEST'
instance.handleFieldRename(rename)
expect(onFieldUpdate).toHaveBeenCalledTimes(1)
expect(onFieldUpdate).toHaveBeenCalledWith({
displayName: rename,
internalName,
visible,
})
})
})
describe('#handleToggleVisible', () => {
it('calls onFieldUpdate once with !visible, internalName, and displayName', () => {
const onFieldUpdate = jest.fn()
const visible = true
const {instance, props: {internalName, displayName}} = setup({
onFieldUpdate,
visible,
})
instance.handleToggleVisible()
expect(onFieldUpdate).toHaveBeenCalledTimes(1)
expect(onFieldUpdate).toHaveBeenCalledWith({
displayName,
internalName,
visible: !visible,
})
})
})
})
})

View File

@ -1,34 +0,0 @@
import React from 'react'
import GraphOptionsCustomizableColumn from 'src/dashboards/components/GraphOptionsCustomizableColumn'
import GraphOptionsCustomizeColumns from 'src/dashboards/components/GraphOptionsCustomizeColumns'
import {TIME_COLUMN_DEFAULT} from 'src/shared/constants/tableGraph'
import {shallow} from 'enzyme'
const setup = (override = {}) => {
const props = {
columns: [],
onColumnRename: () => {},
...override,
}
const wrapper = shallow(<GraphOptionsCustomizeColumns {...props} />)
return {wrapper, props}
}
describe('Dashboards.Components.GraphOptionsCustomizeColumns', () => {
describe('rendering', () => {
it('displays label and all columns passed in', () => {
const columns = [TIME_COLUMN_DEFAULT]
const {wrapper} = setup({columns})
const label = wrapper.find('label')
const customizableColumns = wrapper.find(GraphOptionsCustomizableColumn)
expect(label.exists()).toBe(true)
expect(customizableColumns.exists()).toBe(true)
expect(customizableColumns.length).toBe(columns.length)
})
})
})

View File

@ -0,0 +1,33 @@
import {shallow} from 'enzyme'
import React from 'react'
import GraphOptionsCustomizableField from 'src/dashboards/components/GraphOptionsCustomizableField'
import GraphOptionsCustomizeFields from 'src/dashboards/components/GraphOptionsCustomizeFields'
import {TIME_FIELD_DEFAULT} from 'src/shared/constants/tableGraph'
const setup = (override = {}) => {
const props = {
fields: [],
onFieldUpdate: () => {},
...override,
}
const wrapper = shallow(<GraphOptionsCustomizeFields {...props} />)
return {wrapper, props}
}
describe('Dashboards.Components.GraphOptionsCustomizableField', () => {
describe('rendering', () => {
it('displays label and all fields passed in', () => {
const fields = [TIME_FIELD_DEFAULT]
const {wrapper} = setup({fields})
const label = wrapper.find('label')
const CustomizableFields = wrapper.find(GraphOptionsCustomizableField)
expect(label.exists()).toBe(true)
expect(CustomizableFields.exists()).toBe(true)
expect(CustomizableFields.length).toBe(fields.length)
})
})
})

View File

@ -1,59 +1,26 @@
import {shallow} from 'enzyme'
import React from 'react'
import {TableOptions} from 'src/dashboards/components/TableOptions'
import GraphOptionsCustomizeColumns from 'src/dashboards/components/GraphOptionsCustomizeColumns'
import GraphOptionsCustomizeFields from 'src/dashboards/components/GraphOptionsCustomizeFields'
import GraphOptionsFixFirstColumn from 'src/dashboards/components/GraphOptionsFixFirstColumn'
import GraphOptionsSortBy from 'src/dashboards/components/GraphOptionsSortBy'
import GraphOptionsTextWrapping from 'src/dashboards/components/GraphOptionsTextWrapping'
import GraphOptionsTimeAxis from 'src/dashboards/components/GraphOptionsTimeAxis'
import GraphOptionsTimeFormat from 'src/dashboards/components/GraphOptionsTimeFormat'
import {TableOptions} from 'src/dashboards/components/TableOptions'
import FancyScrollbar from 'src/shared/components/FancyScrollbar'
import ThresholdsList from 'src/shared/components/ThresholdsList'
import ThresholdsListTypeToggle from 'src/shared/components/ThresholdsListTypeToggle'
import {shallow} from 'enzyme'
const queryConfigs = [
{
fields: [
{
alias: 'boom',
value: 'test',
},
{
alias: 'again',
value: 'again',
},
],
measurement: 'dev',
},
{
fields: [
{
alias: 'boom',
value: 'test',
},
{
alias: 'again',
value: 'again',
},
],
measurement: 'prod',
},
]
const defaultProps = {
dataLabels: [],
handleUpdateTableOptions: () => {},
onResetFocus: () => {},
queryConfigs,
tableOptions: {
columnNames: [],
fieldNames: [],
fixFirstColumn: true,
sortBy: {internalName: '', displayName: ''},
sortBy: {displayName: '', internalName: '', visible: true},
timeFormat: '',
verticalTimeAxis: true,
wrapping: '',
},
}
@ -70,89 +37,31 @@ const setup = (override = {}) => {
describe('Dashboards.Components.TableOptions', () => {
describe('getters', () => {
describe('computedColumnNames', () => {
it('returns the correct column names', () => {
const instance = new TableOptions(defaultProps)
const expected = [
{
displayName: '',
internalName: 'time',
},
{
displayName: '',
internalName: 'dev.boom',
},
{
displayName: '',
internalName: 'dev.again',
},
{
displayName: '',
internalName: 'prod.boom',
},
{
displayName: '',
internalName: 'prod.again',
},
]
expect(instance.computedColumnNames).toEqual(expected)
})
})
describe('computedColumnNames', () => {})
})
describe('rendering', () => {
it('should render all components', () => {
const qc = [
{
fields: [
{
alias: 'boom',
value: 'test',
},
],
measurement: 'dev',
},
]
const expectedSortOptions = [
{
key: 'time',
text: 'time',
},
{
key: 'dev.boom',
text: 'dev.boom',
},
]
const {wrapper} = setup({queryConfigs: qc})
const {wrapper} = setup()
const fancyScrollbar = wrapper.find(FancyScrollbar)
const graphOptionsTimeFormat = wrapper.find(GraphOptionsTimeFormat)
const graphOptionsTimeAxis = wrapper.find(GraphOptionsTimeAxis)
const graphOptionsSortBy = wrapper.find(GraphOptionsSortBy)
const graphOptionsTextWrapping = wrapper.find(GraphOptionsTextWrapping)
const graphOptionsFixFirstColumn = wrapper.find(
GraphOptionsFixFirstColumn
)
const graphOptionsCustomizeColumns = wrapper.find(
GraphOptionsCustomizeColumns
const graphOptionsCustomizeFields = wrapper.find(
GraphOptionsCustomizeFields
)
const thresholdsList = wrapper.find(ThresholdsList)
const thresholdsListTypeToggle = wrapper.find(ThresholdsListTypeToggle)
expect(graphOptionsSortBy.props().sortByOptions).toEqual(
expectedSortOptions
)
expect(fancyScrollbar.exists()).toBe(true)
expect(graphOptionsTimeFormat.exists()).toBe(true)
expect(graphOptionsTimeAxis.exists()).toBe(true)
expect(graphOptionsSortBy.exists()).toBe(true)
expect(graphOptionsTextWrapping.exists()).toBe(true)
expect(graphOptionsFixFirstColumn.exists()).toBe(true)
expect(graphOptionsCustomizeColumns.exists()).toBe(true)
expect(graphOptionsCustomizeFields.exists()).toBe(true)
expect(thresholdsList.exists()).toBe(true)
expect(thresholdsListTypeToggle.exists()).toBe(true)
})