Merge branch 'master' into enhancement/introduce-overlay-layer

pull/10616/head
Alex Paxton 2018-05-01 14:16:50 -07:00 committed by GitHub
commit bb37a872e9
65 changed files with 2011 additions and 1246 deletions

View File

@ -12,6 +12,7 @@
1. [#3214](https://github.com/influxdata/chronograf/pull/3214): Remove extra click when creating dashboard cell
1. [#3256](https://github.com/influxdata/chronograf/pull/3256): Reduce font sizes in dashboards for increased space efficiency
1. [#3320](https://github.com/influxdata/chronograf/pull/3320): Add overlay animation to Template Variables Manager
1. [#3245](https://github.com/influxdata/chronograf/pull/3245): Display 'no results' on cells without results
### Bug Fixes

View File

@ -344,7 +344,6 @@ 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 *RenamableField `protobuf:"bytes,3,opt,name=sortBy" json:"sortBy,omitempty"`
Wrapping string `protobuf:"bytes,4,opt,name=wrapping,proto3" json:"wrapping,omitempty"`
@ -357,13 +356,6 @@ func (m *TableOptions) String() string { return proto.CompactTextStri
func (*TableOptions) ProtoMessage() {}
func (*TableOptions) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{3} }
func (m *TableOptions) GetTimeFormat() string {
if m != nil {
return m.TimeFormat
}
return ""
}
func (m *TableOptions) GetVerticalTimeAxis() bool {
if m != nil {
return m.VerticalTimeAxis

View File

@ -273,24 +273,27 @@ func MarshalDashboard(d chronograf.Dashboard) ([]byte, error) {
Visible: c.TableOptions.SortBy.Visible,
}
fieldNames := make([]*RenamableField, len(c.TableOptions.FieldNames))
for i, field := range c.TableOptions.FieldNames {
fieldNames[i] = &RenamableField{
tableOptions := &TableOptions{
VerticalTimeAxis: c.TableOptions.VerticalTimeAxis,
SortBy: sortBy,
Wrapping: c.TableOptions.Wrapping,
FixFirstColumn: c.TableOptions.FixFirstColumn,
}
decimalPlaces := &DecimalPlaces{
IsEnforced: c.DecimalPlaces.IsEnforced,
Digits: c.DecimalPlaces.Digits,
}
fieldOptions := make([]*RenamableField, len(c.FieldOptions))
for i, field := range c.FieldOptions {
fieldOptions[i] = &RenamableField{
InternalName: field.InternalName,
DisplayName: field.DisplayName,
Visible: field.Visible,
}
}
tableOptions := &TableOptions{
TimeFormat: c.TableOptions.TimeFormat,
VerticalTimeAxis: c.TableOptions.VerticalTimeAxis,
SortBy: sortBy,
Wrapping: c.TableOptions.Wrapping,
FieldNames: fieldNames,
FixFirstColumn: c.TableOptions.FixFirstColumn,
}
cells[i] = &DashboardCell{
ID: c.ID,
X: c.X,
@ -306,7 +309,10 @@ func MarshalDashboard(d chronograf.Dashboard) ([]byte, error) {
Type: c.Legend.Type,
Orientation: c.Legend.Orientation,
},
TableOptions: tableOptions,
TableOptions: tableOptions,
FieldOptions: fieldOptions,
TimeFormat: c.TimeFormat,
DecimalPlaces: decimalPlaces,
}
}
templates := make([]*Template, len(d.Templates))
@ -442,20 +448,26 @@ func UnmarshalDashboard(data []byte, d *chronograf.Dashboard) error {
sortBy.Visible = c.TableOptions.SortBy.Visible
}
tableOptions.SortBy = sortBy
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.FieldNames = fieldNames
tableOptions.TimeFormat = c.TableOptions.TimeFormat
tableOptions.VerticalTimeAxis = c.TableOptions.VerticalTimeAxis
tableOptions.Wrapping = c.TableOptions.Wrapping
tableOptions.FixFirstColumn = c.TableOptions.FixFirstColumn
}
fieldOptions := make([]chronograf.RenamableField, len(c.FieldOptions))
for i, field := range c.FieldOptions {
fieldOptions[i] = chronograf.RenamableField{}
fieldOptions[i].InternalName = field.InternalName
fieldOptions[i].DisplayName = field.DisplayName
fieldOptions[i].Visible = field.Visible
}
decimalPlaces := chronograf.DecimalPlaces{}
if c.DecimalPlaces != nil {
decimalPlaces.IsEnforced = c.DecimalPlaces.IsEnforced
decimalPlaces.Digits = c.DecimalPlaces.Digits
} else {
decimalPlaces.IsEnforced = false
decimalPlaces.Digits = 3
}
// FIXME: this is merely for legacy cells and
@ -466,18 +478,21 @@ func UnmarshalDashboard(data []byte, d *chronograf.Dashboard) error {
}
cells[i] = chronograf.DashboardCell{
ID: c.ID,
X: c.X,
Y: c.Y,
W: c.W,
H: c.H,
Name: c.Name,
Queries: queries,
Type: cellType,
Axes: axes,
CellColors: colors,
Legend: legend,
TableOptions: tableOptions,
ID: c.ID,
X: c.X,
Y: c.Y,
W: c.W,
H: c.H,
Name: c.Name,
Queries: queries,
Type: cellType,
Axes: axes,
CellColors: colors,
Legend: legend,
TableOptions: tableOptions,
FieldOptions: fieldOptions,
TimeFormat: c.TimeFormat,
DecimalPlaces: decimalPlaces,
}
}

View File

@ -11,6 +11,7 @@ It has these top-level messages:
Source
Dashboard
DashboardCell
DecimalPlaces
TableOptions
RenamableField
Color
@ -220,18 +221,21 @@ func (m *Dashboard) GetOrganization() string {
}
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]*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"`
Colors []*Color `protobuf:"bytes,10,rep,name=colors" json:"colors,omitempty"`
Legend *Legend `protobuf:"bytes,11,opt,name=legend" json:"legend,omitempty"`
TableOptions *TableOptions `protobuf:"bytes,12,opt,name=tableOptions" json:"tableOptions,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]*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"`
Colors []*Color `protobuf:"bytes,10,rep,name=colors" json:"colors,omitempty"`
Legend *Legend `protobuf:"bytes,11,opt,name=legend" json:"legend,omitempty"`
TableOptions *TableOptions `protobuf:"bytes,12,opt,name=tableOptions" json:"tableOptions,omitempty"`
FieldOptions []*RenamableField `protobuf:"bytes,13,rep,name=fieldOptions" json:"fieldOptions,omitempty"`
TimeFormat string `protobuf:"bytes,14,opt,name=timeFormat,proto3" json:"timeFormat,omitempty"`
DecimalPlaces *DecimalPlaces `protobuf:"bytes,15,opt,name=decimalPlaces" json:"decimalPlaces,omitempty"`
}
func (m *DashboardCell) Reset() { *m = DashboardCell{} }
@ -323,27 +327,63 @@ func (m *DashboardCell) GetTableOptions() *TableOptions {
return nil
}
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 *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 *DashboardCell) GetFieldOptions() []*RenamableField {
if m != nil {
return m.FieldOptions
}
return nil
}
func (m *TableOptions) Reset() { *m = TableOptions{} }
func (m *TableOptions) String() string { return proto.CompactTextString(m) }
func (*TableOptions) ProtoMessage() {}
func (*TableOptions) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{3} }
func (m *TableOptions) GetTimeFormat() string {
func (m *DashboardCell) GetTimeFormat() string {
if m != nil {
return m.TimeFormat
}
return ""
}
func (m *DashboardCell) GetDecimalPlaces() *DecimalPlaces {
if m != nil {
return m.DecimalPlaces
}
return nil
}
type DecimalPlaces struct {
IsEnforced bool `protobuf:"varint,1,opt,name=isEnforced,proto3" json:"isEnforced,omitempty"`
Digits int32 `protobuf:"varint,2,opt,name=digits,proto3" json:"digits,omitempty"`
}
func (m *DecimalPlaces) Reset() { *m = DecimalPlaces{} }
func (m *DecimalPlaces) String() string { return proto.CompactTextString(m) }
func (*DecimalPlaces) ProtoMessage() {}
func (*DecimalPlaces) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{3} }
func (m *DecimalPlaces) GetIsEnforced() bool {
if m != nil {
return m.IsEnforced
}
return false
}
func (m *DecimalPlaces) GetDigits() int32 {
if m != nil {
return m.Digits
}
return 0
}
type TableOptions struct {
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"`
FixFirstColumn bool `protobuf:"varint,6,opt,name=fixFirstColumn,proto3" json:"fixFirstColumn,omitempty"`
}
func (m *TableOptions) Reset() { *m = TableOptions{} }
func (m *TableOptions) String() string { return proto.CompactTextString(m) }
func (*TableOptions) ProtoMessage() {}
func (*TableOptions) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{4} }
func (m *TableOptions) GetVerticalTimeAxis() bool {
if m != nil {
return m.VerticalTimeAxis
@ -365,13 +405,6 @@ func (m *TableOptions) GetWrapping() string {
return ""
}
func (m *TableOptions) GetFieldNames() []*RenamableField {
if m != nil {
return m.FieldNames
}
return nil
}
func (m *TableOptions) GetFixFirstColumn() bool {
if m != nil {
return m.FixFirstColumn
@ -388,7 +421,7 @@ type RenamableField struct {
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 (*RenamableField) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{5} }
func (m *RenamableField) GetInternalName() string {
if m != nil {
@ -422,7 +455,7 @@ type Color struct {
func (m *Color) Reset() { *m = Color{} }
func (m *Color) String() string { return proto.CompactTextString(m) }
func (*Color) ProtoMessage() {}
func (*Color) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{5} }
func (*Color) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{6} }
func (m *Color) GetID() string {
if m != nil {
@ -467,7 +500,7 @@ type Legend struct {
func (m *Legend) Reset() { *m = Legend{} }
func (m *Legend) String() string { return proto.CompactTextString(m) }
func (*Legend) ProtoMessage() {}
func (*Legend) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{6} }
func (*Legend) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{7} }
func (m *Legend) GetType() string {
if m != nil {
@ -496,7 +529,7 @@ type Axis struct {
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{7} }
func (*Axis) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{8} }
func (m *Axis) GetLegacyBounds() []int64 {
if m != nil {
@ -559,7 +592,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{8} }
func (*Template) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{9} }
func (m *Template) GetID() string {
if m != nil {
@ -612,7 +645,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{9} }
func (*TemplateValue) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{10} }
func (m *TemplateValue) GetType() string {
if m != nil {
@ -647,7 +680,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{10} }
func (*TemplateQuery) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{11} }
func (m *TemplateQuery) GetCommand() string {
if m != nil {
@ -706,7 +739,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{11} }
func (*Server) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{12} }
func (m *Server) GetID() int64 {
if m != nil {
@ -782,7 +815,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{12} }
func (*Layout) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{13} }
func (m *Layout) GetID() string {
if m != nil {
@ -836,7 +869,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{13} }
func (*Cell) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{14} }
func (m *Cell) GetX() int32 {
if m != nil {
@ -930,7 +963,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{14} }
func (*Query) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{15} }
func (m *Query) GetCommand() string {
if m != nil {
@ -1004,7 +1037,7 @@ type TimeShift struct {
func (m *TimeShift) Reset() { *m = TimeShift{} }
func (m *TimeShift) String() string { return proto.CompactTextString(m) }
func (*TimeShift) ProtoMessage() {}
func (*TimeShift) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{15} }
func (*TimeShift) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{16} }
func (m *TimeShift) GetLabel() string {
if m != nil {
@ -1035,7 +1068,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{16} }
func (*Range) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{17} }
func (m *Range) GetUpper() int64 {
if m != nil {
@ -1061,7 +1094,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{17} }
func (*AlertRule) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{18} }
func (m *AlertRule) GetID() string {
if m != nil {
@ -1103,7 +1136,7 @@ type User struct {
func (m *User) Reset() { *m = User{} }
func (m *User) String() string { return proto.CompactTextString(m) }
func (*User) ProtoMessage() {}
func (*User) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{18} }
func (*User) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{19} }
func (m *User) GetID() uint64 {
if m != nil {
@ -1155,7 +1188,7 @@ type Role struct {
func (m *Role) Reset() { *m = Role{} }
func (m *Role) String() string { return proto.CompactTextString(m) }
func (*Role) ProtoMessage() {}
func (*Role) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{19} }
func (*Role) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{20} }
func (m *Role) GetOrganization() string {
if m != nil {
@ -1182,7 +1215,7 @@ type Mapping struct {
func (m *Mapping) Reset() { *m = Mapping{} }
func (m *Mapping) String() string { return proto.CompactTextString(m) }
func (*Mapping) ProtoMessage() {}
func (*Mapping) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{20} }
func (*Mapping) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{21} }
func (m *Mapping) GetProvider() string {
if m != nil {
@ -1228,7 +1261,7 @@ type Organization struct {
func (m *Organization) Reset() { *m = Organization{} }
func (m *Organization) String() string { return proto.CompactTextString(m) }
func (*Organization) ProtoMessage() {}
func (*Organization) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{21} }
func (*Organization) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{22} }
func (m *Organization) GetID() string {
if m != nil {
@ -1258,7 +1291,7 @@ type Config struct {
func (m *Config) Reset() { *m = Config{} }
func (m *Config) String() string { return proto.CompactTextString(m) }
func (*Config) ProtoMessage() {}
func (*Config) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{22} }
func (*Config) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{23} }
func (m *Config) GetAuth() *AuthConfig {
if m != nil {
@ -1274,7 +1307,7 @@ type AuthConfig struct {
func (m *AuthConfig) Reset() { *m = AuthConfig{} }
func (m *AuthConfig) String() string { return proto.CompactTextString(m) }
func (*AuthConfig) ProtoMessage() {}
func (*AuthConfig) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{23} }
func (*AuthConfig) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{24} }
func (m *AuthConfig) GetSuperAdminNewUsers() bool {
if m != nil {
@ -1291,7 +1324,7 @@ type BuildInfo struct {
func (m *BuildInfo) Reset() { *m = BuildInfo{} }
func (m *BuildInfo) String() string { return proto.CompactTextString(m) }
func (*BuildInfo) ProtoMessage() {}
func (*BuildInfo) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{24} }
func (*BuildInfo) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{25} }
func (m *BuildInfo) GetVersion() string {
if m != nil {
@ -1311,6 +1344,7 @@ func init() {
proto.RegisterType((*Source)(nil), "internal.Source")
proto.RegisterType((*Dashboard)(nil), "internal.Dashboard")
proto.RegisterType((*DashboardCell)(nil), "internal.DashboardCell")
proto.RegisterType((*DecimalPlaces)(nil), "internal.DecimalPlaces")
proto.RegisterType((*TableOptions)(nil), "internal.TableOptions")
proto.RegisterType((*RenamableField)(nil), "internal.RenamableField")
proto.RegisterType((*Color)(nil), "internal.Color")
@ -1338,105 +1372,110 @@ func init() {
func init() { proto.RegisterFile("internal.proto", fileDescriptorInternal) }
var fileDescriptorInternal = []byte{
// 1599 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x58, 0xcd, 0x6f, 0xdb, 0xc8,
0x15, 0x07, 0x45, 0x51, 0x12, 0x9f, 0x1c, 0xd7, 0x98, 0x1a, 0x09, 0x9b, 0x16, 0x85, 0x4a, 0xf4,
0x43, 0xfd, 0x88, 0x1b, 0x28, 0x28, 0x10, 0x04, 0x6d, 0x00, 0xd9, 0x6a, 0x52, 0x37, 0x4e, 0xec,
0x8c, 0x6c, 0xf7, 0x54, 0x04, 0x23, 0x69, 0x24, 0x11, 0xa1, 0x48, 0x76, 0x48, 0xda, 0x62, 0xcf,
0x3d, 0xf5, 0x8f, 0x28, 0x50, 0xa0, 0xfd, 0x07, 0x8a, 0x5e, 0xf6, 0xb4, 0xf7, 0xfd, 0x87, 0x76,
0x8f, 0x8b, 0x37, 0x1f, 0x24, 0x65, 0x29, 0x41, 0x16, 0x58, 0xec, 0x6d, 0x7e, 0xef, 0x3d, 0xbd,
0x99, 0xf7, 0xfd, 0x28, 0xd8, 0x0f, 0xa2, 0x8c, 0x8b, 0x88, 0x85, 0x47, 0x89, 0x88, 0xb3, 0x98,
0x74, 0x0c, 0xf6, 0xff, 0x61, 0x43, 0x6b, 0x1c, 0xe7, 0x62, 0xca, 0xc9, 0x3e, 0x34, 0x4e, 0x47,
0x9e, 0xd5, 0xb3, 0xfa, 0x36, 0x6d, 0x9c, 0x8e, 0x08, 0x81, 0xe6, 0x1b, 0xb6, 0xe2, 0x5e, 0xa3,
0x67, 0xf5, 0x5d, 0x2a, 0xcf, 0x48, 0xbb, 0x2c, 0x12, 0xee, 0xd9, 0x8a, 0x86, 0x67, 0xf2, 0x10,
0x3a, 0x57, 0x29, 0x6a, 0x5b, 0x71, 0xaf, 0x29, 0xe9, 0x25, 0x46, 0xde, 0x05, 0x4b, 0xd3, 0xdb,
0x58, 0xcc, 0x3c, 0x47, 0xf1, 0x0c, 0x26, 0x07, 0x60, 0x5f, 0xd1, 0x33, 0xaf, 0x25, 0xc9, 0x78,
0x24, 0x1e, 0xb4, 0x47, 0x7c, 0xce, 0xf2, 0x30, 0xf3, 0xda, 0x3d, 0xab, 0xdf, 0xa1, 0x06, 0xa2,
0x9e, 0x4b, 0x1e, 0xf2, 0x85, 0x60, 0x73, 0xaf, 0xa3, 0xf4, 0x18, 0x4c, 0x8e, 0x80, 0x9c, 0x46,
0x29, 0x9f, 0xe6, 0x82, 0x8f, 0xdf, 0x07, 0xc9, 0x35, 0x17, 0xc1, 0xbc, 0xf0, 0x5c, 0xa9, 0x60,
0x07, 0x07, 0x6f, 0x79, 0xcd, 0x33, 0x86, 0x77, 0x83, 0x54, 0x65, 0x20, 0xf1, 0x61, 0x6f, 0xbc,
0x64, 0x82, 0xcf, 0xc6, 0x7c, 0x2a, 0x78, 0xe6, 0x75, 0x25, 0x7b, 0x83, 0x86, 0x32, 0xe7, 0x62,
0xc1, 0xa2, 0xe0, 0xef, 0x2c, 0x0b, 0xe2, 0xc8, 0xdb, 0x53, 0x32, 0x75, 0x1a, 0x7a, 0x89, 0xc6,
0x21, 0xf7, 0xee, 0x29, 0x2f, 0xe1, 0x99, 0xfc, 0x08, 0x5c, 0x6d, 0x0c, 0xbd, 0xf0, 0xf6, 0x25,
0xa3, 0x22, 0xf8, 0xff, 0xb7, 0xc0, 0x1d, 0xb1, 0x74, 0x39, 0x89, 0x99, 0x98, 0x7d, 0x52, 0x24,
0x1e, 0x81, 0x33, 0xe5, 0x61, 0x98, 0x7a, 0x76, 0xcf, 0xee, 0x77, 0x07, 0x0f, 0x8e, 0xca, 0x10,
0x97, 0x7a, 0x4e, 0x78, 0x18, 0x52, 0x25, 0x45, 0x1e, 0x83, 0x9b, 0xf1, 0x55, 0x12, 0xb2, 0x8c,
0xa7, 0x5e, 0x53, 0xfe, 0x84, 0x54, 0x3f, 0xb9, 0xd4, 0x2c, 0x5a, 0x09, 0x6d, 0x19, 0xea, 0x6c,
0x1b, 0xea, 0x7f, 0x66, 0xc3, 0xbd, 0x8d, 0xeb, 0xc8, 0x1e, 0x58, 0x6b, 0xf9, 0x72, 0x87, 0x5a,
0x6b, 0x44, 0x85, 0x7c, 0xb5, 0x43, 0xad, 0x02, 0xd1, 0xad, 0xcc, 0x1c, 0x87, 0x5a, 0xb7, 0x88,
0x96, 0x32, 0x5f, 0x1c, 0x6a, 0x2d, 0xc9, 0x2f, 0xa1, 0xfd, 0xb7, 0x9c, 0x8b, 0x80, 0xa7, 0x9e,
0x23, 0x5f, 0xf7, 0xbd, 0xea, 0x75, 0x6f, 0x73, 0x2e, 0x0a, 0x6a, 0xf8, 0xe8, 0x0d, 0x99, 0x6b,
0x2a, 0x71, 0xe4, 0x19, 0x69, 0x19, 0xe6, 0x65, 0x5b, 0xd1, 0xf0, 0xac, 0xbd, 0xa8, 0xb2, 0x05,
0xbd, 0xf8, 0x3b, 0x68, 0xb2, 0x35, 0x4f, 0x3d, 0x57, 0xea, 0xff, 0xc9, 0x07, 0x1c, 0x76, 0x34,
0x5c, 0xf3, 0xf4, 0x8f, 0x51, 0x26, 0x0a, 0x2a, 0xc5, 0xc9, 0x2f, 0xa0, 0x35, 0x8d, 0xc3, 0x58,
0xa4, 0x1e, 0xdc, 0x7d, 0xd8, 0x09, 0xd2, 0xa9, 0x66, 0x93, 0x3e, 0xb4, 0x42, 0xbe, 0xe0, 0xd1,
0x4c, 0xe6, 0x4d, 0x77, 0x70, 0x50, 0x09, 0x9e, 0x49, 0x3a, 0xd5, 0x7c, 0xf2, 0x0c, 0xf6, 0x32,
0x36, 0x09, 0xf9, 0x79, 0x82, 0x5e, 0x4c, 0x65, 0x0e, 0x75, 0x07, 0xf7, 0x6b, 0xf1, 0xa8, 0x71,
0xe9, 0x86, 0xec, 0xc3, 0x97, 0xe0, 0x96, 0x2f, 0xc4, 0x12, 0x7a, 0xcf, 0x0b, 0xe9, 0x6f, 0x97,
0xe2, 0x91, 0xfc, 0x14, 0x9c, 0x1b, 0x16, 0xe6, 0x2a, 0x57, 0xba, 0x83, 0xfd, 0x4a, 0xe7, 0x70,
0x1d, 0xa4, 0x54, 0x31, 0x9f, 0x35, 0x9e, 0x5a, 0xfe, 0x3f, 0x1b, 0xb0, 0x57, 0xbf, 0x87, 0xfc,
0x18, 0x20, 0x0b, 0x56, 0xfc, 0x45, 0x2c, 0x56, 0x2c, 0xd3, 0x3a, 0x6b, 0x14, 0xf2, 0x2b, 0x38,
0xb8, 0xe1, 0x22, 0x0b, 0xa6, 0x2c, 0xbc, 0x0c, 0x56, 0x1c, 0xf5, 0xc9, 0x5b, 0x3a, 0x74, 0x8b,
0x4e, 0x1e, 0x43, 0x2b, 0x8d, 0x45, 0x76, 0x5c, 0xc8, 0x78, 0x77, 0x07, 0x5e, 0xf5, 0x0e, 0xca,
0x23, 0xb6, 0xc2, 0x7b, 0x5f, 0x04, 0x3c, 0x9c, 0x51, 0x2d, 0x87, 0x15, 0x7e, 0x2b, 0x58, 0x92,
0x04, 0xd1, 0xc2, 0x74, 0x11, 0x83, 0xc9, 0x53, 0x80, 0x39, 0x0a, 0x63, 0xe2, 0x9b, 0xfc, 0xf8,
0xb0, 0xc6, 0x9a, 0x2c, 0xf9, 0x39, 0xec, 0xcf, 0x83, 0xf5, 0x8b, 0x40, 0xa4, 0xd9, 0x49, 0x1c,
0xe6, 0xab, 0x48, 0x66, 0x4d, 0x87, 0xde, 0xa1, 0xfa, 0x09, 0xec, 0x6f, 0x6a, 0xc1, 0xf4, 0x37,
0x17, 0xc8, 0xda, 0x53, 0xfe, 0xd8, 0xa0, 0x91, 0x1e, 0x74, 0x67, 0x41, 0x9a, 0x84, 0xac, 0xa8,
0x95, 0x67, 0x9d, 0x84, 0xbd, 0xe6, 0x26, 0x48, 0x83, 0x49, 0xa8, 0x5a, 0x66, 0x87, 0x1a, 0xe8,
0x2f, 0xc0, 0x91, 0xe9, 0x53, 0x2b, 0x76, 0xd7, 0x14, 0xbb, 0x6c, 0xb1, 0x8d, 0x5a, 0x8b, 0x3d,
0x00, 0xfb, 0x4f, 0x7c, 0xad, 0xbb, 0x2e, 0x1e, 0xcb, 0x96, 0xd0, 0xac, 0xb5, 0x84, 0x43, 0x70,
0xae, 0x65, 0xec, 0x55, 0xa9, 0x2a, 0xe0, 0x3f, 0x87, 0x96, 0x4a, 0xbf, 0x52, 0xb3, 0x55, 0xd3,
0xdc, 0x83, 0xee, 0xb9, 0x08, 0x78, 0x94, 0xa9, 0x22, 0xd7, 0x26, 0xd4, 0x48, 0xfe, 0xff, 0x2c,
0x68, 0xca, 0x98, 0xfa, 0xb0, 0x17, 0xf2, 0x05, 0x9b, 0x16, 0xc7, 0x71, 0x1e, 0xcd, 0x52, 0xcf,
0xea, 0xd9, 0x7d, 0x9b, 0x6e, 0xd0, 0xc8, 0x7d, 0x68, 0x4d, 0x14, 0xb7, 0xd1, 0xb3, 0xfb, 0x2e,
0xd5, 0x08, 0x9f, 0x16, 0xb2, 0x09, 0x0f, 0xb5, 0x09, 0x0a, 0xa0, 0x74, 0x22, 0xf8, 0x3c, 0x58,
0x6b, 0x33, 0x34, 0x42, 0x7a, 0x9a, 0xcf, 0x91, 0xae, 0x2c, 0xd1, 0x08, 0x0d, 0x98, 0xb0, 0xb4,
0xac, 0x7c, 0x3c, 0xa3, 0xe6, 0x74, 0xca, 0x42, 0x53, 0xfa, 0x0a, 0xf8, 0x9f, 0x5b, 0x38, 0x30,
0x54, 0x2b, 0xdb, 0xf2, 0xf0, 0x0f, 0xa0, 0x83, 0x6d, 0xee, 0xdd, 0x0d, 0x13, 0xda, 0xe0, 0x36,
0xe2, 0x6b, 0x26, 0xc8, 0x6f, 0xa1, 0x25, 0x2b, 0x64, 0x47, 0x5b, 0x35, 0xea, 0xa4, 0x57, 0xa9,
0x16, 0x2b, 0x1b, 0x4f, 0xb3, 0xd6, 0x78, 0x4a, 0x63, 0x9d, 0xba, 0xb1, 0x8f, 0xc0, 0xc1, 0x0e,
0x56, 0xc8, 0xd7, 0xef, 0xd4, 0xac, 0xfa, 0x9c, 0x92, 0xf2, 0xaf, 0xe0, 0xde, 0xc6, 0x8d, 0xe5,
0x4d, 0xd6, 0xe6, 0x4d, 0x55, 0xb5, 0xbb, 0xba, 0xba, 0xb1, 0x94, 0x52, 0x1e, 0xf2, 0x69, 0xc6,
0x67, 0x3a, 0xeb, 0x4a, 0xec, 0xff, 0xdb, 0xaa, 0xf4, 0xca, 0xfb, 0x30, 0x45, 0xa7, 0xf1, 0x6a,
0xc5, 0xa2, 0x99, 0x56, 0x6d, 0x20, 0xfa, 0x6d, 0x36, 0xd1, 0xaa, 0x1b, 0xb3, 0x09, 0x62, 0x91,
0xe8, 0x08, 0x36, 0x44, 0x82, 0xb9, 0xb3, 0xe2, 0x2c, 0xcd, 0x05, 0x5f, 0xf1, 0x28, 0xd3, 0x2e,
0xa8, 0x93, 0xc8, 0x03, 0x68, 0x67, 0x6c, 0xf1, 0x0e, 0x7b, 0x94, 0x8e, 0x64, 0xc6, 0x16, 0xaf,
0x78, 0x41, 0x7e, 0x08, 0xae, 0xac, 0x52, 0xc9, 0x52, 0xe1, 0xec, 0x48, 0xc2, 0x2b, 0x5e, 0xf8,
0x5f, 0x59, 0xd0, 0x1a, 0x73, 0x71, 0xc3, 0xc5, 0x27, 0x4d, 0xc2, 0xfa, 0xfe, 0x61, 0x7f, 0x64,
0xff, 0x68, 0xee, 0xde, 0x3f, 0x9c, 0x6a, 0xff, 0x38, 0x04, 0x67, 0x2c, 0xa6, 0xa7, 0x23, 0xf9,
0x22, 0x9b, 0x2a, 0x80, 0xd9, 0x38, 0x9c, 0x66, 0xc1, 0x0d, 0xd7, 0x4b, 0x89, 0x46, 0x5b, 0x03,
0xb2, 0xb3, 0x63, 0x13, 0xf8, 0x86, 0xbb, 0x89, 0xff, 0x2f, 0x0b, 0x5a, 0x67, 0xac, 0x88, 0xf3,
0x6c, 0x2b, 0x6b, 0x7b, 0xd0, 0x1d, 0x26, 0x49, 0x18, 0x4c, 0x37, 0x2a, 0xb5, 0x46, 0x42, 0x89,
0xd7, 0xb5, 0x78, 0x28, 0x5f, 0xd4, 0x49, 0x38, 0x1d, 0x4e, 0xe4, 0xd2, 0xa0, 0x36, 0x80, 0xda,
0x74, 0x50, 0xbb, 0x82, 0x64, 0xa2, 0xd3, 0x86, 0x79, 0x16, 0xcf, 0xc3, 0xf8, 0x56, 0x7a, 0xa7,
0x43, 0x4b, 0xec, 0x7f, 0xd1, 0x80, 0xe6, 0x77, 0x35, 0xe8, 0xf7, 0xc0, 0x0a, 0x74, 0x72, 0x58,
0x41, 0x39, 0xf6, 0xdb, 0xb5, 0xb1, 0xef, 0x41, 0xbb, 0x10, 0x2c, 0x5a, 0xf0, 0xd4, 0xeb, 0xc8,
0x6e, 0x64, 0xa0, 0xe4, 0xc8, 0xba, 0x53, 0xf3, 0xde, 0xa5, 0x06, 0x96, 0x75, 0x04, 0xb5, 0x3a,
0xfa, 0x8d, 0x5e, 0x0d, 0xba, 0x77, 0x47, 0xcb, 0xae, 0x8d, 0xe0, 0xdb, 0x1b, 0xc1, 0x5f, 0x5a,
0xe0, 0x94, 0x45, 0x78, 0xb2, 0x59, 0x84, 0x27, 0x55, 0x11, 0x8e, 0x8e, 0x4d, 0x11, 0x8e, 0x8e,
0x11, 0xd3, 0x0b, 0x53, 0x84, 0xf4, 0x02, 0x83, 0xf5, 0x52, 0xc4, 0x79, 0x72, 0x5c, 0xa8, 0xa8,
0xba, 0xb4, 0xc4, 0x98, 0xb9, 0x7f, 0x59, 0x72, 0xa1, 0x5d, 0xed, 0x52, 0x8d, 0x30, 0xcf, 0xcf,
0x64, 0x83, 0x52, 0xce, 0x55, 0x80, 0xfc, 0x0c, 0x1c, 0x8a, 0xce, 0x93, 0x1e, 0xde, 0x88, 0x8b,
0x24, 0x53, 0xc5, 0x45, 0xa5, 0xea, 0x83, 0x41, 0x27, 0xbc, 0xf9, 0x7c, 0xf8, 0x35, 0xb4, 0xc6,
0xcb, 0x60, 0x9e, 0x99, 0x05, 0xeb, 0xfb, 0xb5, 0x06, 0x17, 0xac, 0xb8, 0xe4, 0x51, 0x2d, 0xe2,
0xbf, 0x05, 0xb7, 0x24, 0x56, 0xcf, 0xb1, 0xea, 0xcf, 0x21, 0xd0, 0xbc, 0x8a, 0x82, 0xcc, 0x94,
0x3a, 0x9e, 0xd1, 0xd8, 0xb7, 0x39, 0x8b, 0xb2, 0x20, 0x2b, 0x4c, 0xa9, 0x1b, 0xec, 0x3f, 0xd1,
0xcf, 0x47, 0x75, 0x57, 0x49, 0xc2, 0x85, 0x6e, 0x1b, 0x0a, 0xc8, 0x4b, 0xe2, 0x5b, 0xae, 0x3a,
0xbe, 0x4d, 0x15, 0xf0, 0xff, 0x0a, 0xee, 0x30, 0xe4, 0x22, 0xa3, 0x79, 0xc8, 0x77, 0x4d, 0xe2,
0x3f, 0x8f, 0xcf, 0xdf, 0x98, 0x17, 0xe0, 0xb9, 0x6a, 0x11, 0xf6, 0x9d, 0x16, 0xf1, 0x8a, 0x25,
0xec, 0x74, 0x24, 0xf3, 0xdc, 0xa6, 0x1a, 0xf9, 0xff, 0xb1, 0xa0, 0x89, 0xbd, 0xa8, 0xa6, 0xba,
0xf9, 0xb1, 0x3e, 0x76, 0x21, 0xe2, 0x9b, 0x60, 0xc6, 0x85, 0x31, 0xce, 0x60, 0xe9, 0xf4, 0xe9,
0x92, 0x97, 0x03, 0x5f, 0x23, 0xcc, 0x35, 0xfc, 0xba, 0x30, 0xb5, 0x54, 0xcb, 0x35, 0x24, 0x53,
0xc5, 0xc4, 0xcd, 0x6e, 0x9c, 0x27, 0x5c, 0x0c, 0x67, 0xab, 0xc0, 0x6c, 0x40, 0x35, 0x8a, 0xff,
0x5c, 0x7d, 0xaf, 0x6c, 0x75, 0x34, 0x6b, 0xf7, 0xb7, 0xcd, 0xdd, 0x97, 0xfb, 0xff, 0xb5, 0xa0,
0xfd, 0x5a, 0xef, 0x6a, 0x75, 0x2b, 0xac, 0x0f, 0x5a, 0xd1, 0xd8, 0xb0, 0x62, 0x00, 0x87, 0x46,
0x66, 0xe3, 0x7e, 0xe5, 0x85, 0x9d, 0x3c, 0xed, 0xd1, 0x66, 0x19, 0xac, 0x4f, 0xf9, 0x5c, 0xb9,
0xdc, 0x94, 0xd9, 0x15, 0xf0, 0xad, 0xa8, 0xf4, 0xa0, 0x6b, 0x3e, 0xd3, 0xe2, 0xd0, 0x0c, 0x98,
0x3a, 0xc9, 0x1f, 0x40, 0xeb, 0x24, 0x8e, 0xe6, 0xc1, 0x82, 0xf4, 0xa1, 0x39, 0xcc, 0xb3, 0xa5,
0xd4, 0xd8, 0x1d, 0x1c, 0xd6, 0x0a, 0x3f, 0xcf, 0x96, 0x4a, 0x86, 0x4a, 0x09, 0xff, 0xf7, 0x00,
0x15, 0x0d, 0xa7, 0x44, 0x15, 0x8d, 0x37, 0xfc, 0x16, 0x53, 0x26, 0x95, 0x5a, 0x3a, 0x74, 0x07,
0xc7, 0xff, 0x03, 0xb8, 0xc7, 0x79, 0x10, 0xce, 0x4e, 0xa3, 0x79, 0x8c, 0xad, 0xe3, 0x9a, 0x8b,
0xb4, 0x8a, 0x97, 0x81, 0xe8, 0x6e, 0xec, 0x22, 0x65, 0x0d, 0x69, 0x34, 0x69, 0xc9, 0x3f, 0x01,
0x9e, 0x7c, 0x1d, 0x00, 0x00, 0xff, 0xff, 0xb6, 0xb2, 0x98, 0x60, 0x16, 0x10, 0x00, 0x00,
// 1667 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x58, 0x5b, 0x6f, 0xe4, 0x48,
0x15, 0x96, 0xbb, 0xed, 0x4e, 0xfb, 0x74, 0x92, 0x8d, 0x8a, 0xd1, 0xae, 0x59, 0x10, 0x6a, 0x2c,
0x2e, 0xe1, 0xb2, 0xc3, 0x2a, 0x2b, 0x24, 0xb4, 0xda, 0x5d, 0x29, 0x97, 0x9d, 0x21, 0x73, 0xcd,
0x54, 0x27, 0xc3, 0x13, 0x5a, 0x55, 0xdb, 0xd5, 0xdd, 0xa5, 0x75, 0xdb, 0xa6, 0x6c, 0x27, 0x31,
0xcf, 0xfc, 0x0e, 0x24, 0x24, 0xf8, 0x03, 0x88, 0x47, 0x24, 0xde, 0xf9, 0x01, 0xfc, 0x15, 0x78,
0x44, 0xa7, 0x2e, 0xee, 0x72, 0xd2, 0x33, 0x1a, 0x24, 0xb4, 0x6f, 0xf5, 0x9d, 0x73, 0xfa, 0x54,
0xd5, 0xb9, 0x7c, 0x75, 0xdc, 0xb0, 0x2f, 0xf2, 0x9a, 0xcb, 0x9c, 0x65, 0x0f, 0x4b, 0x59, 0xd4,
0x05, 0x19, 0x5b, 0x1c, 0xff, 0x61, 0x08, 0xa3, 0x59, 0xd1, 0xc8, 0x84, 0x93, 0x7d, 0x18, 0x9c,
0x9f, 0x45, 0xde, 0xd4, 0x3b, 0x1c, 0xd2, 0xc1, 0xf9, 0x19, 0x21, 0xe0, 0xbf, 0x60, 0x6b, 0x1e,
0x0d, 0xa6, 0xde, 0x61, 0x48, 0xd5, 0x1a, 0x65, 0x97, 0x6d, 0xc9, 0xa3, 0xa1, 0x96, 0xe1, 0x9a,
0x7c, 0x08, 0xe3, 0xab, 0x0a, 0xbd, 0xad, 0x79, 0xe4, 0x2b, 0x79, 0x87, 0x51, 0x77, 0xc1, 0xaa,
0xea, 0xa6, 0x90, 0x69, 0x14, 0x68, 0x9d, 0xc5, 0xe4, 0x00, 0x86, 0x57, 0xf4, 0x59, 0x34, 0x52,
0x62, 0x5c, 0x92, 0x08, 0x76, 0xce, 0xf8, 0x82, 0x35, 0x59, 0x1d, 0xed, 0x4c, 0xbd, 0xc3, 0x31,
0xb5, 0x10, 0xfd, 0x5c, 0xf2, 0x8c, 0x2f, 0x25, 0x5b, 0x44, 0x63, 0xed, 0xc7, 0x62, 0xf2, 0x10,
0xc8, 0x79, 0x5e, 0xf1, 0xa4, 0x91, 0x7c, 0xf6, 0xb5, 0x28, 0x5f, 0x73, 0x29, 0x16, 0x6d, 0x14,
0x2a, 0x07, 0x5b, 0x34, 0xb8, 0xcb, 0x73, 0x5e, 0x33, 0xdc, 0x1b, 0x94, 0x2b, 0x0b, 0x49, 0x0c,
0xbb, 0xb3, 0x15, 0x93, 0x3c, 0x9d, 0xf1, 0x44, 0xf2, 0x3a, 0x9a, 0x28, 0x75, 0x4f, 0x86, 0x36,
0x2f, 0xe5, 0x92, 0xe5, 0xe2, 0xf7, 0xac, 0x16, 0x45, 0x1e, 0xed, 0x6a, 0x1b, 0x57, 0x86, 0x51,
0xa2, 0x45, 0xc6, 0xa3, 0x3d, 0x1d, 0x25, 0x5c, 0x93, 0xef, 0x42, 0x68, 0x2e, 0x43, 0x2f, 0xa2,
0x7d, 0xa5, 0xd8, 0x08, 0xe2, 0xbf, 0x79, 0x10, 0x9e, 0xb1, 0x6a, 0x35, 0x2f, 0x98, 0x4c, 0xdf,
0x29, 0x13, 0x1f, 0x41, 0x90, 0xf0, 0x2c, 0xab, 0xa2, 0xe1, 0x74, 0x78, 0x38, 0x39, 0xfa, 0xe0,
0x61, 0x97, 0xe2, 0xce, 0xcf, 0x29, 0xcf, 0x32, 0xaa, 0xad, 0xc8, 0xc7, 0x10, 0xd6, 0x7c, 0x5d,
0x66, 0xac, 0xe6, 0x55, 0xe4, 0xab, 0x9f, 0x90, 0xcd, 0x4f, 0x2e, 0x8d, 0x8a, 0x6e, 0x8c, 0xee,
0x5d, 0x34, 0xb8, 0x7f, 0xd1, 0xf8, 0x5f, 0x3e, 0xec, 0xf5, 0xb6, 0x23, 0xbb, 0xe0, 0xdd, 0xaa,
0x93, 0x07, 0xd4, 0xbb, 0x45, 0xd4, 0xaa, 0x53, 0x07, 0xd4, 0x6b, 0x11, 0xdd, 0xa8, 0xca, 0x09,
0xa8, 0x77, 0x83, 0x68, 0xa5, 0xea, 0x25, 0xa0, 0xde, 0x8a, 0xfc, 0x04, 0x76, 0x7e, 0xd7, 0x70,
0x29, 0x78, 0x15, 0x05, 0xea, 0x74, 0xef, 0x6d, 0x4e, 0xf7, 0xaa, 0xe1, 0xb2, 0xa5, 0x56, 0x8f,
0xd1, 0x50, 0xb5, 0xa6, 0x0b, 0x47, 0xad, 0x51, 0x56, 0x63, 0x5d, 0xee, 0x68, 0x19, 0xae, 0x4d,
0x14, 0x75, 0xb5, 0x60, 0x14, 0x7f, 0x09, 0x3e, 0xbb, 0xe5, 0x55, 0x14, 0x2a, 0xff, 0xdf, 0x7f,
0x43, 0xc0, 0x1e, 0x1e, 0xdf, 0xf2, 0xea, 0xcb, 0xbc, 0x96, 0x2d, 0x55, 0xe6, 0xe4, 0xc7, 0x30,
0x4a, 0x8a, 0xac, 0x90, 0x55, 0x04, 0x77, 0x0f, 0x76, 0x8a, 0x72, 0x6a, 0xd4, 0xe4, 0x10, 0x46,
0x19, 0x5f, 0xf2, 0x3c, 0x55, 0x75, 0x33, 0x39, 0x3a, 0xd8, 0x18, 0x3e, 0x53, 0x72, 0x6a, 0xf4,
0xe4, 0x53, 0xd8, 0xad, 0xd9, 0x3c, 0xe3, 0x2f, 0x4b, 0x8c, 0x62, 0xa5, 0x6a, 0x68, 0x72, 0xf4,
0xbe, 0x93, 0x0f, 0x47, 0x4b, 0x7b, 0xb6, 0xe4, 0x33, 0xd8, 0x5d, 0x08, 0x9e, 0xa5, 0xf6, 0xb7,
0x7b, 0xea, 0x50, 0xd1, 0xe6, 0xb7, 0x94, 0xe7, 0x6c, 0x8d, 0xbf, 0x78, 0x84, 0x66, 0xb4, 0x67,
0x4d, 0xbe, 0x07, 0x50, 0x8b, 0x35, 0x7f, 0x54, 0xc8, 0x35, 0xab, 0x4d, 0x19, 0x3a, 0x12, 0xf2,
0x39, 0xec, 0xa5, 0x3c, 0x11, 0x6b, 0x96, 0x5d, 0x64, 0x2c, 0xe1, 0x55, 0xf4, 0x9e, 0x3a, 0x9a,
0x5b, 0x5d, 0xae, 0x9a, 0xf6, 0xad, 0x3f, 0x7c, 0x0c, 0x61, 0x17, 0x3e, 0xec, 0xef, 0xaf, 0x79,
0xab, 0x8a, 0x21, 0xa4, 0xb8, 0x24, 0x3f, 0x80, 0xe0, 0x9a, 0x65, 0x8d, 0x2e, 0xe4, 0xc9, 0xd1,
0xfe, 0xc6, 0xeb, 0xf1, 0xad, 0xa8, 0xa8, 0x56, 0x7e, 0x3a, 0xf8, 0x95, 0x17, 0x3f, 0x86, 0xbd,
0xde, 0x46, 0x78, 0x70, 0x51, 0x7d, 0x99, 0x2f, 0x0a, 0x99, 0xf0, 0x54, 0xf9, 0x1c, 0x53, 0x47,
0x42, 0xde, 0x87, 0x51, 0x2a, 0x96, 0xa2, 0xae, 0x4c, 0xb9, 0x19, 0x14, 0xff, 0xdd, 0x83, 0x5d,
0x37, 0x9a, 0xe4, 0xa7, 0x70, 0x70, 0xcd, 0x65, 0x2d, 0x12, 0x96, 0x5d, 0x8a, 0x35, 0xc7, 0x8d,
0xd5, 0x4f, 0xc6, 0xf4, 0x9e, 0x9c, 0x7c, 0x0c, 0xa3, 0xaa, 0x90, 0xf5, 0x49, 0xab, 0xaa, 0xf6,
0x6d, 0x51, 0x36, 0x76, 0xc8, 0x53, 0x37, 0x92, 0x95, 0xa5, 0xc8, 0x97, 0x96, 0x0b, 0x2d, 0x26,
0x3f, 0x82, 0xfd, 0x85, 0xb8, 0x7d, 0x24, 0x64, 0x55, 0x9f, 0x16, 0x59, 0xb3, 0xce, 0x55, 0x05,
0x8f, 0xe9, 0x1d, 0xe9, 0x13, 0x7f, 0xec, 0x1d, 0x0c, 0x9e, 0xf8, 0xe3, 0xe0, 0x60, 0x14, 0x97,
0xb0, 0xdf, 0xdf, 0x09, 0xdb, 0xd2, 0x1e, 0x42, 0x71, 0x82, 0x0e, 0x6f, 0x4f, 0x46, 0xa6, 0x30,
0x49, 0x45, 0x55, 0x66, 0xac, 0x75, 0x68, 0xc3, 0x15, 0x21, 0x07, 0x5e, 0x8b, 0x4a, 0xcc, 0x33,
0x4d, 0xe5, 0x63, 0x6a, 0x61, 0xbc, 0x84, 0x40, 0x95, 0xb5, 0x43, 0x42, 0xa1, 0x25, 0x21, 0x45,
0xfd, 0x03, 0x87, 0xfa, 0x0f, 0x60, 0xf8, 0x6b, 0x7e, 0x6b, 0x5e, 0x03, 0x5c, 0x76, 0x54, 0xe5,
0x3b, 0x54, 0xf5, 0x00, 0x82, 0xd7, 0x2a, 0xed, 0x9a, 0x42, 0x34, 0x88, 0xbf, 0x80, 0x91, 0x6e,
0x8b, 0xce, 0xb3, 0xe7, 0x78, 0x9e, 0xc2, 0xe4, 0xa5, 0x14, 0x3c, 0xaf, 0x35, 0xf9, 0x98, 0x2b,
0x38, 0xa2, 0xf8, 0xaf, 0x1e, 0xf8, 0x2a, 0x4b, 0x31, 0xec, 0x66, 0x7c, 0xc9, 0x92, 0xf6, 0xa4,
0x68, 0xf2, 0xb4, 0x8a, 0xbc, 0xe9, 0xf0, 0x70, 0x48, 0x7b, 0x32, 0x2c, 0x8f, 0xb9, 0xd6, 0x0e,
0xa6, 0xc3, 0xc3, 0x90, 0x1a, 0x84, 0x47, 0xcb, 0xd8, 0x9c, 0x67, 0xe6, 0x0a, 0x1a, 0xa0, 0x75,
0x29, 0xf9, 0x42, 0xdc, 0x9a, 0x6b, 0x18, 0x84, 0xf2, 0xaa, 0x59, 0xa0, 0x5c, 0xdf, 0xc4, 0x20,
0xbc, 0xc0, 0x9c, 0x55, 0x1d, 0x23, 0xe1, 0x1a, 0x3d, 0x57, 0x09, 0xcb, 0x2c, 0x25, 0x69, 0x10,
0xff, 0xc3, 0xc3, 0x87, 0x4c, 0x53, 0xec, 0xbd, 0x08, 0x7f, 0x1b, 0xc6, 0x48, 0xbf, 0x5f, 0x5d,
0x33, 0x69, 0x2e, 0xbc, 0x83, 0xf8, 0x35, 0x93, 0xe4, 0x17, 0x30, 0x52, 0xcd, 0xb1, 0x85, 0xee,
0xad, 0x3b, 0x15, 0x55, 0x6a, 0xcc, 0x3a, 0x42, 0xf4, 0x1d, 0x42, 0xec, 0x2e, 0x1b, 0xb8, 0x97,
0xfd, 0x08, 0x02, 0x64, 0xd6, 0x56, 0x9d, 0x7e, 0xab, 0x67, 0xcd, 0xbf, 0xda, 0x2a, 0xbe, 0x82,
0xbd, 0xde, 0x8e, 0xdd, 0x4e, 0x5e, 0x7f, 0xa7, 0x4d, 0xa3, 0x87, 0xa6, 0xb1, 0xb1, 0x39, 0x2a,
0x9e, 0xf1, 0xa4, 0xe6, 0xa9, 0xa9, 0xba, 0x0e, 0xc7, 0x7f, 0xf2, 0x36, 0x7e, 0xd5, 0x7e, 0x58,
0xa2, 0x49, 0xb1, 0x5e, 0xb3, 0x3c, 0x35, 0xae, 0x2d, 0xc4, 0xb8, 0xa5, 0x73, 0xe3, 0x7a, 0x90,
0xce, 0x11, 0xcb, 0xd2, 0x64, 0x70, 0x20, 0x4b, 0xac, 0x9d, 0x35, 0x67, 0x55, 0x23, 0xf9, 0x9a,
0xe7, 0xb5, 0x09, 0x81, 0x2b, 0x22, 0x1f, 0xc0, 0x4e, 0xcd, 0x96, 0x5f, 0x21, 0x3d, 0x99, 0x4c,
0xd6, 0x6c, 0xf9, 0x94, 0xb7, 0xe4, 0x3b, 0x10, 0x2a, 0xbe, 0x54, 0x2a, 0x9d, 0xce, 0xb1, 0x12,
0x3c, 0xe5, 0x6d, 0xfc, 0x1f, 0x0f, 0x46, 0x33, 0x2e, 0xaf, 0xb9, 0x7c, 0xa7, 0x17, 0xda, 0x9d,
0x8b, 0x86, 0x6f, 0x99, 0x8b, 0xfc, 0xed, 0x73, 0x51, 0xb0, 0x99, 0x8b, 0x1e, 0x40, 0x30, 0x93,
0xc9, 0xf9, 0x99, 0x3a, 0xd1, 0x90, 0x6a, 0x80, 0xd5, 0x78, 0x9c, 0xd4, 0xe2, 0x9a, 0x9b, 0x61,
0xc9, 0xa0, 0x7b, 0x0f, 0xf7, 0x78, 0xcb, 0x84, 0xf2, 0x3f, 0xce, 0x4c, 0xf1, 0x1f, 0x3d, 0x18,
0x3d, 0x63, 0x6d, 0xd1, 0xd4, 0xf7, 0xaa, 0x76, 0x0a, 0x93, 0xe3, 0xb2, 0xcc, 0x44, 0xd2, 0xeb,
0x54, 0x47, 0x84, 0x16, 0xcf, 0x9d, 0x7c, 0xe8, 0x58, 0xb8, 0x22, 0x7c, 0x18, 0x4e, 0xd5, 0x30,
0xa3, 0x27, 0x13, 0xe7, 0x61, 0xd0, 0x33, 0x8c, 0x52, 0x62, 0xd0, 0x8e, 0x9b, 0xba, 0x58, 0x64,
0xc5, 0x8d, 0x8a, 0xce, 0x98, 0x76, 0x38, 0xfe, 0xe7, 0x00, 0xfc, 0x6f, 0x6a, 0x00, 0xd9, 0x05,
0x4f, 0x98, 0xe2, 0xf0, 0x44, 0x37, 0x8e, 0xec, 0x38, 0xe3, 0x48, 0x04, 0x3b, 0xad, 0x64, 0xf9,
0x92, 0x57, 0xd1, 0x58, 0xb1, 0x91, 0x85, 0x4a, 0xa3, 0xfa, 0x4e, 0xcf, 0x21, 0x21, 0xb5, 0xb0,
0xeb, 0x23, 0x70, 0xfa, 0xe8, 0xe7, 0x66, 0x64, 0x99, 0xdc, 0x7d, 0xe4, 0xb7, 0x4d, 0x2a, 0xff,
0xbf, 0xd7, 0xf7, 0xdf, 0x1e, 0x04, 0x5d, 0x13, 0x9e, 0xf6, 0x9b, 0xf0, 0x74, 0xd3, 0x84, 0x67,
0x27, 0xb6, 0x09, 0xcf, 0x4e, 0x10, 0xd3, 0x0b, 0xdb, 0x84, 0xf4, 0x02, 0x93, 0xf5, 0x58, 0x16,
0x4d, 0x79, 0xd2, 0xea, 0xac, 0x86, 0xb4, 0xc3, 0x58, 0xb9, 0xbf, 0x59, 0x71, 0x69, 0x42, 0x1d,
0x52, 0x83, 0xb0, 0xce, 0x9f, 0x29, 0x82, 0xd2, 0xc1, 0xd5, 0x80, 0xfc, 0x10, 0x02, 0x8a, 0xc1,
0x53, 0x11, 0xee, 0xe5, 0x45, 0x89, 0xa9, 0xd6, 0xa2, 0x53, 0xfd, 0x21, 0x63, 0x0a, 0xde, 0x7e,
0xd6, 0xfc, 0x0c, 0x46, 0xb3, 0x95, 0x58, 0xd4, 0x76, 0xf0, 0xfb, 0x96, 0x43, 0x70, 0x62, 0xcd,
0x95, 0x8e, 0x1a, 0x93, 0xf8, 0x15, 0x84, 0x9d, 0x70, 0x73, 0x1c, 0xcf, 0x3d, 0x0e, 0x01, 0xff,
0x2a, 0x17, 0xb5, 0x6d, 0x75, 0x5c, 0xe3, 0x65, 0x5f, 0x35, 0x2c, 0xaf, 0x45, 0xdd, 0xda, 0x56,
0xb7, 0x38, 0xfe, 0xc4, 0x1c, 0x1f, 0xdd, 0x5d, 0x95, 0x25, 0x97, 0x86, 0x36, 0x34, 0x50, 0x9b,
0x14, 0x37, 0x5c, 0x33, 0xfe, 0x90, 0x6a, 0x10, 0xff, 0x16, 0xc2, 0xe3, 0x8c, 0xcb, 0x9a, 0x36,
0x19, 0xdf, 0xf6, 0x12, 0x3f, 0x99, 0xbd, 0x7c, 0x61, 0x4f, 0x80, 0xeb, 0x0d, 0x45, 0x0c, 0xef,
0x50, 0xc4, 0x53, 0x56, 0xb2, 0xf3, 0x33, 0x55, 0xe7, 0x43, 0x6a, 0x50, 0xfc, 0x67, 0x0f, 0x7c,
0xe4, 0x22, 0xc7, 0xb5, 0xff, 0x36, 0x1e, 0xbb, 0x90, 0xc5, 0xb5, 0x48, 0xb9, 0xb4, 0x97, 0xb3,
0x58, 0x05, 0x3d, 0x59, 0xf1, 0xee, 0xc1, 0x37, 0x08, 0x6b, 0x0d, 0xbf, 0x7a, 0x6c, 0x2f, 0x39,
0xb5, 0x86, 0x62, 0xaa, 0x95, 0x38, 0xd4, 0xcd, 0x9a, 0x92, 0xcb, 0xe3, 0x74, 0x2d, 0xec, 0x34,
0xe4, 0x48, 0xe2, 0x2f, 0xf4, 0x77, 0xd4, 0x3d, 0x46, 0xf3, 0xb6, 0x7f, 0x73, 0xdd, 0x3d, 0x79,
0xfc, 0x17, 0x0f, 0x76, 0x9e, 0x9b, 0xe9, 0xcb, 0xbd, 0x85, 0xf7, 0xc6, 0x5b, 0x0c, 0x7a, 0xb7,
0x38, 0x82, 0x07, 0xd6, 0xa6, 0xb7, 0xbf, 0x8e, 0xc2, 0x56, 0x9d, 0x89, 0xa8, 0xdf, 0x25, 0xeb,
0x5d, 0x3e, 0xa3, 0x2e, 0xfb, 0x36, 0xdb, 0x12, 0x7e, 0x2f, 0x2b, 0x53, 0x98, 0xd8, 0xcf, 0xc7,
0x22, 0xb3, 0x0f, 0x8c, 0x2b, 0x8a, 0x8f, 0x60, 0x74, 0x5a, 0xe4, 0x0b, 0xb1, 0x24, 0x87, 0xe0,
0x1f, 0x37, 0xf5, 0x4a, 0x79, 0x9c, 0x1c, 0x3d, 0x70, 0x1a, 0xbf, 0xa9, 0x57, 0xda, 0x86, 0x2a,
0x8b, 0xf8, 0x33, 0x80, 0x8d, 0x0c, 0x5f, 0x89, 0x4d, 0x36, 0x5e, 0xf0, 0x1b, 0x2c, 0x99, 0xca,
0x0c, 0xdf, 0x5b, 0x34, 0xf1, 0xe7, 0x10, 0x9e, 0x34, 0x22, 0x4b, 0xcf, 0xf3, 0x45, 0x81, 0xd4,
0xf1, 0x9a, 0xcb, 0x6a, 0x93, 0x2f, 0x0b, 0x31, 0xdc, 0xc8, 0x22, 0x5d, 0x0f, 0x19, 0x34, 0x1f,
0xa9, 0x3f, 0x27, 0x3e, 0xf9, 0x6f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x77, 0xc4, 0xaa, 0x16, 0xae,
0x10, 0x00, 0x00,
}

View File

@ -27,33 +27,41 @@ 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
map<string, Axis> axes = 9; // Axes represent the graphical viewport for a cell's visualizations
repeated Color colors = 10; // Colors represent encoding data values to color
Legend legend = 11; // Legend is summary information for a cell
TableOptions tableOptions = 12; // TableOptions for visualization of cell with type 'table'
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<string, Axis> axes = 9; // Axes represent the graphical viewport for a cell's visualizations
repeated Color colors = 10; // Colors represent encoding data values to color
Legend legend = 11; // Legend is summary information for a cell
TableOptions tableOptions = 12; // TableOptions for visualization of cell with type 'table'
repeated RenamableField fieldOptions = 13; // Options for each of the fields returned in a cell
string timeFormat = 14; // format for time
DecimalPlaces decimalPlaces = 15; // Represents how precise the values of this field should be
}
message DecimalPlaces {
bool isEnforced = 1; // whether decimal places should be enforced
int32 digits = 2; // the number of digits to display after decical point
}
message TableOptions {
string timeFormat = 1; // format for time
reserved 1;
bool verticalTimeAxis = 2; // time axis should be a column not row
RenamableField sortBy = 3; // which column should a table be sorted by
string wrapping = 4; // option for text wrapping
repeated RenamableField fieldNames = 5; // names and renames for column/row fields
reserved 5;
bool fixFirstColumn = 6; // first column should be fixed/frozen
}
message RenamableField {
string internalName = 1; // name of column
string displayName = 2; // what column is renamed to
bool visible = 3; // Represents whether RenamableField is visible
bool visible = 3; // Represents whether RenamableField is visible
}
message Color {

View File

@ -194,10 +194,9 @@ func Test_MarshalDashboard(t *testing.T) {
Value: "100",
},
},
TableOptions: chronograf.TableOptions{
TimeFormat: "",
FieldNames: []chronograf.RenamableField{},
},
TableOptions: chronograf.TableOptions{},
FieldOptions: []chronograf.RenamableField{},
TimeFormat: "",
},
},
Templates: []chronograf.Template{},
@ -260,10 +259,10 @@ func Test_MarshalDashboard_WithLegacyBounds(t *testing.T) {
Type: "static",
Orientation: "bottom",
},
TableOptions: chronograf.TableOptions{
TimeFormat: "MM:DD:YYYY",
},
Type: "line",
TableOptions: chronograf.TableOptions{},
TimeFormat: "MM:DD:YYYY",
FieldOptions: []chronograf.RenamableField{},
Type: "line",
},
},
Templates: []chronograf.Template{},
@ -317,11 +316,10 @@ func Test_MarshalDashboard_WithLegacyBounds(t *testing.T) {
Type: "static",
Orientation: "bottom",
},
TableOptions: chronograf.TableOptions{
TimeFormat: "MM:DD:YYYY",
FieldNames: []chronograf.RenamableField{},
},
Type: "line",
TableOptions: chronograf.TableOptions{},
FieldOptions: []chronograf.RenamableField{},
TimeFormat: "MM:DD:YYYY",
Type: "line",
},
},
Templates: []chronograf.Template{},
@ -380,10 +378,10 @@ func Test_MarshalDashboard_WithEmptyLegacyBounds(t *testing.T) {
Value: "100",
},
},
Type: "line",
TableOptions: chronograf.TableOptions{
TimeFormat: "MM:DD:YYYY",
},
Type: "line",
TableOptions: chronograf.TableOptions{},
FieldOptions: []chronograf.RenamableField{},
TimeFormat: "MM:DD:YYYY",
},
},
Templates: []chronograf.Template{},
@ -433,11 +431,10 @@ func Test_MarshalDashboard_WithEmptyLegacyBounds(t *testing.T) {
Value: "100",
},
},
TableOptions: chronograf.TableOptions{
TimeFormat: "MM:DD:YYYY",
FieldNames: []chronograf.RenamableField{},
},
Type: "line",
TableOptions: chronograf.TableOptions{},
FieldOptions: []chronograf.RenamableField{},
TimeFormat: "MM:DD:YYYY",
Type: "line",
},
},
Templates: []chronograf.Template{},
@ -468,14 +465,13 @@ func Test_MarshalDashboard_WithEmptyCellType(t *testing.T) {
ID: 1,
Cells: []chronograf.DashboardCell{
{
ID: "9b5367de-c552-4322-a9e8-7f384cbd235c",
Type: "line",
Queries: []chronograf.DashboardQuery{},
Axes: map[string]chronograf.Axis{},
CellColors: []chronograf.CellColor{},
TableOptions: chronograf.TableOptions{
FieldNames: []chronograf.RenamableField{},
},
ID: "9b5367de-c552-4322-a9e8-7f384cbd235c",
Type: "line",
Queries: []chronograf.DashboardQuery{},
Axes: map[string]chronograf.Axis{},
CellColors: []chronograf.CellColor{},
TableOptions: chronograf.TableOptions{},
FieldOptions: []chronograf.RenamableField{},
},
},
Templates: []chronograf.Template{},

View File

@ -563,18 +563,21 @@ type Legend struct {
// 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]Axis `json:"axes"`
Type string `json:"type"`
CellColors []CellColor `json:"colors"`
Legend Legend `json:"legend"`
TableOptions TableOptions `json:"tableOptions,omitempty"`
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"`
CellColors []CellColor `json:"colors"`
Legend Legend `json:"legend"`
TableOptions TableOptions `json:"tableOptions,omitempty"`
FieldOptions []RenamableField `json:"fieldOptions"`
TimeFormat string `json:"timeFormat"`
DecimalPlaces DecimalPlaces `json:"decimalPlaces"`
}
// RenamableField is a column/row field in a DashboardCell of type Table
@ -586,12 +589,16 @@ type RenamableField struct {
// TableOptions is a type of options for a DashboardCell with type Table
type TableOptions struct {
TimeFormat string `json:"timeFormat"`
VerticalTimeAxis bool `json:"verticalTimeAxis"`
SortBy RenamableField `json:"sortBy"`
Wrapping string `json:"wrapping"`
FieldNames []RenamableField `json:"fieldNames"`
FixFirstColumn bool `json:"fixFirstColumn"`
VerticalTimeAxis bool `json:"verticalTimeAxis"`
SortBy RenamableField `json:"sortBy"`
Wrapping string `json:"wrapping"`
FixFirstColumn bool `json:"fixFirstColumn"`
}
// DecimalPlaces indicates whether decimal places should be enforced, and how many digits it should show.
type DecimalPlaces struct {
IsEnforced bool `json:"isEnforced"`
Digits int32 `json:"digits"`
}
// DashboardsStore is the storage and retrieval of dashboards

View File

@ -166,8 +166,8 @@ func TestServer(t *testing.T) {
"id": "5000",
"name": "Kapa 1",
"url": "http://localhost:9092",
"active": true,
"insecureSkipVerify": false,
"active": true,
"insecureSkipVerify": false,
"links": {
"proxy": "/chronograf/v1/sources/5000/kapacitors/5000/proxy",
"self": "/chronograf/v1/sources/5000/kapacitors/5000",
@ -225,8 +225,8 @@ func TestServer(t *testing.T) {
"id": "5000",
"name": "Kapa 1",
"url": "http://localhost:9092",
"active": true,
"insecureSkipVerify": false,
"active": true,
"insecureSkipVerify": false,
"links": {
"proxy": "/chronograf/v1/sources/5000/kapacitors/5000/proxy",
"self": "/chronograf/v1/sources/5000/kapacitors/5000",
@ -292,7 +292,7 @@ func TestServer(t *testing.T) {
"default": true,
"telegraf": "telegraf",
"organization": "howdy",
"defaultRP": "",
"defaultRP": "",
"links": {
"self": "/chronograf/v1/sources/5000",
"kapacitors": "/chronograf/v1/sources/5000/kapacitors",
@ -304,7 +304,7 @@ func TestServer(t *testing.T) {
"roles": "/chronograf/v1/sources/5000/roles",
"databases": "/chronograf/v1/sources/5000/dbs",
"annotations": "/chronograf/v1/sources/5000/annotations",
"health": "/chronograf/v1/sources/5000/health"
"health": "/chronograf/v1/sources/5000/health"
}
}
]
@ -546,19 +546,23 @@ func TestServer(t *testing.T) {
"legend":{
"type": "static",
"orientation": "bottom"
},
"tableOptions":{
"timeFormat": "",
"verticalTimeAxis": false,
"sortBy":{
"internalName": "",
"displayName": "",
"visible": false
},
"wrapping": "",
"fieldNames": null,
"fixFirstColumn": false
},
},
"tableOptions":{
"verticalTimeAxis": false,
"sortBy":{
"internalName": "",
"displayName": "",
"visible": false
},
"wrapping": "",
"fixFirstColumn": false
},
"fieldOptions": null,
"timeFormat": "",
"decimalPlaces":{
"isEnforced": false,
"digits": 0
},
"links": {
"self": "/chronograf/v1/dashboards/1000/cells/8f61c619-dd9b-4761-8aa8-577f27247093"
}
@ -797,22 +801,26 @@ func TestServer(t *testing.T) {
"name": "comet",
"value": "100"
}
],
"tableOptions":{
"timeFormat":"",
"verticalTimeAxis":false,
"sortBy":{
"internalName":"",
"displayName":"",
"visible":false
},
"wrapping":"",
"fieldNames":null,
"fixFirstColumn":false
},
"legend":{
"type": "static",
"orientation": "bottom"
],
"legend": {
"type": "static",
"orientation": "bottom"
},
"tableOptions":{
"verticalTimeAxis": false,
"sortBy":{
"internalName": "",
"displayName": "",
"visible": false
},
"wrapping": "",
"fixFirstColumn": false
},
"fieldOptions": null,
"timeFormat": "",
"decimalPlaces":{
"isEnforced": false,
"digits": 0
},
"links": {
"self": "/chronograf/v1/dashboards/1000/cells/8f61c619-dd9b-4761-8aa8-577f27247093"
@ -2143,7 +2151,7 @@ func TestServer(t *testing.T) {
wants: wants{
statusCode: 403,
body: `
{
{
"code": 403,
"message": "user not found"
}`,
@ -2363,61 +2371,61 @@ func TestServer(t *testing.T) {
statusCode: 200,
body: `
{
"links": {
"self": "/chronograf/v1/mappings"
},
"mappings": [
{
"links": {
"self": "/chronograf/v1/mappings/1"
},
"id": "1",
"organizationId": "1",
"provider": "*",
"scheme": "*",
"providerOrganization": "influxdata"
},
{
"links": {
"self": "/chronograf/v1/mappings/2"
},
"id": "2",
"organizationId": "1",
"provider": "*",
"scheme": "*",
"providerOrganization": "*"
},
{
"links": {
"self": "/chronograf/v1/mappings/3"
},
"id": "3",
"organizationId": "2",
"provider": "github",
"scheme": "*",
"providerOrganization": "*"
},
{
"links": {
"self": "/chronograf/v1/mappings/4"
},
"id": "4",
"organizationId": "3",
"provider": "auth0",
"scheme": "ldap",
"providerOrganization": "*"
},
{
"links": {
"self": "/chronograf/v1/mappings/default"
},
"id": "default",
"organizationId": "default",
"provider": "*",
"scheme": "*",
"providerOrganization": "*"
}
]
"links": {
"self": "/chronograf/v1/mappings"
},
"mappings": [
{
"links": {
"self": "/chronograf/v1/mappings/1"
},
"id": "1",
"organizationId": "1",
"provider": "*",
"scheme": "*",
"providerOrganization": "influxdata"
},
{
"links": {
"self": "/chronograf/v1/mappings/2"
},
"id": "2",
"organizationId": "1",
"provider": "*",
"scheme": "*",
"providerOrganization": "*"
},
{
"links": {
"self": "/chronograf/v1/mappings/3"
},
"id": "3",
"organizationId": "2",
"provider": "github",
"scheme": "*",
"providerOrganization": "*"
},
{
"links": {
"self": "/chronograf/v1/mappings/4"
},
"id": "4",
"organizationId": "3",
"provider": "auth0",
"scheme": "ldap",
"providerOrganization": "*"
},
{
"links": {
"self": "/chronograf/v1/mappings/default"
},
"id": "default",
"organizationId": "default",
"provider": "*",
"scheme": "*",
"providerOrganization": "*"
}
]
}
`,
},
@ -2693,40 +2701,40 @@ func TestServer(t *testing.T) {
wants: wants{
statusCode: 200,
body: `
{
"layouts": "/chronograf/v1/layouts",
"users": "/chronograf/v1/organizations/default/users",
"allUsers": "/chronograf/v1/users",
"organizations": "/chronograf/v1/organizations",
"mappings": "/chronograf/v1/mappings",
"sources": "/chronograf/v1/sources",
"me": "/chronograf/v1/me",
"environment": "/chronograf/v1/env",
"dashboards": "/chronograf/v1/dashboards",
"config": {
"self": "/chronograf/v1/config",
"auth": "/chronograf/v1/config/auth"
},
"auth": [
{
"name": "github",
"label": "Github",
"login": "/oauth/github/login",
"logout": "/oauth/github/logout",
"callback": "/oauth/github/callback"
}
],
"logout": "/oauth/logout",
"external": {
"statusFeed": ""
},
"ifql": {
"ast": "/chronograf/v1/ifql/ast",
"self": "/chronograf/v1/ifql",
"suggestions": "/chronograf/v1/ifql/suggestions"
}
}
`,
{
"layouts": "/chronograf/v1/layouts",
"users": "/chronograf/v1/organizations/default/users",
"allUsers": "/chronograf/v1/users",
"organizations": "/chronograf/v1/organizations",
"mappings": "/chronograf/v1/mappings",
"sources": "/chronograf/v1/sources",
"me": "/chronograf/v1/me",
"environment": "/chronograf/v1/env",
"dashboards": "/chronograf/v1/dashboards",
"config": {
"self": "/chronograf/v1/config",
"auth": "/chronograf/v1/config/auth"
},
"auth": [
{
"name": "github",
"label": "Github",
"login": "/oauth/github/login",
"logout": "/oauth/github/logout",
"callback": "/oauth/github/callback"
}
],
"logout": "/oauth/logout",
"external": {
"statusFeed": ""
},
"ifql": {
"ast": "/chronograf/v1/ifql/ast",
"self": "/chronograf/v1/ifql",
"suggestions": "/chronograf/v1/ifql/suggestions"
}
}
`,
},
},
{
@ -2781,40 +2789,40 @@ func TestServer(t *testing.T) {
wants: wants{
statusCode: 200,
body: `
{
"layouts": "/chronograf/v1/layouts",
"users": "/chronograf/v1/organizations/1/users",
"allUsers": "/chronograf/v1/users",
"organizations": "/chronograf/v1/organizations",
"mappings": "/chronograf/v1/mappings",
"sources": "/chronograf/v1/sources",
"me": "/chronograf/v1/me",
"environment": "/chronograf/v1/env",
"dashboards": "/chronograf/v1/dashboards",
"config": {
"self": "/chronograf/v1/config",
"auth": "/chronograf/v1/config/auth"
},
"auth": [
{
"name": "github",
"label": "Github",
"login": "/oauth/github/login",
"logout": "/oauth/github/logout",
"callback": "/oauth/github/callback"
}
],
"logout": "/oauth/logout",
"external": {
"statusFeed": ""
},
"ifql": {
"ast": "/chronograf/v1/ifql/ast",
"self": "/chronograf/v1/ifql",
"suggestions": "/chronograf/v1/ifql/suggestions"
}
}
`,
{
"layouts": "/chronograf/v1/layouts",
"users": "/chronograf/v1/organizations/1/users",
"allUsers": "/chronograf/v1/users",
"organizations": "/chronograf/v1/organizations",
"mappings": "/chronograf/v1/mappings",
"sources": "/chronograf/v1/sources",
"me": "/chronograf/v1/me",
"environment": "/chronograf/v1/env",
"dashboards": "/chronograf/v1/dashboards",
"config": {
"self": "/chronograf/v1/config",
"auth": "/chronograf/v1/config/auth"
},
"auth": [
{
"name": "github",
"label": "Github",
"login": "/oauth/github/login",
"logout": "/oauth/github/logout",
"callback": "/oauth/github/callback"
}
],
"logout": "/oauth/logout",
"external": {
"statusFeed": ""
},
"ifql": {
"ast": "/chronograf/v1/ifql/ast",
"self": "/chronograf/v1/ifql",
"suggestions": "/chronograf/v1/ifql/suggestions"
}
}
`,
},
},
}

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":"","visible":false},"wrapping":"","fieldNames":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":{"verticalTimeAxis":false,"sortBy":{"internalName":"","displayName":"","visible":false},"wrapping":"","fixFirstColumn":false},"fieldOptions":null,"timeFormat":"","decimalPlaces":{"isEnforced":false,"digits":0},"links":{"self":"/chronograf/v1/dashboards/1/cells/3c5c4102-fa40-4585-a8f9-917c77e37192"}}
`,
},
{

View File

@ -4653,11 +4653,6 @@
}
},
"tableOptions": {
"timeFormat": {
"description":
"timeFormat describes the display format for time values according to moment.js date formatting",
"type": "string"
},
"verticalTimeAxis": {
"description":
"verticalTimeAxis describes the orientation of the table by indicating whether the time axis will be displayed vertically",
@ -4675,20 +4670,41 @@
"type": "string",
"enum": ["truncate", "wrap", "single-line"]
},
"fieldNames": {
"description":
"fieldNames represent the fields retrieved by the query with customization options",
"type": "array",
"items": {
"$ref": "#/definitions/RenamableField"
}
},
"fixFirstColumn": {
"description":
"fixFirstColumn indicates whether the first column of the table should be locked",
"type": "boolean"
}
},
"fieldOptions": {
"description":
"fieldOptions represent the fields retrieved by the query with customization options",
"type": "array",
"items": {
"$ref": "#/definitions/RenamableField"
}
},
"timeFormat": {
"description":
"timeFormat describes the display format for time values according to moment.js date formatting",
"type": "string"
},
"decimalPoints": {
"description":
"decimal points indicates whether and how many digits to show after decimal point",
"type": "object",
"properties": {
"isEnforced": {
"description":
"Indicates whether decimal point setting should be enforced",
"type": "bool"
},
"digits": {
"description": "The number of digits after decimal to display",
"type": "integer"
}
}
},
"links": {
"type": "object",
"properties": {

View File

@ -1,78 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
import {
isUserAuthorized,
ADMIN_ROLE,
SUPERADMIN_ROLE,
} from 'src/auth/Authorized'
import {Tab, Tabs, TabPanel, TabPanels, TabList} from 'shared/components/Tabs'
import OrganizationsPage from 'src/admin/containers/chronograf/OrganizationsPage'
import UsersPage from 'src/admin/containers/chronograf/UsersPage'
import ProvidersPage from 'src/admin/containers/ProvidersPage'
import AllUsersPage from 'src/admin/containers/chronograf/AllUsersPage'
const ORGANIZATIONS_TAB_NAME = 'All Orgs'
const PROVIDERS_TAB_NAME = 'Org Mappings'
const CURRENT_ORG_USERS_TAB_NAME = 'Current Org'
const ALL_USERS_TAB_NAME = 'All Users'
const AdminTabs = ({
me: {currentOrganization: meCurrentOrganization, role: meRole, id: meID},
}) => {
const tabs = [
{
requiredRole: ADMIN_ROLE,
type: CURRENT_ORG_USERS_TAB_NAME,
component: (
<UsersPage meID={meID} meCurrentOrganization={meCurrentOrganization} />
),
},
{
requiredRole: SUPERADMIN_ROLE,
type: ALL_USERS_TAB_NAME,
component: <AllUsersPage meID={meID} />,
},
{
requiredRole: SUPERADMIN_ROLE,
type: ORGANIZATIONS_TAB_NAME,
component: (
<OrganizationsPage meCurrentOrganization={meCurrentOrganization} />
),
},
{
requiredRole: SUPERADMIN_ROLE,
type: PROVIDERS_TAB_NAME,
component: <ProvidersPage />,
},
].filter(t => isUserAuthorized(meRole, t.requiredRole))
return (
<Tabs className="row">
<TabList customClass="col-md-2 admin-tabs">
{tabs.map((t, i) => <Tab key={tabs[i].type}>{tabs[i].type}</Tab>)}
</TabList>
<TabPanels customClass="col-md-10 admin-tabs--content">
{tabs.map((t, i) => (
<TabPanel key={tabs[i].type}>{t.component}</TabPanel>
))}
</TabPanels>
</Tabs>
)
}
const {shape, string} = PropTypes
AdminTabs.propTypes = {
me: shape({
id: string.isRequired,
role: string.isRequired,
currentOrganization: shape({
name: string.isRequired,
id: string.isRequired,
}),
}).isRequired,
}
export default AdminTabs

View File

@ -2,10 +2,52 @@ import React from 'react'
import PropTypes from 'prop-types'
import {connect} from 'react-redux'
import AdminTabs from 'src/admin/components/chronograf/AdminTabs'
import SubSections from 'src/shared/components/SubSections'
import FancyScrollbar from 'shared/components/FancyScrollbar'
const AdminChronografPage = ({me}) => (
import UsersPage from 'src/admin/containers/chronograf/UsersPage'
import AllUsersPage from 'src/admin/containers/chronograf/AllUsersPage'
import OrganizationsPage from 'src/admin/containers/chronograf/OrganizationsPage'
import ProvidersPage from 'src/admin/containers/ProvidersPage'
import {
isUserAuthorized,
ADMIN_ROLE,
SUPERADMIN_ROLE,
} from 'src/auth/Authorized'
const sections = me => [
{
url: 'current-organization',
name: 'Current Org',
enabled: isUserAuthorized(me.role, ADMIN_ROLE),
component: (
<UsersPage meID={me.id} meCurrentOrganization={me.currentOrganization} />
),
},
{
url: 'all-users',
name: 'All Users',
enabled: isUserAuthorized(me.role, SUPERADMIN_ROLE),
component: <AllUsersPage meID={me.id} />,
},
{
url: 'all-organizations',
name: 'All Orgs',
enabled: isUserAuthorized(me.role, SUPERADMIN_ROLE),
component: (
<OrganizationsPage meCurrentOrganization={me.currentOrganization} />
),
},
{
url: 'organization-mappings',
name: 'Org Mappings',
enabled: isUserAuthorized(me.role, SUPERADMIN_ROLE),
component: <ProvidersPage />,
},
]
const AdminChronografPage = ({me, source, params: {tab}}) => (
<div className="page">
<div className="page-header">
<div className="page-header__container">
@ -16,9 +58,12 @@ const AdminChronografPage = ({me}) => (
</div>
<FancyScrollbar className="page-contents">
<div className="container-fluid">
<div className="row">
<AdminTabs me={me} />
</div>
<SubSections
sections={sections(me)}
activeSection={tab}
parentUrl="admin-chronograf"
sourceID={source.id}
/>
</div>
</FancyScrollbar>
</div>
@ -35,6 +80,15 @@ AdminChronografPage.propTypes = {
id: string.isRequired,
}),
}).isRequired,
params: shape({
tab: string,
}).isRequired,
source: shape({
id: string.isRequired,
links: shape({
users: string.isRequired,
}),
}).isRequired,
}
const mapStateToProps = ({auth: {me}}) => ({

View File

@ -64,3 +64,24 @@ export const updateLineColors = lineColors => ({
lineColors,
},
})
export const changeTimeFormat = timeFormat => ({
type: 'CHANGE_TIME_FORMAT',
payload: {
timeFormat,
},
})
export const changeDecimalPlaces = decimalPlaces => ({
type: 'CHANGE_DECIMAL_PLACES',
payload: {
decimalPlaces,
},
})
export const updateFieldOptions = fieldOptions => ({
type: 'UPDATE_FIELD_OPTIONS',
payload: {
fieldOptions,
},
})

View File

@ -21,7 +21,7 @@ interface RenamableField {
visible: boolean
}
interface GraphOptionsCustomizableFieldProps {
interface Props {
internalName: string
displayName: string
visible: boolean
@ -36,7 +36,7 @@ interface GraphOptionsCustomizableFieldProps {
moveField: (dragIndex: number, hoverIndex: number) => void
}
const fieldSource: DragSourceSpec<GraphOptionsCustomizableFieldProps> = {
const fieldSource: DragSourceSpec<Props> = {
beginDrag(props) {
return {
id: props.id,
@ -112,9 +112,7 @@ function MyDragSource(dragv1, dragv2, dragfunc1) {
isDragging: monitor.isDragging(),
})
)
export default class GraphOptionsCustomizableField extends Component<
GraphOptionsCustomizableFieldProps
> {
export default class GraphOptionsCustomizableField extends Component<Props> {
constructor(props) {
super(props)

View File

@ -0,0 +1,60 @@
import React, {PureComponent} from 'react'
import {ErrorHandling} from 'src/shared/decorators/errors'
import OptIn from 'src/shared/components/OptIn'
interface DecimalPlaces {
isEnforced: boolean
digits: number
}
interface Props extends DecimalPlaces {
onDecimalPlacesChange: (decimalPlaces: DecimalPlaces) => void
}
const fixedValueString = 'fixed'
@ErrorHandling
class GraphOptionsDecimalPlaces extends PureComponent<Props> {
constructor(props: Props) {
super(props)
}
public onSetValue = (valueFromSelector: string): void => {
let digits
let isEnforced
if (valueFromSelector === fixedValueString) {
digits = this.props.digits
isEnforced = false
} else if (valueFromSelector === '') {
digits = this.props.digits
isEnforced = true
} else {
digits = Number(valueFromSelector)
if (digits < 0) {
digits = 0
}
isEnforced = true
}
this.props.onDecimalPlacesChange({digits, isEnforced})
}
public render() {
const {digits, isEnforced} = this.props
return (
<div className="form-group col-xs-6">
<label> Decimal Places </label>
<OptIn
customPlaceholder={isEnforced ? digits.toString() : 'unlimited'}
customValue={isEnforced ? digits.toString() : ''}
onSetValue={this.onSetValue}
fixedPlaceholder={''}
fixedValue={fixedValueString}
type="number"
min={'0'}
/>
</div>
)
}
}
export default GraphOptionsDecimalPlaces

View File

@ -25,7 +25,7 @@ const GraphOptionsSortBy = ({
const selectedValue = selected.displayName || selected.internalName
return (
<div className="form-group col-xs-6">
<label>Default Sort By</label>
<label>Default Sort Field</label>
<Dropdown
items={sortByOptions}
selected={selectedValue}

View File

@ -6,7 +6,7 @@ import {
TIME_FORMAT_CUSTOM,
DEFAULT_TIME_FORMAT,
TIME_FORMAT_TOOLTIP_LINK,
} from 'src/shared/constants/tableGraph'
} from 'src/dashboards/constants'
import {ErrorHandling} from 'src/shared/decorators/errors'
interface TimeFormatOptions {
@ -60,7 +60,7 @@ class GraphOptionsTimeFormat extends PureComponent<Props, State> {
const showCustom = !formatOption || customFormat
return (
<div className="form-group col-xs-12">
<div className="form-group col-xs-6">
<label>
Time Format
{showCustom && (

View File

@ -7,6 +7,7 @@ import GraphOptionsFixFirstColumn from 'src/dashboards/components/GraphOptionsFi
import GraphOptionsSortBy from 'src/dashboards/components/GraphOptionsSortBy'
import GraphOptionsTimeAxis from 'src/dashboards/components/GraphOptionsTimeAxis'
import GraphOptionsTimeFormat from 'src/dashboards/components/GraphOptionsTimeFormat'
import GraphOptionsDecimalPlaces from 'src/dashboards/components/GraphOptionsDecimalPlaces'
import FancyScrollbar from 'src/shared/components/FancyScrollbar'
import _ from 'lodash'
@ -14,12 +15,17 @@ 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_FIELD_DEFAULT} from 'src/shared/constants/tableGraph'
import {
updateTableOptions,
updateFieldOptions,
changeTimeFormat,
changeDecimalPlaces,
} from 'src/dashboards/actions/cellEditorOverlay'
import {DEFAULT_TIME_FIELD} from 'src/dashboards/constants'
import {QueryConfig} from 'src/types/query'
import {ErrorHandling} from 'src/shared/decorators/errors'
interface Option {
interface DropdownOption {
text: string
key: string
}
@ -30,18 +36,27 @@ interface RenamableField {
visible: boolean
}
interface Options {
timeFormat: string
interface TableOptionsInterface {
verticalTimeAxis: boolean
sortBy: RenamableField
fieldNames: RenamableField[]
fixFirstColumn: boolean
}
interface DecimalPlaces {
isEnforced: boolean
digits: number
}
interface Props {
queryConfigs: QueryConfig[]
handleUpdateTableOptions: (options: Options) => void
tableOptions: Options
handleUpdateTableOptions: (options: TableOptionsInterface) => void
handleUpdateFieldOptions: (fieldOptions: RenamableField[]) => void
handleChangeTimeFormat: (timeFormat: string) => void
handleChangeDecimalPlaces: (decimalPlaces: number) => void
tableOptions: TableOptionsInterface
fieldOptions: RenamableField[]
timeFormat: string
decimalPlaces: DecimalPlaces
onResetFocus: () => void
}
@ -54,12 +69,15 @@ export class TableOptions extends Component<Props, {}> {
public render() {
const {
tableOptions: {timeFormat, fieldNames, verticalTimeAxis, fixFirstColumn},
tableOptions: {verticalTimeAxis, fixFirstColumn},
fieldOptions,
timeFormat,
onResetFocus,
tableOptions,
decimalPlaces,
} = this.props
const tableSortByOptions = fieldNames.map(field => ({
const tableSortByOptions = fieldOptions.map(field => ({
key: field.internalName,
text: field.displayName || field.internalName,
}))
@ -72,18 +90,23 @@ export class TableOptions extends Component<Props, {}> {
<div className="display-options--cell-wrapper">
<h5 className="display-options--header">Table Controls</h5>
<div className="form-group-wrapper">
<GraphOptionsTimeFormat
timeFormat={timeFormat}
onTimeFormatChange={this.handleTimeFormatChange}
<GraphOptionsSortBy
selected={tableOptions.sortBy || DEFAULT_TIME_FIELD}
sortByOptions={tableSortByOptions}
onChooseSortBy={this.handleChooseSortBy}
/>
<GraphOptionsDecimalPlaces
digits={decimalPlaces.digits}
isEnforced={decimalPlaces.isEnforced}
onDecimalPlacesChange={this.handleDecimalPlacesChange}
/>
<GraphOptionsTimeAxis
verticalTimeAxis={verticalTimeAxis}
onToggleVerticalTimeAxis={this.handleToggleVerticalTimeAxis}
/>
<GraphOptionsSortBy
selected={tableOptions.sortBy || TIME_FIELD_DEFAULT}
sortByOptions={tableSortByOptions}
onChooseSortBy={this.handleChooseSortBy}
<GraphOptionsTimeFormat
timeFormat={timeFormat}
onTimeFormatChange={this.handleTimeFormatChange}
/>
<GraphOptionsFixFirstColumn
fixed={fixFirstColumn}
@ -91,7 +114,7 @@ export class TableOptions extends Component<Props, {}> {
/>
</div>
<GraphOptionsCustomizeFields
fields={fieldNames}
fields={fieldOptions}
onFieldUpdate={this.handleFieldUpdate}
moveField={this.moveField}
/>
@ -105,39 +128,38 @@ export class TableOptions extends Component<Props, {}> {
}
private moveField(dragIndex, hoverIndex) {
const {handleUpdateTableOptions, tableOptions} = this.props
const {fieldNames} = tableOptions
const {handleUpdateFieldOptions, fieldOptions} = this.props
const dragField = fieldNames[dragIndex]
const removedFields = _.concat(
_.slice(fieldNames, 0, dragIndex),
_.slice(fieldNames, dragIndex + 1)
const draggedField = fieldOptions[dragIndex]
const fieldOptionsRemoved = _.concat(
_.slice(fieldOptions, 0, dragIndex),
_.slice(fieldOptions, dragIndex + 1)
)
const addedFields = _.concat(
_.slice(removedFields, 0, hoverIndex),
[dragField],
_.slice(removedFields, hoverIndex)
const fieldOptionsAdded = _.concat(
_.slice(fieldOptionsRemoved, 0, hoverIndex),
[draggedField],
_.slice(fieldOptionsRemoved, hoverIndex)
)
handleUpdateTableOptions({
...tableOptions,
fieldNames: addedFields,
})
handleUpdateFieldOptions(fieldOptionsAdded)
}
private handleChooseSortBy = (option: Option) => {
const {tableOptions, handleUpdateTableOptions} = this.props
const sortBy = {
displayName: option.text === option.key ? '' : option.text,
internalName: option.key,
visible: true,
}
private handleChooseSortBy = (option: DropdownOption) => {
const {tableOptions, handleUpdateTableOptions, fieldOptions} = this.props
const sortBy = fieldOptions.find(f => f.internalName === option.key)
handleUpdateTableOptions({...tableOptions, sortBy})
}
private handleTimeFormatChange = timeFormat => {
const {tableOptions, handleUpdateTableOptions} = this.props
handleUpdateTableOptions({...tableOptions, timeFormat})
const {handleChangeTimeFormat} = this.props
handleChangeTimeFormat(timeFormat)
}
private handleDecimalPlacesChange = decimalPlaces => {
const {handleChangeDecimalPlaces} = this.props
handleChangeDecimalPlaces(decimalPlaces)
}
private handleToggleVerticalTimeAxis = verticalTimeAxis => () => {
@ -152,34 +174,46 @@ export class TableOptions extends Component<Props, {}> {
}
private handleFieldUpdate = field => {
const {handleUpdateTableOptions, tableOptions} = this.props
const {sortBy, fieldNames} = tableOptions
const updatedFields = fieldNames.map(
const {
handleUpdateTableOptions,
handleUpdateFieldOptions,
tableOptions,
fieldOptions,
} = this.props
const {sortBy} = tableOptions
const updatedFieldOptions = fieldOptions.map(
f => (f.internalName === field.internalName ? field : f)
)
const updatedSortBy =
sortBy.internalName === field.internalName
? {...sortBy, displayName: field.displayName}
: sortBy
handleUpdateTableOptions({
...tableOptions,
fieldNames: updatedFields,
sortBy: updatedSortBy,
})
if (sortBy.internalName === field.internalName) {
const updatedSortBy = {...sortBy, displayName: field.displayName}
handleUpdateTableOptions({
...tableOptions,
sortBy: updatedSortBy,
})
}
handleUpdateFieldOptions(updatedFieldOptions)
}
}
const mapStateToProps = ({
cellEditorOverlay: {
cell: {tableOptions},
cell: {tableOptions, timeFormat, fieldOptions, decimalPlaces},
},
}) => ({
tableOptions,
timeFormat,
fieldOptions,
decimalPlaces,
})
const mapDispatchToProps = dispatch => ({
handleUpdateTableOptions: bindActionCreators(updateTableOptions, dispatch),
handleUpdateFieldOptions: bindActionCreators(updateFieldOptions, dispatch),
handleChangeTimeFormat: bindActionCreators(changeTimeFormat, dispatch),
handleChangeDecimalPlaces: bindActionCreators(changeDecimalPlaces, dispatch),
})
export default connect(mapStateToProps, mapDispatchToProps)(TableOptions)

View File

@ -24,6 +24,9 @@ const DashVisualization = (
staticLegend,
thresholdsListColors,
tableOptions,
timeFormat,
decimalPlaces,
fieldOptions,
isInCEO,
},
{
@ -54,6 +57,9 @@ const DashVisualization = (
editQueryStatus={editQueryStatus}
resizerTopHeight={resizerTopHeight}
staticLegend={staticLegend}
timeFormat={timeFormat}
decimalPlaces={decimalPlaces}
fieldOptions={fieldOptions}
isInCEO={isInCEO}
/>
</div>
@ -79,6 +85,18 @@ DashVisualization.propTypes = {
}),
}),
tableOptions: shape({}),
timeFormat: string.isRequired,
decimalPlaces: shape({
isEnforced: bool,
digits: number,
}),
fieldOptions: arrayOf(
shape({
internalName: string.isRequired,
displayName: string.isRequired,
visible: bool.isRequired,
})
),
resizerTopHeight: number,
thresholdsListColors: colorsNumberSchema,
gaugeColors: colorsNumberSchema,
@ -100,7 +118,7 @@ const mapStateToProps = ({
thresholdsListColors,
gaugeColors,
lineColors,
cell: {type, axes, tableOptions},
cell: {type, axes, tableOptions, fieldOptions, timeFormat, decimalPlaces},
},
}) => ({
gaugeColors,
@ -109,6 +127,9 @@ const mapStateToProps = ({
type,
axes,
tableOptions,
fieldOptions,
timeFormat,
decimalPlaces,
})
export default connect(mapStateToProps, null)(DashVisualization)

View File

@ -1,4 +1,4 @@
import {DEFAULT_TABLE_OPTIONS} from 'src/shared/constants/tableGraph'
import {DEFAULT_TABLE_OPTIONS} from 'src/dashboards/constants'
import {stringifyColorValues} from 'src/shared/constants/colorOperations'
import {
CELL_TYPE_LINE,

View File

@ -1,4 +1,7 @@
import {DEFAULT_TABLE_OPTIONS} from 'src/shared/constants/tableGraph'
import {
DEFAULT_VERTICAL_TIME_AXIS,
DEFAULT_FIX_FIRST_COLUMN,
} from 'src/shared/constants/tableGraph'
import {CELL_TYPE_LINE} from 'src/dashboards/graphics/graph'
export const UNTITLED_CELL_LINE = 'Untitled Line Graph'
@ -11,6 +14,41 @@ export const UNTITLED_CELL_SINGLE_STAT = 'Untitled Single Stat'
export const UNTITLED_CELL_GAUGE = 'Untitled Gauge'
export const UNTITLED_CELL_TABLE = 'Untitled Table'
export const TIME_FORMAT_TOOLTIP_LINK =
'http://momentjs.com/docs/#/parsing/string-format/'
export const DEFAULT_DECIMAL_PLACES = {
isEnforced: false,
digits: 3,
}
export const DEFAULT_TIME_FIELD = {
internalName: 'time',
displayName: '',
visible: true,
}
export const DEFAULT_TABLE_OPTIONS = {
verticalTimeAxis: DEFAULT_VERTICAL_TIME_AXIS,
sortBy: DEFAULT_TIME_FIELD,
wrapping: 'truncate',
fixFirstColumn: DEFAULT_FIX_FIRST_COLUMN,
}
export const DEFAULT_TIME_FORMAT = 'MM/DD/YYYY HH:mm:ss'
export const TIME_FORMAT_CUSTOM = 'Custom'
export const FORMAT_OPTIONS = [
{text: DEFAULT_TIME_FORMAT},
{text: 'MM/DD/YYYY HH:mm:ss.SSS'},
{text: 'YYYY-MM-DD HH:mm:ss'},
{text: 'HH:mm:ss'},
{text: 'HH:mm:ss.SSS'},
{text: 'MMMM D, YYYY HH:mm:ss'},
{text: 'dddd, MMMM D, YYYY HH:mm:ss'},
{text: TIME_FORMAT_CUSTOM},
]
export const NEW_DEFAULT_DASHBOARD_CELL = {
x: 0,
y: 0,
@ -20,6 +58,9 @@ export const NEW_DEFAULT_DASHBOARD_CELL = {
type: CELL_TYPE_LINE,
queries: [],
tableOptions: DEFAULT_TABLE_OPTIONS,
timeFormat: DEFAULT_TIME_FORMAT,
decimalPlaces: DEFAULT_DECIMAL_PLACES,
fieldOptions: [DEFAULT_TIME_FIELD],
}
export const EMPTY_DASHBOARD = {

View File

@ -114,6 +114,25 @@ export default function cellEditorOverlay(state = initialState, action) {
return {...state, cell}
}
case 'CHANGE_TIME_FORMAT': {
const {timeFormat} = action.payload
const cell = {...state.cell, timeFormat}
return {...state, cell}
}
case 'CHANGE_DECIMAL_PLACES': {
const {decimalPlaces} = action.payload
const cell = {...state.cell, decimalPlaces}
return {...state, cell}
}
case 'UPDATE_FIELD_OPTIONS': {
const {fieldOptions} = action.payload
const cell = {...state.cell, fieldOptions}
return {...state, cell}
}
case 'UPDATE_LINE_COLORS': {
const {lineColors} = action.payload

View File

@ -2,11 +2,8 @@ import calculateSize from 'calculate-size'
import _ from 'lodash'
import {map, reduce, filter} from 'fast.js'
import {
CELL_HORIZONTAL_PADDING,
TIME_FIELD_DEFAULT,
DEFAULT_TIME_FORMAT,
} from 'src/shared/constants/tableGraph'
import {CELL_HORIZONTAL_PADDING} from 'src/shared/constants/tableGraph'
import {DEFAULT_TIME_FIELD, DEFAULT_TIME_FORMAT} from 'src/dashboards/constants'
const calculateTimeColumnWidth = timeFormat => {
// Force usage of longest format names for ideal measurement
@ -30,9 +27,10 @@ const updateMaxWidths = (
maxColumnWidths,
topRow,
isTopRow,
fieldNames,
fieldOptions,
timeFormatWidth,
verticalTimeAxis
verticalTimeAxis,
decimalPlaces
) => {
return reduce(
row,
@ -41,21 +39,27 @@ const updateMaxWidths = (
(verticalTimeAxis && isTopRow) || (!verticalTimeAxis && c === 0)
const foundField = isLabel
? fieldNames.find(field => field.internalName === col)
? fieldOptions.find(field => field.internalName === col)
: undefined
const colValue =
foundField && foundField.displayName ? foundField.displayName : `${col}`
const isNumerical = _.isNumber(col)
let colValue = `${col}`
if (foundField && foundField.displayName) {
colValue = foundField.displayName
} else if (isNumerical && decimalPlaces.isEnforced) {
colValue = col.toFixed(decimalPlaces.digits)
}
const columnLabel = topRow[c]
const useTimeWidth =
(columnLabel === TIME_FIELD_DEFAULT.internalName &&
(columnLabel === DEFAULT_TIME_FIELD.internalName &&
verticalTimeAxis &&
!isTopRow) ||
(!verticalTimeAxis &&
isTopRow &&
topRow[0] === TIME_FIELD_DEFAULT.internalName &&
topRow[0] === DEFAULT_TIME_FIELD.internalName &&
c !== 0)
const currentWidth = useTimeWidth
? timeFormatWidth
: calculateSize(colValue, {
@ -77,23 +81,27 @@ const updateMaxWidths = (
)
}
export const computeFieldNames = (existingFieldNames, sortedLabels) => {
export const computeFieldOptions = (existingFieldOptions, sortedLabels) => {
const timeField =
existingFieldNames.find(f => f.internalName === 'time') ||
TIME_FIELD_DEFAULT
existingFieldOptions.find(f => f.internalName === 'time') ||
DEFAULT_TIME_FIELD
let astNames = [timeField]
sortedLabels.forEach(({label}) => {
const field = {internalName: label, displayName: '', visible: true}
const field = {
internalName: label,
displayName: '',
visible: true,
}
astNames = [...astNames, field]
})
const intersection = existingFieldNames.filter(f => {
const intersection = existingFieldOptions.filter(f => {
return astNames.find(a => a.internalName === f.internalName)
})
const newFields = astNames.filter(a => {
return !existingFieldNames.find(f => f.internalName === a.internalName)
return !existingFieldOptions.find(f => f.internalName === a.internalName)
})
return [...intersection, ...newFields]
@ -101,9 +109,10 @@ export const computeFieldNames = (existingFieldNames, sortedLabels) => {
export const calculateColumnWidths = (
data,
fieldNames,
fieldOptions,
timeFormat,
verticalTimeAxis
verticalTimeAxis,
decimalPlaces
) => {
const timeFormatWidth = calculateTimeColumnWidth(
timeFormat === '' ? DEFAULT_TIME_FORMAT : timeFormat
@ -116,21 +125,24 @@ export const calculateColumnWidths = (
acc,
data[0],
r === 0,
fieldNames,
fieldOptions,
timeFormatWidth,
verticalTimeAxis
verticalTimeAxis,
decimalPlaces
)
},
{widths: {}, totalWidths: 0}
)
}
export const filterTableColumns = (data, fieldNames) => {
export const filterTableColumns = (data, fieldOptions) => {
const visibility = {}
const filteredData = map(data, (row, i) => {
return filter(row, (col, j) => {
if (i === 0) {
const foundField = fieldNames.find(field => field.internalName === col)
const foundField = fieldOptions.find(
field => field.internalName === col
)
visibility[j] = foundField ? foundField.visible : true
}
return visibility[j]
@ -139,35 +151,43 @@ export const filterTableColumns = (data, fieldNames) => {
return filteredData[0].length ? filteredData : [[]]
}
export const orderTableColumns = (data, fieldNames) => {
const fieldsSortOrder = fieldNames.map(fieldName => {
export const orderTableColumns = (data, fieldOptions) => {
const fieldsSortOrder = fieldOptions.map(fieldOption => {
return _.findIndex(data[0], dataLabel => {
return dataLabel === fieldName.internalName
return dataLabel === fieldOption.internalName
})
})
const filteredFieldSortOrder = filter(fieldsSortOrder, f => f !== -1)
const orderedData = map(data, row => {
return row.map((v, j, arr) => arr[filteredFieldSortOrder[j]] || v)
return row.map((__, j, arr) => arr[filteredFieldSortOrder[j]])
})
return orderedData[0].length ? orderedData : [[]]
}
export const transformTableData = (data, sort, fieldNames, tableOptions) => {
const {verticalTimeAxis, timeFormat} = tableOptions
export const transformTableData = (
data,
sort,
fieldOptions,
tableOptions,
timeFormat,
decimalPlaces
) => {
const {verticalTimeAxis} = tableOptions
const sortIndex = _.indexOf(data[0], sort.field)
const sortedData = [
data[0],
..._.orderBy(_.drop(data, 1), sortIndex, [sort.direction]),
]
const sortedTimeVals = map(sortedData, r => r[0])
const filteredData = filterTableColumns(sortedData, fieldNames)
const orderedData = orderTableColumns(filteredData, fieldNames)
const filteredData = filterTableColumns(sortedData, fieldOptions)
const orderedData = orderTableColumns(filteredData, fieldOptions)
const transformedData = verticalTimeAxis ? orderedData : _.unzip(orderedData)
const {widths: columnWidths, totalWidths} = calculateColumnWidths(
transformedData,
fieldNames,
fieldOptions,
timeFormat,
verticalTimeAxis
verticalTimeAxis,
decimalPlaces
)
return {transformedData, sortedTimeVals, columnWidths, totalWidths}
}

View File

@ -1,5 +1,4 @@
import React, {PureComponent} from 'react'
import PropTypes from 'prop-types'
import {connect} from 'react-redux'
import {bindActionCreators} from 'redux'
import {withRouter, InjectedRouter} from 'react-router'
@ -52,15 +51,6 @@ interface State {
@ErrorHandling
export class DataExplorer extends PureComponent<Props, State> {
public static childContextTypes = {
source: PropTypes.shape({
links: PropTypes.shape({
proxy: PropTypes.string.isRequired,
self: PropTypes.string.isRequired,
}).isRequired,
}).isRequired,
}
constructor(props) {
super(props)

View File

@ -21,25 +21,33 @@ class BodyBuilder extends PureComponent<Props> {
return b.declarations.map(d => {
if (d.funcs) {
return (
<ExpressionNode
id={d.id}
key={d.id}
funcNames={this.funcNames}
funcs={d.funcs}
/>
<div key={b.id}>
<div className="func-node--name">{d.name} =</div>
<ExpressionNode
key={b.id}
bodyID={b.id}
declarationID={d.id}
funcNames={this.funcNames}
funcs={d.funcs}
/>
</div>
)
}
return <div key={b.id}>{b.source}</div>
return (
<div className="func-node--name" key={b.id}>
{b.source}
</div>
)
})
}
return (
<ExpressionNode
id={b.id}
key={b.id}
funcNames={this.funcNames}
bodyID={b.id}
funcs={b.funcs}
funcNames={this.funcNames}
/>
)
})

View File

@ -8,36 +8,37 @@ import {Func} from 'src/types/ifql'
interface Props {
funcNames: any[]
id: string
bodyID: string
funcs: Func[]
declarationID?: string
}
// an Expression is a group of one or more functions
class ExpressionNode extends PureComponent<Props> {
public render() {
const {id, funcNames, funcs} = this.props
const {declarationID, bodyID, funcNames, funcs} = this.props
return (
<IFQLContext.Consumer>
{({onDeleteFuncNode, onAddNode, onChangeArg, onGenerateScript}) => {
return (
<div className="func-nodes-container">
<h4>
<FuncSelector
expressionID={id}
funcs={funcNames}
onAddNode={onAddNode}
/>
</h4>
{funcs.map(func => (
<FuncNode
key={func.id}
func={func}
expressionID={func.id}
bodyID={bodyID}
onChangeArg={onChangeArg}
onDelete={onDeleteFuncNode}
declarationID={declarationID}
onGenerateScript={onGenerateScript}
/>
))}
<FuncSelector
bodyID={bodyID}
funcs={funcNames}
onAddNode={onAddNode}
declarationID={declarationID}
/>
</div>
)
}}

View File

@ -9,7 +9,8 @@ interface Props {
funcID: string
argKey: string
value: string
expressionID: string
bodyID: string
declarationID: string
onChangeArg: OnChangeArg
}
@ -56,12 +57,13 @@ class From extends PureComponent<Props, State> {
}
private handleChooseDatabase = (item: DropdownItem): void => {
const {argKey, funcID, onChangeArg, expressionID} = this.props
const {argKey, funcID, onChangeArg, bodyID, declarationID} = this.props
onChangeArg({
funcID,
key: argKey,
value: item.text,
expressionID,
bodyID,
declarationID,
generate: true,
})
}

View File

@ -14,7 +14,8 @@ interface Props {
argKey: string
value: string | boolean
type: string
expressionID: string
bodyID: string
declarationID: string
onChangeArg: OnChangeArg
onGenerateScript: () => void
}
@ -26,10 +27,11 @@ class FuncArg extends PureComponent<Props> {
argKey,
value,
type,
funcName,
bodyID,
funcID,
funcName,
onChangeArg,
expressionID,
declarationID,
onGenerateScript,
} = this.props
@ -39,7 +41,8 @@ class FuncArg extends PureComponent<Props> {
argKey={argKey}
funcID={funcID}
value={this.value}
expressionID={expressionID}
bodyID={bodyID}
declarationID={declarationID}
onChangeArg={onChangeArg}
/>
)
@ -60,8 +63,9 @@ class FuncArg extends PureComponent<Props> {
value={this.value}
argKey={argKey}
funcID={funcID}
expressionID={expressionID}
bodyID={bodyID}
onChangeArg={onChangeArg}
declarationID={declarationID}
onGenerateScript={onGenerateScript}
/>
)
@ -72,9 +76,10 @@ class FuncArg extends PureComponent<Props> {
<FuncArgBool
value={this.boolValue}
argKey={argKey}
bodyID={bodyID}
funcID={funcID}
onChangeArg={onChangeArg}
expressionID={expressionID}
declarationID={declarationID}
onGenerateScript={onGenerateScript}
/>
)

View File

@ -7,7 +7,8 @@ interface Props {
argKey: string
value: boolean
funcID: string
expressionID: string
bodyID: string
declarationID: string
onChangeArg: OnChangeArg
onGenerateScript: () => void
}
@ -23,8 +24,15 @@ class FuncArgBool extends PureComponent<Props> {
}
private handleToggle = (value: boolean): void => {
const {argKey, funcID, expressionID, onChangeArg} = this.props
onChangeArg({funcID, key: argKey, value, generate: true, expressionID})
const {argKey, funcID, bodyID, onChangeArg, declarationID} = this.props
onChangeArg({
key: argKey,
value,
funcID,
bodyID,
declarationID,
generate: true,
})
}
}

View File

@ -7,7 +7,8 @@ interface Props {
argKey: string
value: string
type: string
expressionID: string
bodyID: string
declarationID: string
onChangeArg: OnChangeArg
onGenerateScript: () => void
}
@ -44,13 +45,14 @@ class FuncArgInput extends PureComponent<Props> {
}
private handleChange = (e: ChangeEvent<HTMLInputElement>) => {
const {funcID, argKey, expressionID} = this.props
const {funcID, argKey, bodyID, declarationID} = this.props
this.props.onChangeArg({
funcID,
key: argKey,
value: e.target.value,
expressionID,
declarationID,
bodyID,
})
}
}

View File

@ -6,15 +6,22 @@ import {Func} from 'src/types/ifql'
interface Props {
func: Func
expressionID: string
bodyID: string
onChangeArg: OnChangeArg
declarationID: string
onGenerateScript: () => void
}
@ErrorHandling
export default class FuncArgs extends PureComponent<Props> {
public render() {
const {expressionID, func, onChangeArg, onGenerateScript} = this.props
const {
func,
bodyID,
onChangeArg,
declarationID,
onGenerateScript,
} = this.props
return (
<div className="func-args">
@ -25,10 +32,11 @@ export default class FuncArgs extends PureComponent<Props> {
type={type}
argKey={key}
value={value}
bodyID={bodyID}
funcID={func.id}
funcName={func.name}
onChangeArg={onChangeArg}
expressionID={expressionID}
declarationID={declarationID}
onGenerateScript={onGenerateScript}
/>
)

View File

@ -1,12 +1,13 @@
import React, {PureComponent, MouseEvent} from 'react'
import FuncArgs from 'src/ifql/components/FuncArgs'
import {OnChangeArg, Func} from 'src/types/ifql'
import {OnDeleteFuncNode, OnChangeArg, Func} from 'src/types/ifql'
import {ErrorHandling} from 'src/shared/decorators/errors'
interface Props {
func: Func
expressionID: string
onDelete: (funcID: string, expressionID: string) => void
bodyID: string
declarationID?: string
onDelete: OnDeleteFuncNode
onChangeArg: OnChangeArg
onGenerateScript: () => void
}
@ -17,6 +18,10 @@ interface State {
@ErrorHandling
export default class FuncNode extends PureComponent<Props, State> {
public static defaultProps: Partial<Props> = {
declarationID: '',
}
constructor(props) {
super(props)
this.state = {
@ -25,7 +30,13 @@ export default class FuncNode extends PureComponent<Props, State> {
}
public render() {
const {expressionID, func, onChangeArg, onGenerateScript} = this.props
const {
func,
bodyID,
onChangeArg,
declarationID,
onGenerateScript,
} = this.props
const {isOpen} = this.state
return (
@ -36,8 +47,9 @@ export default class FuncNode extends PureComponent<Props, State> {
{isOpen && (
<FuncArgs
func={func}
bodyID={bodyID}
onChangeArg={onChangeArg}
expressionID={expressionID}
declarationID={declarationID}
onGenerateScript={onGenerateScript}
/>
)}
@ -49,7 +61,9 @@ export default class FuncNode extends PureComponent<Props, State> {
}
private handleDelete = (): void => {
this.props.onDelete(this.props.func.id, this.props.expressionID)
const {func, bodyID, declarationID} = this.props
this.props.onDelete({funcID: func.id, bodyID, declarationID})
}
private handleClick = (e: MouseEvent<HTMLElement>): void => {

View File

@ -14,7 +14,8 @@ interface State {
interface Props {
funcs: string[]
expressionID: string
bodyID: string
declarationID: string
onAddNode: OnAddNode
}
@ -65,8 +66,9 @@ export class FuncSelector extends PureComponent<Props, State> {
}
private handleAddNode = (name: string) => {
const {bodyID, declarationID} = this.props
this.handleCloseList()
this.props.onAddNode(name, this.props.expressionID)
this.props.onAddNode(name, bodyID, declarationID)
}
private get availableFuncs() {

View File

@ -1,11 +1,12 @@
import React, {PureComponent} from 'react'
import {connect} from 'react-redux'
import _ from 'lodash'
import TimeMachine from 'src/ifql/components/TimeMachine'
import KeyboardShortcuts from 'src/shared/components/KeyboardShortcuts'
import {Suggestion, FlatBody} from 'src/types/ifql'
import {InputArg, Handlers} from 'src/types/ifql'
import {InputArg, Handlers, DeleteFuncNodeArgs, Func} from 'src/types/ifql'
import {bodyNodes} from 'src/ifql/helpers'
import {getSuggestions, getAST} from 'src/ifql/apis'
@ -44,7 +45,7 @@ export class IFQLPage extends PureComponent<Props, State> {
ast: null,
suggestions: [],
script:
'foo = from(db: "telegraf")\n\t|> filter() \n\t|> range(start: -15m)\n\nfrom(db: "telegraf")\n\t|> filter() \n\t|> range(start: -15m)\n\n',
'baz = "baz"\n\nfoo = from(db: "telegraf")\n\t|> filter() \n\t|> range(start: -15m)\n\nbar = from(db: "telegraf")\n\t|> filter() \n\t|> range(start: -15m)\n\n',
}
}
@ -108,7 +109,7 @@ export class IFQLPage extends PureComponent<Props, State> {
}
private handleGenerateScript = (): void => {
this.getASTResponse(this.expressionsToScript)
this.getASTResponse(this.bodyToScript)
}
private handleChangeArg = ({
@ -116,30 +117,41 @@ export class IFQLPage extends PureComponent<Props, State> {
value,
generate,
funcID,
expressionID,
declarationID = '',
bodyID,
}: InputArg): void => {
const body = this.state.body.map(expression => {
if (expression.id !== expressionID) {
return expression
const body = this.state.body.map(b => {
if (b.id !== bodyID) {
return b
}
const funcs = expression.funcs.map(f => {
if (f.id !== funcID) {
return f
}
const args = f.args.map(a => {
if (a.key === key) {
return {...a, value}
if (declarationID) {
const declarations = b.declarations.map(d => {
if (d.id !== declarationID) {
return d
}
return a
const functions = this.editFuncArgs({
funcs: d.funcs,
funcID,
key,
value,
})
return {...d, funcs: functions}
})
return {...f, args}
return {...b, declarations}
}
const funcs = this.editFuncArgs({
funcs: b.funcs,
funcID,
key,
value,
})
return {...expression, funcs}
return {...b, funcs}
})
this.setState({body}, () => {
@ -149,9 +161,42 @@ export class IFQLPage extends PureComponent<Props, State> {
})
}
private get expressionsToScript(): string {
return this.state.body.reduce((acc, expression) => {
return `${acc + this.funcsToScript(expression.funcs)}\n\n`
private editFuncArgs = ({funcs, funcID, key, value}): Func[] => {
return funcs.map(f => {
if (f.id !== funcID) {
return f
}
const args = f.args.map(a => {
if (a.key === key) {
return {...a, value}
}
return a
})
return {...f, args}
})
}
private get bodyToScript(): string {
return this.state.body.reduce((acc, b) => {
if (b.declarations.length) {
const declaration = _.get(b, 'declarations.0', false)
if (!declaration) {
return acc
}
if (!declaration.funcs) {
return `${acc}${b.source}\n\n`
}
return `${acc}${declaration.name} = ${this.funcsToScript(
declaration.funcs
)}\n\n`
}
return `${acc}${this.funcsToScript(b.funcs)}\n\n`
}, '')
}
@ -183,51 +228,104 @@ export class IFQLPage extends PureComponent<Props, State> {
this.setState({script})
}
private handleAddNode = (name: string, expressionID: string): void => {
const script = this.state.body.reduce((acc, expression) => {
if (expression.id === expressionID) {
const {funcs} = expression
return `${acc}${this.funcsToScript(funcs)}\n\t|> ${name}()\n\n`
private handleAddNode = (
name: string,
bodyID: string,
declarationID: string
): void => {
const script = this.state.body.reduce((acc, body) => {
const {id, source, funcs} = body
if (id === bodyID) {
const declaration = body.declarations.find(d => d.id === declarationID)
if (declaration) {
return `${acc}${declaration.name} = ${this.appendFunc(
declaration.funcs,
name
)}`
}
return `${acc}${this.appendFunc(funcs, name)}`
}
return acc + expression.source
return `${acc}${this.formatSource(source)}`
}, '')
this.getASTResponse(script)
}
private handleDeleteFuncNode = (
funcID: string,
expressionID: string
): void => {
// TODO: export this and test functionality
private appendFunc = (funcs, name): string => {
return `${this.funcsToScript(funcs)}\n\t|> ${name}()\n\n`
}
private handleDeleteFuncNode = (ids: DeleteFuncNodeArgs): void => {
const {funcID, declarationID = '', bodyID} = ids
const script = this.state.body
.map((expression, expressionIndex) => {
if (expression.id !== expressionID) {
return expression.source
.map((body, bodyIndex) => {
if (body.id !== bodyID) {
return this.formatSource(body.source)
}
const funcs = expression.funcs.filter(f => f.id !== funcID)
const source = funcs.reduce((acc, f, i) => {
if (i === 0) {
return `${f.source}`
const isLast = bodyIndex === this.state.body.length - 1
if (declarationID) {
const declaration = body.declarations.find(
d => d.id === declarationID
)
if (!declaration) {
return
}
return `${acc}\n\t${f.source}`
}, '')
const isLast = expressionIndex === this.state.body.length - 1
if (isLast) {
return `${source}`
const functions = declaration.funcs.filter(f => f.id !== funcID)
const s = this.funcsToSource(functions)
return `${declaration.name} = ${this.formatLastSource(s, isLast)}`
}
return `${source}\n\n`
const funcs = body.funcs.filter(f => f.id !== funcID)
const source = this.funcsToSource(funcs)
return this.formatLastSource(source, isLast)
})
.join('')
this.getASTResponse(script)
}
private formatSource = (source: string): string => {
// currently a bug in the AST which does not add newlines to literal variable assignment bodies
if (!source.match(/\n\n/)) {
return `${source}\n\n`
}
return `${source}`
}
// formats the last line of a body string to include two new lines
private formatLastSource = (source: string, isLast: boolean): string => {
if (isLast) {
return `${source}`
}
// currently a bug in the AST which does not add newlines to literal variable assignment bodies
if (!source.match(/\n\n/)) {
return `${source}\n\n`
}
return `${source}\n\n`
}
// funcsToSource takes a list of funtion nodes and returns an ifql script
private funcsToSource = (funcs): string => {
return funcs.reduce((acc, f, i) => {
if (i === 0) {
return `${f.source}`
}
return `${acc}\n\t${f.source}`
}, '')
}
private getASTResponse = async (script: string) => {
const {links} = this.props

View File

@ -146,7 +146,10 @@ class Root extends PureComponent<{}, State> {
path="kapacitors/:id/edit:hash"
component={KapacitorPage}
/>
<Route path="admin-chronograf" component={AdminChronografPage} />
<Route
path="admin-chronograf/:tab"
component={AdminChronografPage}
/>
<Route path="admin-influxdb/:tab" component={AdminInfluxDBPage} />
<Route path="manage-sources" component={ManageSources} />
<Route path="manage-sources/new" component={SourcePage} />

View File

@ -0,0 +1,74 @@
import _ from 'lodash'
import {fetchTimeSeriesAsync} from 'src/shared/actions/timeSeries'
import {removeUnselectedTemplateValues} from 'src/dashboards/constants'
import {intervalValuesPoints} from 'src/shared/constants'
interface TemplateQuery {
db: string
rp: string
influxql: string
}
interface TemplateValue {
type: string
value: string
selected: boolean
}
interface Template {
type: string
tempVar: string
query: TemplateQuery
values: TemplateValue[]
}
interface Query {
host: string | string[]
text: string
database: string
db: string
rp: string
}
export const fetchTimeSeries = async (
queries: Query[],
resolution: number,
templates: Template[],
editQueryStatus: () => void
) => {
const timeSeriesPromises = queries.map(query => {
const {host, database, rp} = query
// the key `database` was used upstream in HostPage.js, and since as of this writing
// the codebase has not been fully converted to TypeScript, it's not clear where else
// it may be used, but this slight modification is intended to allow for the use of
// `database` while moving over to `db` for consistency over time
const db = _.get(query, 'db', database)
const templatesWithIntervalVals = templates.map(temp => {
if (temp.tempVar === ':interval:') {
if (resolution) {
const values = temp.values.map(v => ({
...v,
value: `${_.toInteger(Number(resolution) / 3)}`,
}))
return {...temp, values}
}
return {...temp, values: intervalValuesPoints}
}
return temp
})
const tempVars = removeUnselectedTemplateValues(templatesWithIntervalVals)
const source = host
return fetchTimeSeriesAsync(
{source, db, rp, query, tempVars, resolution},
editQueryStatus
)
})
return Promise.all(timeSeriesPromises)
}

View File

@ -1,294 +0,0 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import _ from 'lodash'
import {fetchTimeSeriesAsync} from 'shared/actions/timeSeries'
import {removeUnselectedTemplateValues} from 'src/dashboards/constants'
import {intervalValuesPoints} from 'src/shared/constants'
import {getQueryConfig} from 'shared/apis'
const AutoRefresh = ComposedComponent => {
class wrapper extends Component {
constructor() {
super()
this.state = {
lastQuerySuccessful: true,
timeSeries: [],
resolution: null,
queryASTs: [],
}
}
async componentDidMount() {
const {queries, templates, autoRefresh, type} = this.props
this.executeQueries(queries, templates)
if (type === 'table') {
const queryASTs = await this.getQueryASTs(queries, templates)
this.setState({queryASTs})
}
if (autoRefresh) {
this.intervalID = setInterval(
() => this.executeQueries(queries, templates),
autoRefresh
)
}
}
getQueryASTs = async (queries, templates) => {
return await Promise.all(
queries.map(async q => {
const host = _.isArray(q.host) ? q.host[0] : q.host
const url = host.replace('proxy', 'queries')
const text = q.text
const {data} = await getQueryConfig(url, [{query: text}], templates)
return data.queries[0].queryAST
})
)
}
async componentWillReceiveProps(nextProps) {
const inViewDidUpdate = this.props.inView !== nextProps.inView
const queriesDidUpdate = this.queryDifference(
this.props.queries,
nextProps.queries
).length
const tempVarsDidUpdate = !_.isEqual(
this.props.templates,
nextProps.templates
)
const shouldRefetch =
queriesDidUpdate || tempVarsDidUpdate || inViewDidUpdate
if (shouldRefetch) {
if (this.props.type === 'table') {
const queryASTs = await this.getQueryASTs(
nextProps.queries,
nextProps.templates
)
this.setState({queryASTs})
}
this.executeQueries(
nextProps.queries,
nextProps.templates,
nextProps.inView
)
}
if (this.props.autoRefresh !== nextProps.autoRefresh || shouldRefetch) {
clearInterval(this.intervalID)
if (nextProps.autoRefresh) {
this.intervalID = setInterval(
() =>
this.executeQueries(
nextProps.queries,
nextProps.templates,
nextProps.inView
),
nextProps.autoRefresh
)
}
}
}
queryDifference = (left, right) => {
const leftStrs = left.map(q => `${q.host}${q.text}`)
const rightStrs = right.map(q => `${q.host}${q.text}`)
return _.difference(
_.union(leftStrs, rightStrs),
_.intersection(leftStrs, rightStrs)
)
}
executeQueries = async (
queries,
templates = [],
inView = this.props.inView
) => {
const {editQueryStatus, grabDataForDownload} = this.props
const {resolution} = this.state
if (!inView) {
return
}
if (!queries.length) {
this.setState({timeSeries: []})
return
}
this.setState({isFetching: true})
const timeSeriesPromises = queries.map(query => {
const {host, database, rp} = query
// the key `database` was used upstream in HostPage.js, and since as of this writing
// the codebase has not been fully converted to TypeScript, it's not clear where else
// it may be used, but this slight modification is intended to allow for the use of
// `database` while moving over to `db` for consistency over time
const db = _.get(query, 'db', database)
const templatesWithIntervalVals = templates.map(temp => {
if (temp.tempVar === ':interval:') {
if (resolution) {
// resize event
return {
...temp,
values: temp.values.map(v => ({
...v,
value: `${_.toInteger(Number(resolution) / 3)}`,
})),
}
}
return {
...temp,
values: intervalValuesPoints,
}
}
return temp
})
const tempVars = removeUnselectedTemplateValues(
templatesWithIntervalVals
)
return fetchTimeSeriesAsync(
{
source: host,
db,
rp,
query,
tempVars,
resolution,
},
editQueryStatus
)
})
try {
const timeSeries = await Promise.all(timeSeriesPromises)
const newSeries = timeSeries.map(response => ({response}))
const lastQuerySuccessful = this._resultsForQuery(newSeries)
this.setState({
timeSeries: newSeries,
lastQuerySuccessful,
isFetching: false,
})
if (grabDataForDownload) {
grabDataForDownload(timeSeries)
}
} catch (err) {
console.error(err)
}
}
componentWillUnmount() {
clearInterval(this.intervalID)
this.intervalID = false
}
setResolution = resolution => {
if (resolution !== this.state.resolution) {
this.setState({resolution})
}
}
render() {
const {timeSeries, queryASTs} = this.state
if (this.state.isFetching && this.state.lastQuerySuccessful) {
return (
<ComposedComponent
{...this.props}
data={timeSeries}
setResolution={this.setResolution}
isFetchingInitially={false}
isRefreshing={true}
queryASTs={queryASTs}
/>
)
}
return (
<ComposedComponent
{...this.props}
data={timeSeries}
setResolution={this.setResolution}
queryASTs={queryASTs}
/>
)
}
_resultsForQuery = data =>
data.length
? data.every(({response}) =>
_.get(response, 'results', []).every(
result =>
Object.keys(result).filter(k => k !== 'statement_id').length !==
0
)
)
: false
}
wrapper.defaultProps = {
inView: true,
}
const {
array,
arrayOf,
bool,
element,
func,
number,
oneOfType,
shape,
string,
} = PropTypes
wrapper.propTypes = {
type: string.isRequired,
children: element,
autoRefresh: number.isRequired,
inView: bool,
templates: arrayOf(
shape({
type: string.isRequired,
tempVar: string.isRequired,
query: shape({
db: string,
rp: string,
influxql: string,
}),
values: arrayOf(
shape({
type: string.isRequired,
value: string.isRequired,
selected: bool,
})
).isRequired,
})
),
queries: arrayOf(
shape({
host: oneOfType([string, arrayOf(string)]),
text: string,
}).isRequired
).isRequired,
axes: shape({
bounds: shape({
y: array,
y2: array,
}),
}),
editQueryStatus: func,
grabDataForDownload: func,
}
return wrapper
}
export default AutoRefresh

View File

@ -0,0 +1,288 @@
import React, {Component, ComponentClass} from 'react'
import _ from 'lodash'
import {getQueryConfig} from 'src/shared/apis'
import {fetchTimeSeries} from 'src/shared/apis/query'
import {DEFAULT_TIME_SERIES} from 'src/shared/constants/series'
import {TimeSeriesServerResponse, TimeSeriesResponse} from 'src/types/series'
interface Axes {
bounds: {
y: number[]
y2: number[]
}
}
interface Query {
host: string | string[]
text: string
database: string
db: string
rp: string
}
interface TemplateQuery {
db: string
rp: string
influxql: string
}
interface TemplateValue {
type: string
value: string
selected: boolean
}
interface Template {
type: string
tempVar: string
query: TemplateQuery
values: TemplateValue[]
}
export interface Props {
type: string
autoRefresh: number
inView: boolean
templates: Template[]
queries: Query[]
axes: Axes
editQueryStatus: () => void
grabDataForDownload: (timeSeries: TimeSeriesServerResponse[]) => void
}
interface QueryAST {
groupBy?: {
tags: string[]
}
}
interface State {
isFetching: boolean
isLastQuerySuccessful: boolean
timeSeries: TimeSeriesServerResponse[]
resolution: number | null
queryASTs?: QueryAST[]
}
export interface OriginalProps {
data: TimeSeriesServerResponse[]
setResolution: (resolution: number) => void
isFetchingInitially?: boolean
isRefreshing?: boolean
queryASTs?: QueryAST[]
}
const AutoRefresh = (
ComposedComponent: ComponentClass<OriginalProps & Props>
) => {
class Wrapper extends Component<Props, State> {
public static defaultProps = {
inView: true,
}
private intervalID: NodeJS.Timer | null
constructor(props: Props) {
super(props)
this.state = {
isFetching: false,
isLastQuerySuccessful: true,
timeSeries: DEFAULT_TIME_SERIES,
resolution: null,
queryASTs: [],
}
}
public async componentDidMount() {
if (this.isTable) {
const queryASTs = await this.getQueryASTs()
this.setState({queryASTs})
}
this.startNewPolling()
}
public async componentDidUpdate(prevProps: Props) {
if (!this.isPropsDifferent(prevProps)) {
return
}
if (this.isTable) {
const queryASTs = await this.getQueryASTs()
this.setState({queryASTs})
}
this.startNewPolling()
}
public executeQueries = async () => {
const {editQueryStatus, grabDataForDownload, inView, queries} = this.props
const {resolution} = this.state
if (!inView) {
return
}
if (!queries.length) {
this.setState({timeSeries: DEFAULT_TIME_SERIES})
return
}
this.setState({isFetching: true})
const templates: Template[] = _.get(this.props, 'templates', [])
try {
const timeSeries = await fetchTimeSeries(
queries,
resolution,
templates,
editQueryStatus
)
const newSeries = timeSeries.map((response: TimeSeriesResponse) => ({
response,
}))
const isLastQuerySuccessful = this.hasResultsForQuery(newSeries)
this.setState({
timeSeries: newSeries,
isLastQuerySuccessful,
isFetching: false,
})
if (grabDataForDownload) {
grabDataForDownload(newSeries)
}
} catch (err) {
console.error(err)
}
}
public componentWillUnmount() {
this.clearInterval()
}
public render() {
const {
timeSeries,
queryASTs,
isFetching,
isLastQuerySuccessful,
} = this.state
const hasValues = _.some(timeSeries, s => {
const results = _.get(s, 'response.results', [])
const v = _.some(results, r => r.series)
return v
})
if (!hasValues) {
return (
<div className="graph-empty">
<p>No Results</p>
</div>
)
}
if (isFetching && isLastQuerySuccessful) {
return (
<ComposedComponent
{...this.props}
data={timeSeries}
setResolution={this.setResolution}
isFetchingInitially={false}
isRefreshing={true}
queryASTs={queryASTs}
/>
)
}
return (
<ComposedComponent
{...this.props}
data={timeSeries}
setResolution={this.setResolution}
queryASTs={queryASTs}
/>
)
}
private setResolution = resolution => {
if (resolution !== this.state.resolution) {
this.setState({resolution})
}
}
private clearInterval() {
if (!this.intervalID) {
return
}
clearInterval(this.intervalID)
this.intervalID = null
}
private isPropsDifferent(nextProps: Props) {
return (
this.props.inView !== nextProps.inView ||
!!this.queryDifference(this.props.queries, nextProps.queries).length ||
!_.isEqual(this.props.templates, nextProps.templates) ||
this.props.autoRefresh !== nextProps.autoRefresh
)
}
private startNewPolling() {
this.clearInterval()
const {autoRefresh} = this.props
this.executeQueries()
if (autoRefresh) {
this.intervalID = setInterval(this.executeQueries, autoRefresh)
}
}
private queryDifference = (left, right) => {
const mapper = q => `${q.host}${q.text}`
const leftStrs = left.map(mapper)
const rightStrs = right.map(mapper)
return _.difference(
_.union(leftStrs, rightStrs),
_.intersection(leftStrs, rightStrs)
)
}
private get isTable(): boolean {
return this.props.type === 'table'
}
private getQueryASTs = async (): Promise<QueryAST[]> => {
const {queries, templates} = this.props
return await Promise.all(
queries.map(async q => {
const host = _.isArray(q.host) ? q.host[0] : q.host
const url = host.replace('proxy', 'queries')
const text = q.text
const {data} = await getQueryConfig(url, [{query: text}], templates)
return data.queries[0].queryAST
})
)
}
private hasResultsForQuery = (data): boolean => {
if (!data.length) {
return false
}
data.every(({resp}) =>
_.get(resp, 'results', []).every(r => Object.keys(r).length > 1)
)
}
}
return Wrapper
}
export default AutoRefresh

View File

@ -41,7 +41,7 @@ class AutoRefreshDropdown extends Component {
paused: +milliseconds === 0,
})}
>
<div className={classnames('dropdown dropdown-160', {open: isOpen})}>
<div className={classnames('dropdown dropdown-120', {open: isOpen})}>
<div
className="btn btn-sm btn-default dropdown-toggle"
onClick={this.toggleMenu}
@ -56,7 +56,7 @@ class AutoRefreshDropdown extends Component {
<span className="caret" />
</div>
<ul className="dropdown-menu">
<li className="dropdown-header">AutoRefresh Interval</li>
<li className="dropdown-header">AutoRefresh</li>
{autoRefreshItems.map(item => (
<li className="dropdown-item" key={item.menuOption}>
<a href="#" onClick={this.handleSelection(item.milliseconds)}>

View File

@ -1,3 +1,4 @@
import _ from 'lodash'
import React, {PureComponent} from 'react'
import Dygraph from 'dygraphs'
import {connect} from 'react-redux'
@ -35,7 +36,7 @@ class Crosshair extends PureComponent<Props> {
private get isVisible() {
const {hoverTime} = this.props
return hoverTime !== 0
return hoverTime !== 0 && _.isFinite(hoverTime)
}
private get crosshairLeft(): number {

View File

@ -0,0 +1,16 @@
import React, {PureComponent} from 'react'
class InvalidData extends PureComponent<{}> {
public render() {
return (
<div className="graph-empty">
<p>
The data returned from the query can't be visualized with this graph
type.<br />Try updating the query or selecting a different graph type.
</p>
</div>
)
}
}
export default InvalidData

View File

@ -45,7 +45,17 @@ const Layout = (
{
host,
cell,
cell: {h, axes, type, colors, legend, tableOptions},
cell: {
h,
axes,
type,
colors,
legend,
timeFormat,
fieldOptions,
tableOptions,
decimalPlaces,
},
source,
sources,
onZoom,
@ -87,6 +97,9 @@ const Layout = (
type={type}
isDragging={isDragging}
tableOptions={tableOptions}
fieldOptions={fieldOptions}
timeFormat={timeFormat}
decimalPlaces={decimalPlaces}
staticLegend={IS_STATIC_LEGEND(legend)}
cellHeight={h}
onZoom={onZoom}
@ -140,6 +153,28 @@ const propTypes = {
name: string.isRequired,
type: string.isRequired,
colors: colorsStringSchema,
tableOptions: shape({
verticalTimeAxis: bool.isRequired,
sortBy: shape({
internalName: string.isRequired,
displayName: string.isRequired,
visible: bool.isRequired,
}).isRequired,
wrapping: string.isRequired,
fixFirstColumn: bool.isRequired,
}),
timeFormat: string,
decimalPlaces: shape({
isEnforced: bool.isRequired,
digits: number.isRequired,
}),
fieldOptions: arrayOf(
shape({
internalName: string.isRequired,
displayName: string.isRequired,
visible: bool.isRequired,
}).isRequired
),
}).isRequired,
templates: arrayOf(shape()),
host: string,

View File

@ -173,6 +173,24 @@ LayoutRenderer.propTypes = {
i: string.isRequired,
name: string.isRequired,
type: string.isRequired,
timeFormat: string,
tableOptions: shape({
verticalTimeAxis: bool.isRequired,
sortBy: shape({
internalName: string.isRequired,
displayName: string.isRequired,
visible: bool.isRequired,
}).isRequired,
wrapping: string.isRequired,
fixFirstColumn: bool.isRequired,
}),
fieldOptions: arrayOf(
shape({
internalName: string.isRequired,
displayName: string.isRequired,
visible: bool.isRequired,
}).isRequired
),
}).isRequired
),
templates: arrayOf(shape()),

View File

@ -1,3 +1,4 @@
import _ from 'lodash'
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import Dygraph from 'shared/components/Dygraph'
@ -6,17 +7,34 @@ import SingleStat from 'src/shared/components/SingleStat'
import {timeSeriesToDygraph} from 'utils/timeSeriesTransformers'
import {colorsStringSchema} from 'shared/schemas'
import {ErrorHandling} from 'src/shared/decorators/errors'
import {ErrorHandlingWith} from 'src/shared/decorators/errors'
import InvalidData from 'src/shared/components/InvalidData'
@ErrorHandling
const validateTimeSeries = timeseries => {
return _.every(timeseries, r =>
_.every(
r,
(v, i) => (i === 0 && Date.parse(v)) || _.isNumber(v) || _.isNull(v)
)
)
}
@ErrorHandlingWith(InvalidData)
class LineGraph extends Component {
constructor(props) {
super(props)
this.isValidData = true
}
componentWillMount() {
const {data, isInDataExplorer} = this.props
this.parseTimeSeries(data, isInDataExplorer)
}
parseTimeSeries(data, isInDataExplorer) {
this._timeSeries = timeSeriesToDygraph(data, isInDataExplorer)
this.isValidData = validateTimeSeries(
_.get(this._timeSeries, 'timeSeries', [])
)
}
componentWillUpdate(nextProps) {
@ -25,14 +43,15 @@ class LineGraph extends Component {
data !== nextProps.data ||
activeQueryIndex !== nextProps.activeQueryIndex
) {
this._timeSeries = timeSeriesToDygraph(
nextProps.data,
nextProps.isInDataExplorer
)
this.parseTimeSeries(nextProps.data, nextProps.isInDataExplorer)
}
}
render() {
if (!this.isValidData) {
return <InvalidData />
}
const {
data,
axes,

View File

@ -13,6 +13,10 @@ import TableGraph from 'shared/components/TableGraph'
import {colorsStringSchema} from 'shared/schemas'
import {setHoverTime} from 'src/dashboards/actions'
import {
DEFAULT_TIME_FORMAT,
DEFAULT_DECIMAL_PLACES,
} from 'src/dashboards/constants'
const RefreshingLineGraph = AutoRefresh(LineGraph)
const RefreshingSingleStat = AutoRefresh(SingleStat)
@ -33,6 +37,9 @@ const RefreshingGraph = ({
timeRange,
cellHeight,
autoRefresh,
fieldOptions,
timeFormat,
decimalPlaces,
resizerTopHeight,
staticLegend,
manualRefresh, // when changed, re-mounts the component
@ -44,7 +51,6 @@ const RefreshingGraph = ({
}) => {
const prefix = (axes && axes.y.prefix) || ''
const suffix = (axes && axes.y.suffix) || ''
if (!queries.length) {
return (
<div className="graph-empty">
@ -63,6 +69,7 @@ const RefreshingGraph = ({
templates={templates}
autoRefresh={autoRefresh}
cellHeight={cellHeight}
editQueryStatus={editQueryStatus}
prefix={prefix}
suffix={suffix}
inView={inView}
@ -81,6 +88,7 @@ const RefreshingGraph = ({
autoRefresh={autoRefresh}
cellHeight={cellHeight}
resizerTopHeight={resizerTopHeight}
editQueryStatus={editQueryStatus}
resizeCoords={resizeCoords}
cellID={cellID}
prefix={prefix}
@ -105,6 +113,10 @@ const RefreshingGraph = ({
cellHeight={cellHeight}
resizeCoords={resizeCoords}
tableOptions={tableOptions}
fieldOptions={fieldOptions}
timeFormat={timeFormat}
decimalPlaces={decimalPlaces}
editQueryStatus={editQueryStatus}
resizerTopHeight={resizerTopHeight}
handleSetHoverTime={handleSetHoverTime}
isInCEO={isInCEO}
@ -164,7 +176,28 @@ RefreshingGraph.propTypes = {
colors: colorsStringSchema,
cellID: string,
inView: bool,
tableOptions: shape({}),
tableOptions: shape({
verticalTimeAxis: bool.isRequired,
sortBy: shape({
internalName: string.isRequired,
displayName: string.isRequired,
visible: bool.isRequired,
}).isRequired,
wrapping: string.isRequired,
fixFirstColumn: bool.isRequired,
}),
fieldOptions: arrayOf(
shape({
internalName: string.isRequired,
displayName: string.isRequired,
visible: bool.isRequired,
}).isRequired
),
timeFormat: string.isRequired,
decimalPlaces: shape({
isEnforced: bool.isRequired,
digits: number.isRequired,
}).isRequired,
hoverTime: string.isRequired,
handleSetHoverTime: func.isRequired,
isInCEO: bool,
@ -174,6 +207,8 @@ RefreshingGraph.defaultProps = {
manualRefresh: 0,
staticLegend: false,
inView: true,
timeFormat: DEFAULT_TIME_FORMAT,
decimalPlaces: DEFAULT_DECIMAL_PLACES,
}
const mapStateToProps = ({dashboardUI, annotations: {mode}}) => ({

View File

@ -9,27 +9,22 @@ import {bindActionCreators} from 'redux'
import moment from 'moment'
import {reduce} from 'fast.js'
const {arrayOf, bool, shape, string, func} = PropTypes
import {timeSeriesToTableGraph} from 'src/utils/timeSeriesTransformers'
import {
computeFieldNames,
computeFieldOptions,
transformTableData,
} from 'src/dashboards/utils/tableGraph'
import {updateTableOptions} from 'src/dashboards/actions/cellEditorOverlay'
import {updateFieldOptions} from 'src/dashboards/actions/cellEditorOverlay'
import {DEFAULT_TIME_FIELD} from 'src/dashboards/constants'
import {
NULL_ARRAY_INDEX,
NULL_HOVER_TIME,
DEFAULT_TIME_FORMAT,
TIME_FIELD_DEFAULT,
ASCENDING,
DESCENDING,
NULL_HOVER_TIME,
NULL_ARRAY_INDEX,
DEFAULT_FIX_FIRST_COLUMN,
DEFAULT_VERTICAL_TIME_AXIS,
DEFAULT_SORT_DIRECTION,
FIX_FIRST_COLUMN_DEFAULT,
VERTICAL_TIME_AXIS_DEFAULT,
} from 'src/shared/constants/tableGraph'
import {generateThresholdsListHexs} from 'shared/constants/colorOperations'
import {colorsStringSchema} from 'shared/schemas'
import {ErrorHandling} from 'src/shared/decorators/errors'
@ -42,7 +37,7 @@ class TableGraph extends Component {
const sortField = _.get(
this.props,
['tableOptions', 'sortBy', 'internalName'],
TIME_FIELD_DEFAULT.internalName
DEFAULT_TIME_FIELD.internalName
)
this.state = {
@ -58,19 +53,19 @@ class TableGraph extends Component {
}
}
handleUpdateTableOptions = (fieldNames, tableOptions) => {
handleUpdateFieldOptions = fieldOptions => {
const {isInCEO} = this.props
if (!isInCEO) {
return
}
this.props.handleUpdateTableOptions({...tableOptions, fieldNames})
this.props.handleUpdateFieldOptions(fieldOptions)
}
componentWillReceiveProps(nextProps) {
const updatedProps = _.keys(nextProps).filter(
k => !_.isEqual(this.props[k], nextProps[k])
)
const {tableOptions} = nextProps
const {tableOptions, fieldOptions, timeFormat, decimalPlaces} = nextProps
let result = {}
@ -80,10 +75,11 @@ class TableGraph extends Component {
const data = _.get(result, 'data', this.state.data)
const sortedLabels = _.get(result, 'sortedLabels', this.state.sortedLabels)
const fieldNames = computeFieldNames(tableOptions.fieldNames, sortedLabels)
const computedFieldOptions = computeFieldOptions(fieldOptions, sortedLabels)
if (_.includes(updatedProps, 'data')) {
this.handleUpdateTableOptions(fieldNames, tableOptions)
this.handleUpdateFieldOptions(computedFieldOptions)
}
if (_.isEmpty(data[0])) {
@ -106,14 +102,23 @@ class TableGraph extends Component {
if (
_.includes(updatedProps, 'data') ||
_.includes(updatedProps, 'tableOptions')
_.includes(updatedProps, 'tableOptions') ||
_.includes(updatedProps, 'fieldOptions') ||
_.includes(updatedProps, 'timeFormat')
) {
const {
transformedData,
sortedTimeVals,
columnWidths,
totalWidths,
} = transformTableData(data, sort, fieldNames, tableOptions)
} = transformTableData(
data,
sort,
computedFieldOptions,
tableOptions,
timeFormat,
decimalPlaces
)
this.setState({
data,
@ -186,34 +191,30 @@ class TableGraph extends Component {
}
}
handleClickFieldName = fieldName => () => {
const {tableOptions} = this.props
const {timeFormat} = tableOptions
const {data, sortField, sortDirection} = this.state
const verticalTimeAxis = _.get(tableOptions, 'verticalTimeAxis', true)
const fieldNames = _.get(tableOptions, 'fieldNames', [TIME_FIELD_DEFAULT])
handleClickFieldName = clickedFieldName => () => {
const {tableOptions, fieldOptions, timeFormat, decimalPlaces} = this.props
const {data, sort} = this.state
let direction
if (fieldName === sortField) {
direction = sortDirection === ASCENDING ? DESCENDING : ASCENDING
if (clickedFieldName === sort.field) {
sort.direction = sort.direction === ASCENDING ? DESCENDING : ASCENDING
} else {
direction = DEFAULT_SORT_DIRECTION
sort.field = clickedFieldName
sort.direction = DEFAULT_SORT_DIRECTION
}
const {transformedData, sortedTimeVals} = transformTableData(
data,
fieldName,
direction,
verticalTimeAxis,
fieldNames,
timeFormat
sort,
fieldOptions,
tableOptions,
timeFormat,
decimalPlaces
)
this.setState({
transformedData,
sortedTimeVals,
sortField: fieldName,
sortDirection: direction,
sort,
})
}
@ -246,30 +247,52 @@ class TableGraph extends Component {
return adjustedColumnSizerWidth
}
createCellContents = (
cellData,
fieldName,
isTimeData,
isFieldName,
isNumerical
) => {
const {timeFormat, decimalPlaces} = this.props
if (isTimeData) {
return `${moment(cellData).format(timeFormat)}`
}
if (isFieldName) {
return fieldName
}
if (isNumerical && decimalPlaces.isEnforced) {
return cellData.toFixed(decimalPlaces.digits)
}
return `${cellData}`
}
cellRenderer = ({columnIndex, rowIndex, key, parent, style}) => {
const {
hoveredColumnIndex,
hoveredRowIndex,
transformedData,
sortField,
sortDirection,
sort,
} = this.state
const {tableOptions, colors} = parent.props
const {
timeFormat = DEFAULT_TIME_FORMAT,
verticalTimeAxis = VERTICAL_TIME_AXIS_DEFAULT,
fixFirstColumn = FIX_FIRST_COLUMN_DEFAULT,
fieldNames = [TIME_FIELD_DEFAULT],
fieldOptions = [DEFAULT_TIME_FIELD],
tableOptions,
colors,
} = parent.props
const {
verticalTimeAxis = DEFAULT_VERTICAL_TIME_AXIS,
fixFirstColumn = DEFAULT_FIX_FIRST_COLUMN,
} = tableOptions
const cellData = transformedData[rowIndex][columnIndex]
const timeFieldIndex = fieldNames.findIndex(
field => field.internalName === TIME_FIELD_DEFAULT.internalName
const timeFieldIndex = fieldOptions.findIndex(
field => field.internalName === DEFAULT_TIME_FIELD.internalName
)
const visibleTime = _.get(fieldNames, [timeFieldIndex, 'visible'], true)
const visibleTime = _.get(fieldOptions, [timeFieldIndex, 'visible'], true)
const isFixedRow = rowIndex === 0 && columnIndex > 0
const isFixedColumn = fixFirstColumn && rowIndex > 0 && columnIndex === 0
@ -280,7 +303,7 @@ class TableGraph extends Component {
: rowIndex === timeFieldIndex && columnIndex !== 0)
const isFieldName = verticalTimeAxis ? rowIndex === 0 : columnIndex === 0
const isFixedCorner = rowIndex === 0 && columnIndex === 0
const dataIsNumerical = _.isNumber(cellData)
const isNumerical = _.isNumber(cellData)
const isHighlightedRow =
rowIndex === parent.props.scrollToRow ||
(rowIndex === hoveredRowIndex && hoveredRowIndex !== 0)
@ -305,7 +328,7 @@ class TableGraph extends Component {
}
const foundField =
isFieldName && fieldNames.find(field => field.internalName === cellData)
isFieldName && fieldOptions.find(field => field.internalName === cellData)
const fieldName =
foundField && (foundField.displayName || foundField.internalName)
@ -315,19 +338,21 @@ class TableGraph extends Component {
'table-graph-cell__fixed-corner': isFixedCorner,
'table-graph-cell__highlight-row': isHighlightedRow,
'table-graph-cell__highlight-column': isHighlightedColumn,
'table-graph-cell__numerical': dataIsNumerical,
'table-graph-cell__numerical': isNumerical,
'table-graph-cell__field-name': isFieldName,
'table-graph-cell__sort-asc':
isFieldName && sortField === cellData && sortDirection === ASCENDING,
isFieldName && sort.field === cellData && sort.direction === ASCENDING,
'table-graph-cell__sort-desc':
isFieldName && sortField === cellData && sortDirection === DESCENDING,
isFieldName && sort.field === cellData && sort.direction === DESCENDING,
})
const cellContents = isTimeData
? `${moment(cellData).format(
timeFormat === '' ? DEFAULT_TIME_FORMAT : timeFormat
)}`
: fieldName || `${cellData}`
const cellContents = this.createCellContents(
cellData,
fieldName,
isTimeData,
isFieldName,
isNumerical
)
return (
<div
@ -353,12 +378,18 @@ class TableGraph extends Component {
hoveredColumnIndex,
hoveredRowIndex,
timeColumnWidth,
sortField,
sortDirection,
sort,
transformedData,
} = this.state
const {hoverTime, tableOptions, colors} = this.props
const {fixFirstColumn = FIX_FIRST_COLUMN_DEFAULT} = tableOptions
const {
hoverTime,
tableOptions,
colors,
fieldOptions,
timeFormat,
decimalPlaces,
} = this.props
const {fixFirstColumn = DEFAULT_FIX_FIRST_COLUMN} = tableOptions
const columnCount = _.get(transformedData, ['0', 'length'], 0)
const rowCount = columnCount === 0 ? 0 : transformedData.length
@ -399,14 +430,16 @@ class TableGraph extends Component {
enableFixedRowScroll={true}
scrollToRow={scrollToRow}
scrollToColumn={scrollToColumn}
sortField={sortField}
sortDirection={sortDirection}
sort={sort}
cellRenderer={this.cellRenderer}
hoveredColumnIndex={hoveredColumnIndex}
hoveredRowIndex={hoveredRowIndex}
hoverTime={hoverTime}
colors={colors}
fieldOptions={fieldOptions}
tableOptions={tableOptions}
timeFormat={timeFormat}
decimalPlaces={decimalPlaces}
timeColumnWidth={timeColumnWidth}
classNameBottomRightGrid="table-graph--scroll-window"
/>
@ -417,11 +450,11 @@ class TableGraph extends Component {
)
}
}
const {arrayOf, bool, number, shape, string, func} = PropTypes
TableGraph.propTypes = {
data: arrayOf(shape()),
tableOptions: shape({
timeFormat: string.isRequired,
verticalTimeAxis: bool.isRequired,
sortBy: shape({
internalName: string.isRequired,
@ -429,17 +462,22 @@ TableGraph.propTypes = {
visible: bool.isRequired,
}).isRequired,
wrapping: string.isRequired,
fieldNames: arrayOf(
shape({
internalName: string.isRequired,
displayName: string.isRequired,
visible: bool.isRequired,
})
).isRequired,
fixFirstColumn: bool,
fixFirstColumn: bool.isRequired,
}),
timeFormat: string.isRequired,
decimalPlaces: shape({
isEnforced: bool.isRequired,
digits: number.isRequired,
}).isRequired,
fieldOptions: arrayOf(
shape({
internalName: string.isRequired,
displayName: string.isRequired,
visible: bool.isRequired,
})
).isRequired,
hoverTime: string,
handleUpdateTableOptions: func,
handleUpdateFieldOptions: func,
handleSetHoverTime: func,
colors: colorsStringSchema,
queryASTs: arrayOf(shape()),
@ -447,7 +485,7 @@ TableGraph.propTypes = {
}
const mapDispatchToProps = dispatch => ({
handleUpdateTableOptions: bindActionCreators(updateTableOptions, dispatch),
handleUpdateFieldOptions: bindActionCreators(updateFieldOptions, dispatch),
})
export default connect(null, mapDispatchToProps)(TableGraph)

View File

@ -85,7 +85,7 @@ class TimeRangeDropdown extends Component {
<div className="time-range-dropdown">
<div
className={classnames('dropdown', {
'dropdown-160': isRelativeTimeRange,
'dropdown-120': isRelativeTimeRange,
'dropdown-210': isNow,
'dropdown-290': !isRelativeTimeRange && !isNow,
open: isOpen,
@ -109,7 +109,7 @@ class TimeRangeDropdown extends Component {
>
{preventCustomTimeRange ? null : (
<div>
<li className="dropdown-header">Absolute Time Ranges</li>
<li className="dropdown-header">Absolute Time</li>
<li
className={
isCustomTimeRangeOpen
@ -118,13 +118,13 @@ class TimeRangeDropdown extends Component {
}
>
<a href="#" onClick={this.showCustomTimeRange}>
Custom Date Picker
Date Picker
</a>
</li>
</div>
)}
<li className="dropdown-header">
{preventCustomTimeRange ? '' : 'Relative '}Time Ranges
{preventCustomTimeRange ? '' : 'Relative '}Time
</li>
{timeRanges.map(item => {
return (

View File

@ -0,0 +1,7 @@
export const DEFAULT_TIME_SERIES = [
{
response: {
results: [],
},
},
]

View File

@ -2,43 +2,11 @@ export const NULL_ARRAY_INDEX = -1
export const NULL_HOVER_TIME = '0'
export const TIME_FORMAT_TOOLTIP_LINK =
'http://momentjs.com/docs/#/parsing/string-format/'
export const TIME_FIELD_DEFAULT = {
internalName: 'time',
displayName: '',
visible: true,
}
export const ASCENDING = 'asc'
export const DESCENDING = 'desc'
export const DEFAULT_SORT_DIRECTION = ASCENDING
export const FIX_FIRST_COLUMN_DEFAULT = true
export const VERTICAL_TIME_AXIS_DEFAULT = true
export const DEFAULT_FIX_FIRST_COLUMN = true
export const DEFAULT_VERTICAL_TIME_AXIS = true
export const CELL_HORIZONTAL_PADDING = 30
export const DEFAULT_TIME_FORMAT = 'MM/DD/YYYY HH:mm:ss'
export const TIME_FORMAT_CUSTOM = 'Custom'
export const FORMAT_OPTIONS = [
{text: DEFAULT_TIME_FORMAT},
{text: 'MM/DD/YYYY HH:mm:ss.SSS'},
{text: 'YYYY-MM-DD HH:mm:ss'},
{text: 'HH:mm:ss'},
{text: 'HH:mm:ss.SSS'},
{text: 'MMMM D, YYYY HH:mm:ss'},
{text: 'dddd, MMMM D, YYYY HH:mm:ss'},
{text: TIME_FORMAT_CUSTOM},
]
export const DEFAULT_TABLE_OPTIONS = {
verticalTimeAxis: VERTICAL_TIME_AXIS_DEFAULT,
timeFormat: DEFAULT_TIME_FORMAT,
sortBy: TIME_FIELD_DEFAULT,
wrapping: 'truncate',
fieldNames: [TIME_FIELD_DEFAULT],
fixFirstColumn: FIX_FIRST_COLUMN_DEFAULT,
}

View File

@ -2,28 +2,28 @@ const autoRefreshItems = [
{milliseconds: 0, inputValue: 'Paused', menuOption: 'Paused'},
{
milliseconds: 5000,
inputValue: 'Every 5 seconds',
menuOption: 'Every 5 seconds',
inputValue: 'Every 5s',
menuOption: 'Every 5s',
},
{
milliseconds: 10000,
inputValue: 'Every 10 seconds',
menuOption: 'Every 10 seconds',
inputValue: 'Every 10s',
menuOption: 'Every 10s',
},
{
milliseconds: 15000,
inputValue: 'Every 15 seconds',
menuOption: 'Every 15 seconds',
inputValue: 'Every 15s',
menuOption: 'Every 15s',
},
{
milliseconds: 30000,
inputValue: 'Every 30 seconds',
menuOption: 'Every 30 seconds',
inputValue: 'Every 30s',
menuOption: 'Every 30s',
},
{
milliseconds: 60000,
inputValue: 'Every 60 seconds',
menuOption: 'Every 60 seconds',
inputValue: 'Every 60s',
menuOption: 'Every 60s',
},
]

View File

@ -2,73 +2,73 @@ export const timeRanges = [
{
defaultGroupBy: '10s',
seconds: 300,
inputValue: 'Past 5 minutes',
inputValue: 'Past 5m',
lower: 'now() - 5m',
upper: null,
menuOption: 'Past 5 minutes',
menuOption: 'Past 5m',
},
{
defaultGroupBy: '1m',
seconds: 900,
inputValue: 'Past 15 minutes',
inputValue: 'Past 15m',
lower: 'now() - 15m',
upper: null,
menuOption: 'Past 15 minutes',
menuOption: 'Past 15m',
},
{
defaultGroupBy: '1m',
seconds: 3600,
inputValue: 'Past hour',
inputValue: 'Past 1h',
lower: 'now() - 1h',
upper: null,
menuOption: 'Past hour',
menuOption: 'Past 1h',
},
{
defaultGroupBy: '1m',
seconds: 21600,
inputValue: 'Past 6 hours',
inputValue: 'Past 6h',
lower: 'now() - 6h',
upper: null,
menuOption: 'Past 6 hours',
menuOption: 'Past 6h',
},
{
defaultGroupBy: '5m',
seconds: 43200,
inputValue: 'Past 12 hours',
inputValue: 'Past 12h',
lower: 'now() - 12h',
upper: null,
menuOption: 'Past 12 hours',
menuOption: 'Past 12h',
},
{
defaultGroupBy: '10m',
seconds: 86400,
inputValue: 'Past 24 hours',
inputValue: 'Past 24h',
lower: 'now() - 24h',
upper: null,
menuOption: 'Past 24 hours',
menuOption: 'Past 24h',
},
{
defaultGroupBy: '30m',
seconds: 172800,
inputValue: 'Past 2 days',
inputValue: 'Past 2d',
lower: 'now() - 2d',
upper: null,
menuOption: 'Past 2 days',
menuOption: 'Past 2d',
},
{
defaultGroupBy: '1h',
seconds: 604800,
inputValue: 'Past 7 days',
inputValue: 'Past 7d',
lower: 'now() - 7d',
upper: null,
menuOption: 'Past 7 days',
menuOption: 'Past 7d',
},
{
defaultGroupBy: '6h',
seconds: 2592000,
inputValue: 'Past 30 days',
inputValue: 'Past 30d',
lower: 'now() - 30d',
upper: null,
menuOption: 'Past 30 days',
menuOption: 'Past 30d',
},
]

View File

@ -122,14 +122,16 @@ class SideNav extends PureComponent<Props> {
<NavBlock
highlightWhen={['admin-chronograf', 'admin-influxdb']}
icon="crown2"
link={`${sourcePrefix}/admin-chronograf`}
link={`${sourcePrefix}/admin-chronograf/current-organization`}
location={location}
>
<NavHeader
link={`${sourcePrefix}/admin-chronograf`}
link={`${sourcePrefix}/admin-chronograf/current-organization`}
title="Admin"
/>
<NavListItem link={`${sourcePrefix}/admin-chronograf`}>
<NavListItem
link={`${sourcePrefix}/admin-chronograf/current-organization`}
>
Chronograf
</NavListItem>
<NavListItem link={`${sourcePrefix}/admin-influxdb/databases`}>

View File

@ -14,7 +14,6 @@
width: auto;
display: flex;
color: $ix-text-default;
text-transform: uppercase;
margin-bottom: $ix-marg-a;
font-family: $ix-text-font;
font-weight: 500;

View File

@ -21,12 +21,10 @@ interface FieldName {
}
interface TableOptions {
timeFormat: string
verticalTimeAxis: boolean
sortBy: FieldName
wrapping: string
fixFirstColumn: boolean
fieldNames: FieldName[]
}
interface CellLinks {
@ -43,6 +41,11 @@ export interface Legend {
orientation?: string
}
interface DecimalPlaces {
isEnforced: boolean
digits: number
}
export interface Cell {
id: string
x: number
@ -55,6 +58,9 @@ export interface Cell {
axes: Axes
colors: ColorString[]
tableOptions: TableOptions
fieldOptions: FieldName[]
timeFormat: string
decimalPlaces: DecimalPlaces
links: CellLinks
legend: Legend
}

View File

@ -1,7 +1,11 @@
// function definitions
export type OnDeleteFuncNode = (funcID: string, expressionID: string) => void
export type OnDeleteFuncNode = (ids: DeleteFuncNodeArgs) => void
export type OnChangeArg = (inputArg: InputArg) => void
export type OnAddNode = (expressionID: string, funcName: string) => void
export type OnAddNode = (
bodyID: string,
funcName: string,
declarationID: string
) => void
export type OnGenerateScript = (script: string) => void
export type OnChangeScript = (script: string) => void
export type OnSubmitScript = () => void
@ -15,9 +19,16 @@ export interface Handlers {
onGenerateScript: OnGenerateScript
}
export interface DeleteFuncNodeArgs {
funcID: string
bodyID: string
declarationID?: string
}
export interface InputArg {
funcID: string
expressionID: string
bodyID: string
declarationID?: string
key: string
value: string | boolean
generate?: boolean

20
ui/src/types/series.ts Normal file
View File

@ -0,0 +1,20 @@
export type TimeSeriesValue = string | number | Date | null
export interface Series {
name: string
columns: string[]
values: TimeSeriesValue[]
}
export interface Result {
series: Series[]
statement_id: number
}
export interface TimeSeriesResponse {
results: Result[]
}
export interface TimeSeriesServerResponse {
response: TimeSeriesResponse
}

View File

@ -2,8 +2,11 @@ import _ from 'lodash'
import {shiftDate} from 'shared/query/helpers'
import {map, reduce, forEach, concat, clone} from 'fast.js'
const groupByMap = (responses, responseIndex, groupByColumns) => {
const firstColumns = _.get(responses, [0, 'series', 0, 'columns'])
const groupByMap = (results, responseIndex, groupByColumns) => {
if (_.isEmpty(results)) {
return []
}
const firstColumns = _.get(results, [0, 'series', 0, 'columns'])
const accum = [
{
responseIndex,
@ -15,14 +18,14 @@ const groupByMap = (responses, responseIndex, groupByColumns) => {
...firstColumns.slice(1),
],
groupByColumns,
name: _.get(responses, [0, 'series', 0, 'name']),
name: _.get(results, [0, 'series', 0, 'name'], ''),
values: [],
},
],
},
]
const seriesArray = _.get(responses, [0, 'series'])
const seriesArray = _.get(results, [0, 'series'])
seriesArray.forEach(s => {
const prevValues = accum[0].series[0].values
const tagsToAdd = groupByColumns.map(gb => s.tags[gb])
@ -35,13 +38,14 @@ const groupByMap = (responses, responseIndex, groupByColumns) => {
const constructResults = (raw, groupBys) => {
return _.flatten(
map(raw, (response, index) => {
const responses = _.get(response, 'response.results', [])
const results = _.get(response, 'response.results', [])
const successfulResults = _.filter(results, r => _.isNil(r.error))
if (groupBys[index]) {
return groupByMap(responses, index, groupBys[index])
return groupByMap(successfulResults, index, groupBys[index])
}
return map(responses, r => ({...r, responseIndex: index}))
return map(successfulResults, r => ({...r, responseIndex: index}))
})
)
}
@ -81,22 +85,24 @@ const constructCells = serieses => {
name: measurement,
columns,
groupByColumns,
values,
values = [],
seriesIndex,
responseIndex,
tags = {},
},
ind
) => {
const rows = map(values || [], vals => ({
vals,
}))
const rows = map(values, vals => ({vals}))
const tagSet = map(Object.keys(tags), tag => `[${tag}=${tags[tag]}]`)
.sort()
.join('')
const unsortedLabels = map(columns.slice(1), (field, i) => ({
label:
groupByColumns && i <= groupByColumns.length - 1
? `${field}`
: `${measurement}.${field}`,
: `${measurement}.${field}${tagSet}`,
responseIndex,
seriesIndex,
}))
@ -221,8 +227,8 @@ export const groupByTimeSeriesTransform = (raw, groupBys) => {
if (!groupBys) {
groupBys = Array(raw.length).fill(false)
}
const results = constructResults(raw, groupBys)
const results = constructResults(raw, groupBys)
const serieses = constructSerieses(results)
const {cells, sortedLabels, seriesLabels} = constructCells(serieses)

View File

@ -8,7 +8,7 @@ import QuestionMarkTooltip from 'src/shared/components/QuestionMarkTooltip'
import {
TIME_FORMAT_CUSTOM,
TIME_FORMAT_TOOLTIP_LINK,
} from 'src/shared/constants/tableGraph'
} from 'src/dashboards/constants'
const setup = (override = {}) => {
const props = {

View File

@ -12,17 +12,22 @@ import ThresholdsListTypeToggle from 'src/shared/components/ThresholdsListTypeTo
const defaultProps = {
handleUpdateTableOptions: () => {},
handleUpdateFieldOptions: () => {},
handleChangeTimeFormat: () => {},
handleChangeDecimalPlaces: () => {},
onResetFocus: () => {},
queryConfigs: [],
tableOptions: {
columnNames: [],
fieldNames: [],
fixFirstColumn: true,
sortBy: {displayName: '', internalName: '', visible: true},
timeFormat: '',
verticalTimeAxis: true,
},
queryASTs: [],
fieldOptions: [],
timeFormat: '',
decimalPlaces: {
isEnforced: true,
digits: 2,
},
}
const setup = (override = {}) => {

View File

@ -11,7 +11,7 @@ import {
updateLineColors,
updateAxes,
} from 'src/dashboards/actions/cellEditorOverlay'
import {DEFAULT_TABLE_OPTIONS} from 'src/shared/constants/tableGraph'
import {DEFAULT_TABLE_OPTIONS} from 'src/dashboards/constants'
import {
validateGaugeColors,

View File

@ -9,7 +9,8 @@ const setup = () => {
funcID: '1',
argKey: 'db',
value: 'db1',
expressionID: '2',
bodyID: '2',
declarationID: '1',
onChangeArg: () => {},
}

View File

@ -5,8 +5,9 @@ import FuncArg from 'src/ifql/components/FuncArg'
const setup = () => {
const props = {
funcID: '',
expressionID: '',
bodyID: '',
funcName: '',
declarationID: '',
argKey: '',
value: '',
type: '',

View File

@ -8,7 +8,8 @@ import FuncList from 'src/ifql/components/FuncList'
const setup = (override = {}) => {
const props = {
funcs: ['count', 'range'],
expressionID: '1',
bodyID: '1',
declarationID: '2',
onAddNode: () => {},
...override,
}
@ -133,7 +134,7 @@ describe('IFQL.Components.FuncsButton', () => {
const onAddNode = jest.fn()
const {wrapper, props} = setup({onAddNode})
const [, func2] = props.funcs
const {expressionID} = props
const {bodyID, declarationID} = props
const dropdownButton = wrapper.find('button')
dropdownButton.simulate('click')
@ -148,7 +149,7 @@ describe('IFQL.Components.FuncsButton', () => {
input.simulate('keyDown', {key: 'ArrowDown'})
input.simulate('keyDown', {key: 'Enter'})
expect(onAddNode).toHaveBeenCalledWith(func2, expressionID)
expect(onAddNode).toHaveBeenCalledWith(func2, bodyID, declarationID)
})
})
})

View File

@ -0,0 +1,74 @@
import AutoRefresh, {
Props,
OriginalProps,
} from 'src/shared/components/AutoRefresh'
import React, {Component} from 'react'
import {shallow} from 'enzyme'
type ComponentProps = Props & OriginalProps
class MyComponent extends Component<ComponentProps> {
public render(): JSX.Element {
return <p>Here</p>
}
}
const axes = {
bounds: {
y: [1],
y2: [2],
},
}
const defaultProps = {
type: 'table',
autoRefresh: 1,
inView: true,
templates: [],
queries: [],
axes,
editQueryStatus: () => {},
grabDataForDownload: () => {},
data: [],
setResolution: () => {},
isFetchingInitially: false,
isRefreshing: false,
queryASTs: [],
}
const setup = (overrides: Partial<ComponentProps> = {}) => {
const ARComponent = AutoRefresh(MyComponent)
const props = {...defaultProps, ...overrides}
return shallow(<ARComponent {...props} />)
}
describe('Shared.Components.AutoRefresh', () => {
describe('render', () => {
describe('when there are no results', () => {
it('renders the no results component', () => {
const wrapped = setup()
expect(wrapped.find('.graph-empty').exists()).toBe(true)
})
})
describe('when there are results', () => {
it('renderes the wrapped component', () => {
const wrapped = setup()
const timeSeries = [
{
response: {
results: [{series: [1]}],
},
},
]
wrapped.update()
wrapped.setState({timeSeries})
process.nextTick(() => {
expect(wrapped.find(MyComponent).exists()).toBe(true)
})
})
})
})
})

View File

@ -8,10 +8,11 @@ import {
transformTableData,
} from 'src/dashboards/utils/tableGraph'
import {DEFAULT_SORT_DIRECTION} from 'src/shared/constants/tableGraph'
import {
DEFAULT_SORT_DIRECTION,
DEFAULT_TIME_FORMAT,
} from 'src/shared/constants/tableGraph'
DEFAULT_DECIMAL_PLACES,
} from 'src/dashboards/constants'
describe('timeSeriesToDygraph', () => {
it('parses a raw InfluxDB response into a dygraph friendly data format', () => {
@ -494,18 +495,18 @@ describe('filterTableColumns', () => {
[3000, 2000, 1000],
]
const fieldNames = [
const fieldOptions = [
{internalName: 'time', displayName: 'Time', visible: true},
{internalName: 'f1', displayName: '', visible: false},
{internalName: 'f2', displayName: 'F2', visible: false},
]
const actual = filterTableColumns(data, fieldNames)
const actual = filterTableColumns(data, fieldOptions)
const expected = [['time'], [1000], [2000], [3000]]
expect(actual).toEqual(expected)
})
it('returns an array of an empty array if all fieldNames are not visible', () => {
it('returns an array of an empty array if all fieldOptions are not visible', () => {
const data = [
['time', 'f1', 'f2'],
[1000, 3000, 2000],
@ -513,13 +514,13 @@ describe('filterTableColumns', () => {
[3000, 2000, 1000],
]
const fieldNames = [
const fieldOptions = [
{internalName: 'time', displayName: 'Time', visible: false},
{internalName: 'f1', displayName: '', visible: false},
{internalName: 'f2', displayName: 'F2', visible: false},
]
const actual = filterTableColumns(data, fieldNames)
const actual = filterTableColumns(data, fieldOptions)
const expected = [[]]
expect(actual).toEqual(expected)
})
@ -534,18 +535,23 @@ describe('transformTableData', () => {
[3000, 2000, 1000],
]
const sort = {field: 'f1', direction: DEFAULT_SORT_DIRECTION}
const tableOptions = {
verticalTimeAxis: true,
timeFormat: DEFAULT_TIME_FORMAT,
}
const fieldNames = [
const tableOptions = {verticalTimeAxis: true}
const timeFormat = DEFAULT_TIME_FORMAT
const decimalPlaces = DEFAULT_DECIMAL_PLACES
const fieldOptions = [
{internalName: 'time', displayName: 'Time', visible: true},
{internalName: 'f1', displayName: '', visible: true},
{internalName: 'f2', displayName: 'F2', visible: true},
]
const actual = transformTableData(data, sort, fieldNames, tableOptions)
const actual = transformTableData(
data,
sort,
fieldOptions,
tableOptions,
timeFormat,
decimalPlaces
)
const expected = [
['time', 'f1', 'f2'],
[2000, 1000, 3000],
@ -563,21 +569,24 @@ describe('transformTableData', () => {
[2000, 1000, 3000],
[3000, 2000, 1000],
]
const sort = {field: 'time', direction: DEFAULT_SORT_DIRECTION}
const tableOptions = {
verticalTimeAxis: true,
timeFormat: DEFAULT_TIME_FORMAT,
}
const fieldNames = [
const tableOptions = {verticalTimeAxis: true}
const timeFormat = DEFAULT_TIME_FORMAT
const decimalPlaces = DEFAULT_DECIMAL_PLACES
const fieldOptions = [
{internalName: 'time', displayName: 'Time', visible: true},
{internalName: 'f1', displayName: '', visible: false},
{internalName: 'f2', displayName: 'F2', visible: true},
]
const actual = transformTableData(data, sort, fieldNames, tableOptions)
const actual = transformTableData(
data,
sort,
fieldOptions,
tableOptions,
timeFormat,
decimalPlaces
)
const expected = [['time', 'f2'], [1000, 2000], [2000, 3000], [3000, 1000]]
@ -593,19 +602,23 @@ describe('transformTableData', () => {
]
const sort = {field: 'f1', direction: DEFAULT_SORT_DIRECTION}
const tableOptions = {
verticalTimeAxis: true,
timeFormat: DEFAULT_TIME_FORMAT,
}
const fieldNames = [
const tableOptions = {verticalTimeAxis: true}
const timeFormat = DEFAULT_TIME_FORMAT
const decimalPlaces = DEFAULT_DECIMAL_PLACES
const fieldOptions = [
{internalName: 'time', displayName: 'Time', visible: true},
{internalName: 'f1', displayName: '', visible: false},
{internalName: 'f2', displayName: 'F2', visible: true},
]
const actual = transformTableData(data, sort, fieldNames, tableOptions)
const actual = transformTableData(
data,
sort,
fieldOptions,
tableOptions,
timeFormat,
decimalPlaces
)
const expected = [['time', 'f2'], [2000, 3000], [3000, 1000], [1000, 2000]]
@ -623,19 +636,23 @@ describe('if verticalTimeAxis is false', () => {
]
const sort = {field: 'time', direction: DEFAULT_SORT_DIRECTION}
const tableOptions = {
verticalTimeAxis: false,
timeFormat: DEFAULT_TIME_FORMAT,
}
const fieldNames = [
const tableOptions = {verticalTimeAxis: false}
const timeFormat = DEFAULT_TIME_FORMAT
const decimalPlaces = DEFAULT_DECIMAL_PLACES
const fieldOptions = [
{internalName: 'time', displayName: 'Time', visible: true},
{internalName: 'f1', displayName: '', visible: true},
{internalName: 'f2', displayName: 'F2', visible: true},
]
const actual = transformTableData(data, sort, fieldNames, tableOptions)
const actual = transformTableData(
data,
sort,
fieldOptions,
tableOptions,
timeFormat,
decimalPlaces
)
const expected = [
['time', 1000, 2000, 3000],
@ -655,19 +672,23 @@ describe('if verticalTimeAxis is false', () => {
]
const sort = {field: 'f1', direction: DEFAULT_SORT_DIRECTION}
const tableOptions = {
verticalTimeAxis: false,
timeFormat: DEFAULT_TIME_FORMAT,
}
const fieldNames = [
const tableOptions = {verticalTimeAxis: false}
const timeFormat = DEFAULT_TIME_FORMAT
const decimalPlaces = DEFAULT_DECIMAL_PLACES
const fieldOptions = [
{internalName: 'time', displayName: 'Time', visible: true},
{internalName: 'f1', displayName: '', visible: false},
{internalName: 'f2', displayName: 'F2', visible: true},
]
const actual = transformTableData(data, sort, fieldNames, tableOptions)
const actual = transformTableData(
data,
sort,
fieldOptions,
tableOptions,
timeFormat,
decimalPlaces
)
const expected = [['time', 2000, 3000, 1000], ['f2', 3000, 1000, 2000]]