From 5cd8cc7cddbe6e816f6bc479114291543eba1b14 Mon Sep 17 00:00:00 2001 From: Chris Goller Date: Wed, 16 May 2018 18:04:52 -0500 Subject: [PATCH 01/49] feat(server/services): add generic services scoped under server --- bolt/internal/internal.go | 2 + bolt/internal/internal.pb.go | 220 +++++++++++++------------ bolt/internal/internal.proto | 17 +- chronograf.go | 1 + server/kapacitors.go | 8 +- server/mux.go | 21 ++- server/proxy.go | 28 ++-- server/services.go | 306 +++++++++++++++++++++++++++++++++++ server/sources.go | 2 + 9 files changed, 469 insertions(+), 136 deletions(-) create mode 100644 server/services.go diff --git a/bolt/internal/internal.go b/bolt/internal/internal.go index 8b7fe2e6e..e3a1fd55c 100644 --- a/bolt/internal/internal.go +++ b/bolt/internal/internal.go @@ -86,6 +86,7 @@ func MarshalServer(s chronograf.Server) ([]byte, error) { Active: s.Active, Organization: s.Organization, InsecureSkipVerify: s.InsecureSkipVerify, + Type: s.Type, }) } @@ -105,6 +106,7 @@ func UnmarshalServer(data []byte, s *chronograf.Server) error { s.Active = pb.Active s.Organization = pb.Organization s.InsecureSkipVerify = pb.InsecureSkipVerify + s.Type = pb.Type return nil } diff --git a/bolt/internal/internal.pb.go b/bolt/internal/internal.pb.go index 48177f7e1..548d80a5e 100644 --- a/bolt/internal/internal.pb.go +++ b/bolt/internal/internal.pb.go @@ -734,6 +734,7 @@ type Server struct { Active bool `protobuf:"varint,7,opt,name=Active,proto3" json:"Active,omitempty"` Organization string `protobuf:"bytes,8,opt,name=Organization,proto3" json:"Organization,omitempty"` InsecureSkipVerify bool `protobuf:"varint,9,opt,name=InsecureSkipVerify,proto3" json:"InsecureSkipVerify,omitempty"` + Type string `protobuf:"bytes,10,opt,name=Type,proto3" json:"Type,omitempty"` } func (m *Server) Reset() { *m = Server{} } @@ -804,6 +805,13 @@ func (m *Server) GetInsecureSkipVerify() bool { return false } +func (m *Server) GetType() string { + if m != nil { + return m.Type + } + return "" +} + type Layout struct { ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"` Application string `protobuf:"bytes,2,opt,name=Application,proto3" json:"Application,omitempty"` @@ -1372,110 +1380,110 @@ func init() { func init() { proto.RegisterFile("internal.proto", fileDescriptorInternal) } var fileDescriptorInternal = []byte{ - // 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, + // 1673 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x58, 0x5b, 0x8f, 0xe3, 0x48, + 0x15, 0x96, 0x13, 0x3b, 0x89, 0x4f, 0xba, 0x7b, 0x5b, 0x66, 0xb4, 0x6b, 0x16, 0x84, 0x82, 0xc5, + 0xa5, 0xb9, 0xec, 0xb0, 0xea, 0x15, 0x12, 0x5a, 0xed, 0xae, 0xd4, 0x97, 0x9d, 0xa1, 0xe7, 0xda, + 0x53, 0xe9, 0x1e, 0x9e, 0xd0, 0xaa, 0x62, 0x57, 0x92, 0xd2, 0x3a, 0xb6, 0x29, 0xdb, 0xdd, 0x6d, + 0x9e, 0x79, 0xe4, 0x37, 0x20, 0x21, 0xc1, 0x1f, 0x40, 0x3c, 0x22, 0xf1, 0xce, 0x0f, 0xe0, 0xaf, + 0xf0, 0x8a, 0x4e, 0x5d, 0xec, 0x72, 0x27, 0x33, 0x1a, 0x24, 0xb4, 0x6f, 0xf5, 0x9d, 0x73, 0x72, + 0xaa, 0xea, 0x5c, 0xbe, 0x3a, 0x0e, 0x1c, 0xf0, 0xac, 0x62, 0x22, 0xa3, 0xe9, 0xc3, 0x42, 0xe4, + 0x55, 0x1e, 0x4c, 0x0c, 0x8e, 0xfe, 0x30, 0x84, 0xd1, 0x3c, 0xaf, 0x45, 0xcc, 0x82, 0x03, 0x18, + 0x5c, 0x9c, 0x87, 0xce, 0xcc, 0x39, 0x1a, 0x92, 0xc1, 0xc5, 0x79, 0x10, 0x80, 0xfb, 0x82, 0x6e, + 0x58, 0x38, 0x98, 0x39, 0x47, 0x3e, 0x91, 0x6b, 0x94, 0x5d, 0x35, 0x05, 0x0b, 0x87, 0x4a, 0x86, + 0xeb, 0xe0, 0x43, 0x98, 0x5c, 0x97, 0xe8, 0x6d, 0xc3, 0x42, 0x57, 0xca, 0x5b, 0x8c, 0xba, 0x4b, + 0x5a, 0x96, 0xb7, 0xb9, 0x48, 0x42, 0x4f, 0xe9, 0x0c, 0x0e, 0x0e, 0x61, 0x78, 0x4d, 0x9e, 0x85, + 0x23, 0x29, 0xc6, 0x65, 0x10, 0xc2, 0xf8, 0x9c, 0x2d, 0x69, 0x9d, 0x56, 0xe1, 0x78, 0xe6, 0x1c, + 0x4d, 0x88, 0x81, 0xe8, 0xe7, 0x8a, 0xa5, 0x6c, 0x25, 0xe8, 0x32, 0x9c, 0x28, 0x3f, 0x06, 0x07, + 0x0f, 0x21, 0xb8, 0xc8, 0x4a, 0x16, 0xd7, 0x82, 0xcd, 0xbf, 0xe6, 0xc5, 0x6b, 0x26, 0xf8, 0xb2, + 0x09, 0x7d, 0xe9, 0x60, 0x87, 0x06, 0x77, 0x79, 0xce, 0x2a, 0x8a, 0x7b, 0x83, 0x74, 0x65, 0x60, + 0x10, 0xc1, 0xde, 0x7c, 0x4d, 0x05, 0x4b, 0xe6, 0x2c, 0x16, 0xac, 0x0a, 0xa7, 0x52, 0xdd, 0x93, + 0xa1, 0xcd, 0x4b, 0xb1, 0xa2, 0x19, 0xff, 0x3d, 0xad, 0x78, 0x9e, 0x85, 0x7b, 0xca, 0xc6, 0x96, + 0x61, 0x94, 0x48, 0x9e, 0xb2, 0x70, 0x5f, 0x45, 0x09, 0xd7, 0xc1, 0x77, 0xc1, 0xd7, 0x97, 0x21, + 0x97, 0xe1, 0x81, 0x54, 0x74, 0x82, 0xe8, 0xef, 0x0e, 0xf8, 0xe7, 0xb4, 0x5c, 0x2f, 0x72, 0x2a, + 0x92, 0x77, 0xca, 0xc4, 0x47, 0xe0, 0xc5, 0x2c, 0x4d, 0xcb, 0x70, 0x38, 0x1b, 0x1e, 0x4d, 0x8f, + 0x3f, 0x78, 0xd8, 0xa6, 0xb8, 0xf5, 0x73, 0xc6, 0xd2, 0x94, 0x28, 0xab, 0xe0, 0x63, 0xf0, 0x2b, + 0xb6, 0x29, 0x52, 0x5a, 0xb1, 0x32, 0x74, 0xe5, 0x4f, 0x82, 0xee, 0x27, 0x57, 0x5a, 0x45, 0x3a, + 0xa3, 0xad, 0x8b, 0x7a, 0xdb, 0x17, 0x8d, 0xfe, 0xed, 0xc2, 0x7e, 0x6f, 0xbb, 0x60, 0x0f, 0x9c, + 0x3b, 0x79, 0x72, 0x8f, 0x38, 0x77, 0x88, 0x1a, 0x79, 0x6a, 0x8f, 0x38, 0x0d, 0xa2, 0x5b, 0x59, + 0x39, 0x1e, 0x71, 0x6e, 0x11, 0xad, 0x65, 0xbd, 0x78, 0xc4, 0x59, 0x07, 0x3f, 0x81, 0xf1, 0xef, + 0x6a, 0x26, 0x38, 0x2b, 0x43, 0x4f, 0x9e, 0xee, 0xbd, 0xee, 0x74, 0xaf, 0x6a, 0x26, 0x1a, 0x62, + 0xf4, 0x18, 0x0d, 0x59, 0x6b, 0xaa, 0x70, 0xe4, 0x1a, 0x65, 0x15, 0xd6, 0xe5, 0x58, 0xc9, 0x70, + 0xad, 0xa3, 0xa8, 0xaa, 0x05, 0xa3, 0xf8, 0x4b, 0x70, 0xe9, 0x1d, 0x2b, 0x43, 0x5f, 0xfa, 0xff, + 0xfe, 0x1b, 0x02, 0xf6, 0xf0, 0xe4, 0x8e, 0x95, 0x5f, 0x66, 0x95, 0x68, 0x88, 0x34, 0x0f, 0x7e, + 0x0c, 0xa3, 0x38, 0x4f, 0x73, 0x51, 0x86, 0x70, 0xff, 0x60, 0x67, 0x28, 0x27, 0x5a, 0x1d, 0x1c, + 0xc1, 0x28, 0x65, 0x2b, 0x96, 0x25, 0xb2, 0x6e, 0xa6, 0xc7, 0x87, 0x9d, 0xe1, 0x33, 0x29, 0x27, + 0x5a, 0x1f, 0x7c, 0x0a, 0x7b, 0x15, 0x5d, 0xa4, 0xec, 0x65, 0x81, 0x51, 0x2c, 0x65, 0x0d, 0x4d, + 0x8f, 0xdf, 0xb7, 0xf2, 0x61, 0x69, 0x49, 0xcf, 0x36, 0xf8, 0x0c, 0xf6, 0x96, 0x9c, 0xa5, 0x89, + 0xf9, 0xed, 0xbe, 0x3c, 0x54, 0xd8, 0xfd, 0x96, 0xb0, 0x8c, 0x6e, 0xf0, 0x17, 0x8f, 0xd0, 0x8c, + 0xf4, 0xac, 0x83, 0xef, 0x01, 0x54, 0x7c, 0xc3, 0x1e, 0xe5, 0x62, 0x43, 0x2b, 0x5d, 0x86, 0x96, + 0x24, 0xf8, 0x1c, 0xf6, 0x13, 0x16, 0xf3, 0x0d, 0x4d, 0x2f, 0x53, 0x1a, 0xb3, 0x32, 0x7c, 0x4f, + 0x1e, 0xcd, 0xae, 0x2e, 0x5b, 0x4d, 0xfa, 0xd6, 0x1f, 0x3e, 0x06, 0xbf, 0x0d, 0x1f, 0xf6, 0xf7, + 0xd7, 0xac, 0x91, 0xc5, 0xe0, 0x13, 0x5c, 0x06, 0x3f, 0x00, 0xef, 0x86, 0xa6, 0xb5, 0x2a, 0xe4, + 0xe9, 0xf1, 0x41, 0xe7, 0xf5, 0xe4, 0x8e, 0x97, 0x44, 0x29, 0x3f, 0x1d, 0xfc, 0xca, 0x89, 0x1e, + 0xc3, 0x7e, 0x6f, 0x23, 0x3c, 0x38, 0x2f, 0xbf, 0xcc, 0x96, 0xb9, 0x88, 0x59, 0x22, 0x7d, 0x4e, + 0x88, 0x25, 0x09, 0xde, 0x87, 0x51, 0xc2, 0x57, 0xbc, 0x2a, 0x75, 0xb9, 0x69, 0x14, 0xfd, 0xc3, + 0x81, 0x3d, 0x3b, 0x9a, 0xc1, 0x4f, 0xe1, 0xf0, 0x86, 0x89, 0x8a, 0xc7, 0x34, 0xbd, 0xe2, 0x1b, + 0x86, 0x1b, 0xcb, 0x9f, 0x4c, 0xc8, 0x96, 0x3c, 0xf8, 0x18, 0x46, 0x65, 0x2e, 0xaa, 0xd3, 0x46, + 0x56, 0xed, 0xdb, 0xa2, 0xac, 0xed, 0x90, 0xa7, 0x6e, 0x05, 0x2d, 0x0a, 0x9e, 0xad, 0x0c, 0x17, + 0x1a, 0x1c, 0xfc, 0x08, 0x0e, 0x96, 0xfc, 0xee, 0x11, 0x17, 0x65, 0x75, 0x96, 0xa7, 0xf5, 0x26, + 0x93, 0x15, 0x3c, 0x21, 0xf7, 0xa4, 0x4f, 0xdc, 0x89, 0x73, 0x38, 0x78, 0xe2, 0x4e, 0xbc, 0xc3, + 0x51, 0x54, 0xc0, 0x41, 0x7f, 0x27, 0x6c, 0x4b, 0x73, 0x08, 0xc9, 0x09, 0x2a, 0xbc, 0x3d, 0x59, + 0x30, 0x83, 0x69, 0xc2, 0xcb, 0x22, 0xa5, 0x8d, 0x45, 0x1b, 0xb6, 0x08, 0x39, 0xf0, 0x86, 0x97, + 0x7c, 0x91, 0x2a, 0x2a, 0x9f, 0x10, 0x03, 0xa3, 0x15, 0x78, 0xb2, 0xac, 0x2d, 0x12, 0xf2, 0x0d, + 0x09, 0x49, 0xea, 0x1f, 0x58, 0xd4, 0x7f, 0x08, 0xc3, 0x5f, 0xb3, 0x3b, 0xfd, 0x1a, 0xe0, 0xb2, + 0xa5, 0x2a, 0xd7, 0xa2, 0xaa, 0x07, 0xe0, 0xbd, 0x96, 0x69, 0x57, 0x14, 0xa2, 0x40, 0xf4, 0x05, + 0x8c, 0x54, 0x5b, 0xb4, 0x9e, 0x1d, 0xcb, 0xf3, 0x0c, 0xa6, 0x2f, 0x05, 0x67, 0x59, 0xa5, 0xc8, + 0x47, 0x5f, 0xc1, 0x12, 0x45, 0x7f, 0x73, 0xc0, 0x95, 0x59, 0x8a, 0x60, 0x2f, 0x65, 0x2b, 0x1a, + 0x37, 0xa7, 0x79, 0x9d, 0x25, 0x65, 0xe8, 0xcc, 0x86, 0x47, 0x43, 0xd2, 0x93, 0x61, 0x79, 0x2c, + 0x94, 0x76, 0x30, 0x1b, 0x1e, 0xf9, 0x44, 0x23, 0x3c, 0x5a, 0x4a, 0x17, 0x2c, 0xd5, 0x57, 0x50, + 0x00, 0xad, 0x0b, 0xc1, 0x96, 0xfc, 0x4e, 0x5f, 0x43, 0x23, 0x94, 0x97, 0xf5, 0x12, 0xe5, 0xea, + 0x26, 0x1a, 0xe1, 0x05, 0x16, 0xb4, 0x6c, 0x19, 0x09, 0xd7, 0xe8, 0xb9, 0x8c, 0x69, 0x6a, 0x28, + 0x49, 0x81, 0xe8, 0x9f, 0x0e, 0x3e, 0x64, 0x8a, 0x62, 0xb7, 0x22, 0xfc, 0x6d, 0x98, 0x20, 0xfd, + 0x7e, 0x75, 0x43, 0x85, 0xbe, 0xf0, 0x18, 0xf1, 0x6b, 0x2a, 0x82, 0x5f, 0xc0, 0x48, 0x36, 0xc7, + 0x0e, 0xba, 0x37, 0xee, 0x64, 0x54, 0x89, 0x36, 0x6b, 0x09, 0xd1, 0xb5, 0x08, 0xb1, 0xbd, 0xac, + 0x67, 0x5f, 0xf6, 0x23, 0xf0, 0x90, 0x59, 0x1b, 0x79, 0xfa, 0x9d, 0x9e, 0x15, 0xff, 0x2a, 0xab, + 0xe8, 0x1a, 0xf6, 0x7b, 0x3b, 0xb6, 0x3b, 0x39, 0xfd, 0x9d, 0xba, 0x46, 0xf7, 0x75, 0x63, 0x63, + 0x73, 0x94, 0x2c, 0x65, 0x71, 0xc5, 0x12, 0x5d, 0x75, 0x2d, 0x8e, 0xfe, 0xec, 0x74, 0x7e, 0xe5, + 0x7e, 0x58, 0xa2, 0x71, 0xbe, 0xd9, 0xd0, 0x2c, 0xd1, 0xae, 0x0d, 0xc4, 0xb8, 0x25, 0x0b, 0xed, + 0x7a, 0x90, 0x2c, 0x10, 0x8b, 0x42, 0x67, 0x70, 0x20, 0x0a, 0xac, 0x9d, 0x0d, 0xa3, 0x65, 0x2d, + 0xd8, 0x86, 0x65, 0x95, 0x0e, 0x81, 0x2d, 0x0a, 0x3e, 0x80, 0x71, 0x45, 0x57, 0x5f, 0x21, 0x3d, + 0xe9, 0x4c, 0x56, 0x74, 0xf5, 0x94, 0x35, 0xc1, 0x77, 0xc0, 0x97, 0x7c, 0x29, 0x55, 0x2a, 0x9d, + 0x13, 0x29, 0x78, 0xca, 0x9a, 0xe8, 0x8f, 0x03, 0x18, 0xcd, 0x99, 0xb8, 0x61, 0xe2, 0x9d, 0x5e, + 0x68, 0x7b, 0x2e, 0x1a, 0xbe, 0x65, 0x2e, 0x72, 0x77, 0xcf, 0x45, 0x5e, 0x37, 0x17, 0x3d, 0x00, + 0x6f, 0x2e, 0xe2, 0x8b, 0x73, 0x79, 0xa2, 0x21, 0x51, 0x00, 0xab, 0xf1, 0x24, 0xae, 0xf8, 0x0d, + 0xd3, 0xc3, 0x92, 0x46, 0x5b, 0x0f, 0xf7, 0x64, 0xc7, 0x84, 0xf2, 0xbf, 0xce, 0x4c, 0xa6, 0x45, + 0xa1, 0x6b, 0xd1, 0xe8, 0x4f, 0x0e, 0x8c, 0x9e, 0xd1, 0x26, 0xaf, 0xab, 0xad, 0x4a, 0x9e, 0xc1, + 0xf4, 0xa4, 0x28, 0x52, 0x1e, 0xf7, 0xba, 0xd7, 0x12, 0xa1, 0xc5, 0x73, 0x2b, 0x47, 0x2a, 0x3e, + 0xb6, 0x08, 0x1f, 0x8b, 0x33, 0x39, 0xe0, 0xa8, 0x69, 0xc5, 0x7a, 0x2c, 0xd4, 0x5c, 0x23, 0x95, + 0x18, 0xc8, 0x93, 0xba, 0xca, 0x97, 0x69, 0x7e, 0x2b, 0x23, 0x36, 0x21, 0x2d, 0x8e, 0xfe, 0x35, + 0x00, 0xf7, 0x9b, 0x1a, 0x4a, 0xf6, 0xc0, 0xe1, 0xba, 0x60, 0x1c, 0xde, 0x8e, 0x28, 0x63, 0x6b, + 0x44, 0x09, 0x61, 0xdc, 0x08, 0x9a, 0xad, 0x58, 0x19, 0x4e, 0x24, 0x43, 0x19, 0x28, 0x35, 0xb2, + 0x17, 0xd5, 0x6c, 0xe2, 0x13, 0x03, 0xdb, 0xde, 0x02, 0xab, 0xb7, 0x7e, 0xae, 0xc7, 0x98, 0xe9, + 0xfd, 0x87, 0x7f, 0xd7, 0xf4, 0xf2, 0xff, 0x7b, 0x91, 0xff, 0xe3, 0x80, 0xd7, 0x36, 0xe6, 0x59, + 0xbf, 0x31, 0xcf, 0xba, 0xc6, 0x3c, 0x3f, 0x35, 0x8d, 0x79, 0x7e, 0x8a, 0x98, 0x5c, 0x9a, 0xc6, + 0x24, 0x97, 0x98, 0xac, 0xc7, 0x22, 0xaf, 0x8b, 0xd3, 0x46, 0x65, 0xd5, 0x27, 0x2d, 0xc6, 0x6a, + 0xfe, 0xcd, 0x9a, 0x09, 0x1d, 0x6a, 0x9f, 0x68, 0x84, 0xb5, 0xff, 0x4c, 0x92, 0x96, 0x0a, 0xae, + 0x02, 0xc1, 0x0f, 0xc1, 0x23, 0x18, 0x3c, 0x19, 0xe1, 0x5e, 0x5e, 0xa4, 0x98, 0x28, 0x2d, 0x3a, + 0x55, 0x1f, 0x37, 0xba, 0x09, 0xcc, 0xa7, 0xce, 0xcf, 0x60, 0x34, 0x5f, 0xf3, 0x65, 0x65, 0x86, + 0xc1, 0x6f, 0x59, 0xa4, 0xc7, 0x37, 0x4c, 0xea, 0x88, 0x36, 0x89, 0x5e, 0x81, 0xdf, 0x0a, 0xbb, + 0xe3, 0x38, 0xf6, 0x71, 0x02, 0x70, 0xaf, 0x33, 0x5e, 0x99, 0xf6, 0xc7, 0x35, 0x5e, 0xf6, 0x55, + 0x4d, 0xb3, 0x8a, 0x57, 0x8d, 0x69, 0x7f, 0x83, 0xa3, 0x4f, 0xf4, 0xf1, 0xd1, 0xdd, 0x75, 0x51, + 0x30, 0xa1, 0xa9, 0x44, 0x01, 0xb9, 0x49, 0x7e, 0xcb, 0xd4, 0x2b, 0x30, 0x24, 0x0a, 0x44, 0xbf, + 0x05, 0xff, 0x24, 0x65, 0xa2, 0x22, 0x75, 0xca, 0x76, 0xbd, 0xce, 0x4f, 0xe6, 0x2f, 0x5f, 0x98, + 0x13, 0xe0, 0xba, 0xa3, 0x8d, 0xe1, 0x3d, 0xda, 0x78, 0x4a, 0x0b, 0x7a, 0x71, 0x2e, 0xeb, 0x7c, + 0x48, 0x34, 0x8a, 0xfe, 0xe2, 0x80, 0x8b, 0xfc, 0x64, 0xb9, 0x76, 0xdf, 0xc6, 0x6d, 0x97, 0x22, + 0xbf, 0xe1, 0x09, 0x13, 0xe6, 0x72, 0x06, 0xcb, 0xa0, 0xc7, 0x6b, 0xd6, 0x0e, 0x01, 0x1a, 0x61, + 0xad, 0xe1, 0x97, 0x90, 0xe9, 0x25, 0xab, 0xd6, 0x50, 0x4c, 0x94, 0x12, 0x07, 0xbd, 0x79, 0x5d, + 0x30, 0x71, 0x92, 0x6c, 0xb8, 0x99, 0x90, 0x2c, 0x49, 0xf4, 0x85, 0xfa, 0xb6, 0xda, 0x62, 0x39, + 0x67, 0xf7, 0x77, 0xd8, 0xfd, 0x93, 0x47, 0x7f, 0x75, 0x60, 0xfc, 0x5c, 0x4f, 0x64, 0xf6, 0x2d, + 0x9c, 0x37, 0xde, 0x62, 0xd0, 0xbb, 0xc5, 0x31, 0x3c, 0x30, 0x36, 0xbd, 0xfd, 0x55, 0x14, 0x76, + 0xea, 0x74, 0x44, 0xdd, 0x36, 0x59, 0xef, 0xf2, 0x69, 0x75, 0xd5, 0xb7, 0xd9, 0x95, 0xf0, 0xad, + 0xac, 0xcc, 0x60, 0x6a, 0x3e, 0x29, 0xf3, 0xd4, 0x3c, 0x3a, 0xb6, 0x28, 0x3a, 0x86, 0xd1, 0x59, + 0x9e, 0x2d, 0xf9, 0x2a, 0x38, 0x02, 0xf7, 0xa4, 0xae, 0xd6, 0xd2, 0xe3, 0xf4, 0xf8, 0x81, 0xd5, + 0xf8, 0x75, 0xb5, 0x56, 0x36, 0x44, 0x5a, 0x44, 0x9f, 0x01, 0x74, 0x32, 0x7c, 0x39, 0xba, 0x6c, + 0xbc, 0x60, 0xb7, 0x58, 0x32, 0xa5, 0x1e, 0xc8, 0x77, 0x68, 0xa2, 0xcf, 0xc1, 0x3f, 0xad, 0x79, + 0x9a, 0x5c, 0x64, 0xcb, 0x1c, 0xa9, 0xe3, 0x35, 0x13, 0x65, 0x97, 0x2f, 0x03, 0x31, 0xdc, 0xc8, + 0x22, 0x6d, 0x0f, 0x69, 0xb4, 0x18, 0xc9, 0x3f, 0x2c, 0x3e, 0xf9, 0x6f, 0x00, 0x00, 0x00, 0xff, + 0xff, 0x59, 0xb8, 0x9d, 0x3e, 0xc2, 0x10, 0x00, 0x00, } diff --git a/bolt/internal/internal.proto b/bolt/internal/internal.proto index 7633ed3af..99b887453 100644 --- a/bolt/internal/internal.proto +++ b/bolt/internal/internal.proto @@ -112,15 +112,16 @@ message TemplateQuery { } message Server { - int64 ID = 1; // ID is the unique ID of the server - string Name = 2; // Name is the user-defined name for the server - string Username = 3; // Username is the username to connect to the server + int64 ID = 1; // ID is the unique ID of the server + string Name = 2; // Name is the user-defined name for the server + string Username = 3; // Username is the username to connect to the server string Password = 4; - string URL = 5; // URL is the path to the server - int64 SrcID = 6; // SrcID is the ID of the data source - bool Active = 7; // is this the currently active server for the source - string Organization = 8; // Organization is the organization ID that resource belongs to - bool InsecureSkipVerify = 9; // InsecureSkipVerify accepts any certificate from the client + string URL = 5; // URL is the path to the server + int64 SrcID = 6; // SrcID is the ID of the data source + bool Active = 7; // is this the currently active server for the source + string Organization = 8; // Organization is the organization ID that resource belongs to + bool InsecureSkipVerify = 9; // InsecureSkipVerify accepts any certificate from the client + string Type = 10; // Type is the kind of the server (e.g. ifql) } message Layout { diff --git a/chronograf.go b/chronograf.go index 47ece6d95..a001310aa 100644 --- a/chronograf.go +++ b/chronograf.go @@ -368,6 +368,7 @@ type Server struct { InsecureSkipVerify bool `json:"insecureSkipVerify"` // InsecureSkipVerify as true means any certificate presented by the server is accepted. Active bool `json:"active"` // Is this the active server for the source? Organization string `json:"organization"` // Organization is the organization ID that resource belongs to + Type string `json:"type"` // Type is the kind of service (e.g. kapacitor or ifql) } // ServersStore stores connection information for a `Server` diff --git a/server/kapacitors.go b/server/kapacitors.go index 97b581f6d..8ea4e2e3f 100644 --- a/server/kapacitors.go +++ b/server/kapacitors.go @@ -154,7 +154,7 @@ func (s *Service) Kapacitors(w http.ResponseWriter, r *http.Request) { srvs := []kapacitor{} for _, srv := range mrSrvs { - if srv.SrcID == srcID { + if srv.SrcID == srcID && srv.Type == "" { srvs = append(srvs, newKapacitor(srv)) } } @@ -182,7 +182,7 @@ func (s *Service) KapacitorsID(w http.ResponseWriter, r *http.Request) { ctx := r.Context() srv, err := s.Store.Servers(ctx).Get(ctx, id) - if err != nil || srv.SrcID != srcID { + if err != nil || srv.SrcID != srcID || srv.Type != "" { notFound(w, id, s.Logger) return } @@ -207,7 +207,7 @@ func (s *Service) RemoveKapacitor(w http.ResponseWriter, r *http.Request) { ctx := r.Context() srv, err := s.Store.Servers(ctx).Get(ctx, id) - if err != nil || srv.SrcID != srcID { + if err != nil || srv.SrcID != srcID || srv.Type != "" { notFound(w, id, s.Logger) return } @@ -258,7 +258,7 @@ func (s *Service) UpdateKapacitor(w http.ResponseWriter, r *http.Request) { ctx := r.Context() srv, err := s.Store.Servers(ctx).Get(ctx, id) - if err != nil || srv.SrcID != srcID { + if err != nil || srv.SrcID != srcID || srv.Type != "" { notFound(w, id, s.Logger) return } diff --git a/server/mux.go b/server/mux.go index cecf7a99b..c6ca32c6c 100644 --- a/server/mux.go +++ b/server/mux.go @@ -203,6 +203,19 @@ func NewMux(opts MuxOpts, service Service) http.Handler { router.DELETE("/chronograf/v1/sources/:id/roles/:rid", EnsureEditor(service.RemoveSourceRole)) router.PATCH("/chronograf/v1/sources/:id/roles/:rid", EnsureEditor(service.UpdateSourceRole)) + // Services are resources that chronograf proxies to + router.GET("/chronograf/v1/sources/:id/services", EnsureViewer(service.Services)) + router.POST("/chronograf/v1/sources/:id/services", EnsureEditor(service.NewService)) + router.GET("/chronograf/v1/sources/:id/services/:kid", EnsureViewer(service.ServiceID)) + router.PATCH("/chronograf/v1/sources/:id/services/:kid", EnsureEditor(service.UpdateService)) + router.DELETE("/chronograf/v1/sources/:id/services/:kid", EnsureEditor(service.RemoveService)) + + // Service Proxy + router.GET("/chronograf/v1/sources/:id/services/:kid/proxy", EnsureViewer(service.ProxyGet)) + router.POST("/chronograf/v1/sources/:id/services/:kid/proxy", EnsureEditor(service.ProxyPost)) + router.PATCH("/chronograf/v1/sources/:id/services/:kid/proxy", EnsureEditor(service.ProxyPatch)) + router.DELETE("/chronograf/v1/sources/:id/services/:kid/proxy", EnsureEditor(service.ProxyDelete)) + // Kapacitor router.GET("/chronograf/v1/sources/:id/kapacitors", EnsureViewer(service.Kapacitors)) router.POST("/chronograf/v1/sources/:id/kapacitors", EnsureEditor(service.NewKapacitor)) @@ -221,10 +234,10 @@ func NewMux(opts MuxOpts, service Service) http.Handler { router.DELETE("/chronograf/v1/sources/:id/kapacitors/:kid/rules/:tid", EnsureEditor(service.KapacitorRulesDelete)) // Kapacitor Proxy - router.GET("/chronograf/v1/sources/:id/kapacitors/:kid/proxy", EnsureViewer(service.KapacitorProxyGet)) - router.POST("/chronograf/v1/sources/:id/kapacitors/:kid/proxy", EnsureEditor(service.KapacitorProxyPost)) - router.PATCH("/chronograf/v1/sources/:id/kapacitors/:kid/proxy", EnsureEditor(service.KapacitorProxyPatch)) - router.DELETE("/chronograf/v1/sources/:id/kapacitors/:kid/proxy", EnsureEditor(service.KapacitorProxyDelete)) + router.GET("/chronograf/v1/sources/:id/kapacitors/:kid/proxy", EnsureViewer(service.ProxyGet)) + router.POST("/chronograf/v1/sources/:id/kapacitors/:kid/proxy", EnsureEditor(service.ProxyPost)) + router.PATCH("/chronograf/v1/sources/:id/kapacitors/:kid/proxy", EnsureEditor(service.ProxyPatch)) + router.DELETE("/chronograf/v1/sources/:id/kapacitors/:kid/proxy", EnsureEditor(service.ProxyDelete)) // Layouts router.GET("/chronograf/v1/layouts", EnsureViewer(service.Layouts)) diff --git a/server/proxy.go b/server/proxy.go index 0f72a2105..ad313184a 100644 --- a/server/proxy.go +++ b/server/proxy.go @@ -11,8 +11,8 @@ import ( "time" ) -// KapacitorProxy proxies requests to kapacitor using the path query parameter. -func (s *Service) KapacitorProxy(w http.ResponseWriter, r *http.Request) { +// Proxy proxies requests to services using the path query parameter. +func (s *Service) Proxy(w http.ResponseWriter, r *http.Request) { srcID, err := paramID("id", r) if err != nil { Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) @@ -88,24 +88,24 @@ func (s *Service) KapacitorProxy(w http.ResponseWriter, r *http.Request) { proxy.ServeHTTP(w, r) } -// KapacitorProxyPost proxies POST to kapacitor -func (s *Service) KapacitorProxyPost(w http.ResponseWriter, r *http.Request) { - s.KapacitorProxy(w, r) +// ProxyPost proxies POST to service +func (s *Service) ProxyPost(w http.ResponseWriter, r *http.Request) { + s.Proxy(w, r) } -// KapacitorProxyPatch proxies PATCH to kapacitor -func (s *Service) KapacitorProxyPatch(w http.ResponseWriter, r *http.Request) { - s.KapacitorProxy(w, r) +// ProxyPatch proxies PATCH to Service +func (s *Service) ProxyPatch(w http.ResponseWriter, r *http.Request) { + s.Proxy(w, r) } -// KapacitorProxyGet proxies GET to kapacitor -func (s *Service) KapacitorProxyGet(w http.ResponseWriter, r *http.Request) { - s.KapacitorProxy(w, r) +// ProxyGet proxies GET to service +func (s *Service) ProxyGet(w http.ResponseWriter, r *http.Request) { + s.Proxy(w, r) } -// KapacitorProxyDelete proxies DELETE to kapacitor -func (s *Service) KapacitorProxyDelete(w http.ResponseWriter, r *http.Request) { - s.KapacitorProxy(w, r) +// ProxyDelete proxies DELETE to service +func (s *Service) ProxyDelete(w http.ResponseWriter, r *http.Request) { + s.Proxy(w, r) } func singleJoiningSlash(a, b string) string { diff --git a/server/services.go b/server/services.go new file mode 100644 index 000000000..62aa8d8d4 --- /dev/null +++ b/server/services.go @@ -0,0 +1,306 @@ +package server + +import ( + "encoding/json" + "fmt" + "net/http" + "net/url" + + "github.com/influxdata/chronograf" +) + +type postServiceRequest struct { + Name *string `json:"name"` // User facing name of service instance.; Required: true + URL *string `json:"url"` // URL for the service backend (e.g. http://localhost:9092);/ Required: true + Type *string `json:"type"` // Type is the kind of service (e.g. ifql); Required + Username string `json:"username,omitempty"` // Username for authentication to service + Password string `json:"password,omitempty"` + InsecureSkipVerify bool `json:"insecureSkipVerify"` // InsecureSkipVerify as true means any certificate presented by the service is accepted. + Organization string `json:"organization"` // Organization is the organization ID that resource belongs to + +} + +func (p *postServiceRequest) Valid(defaultOrgID string) error { + if p.Name == nil || p.URL == nil { + return fmt.Errorf("name and url required") + } + + if p.Type == nil { + return fmt.Errorf("type required") + } + + if p.Organization == "" { + p.Organization = defaultOrgID + } + + url, err := url.ParseRequestURI(*p.URL) + if err != nil { + return fmt.Errorf("invalid source URI: %v", err) + } + if len(url.Scheme) == 0 { + return fmt.Errorf("Invalid URL; no URL scheme defined") + } + + return nil +} + +type serviceLinks struct { + Proxy string `json:"proxy"` // URL location of proxy endpoint for this source + Self string `json:"self"` // Self link mapping to this resource +} + +type service struct { + ID int `json:"id,string"` // Unique identifier representing a service instance. + Name string `json:"name"` // User facing name of service instance. + URL string `json:"url"` // URL for the service backend (e.g. http://localhost:9092) + Username string `json:"username,omitempty"` // Username for authentication to service + Password string `json:"password,omitempty"` + InsecureSkipVerify bool `json:"insecureSkipVerify"` // InsecureSkipVerify as true means any certificate presented by the service is accepted. + Type string `json:"type"` // Type is the kind of service (e.g. ifql) + Links serviceLinks `json:"links"` // Links are URI locations related to service +} + +// NewService adds valid service store store. +func (s *Service) NewService(w http.ResponseWriter, r *http.Request) { + srcID, err := paramID("id", r) + if err != nil { + Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) + return + } + + ctx := r.Context() + _, err = s.Store.Sources(ctx).Get(ctx, srcID) + if err != nil { + notFound(w, srcID, s.Logger) + return + } + + var req postServiceRequest + if err = json.NewDecoder(r.Body).Decode(&req); err != nil { + invalidJSON(w, s.Logger) + return + } + + defaultOrg, err := s.Store.Organizations(ctx).DefaultOrganization(ctx) + if err != nil { + unknownErrorWithMessage(w, err, s.Logger) + return + } + + if err := req.Valid(defaultOrg.ID); err != nil { + invalidData(w, err, s.Logger) + return + } + + srv := chronograf.Server{ + SrcID: srcID, + Name: *req.Name, + Username: req.Username, + Password: req.Password, + InsecureSkipVerify: req.InsecureSkipVerify, + URL: *req.URL, + Organization: req.Organization, + Type: *req.Type, + } + + if srv, err = s.Store.Servers(ctx).Add(ctx, srv); err != nil { + msg := fmt.Errorf("Error storing service %v: %v", req, err) + unknownErrorWithMessage(w, msg, s.Logger) + return + } + + res := newService(srv) + location(w, res.Links.Self) + encodeJSON(w, http.StatusCreated, res, s.Logger) +} + +func newService(srv chronograf.Server) service { + httpAPISrcs := "/chronograf/v1/sources" + return service{ + ID: srv.ID, + Name: srv.Name, + Username: srv.Username, + URL: srv.URL, + InsecureSkipVerify: srv.InsecureSkipVerify, + Type: srv.Type, + Links: serviceLinks{ + Self: fmt.Sprintf("%s/%d/services/%d", httpAPISrcs, srv.SrcID, srv.ID), + Proxy: fmt.Sprintf("%s/%d/services/%d/proxy", httpAPISrcs, srv.SrcID, srv.ID), + }, + } +} + +type services struct { + Services []service `json:"services"` +} + +// Services retrieves all services from store. +func (s *Service) Services(w http.ResponseWriter, r *http.Request) { + srcID, err := paramID("id", r) + if err != nil { + Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) + return + } + + ctx := r.Context() + mrSrvs, err := s.Store.Servers(ctx).All(ctx) + if err != nil { + Error(w, http.StatusInternalServerError, "Error loading services", s.Logger) + return + } + + srvs := []service{} + for _, srv := range mrSrvs { + if srv.SrcID == srcID && srv.Type != "" { + srvs = append(srvs, newService(srv)) + } + } + + res := services{ + Services: srvs, + } + + encodeJSON(w, http.StatusOK, res, s.Logger) +} + +// ServiceID retrieves a service with ID from store. +func (s *Service) ServiceID(w http.ResponseWriter, r *http.Request) { + id, err := paramID("kid", r) + if err != nil { + Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) + return + } + + srcID, err := paramID("id", r) + if err != nil { + Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) + return + } + + ctx := r.Context() + srv, err := s.Store.Servers(ctx).Get(ctx, id) + if err != nil || srv.SrcID != srcID || srv.Type == "" { + notFound(w, id, s.Logger) + return + } + + res := newService(srv) + encodeJSON(w, http.StatusOK, res, s.Logger) +} + +// RemoveService deletes service from store. +func (s *Service) RemoveService(w http.ResponseWriter, r *http.Request) { + id, err := paramID("kid", r) + if err != nil { + Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) + return + } + + srcID, err := paramID("id", r) + if err != nil { + Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) + return + } + + ctx := r.Context() + srv, err := s.Store.Servers(ctx).Get(ctx, id) + if err != nil || srv.SrcID != srcID || srv.Type == "" { + notFound(w, id, s.Logger) + return + } + + if err = s.Store.Servers(ctx).Delete(ctx, srv); err != nil { + unknownErrorWithMessage(w, err, s.Logger) + return + } + + w.WriteHeader(http.StatusNoContent) +} + +type patchServiceRequest struct { + Name *string `json:"name,omitempty"` // User facing name of service instance. + Type *string `json:"type,omitempty"` // Type is the kind of service (e.g. ifql) + URL *string `json:"url,omitempty"` // URL for the service + Username *string `json:"username,omitempty"` // Username for service auth + Password *string `json:"password,omitempty"` + InsecureSkipVerify *bool `json:"insecureSkipVerify"` // InsecureSkipVerify as true means any certificate presented by the service is accepted. +} + +func (p *patchServiceRequest) Valid() error { + if p.URL != nil { + url, err := url.ParseRequestURI(*p.URL) + if err != nil { + return fmt.Errorf("invalid source URI: %v", err) + } + if len(url.Scheme) == 0 { + return fmt.Errorf("Invalid URL; no URL scheme defined") + } + } + + if p.Type != nil && *p.Type == "" { + return fmt.Errorf("Invalid type; type must not be an empty string") + } + + return nil +} + +// UpdateService incrementally updates a service definition in the store +func (s *Service) UpdateService(w http.ResponseWriter, r *http.Request) { + id, err := paramID("kid", r) + if err != nil { + Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) + return + } + + srcID, err := paramID("id", r) + if err != nil { + Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) + return + } + + ctx := r.Context() + srv, err := s.Store.Servers(ctx).Get(ctx, id) + if err != nil || srv.SrcID != srcID || srv.Type == "" { + notFound(w, id, s.Logger) + return + } + + var req patchServiceRequest + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + invalidJSON(w, s.Logger) + return + } + + if err := req.Valid(); err != nil { + invalidData(w, err, s.Logger) + return + } + + if req.Name != nil { + srv.Name = *req.Name + } + if req.Type != nil { + srv.Type = *req.Type + } + if req.URL != nil { + srv.URL = *req.URL + } + if req.Password != nil { + srv.Password = *req.Password + } + if req.Username != nil { + srv.Username = *req.Username + } + if req.InsecureSkipVerify != nil { + srv.InsecureSkipVerify = *req.InsecureSkipVerify + } + + if err := s.Store.Servers(ctx).Update(ctx, srv); err != nil { + msg := fmt.Sprintf("Error updating service ID %d", id) + Error(w, http.StatusInternalServerError, msg, s.Logger) + return + } + + res := newService(srv) + encodeJSON(w, http.StatusOK, res, s.Logger) +} diff --git a/server/sources.go b/server/sources.go index 2b1b71a0f..9cd829381 100644 --- a/server/sources.go +++ b/server/sources.go @@ -17,6 +17,7 @@ import ( type sourceLinks struct { Self string `json:"self"` // Self link mapping to this resource Kapacitors string `json:"kapacitors"` // URL for kapacitors endpoint + Services string `json:"services"` // URL for services endpoint Proxy string `json:"proxy"` // URL for proxy endpoint Queries string `json:"queries"` // URL for the queries analysis endpoint Write string `json:"write"` // URL for the write line-protocol endpoint @@ -49,6 +50,7 @@ func newSourceResponse(src chronograf.Source) sourceResponse { Links: sourceLinks{ Self: fmt.Sprintf("%s/%d", httpAPISrcs, src.ID), Kapacitors: fmt.Sprintf("%s/%d/kapacitors", httpAPISrcs, src.ID), + Services: fmt.Sprintf("%s/%d/services", httpAPISrcs, src.ID), Proxy: fmt.Sprintf("%s/%d/proxy", httpAPISrcs, src.ID), Queries: fmt.Sprintf("%s/%d/queries", httpAPISrcs, src.ID), Write: fmt.Sprintf("%s/%d/write", httpAPISrcs, src.ID), From 3618c1a8e8492cc0a4182f598cbddf145c569eac Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Mon, 14 May 2018 15:36:51 -0700 Subject: [PATCH 02/49] Introduce getting actual data --- ui/mocks/ifql/apis/index.ts | 1 + ui/src/ifql/apis/index.ts | 14 +++++++ ui/src/ifql/components/TimeMachine.tsx | 6 ++- ui/src/ifql/components/TimeMachineVis.tsx | 40 +++++++++++++++----- ui/src/ifql/containers/IFQLPage.tsx | 27 +++++++++---- ui/test/ifql/components/TimeMachine.test.tsx | 1 + 6 files changed, 70 insertions(+), 19 deletions(-) diff --git a/ui/mocks/ifql/apis/index.ts b/ui/mocks/ifql/apis/index.ts index 7e1b404d8..a16dc7a35 100644 --- a/ui/mocks/ifql/apis/index.ts +++ b/ui/mocks/ifql/apis/index.ts @@ -5,3 +5,4 @@ export const getAST = jest.fn(() => Promise.resolve({})) export const getDatabases = jest.fn(() => Promise.resolve(['db1', 'db2', 'db3']) ) +export const getTimeSeries = jest.fn(() => Promise.resolve({data: ''})) diff --git a/ui/src/ifql/apis/index.ts b/ui/src/ifql/apis/index.ts index 36467dcac..85cd24b02 100644 --- a/ui/src/ifql/apis/index.ts +++ b/ui/src/ifql/apis/index.ts @@ -34,6 +34,20 @@ export const getAST = async (request: ASTRequest) => { } } +export const getTimeSeries = async (script: string) => { + try { + const data = await AJAX({ + method: 'POST', + url: `http://localhost:8093/query?q=${script}`, + }) + + return data + } catch (error) { + console.error('Problem fetching data', error) + throw error + } +} + // TODO: replace with actual requests to IFQL daemon export const getDatabases = async () => { try { diff --git a/ui/src/ifql/components/TimeMachine.tsx b/ui/src/ifql/components/TimeMachine.tsx index 3d013f55a..fddffe3dc 100644 --- a/ui/src/ifql/components/TimeMachine.tsx +++ b/ui/src/ifql/components/TimeMachine.tsx @@ -9,9 +9,10 @@ import {ErrorHandling} from 'src/shared/decorators/errors' import {HANDLE_VERTICAL, HANDLE_HORIZONTAL} from 'src/shared/constants' interface Props { + data: string script: string - suggestions: Suggestion[] body: Body[] + suggestions: Suggestion[] onChangeScript: OnChangeScript } @@ -32,6 +33,7 @@ class TimeMachine extends PureComponent { } private get mainSplit() { + const {data} = this.props return [ { handleDisplay: 'none', @@ -44,7 +46,7 @@ class TimeMachine extends PureComponent { }, { handlePixels: 8, - render: () => , + render: () => , }, ] } diff --git a/ui/src/ifql/components/TimeMachineVis.tsx b/ui/src/ifql/components/TimeMachineVis.tsx index 53214b72a..d111048ee 100644 --- a/ui/src/ifql/components/TimeMachineVis.tsx +++ b/ui/src/ifql/components/TimeMachineVis.tsx @@ -1,14 +1,36 @@ -import React, {SFC} from 'react' +import React, {PureComponent} from 'react' +import FancyScrollbar from 'src/shared/components/FancyScrollbar' +import {ErrorHandling} from 'src/shared/decorators/errors' interface Props { - blob: string + data: string +} + +@ErrorHandling +class TimeMachineVis extends PureComponent { + public render() { + return ( +
+
+ +
+ {this.data.map((d, i) => { + return ( +
+ {d} +
+ ) + })} +
+
+
+
+ ) + } + + private get data(): string[] { + return this.props.data.split('\n') + } } -const TimeMachineVis: SFC = ({blob}) => ( -
-
-
{blob}
-
-
-) export default TimeMachineVis diff --git a/ui/src/ifql/containers/IFQLPage.tsx b/ui/src/ifql/containers/IFQLPage.tsx index 24e502f48..792d8d6ac 100644 --- a/ui/src/ifql/containers/IFQLPage.tsx +++ b/ui/src/ifql/containers/IFQLPage.tsx @@ -9,7 +9,7 @@ import {Suggestion, FlatBody, Links} 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' +import {getSuggestions, getAST, getTimeSeries} from 'src/ifql/apis' import * as argTypes from 'src/ifql/constants/argumentTypes' import {ErrorHandling} from 'src/shared/decorators/errors' @@ -25,6 +25,7 @@ interface State { body: Body[] ast: object script: string + data: string suggestions: Suggestion[] } @@ -37,11 +38,12 @@ export class IFQLPage extends PureComponent { this.state = { body: [], ast: null, + data: 'fetching data...', suggestions: [], - script: `from(db:"foo") - |> filter(fn: (r) => - (r["a"] == 1 OR r.b == "two") AND - (r["b"] == true OR r.d == "four"))`, + script: `from(db:"telegraf") + |> filter(fn: (r) => r["_measurement"] == "cpu" AND r["_field"] == "usage_user") + |> range(start:-170h) + |> sum()`, } } @@ -59,7 +61,7 @@ export class IFQLPage extends PureComponent { } public render() { - const {suggestions, script} = this.state + const {suggestions, script, data, body} = this.state return ( @@ -81,8 +83,9 @@ export class IFQLPage extends PureComponent { @@ -327,13 +330,21 @@ export class IFQLPage extends PureComponent { private getASTResponse = async (script: string) => { const {links} = this.props + this.setState({data: 'fetching data...'}) try { const ast = await getAST({url: links.ast, body: script}) const body = bodyNodes(ast, this.state.suggestions) this.setState({ast, script, body}) } catch (error) { - console.error('Could not parse AST', error) + return console.error('Could not parse AST', error) + } + + try { + const {data} = await getTimeSeries(script) + this.setState({data}) + } catch (error) { + console.error('Could not get timeSeries', error) } } } diff --git a/ui/test/ifql/components/TimeMachine.test.tsx b/ui/test/ifql/components/TimeMachine.test.tsx index 3fb702d80..e05ecc2e3 100644 --- a/ui/test/ifql/components/TimeMachine.test.tsx +++ b/ui/test/ifql/components/TimeMachine.test.tsx @@ -6,6 +6,7 @@ const setup = () => { const props = { script: '', body: [], + data: '', suggestions: [], onSubmitScript: () => {}, onChangeScript: () => {}, From fbc1fefb7dc4ae857d9af48dcb1801d61b5d86f0 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Mon, 14 May 2018 16:29:53 -0700 Subject: [PATCH 03/49] Cleanup --- ui/src/ifql/components/TimeMachineVis.tsx | 5 +++++ ui/src/ifql/containers/IFQLPage.tsx | 4 +++- ui/src/ifql/helpers/index.ts | 3 ++- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/ui/src/ifql/components/TimeMachineVis.tsx b/ui/src/ifql/components/TimeMachineVis.tsx index d111048ee..b5e67ef12 100644 --- a/ui/src/ifql/components/TimeMachineVis.tsx +++ b/ui/src/ifql/components/TimeMachineVis.tsx @@ -29,6 +29,11 @@ class TimeMachineVis extends PureComponent { } private get data(): string[] { + const {data} = this.props + if (!data) { + return ['Your query was syntactically correct but returned no data'] + } + return this.props.data.split('\n') } } diff --git a/ui/src/ifql/containers/IFQLPage.tsx b/ui/src/ifql/containers/IFQLPage.tsx index 792d8d6ac..0a31a265e 100644 --- a/ui/src/ifql/containers/IFQLPage.tsx +++ b/ui/src/ifql/containers/IFQLPage.tsx @@ -43,7 +43,8 @@ export class IFQLPage extends PureComponent { script: `from(db:"telegraf") |> filter(fn: (r) => r["_measurement"] == "cpu" AND r["_field"] == "usage_user") |> range(start:-170h) - |> sum()`, + |> sum() + |> limit(n: 100)`, } } @@ -344,6 +345,7 @@ export class IFQLPage extends PureComponent { const {data} = await getTimeSeries(script) this.setState({data}) } catch (error) { + this.setState({data: 'Error fetching data'}) console.error('Could not get timeSeries', error) } } diff --git a/ui/src/ifql/helpers/index.ts b/ui/src/ifql/helpers/index.ts index 00354c52a..8d9e197ee 100644 --- a/ui/src/ifql/helpers/index.ts +++ b/ui/src/ifql/helpers/index.ts @@ -49,7 +49,8 @@ export const bodyNodes = (ast, suggestions): Body[] => { const functions = (funcs, suggestions): Func[] => { const funcList = funcs.map(func => { - const {params, name} = suggestions.find(f => f.name === func.name) + const suggestion = suggestions.find(f => f.name === func.name) + const {params, name} = suggestion const args = Object.entries(params).map(([key, type]) => { const value = _.get(func.args.find(arg => arg.key === key), 'value', '') From 6c6e918170659c35dfc626ccd209d7038fa3b031 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Mon, 14 May 2018 16:50:55 -0700 Subject: [PATCH 04/49] Fix component flickering --- ui/src/ifql/components/BodyBuilder.tsx | 9 ++++----- ui/src/ifql/components/ExpressionNode.tsx | 4 ++-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/ui/src/ifql/components/BodyBuilder.tsx b/ui/src/ifql/components/BodyBuilder.tsx index 91d6977b3..7232e4d9a 100644 --- a/ui/src/ifql/components/BodyBuilder.tsx +++ b/ui/src/ifql/components/BodyBuilder.tsx @@ -18,15 +18,14 @@ interface Body extends FlatBody { class BodyBuilder extends PureComponent { public render() { - const bodybuilder = this.props.body.map(b => { + const bodybuilder = this.props.body.map((b, i) => { if (b.declarations.length) { return b.declarations.map(d => { if (d.funcs) { return ( -
+
{ } return ( -
+
) @@ -45,7 +44,7 @@ class BodyBuilder extends PureComponent { } return ( -
+
{ {({onDeleteFuncNode, onAddNode, onChangeArg, onGenerateScript}) => { return ( <> - {funcs.map(func => ( + {funcs.map((func, i) => ( Date: Tue, 15 May 2018 13:01:28 -0700 Subject: [PATCH 05/49] Add Get Data! button --- ui/src/ifql/containers/IFQLPage.tsx | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/ui/src/ifql/containers/IFQLPage.tsx b/ui/src/ifql/containers/IFQLPage.tsx index 0a31a265e..a122061c5 100644 --- a/ui/src/ifql/containers/IFQLPage.tsx +++ b/ui/src/ifql/containers/IFQLPage.tsx @@ -38,7 +38,7 @@ export class IFQLPage extends PureComponent { this.state = { body: [], ast: null, - data: 'fetching data...', + data: 'Hit "Get Data!" or Ctrl + Enter to run your script', suggestions: [], script: `from(db:"telegraf") |> filter(fn: (r) => r["_measurement"] == "cpu" AND r["_field"] == "usage_user") @@ -78,7 +78,13 @@ export class IFQLPage extends PureComponent { className="btn btn-sm btn-primary" onClick={this.handleSubmitScript} > - Submit Script + Analyze Script + +
@@ -331,7 +337,6 @@ export class IFQLPage extends PureComponent { private getASTResponse = async (script: string) => { const {links} = this.props - this.setState({data: 'fetching data...'}) try { const ast = await getAST({url: links.ast, body: script}) @@ -340,9 +345,13 @@ export class IFQLPage extends PureComponent { } catch (error) { return console.error('Could not parse AST', error) } + } + + private getTimeSeries = async () => { + this.setState({data: 'fetching data...'}) try { - const {data} = await getTimeSeries(script) + const {data} = await getTimeSeries(this.state.script) this.setState({data}) } catch (error) { this.setState({data: 'Error fetching data'}) From 316308df2bb4b6671a432e2374aeaabd4070461f Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Tue, 15 May 2018 14:03:28 -0700 Subject: [PATCH 06/49] Introduce foundation for forking --- ui/src/ifql/ast/walker.ts | 7 +- ui/src/ifql/helpers/index.ts | 9 +++ ui/test/ifql/ast/variable.ts | 130 ++++++++++++++++++++++++++++++++ ui/test/ifql/ast/walker.test.ts | 48 +++++++++++- 4 files changed, 188 insertions(+), 6 deletions(-) diff --git a/ui/src/ifql/ast/walker.ts b/ui/src/ifql/ast/walker.ts index 193a3e80c..02670206c 100644 --- a/ui/src/ifql/ast/walker.ts +++ b/ui/src/ifql/ast/walker.ts @@ -218,10 +218,9 @@ export default class Walker { return [...this.walk(currentNode.argument), {name, args, source}] } - if (currentNode.type === 'ArrowFunctionExpression') { - const params = currentNode.params - const body = currentNode.body - return [{name, params, body}] + if (currentNode.type === 'Identifier') { + name = currentNode.name + return [{name, source}] } name = currentNode.callee.name diff --git a/ui/src/ifql/helpers/index.ts b/ui/src/ifql/helpers/index.ts index 8d9e197ee..e7cd0358d 100644 --- a/ui/src/ifql/helpers/index.ts +++ b/ui/src/ifql/helpers/index.ts @@ -50,6 +50,15 @@ export const bodyNodes = (ast, suggestions): Body[] => { const functions = (funcs, suggestions): Func[] => { const funcList = funcs.map(func => { const suggestion = suggestions.find(f => f.name === func.name) + if (!suggestion) { + return { + id: uuid.v4(), + source: func.source, + name: func.name, + args: func.args, + } + } + const {params, name} = suggestion const args = Object.entries(params).map(([key, type]) => { const value = _.get(func.args.find(arg => arg.key === key), 'value', '') diff --git a/ui/test/ifql/ast/variable.ts b/ui/test/ifql/ast/variable.ts index f42a82873..c5a162a9b 100644 --- a/ui/test/ifql/ast/variable.ts +++ b/ui/test/ifql/ast/variable.ts @@ -215,3 +215,133 @@ export const ArrowFunction = { }, ], } + +export const Fork = { + type: 'Program', + location: { + start: {line: 1, column: 1}, + end: {line: 1, column: 42}, + source: 'tele = from(db: "telegraf")\ntele |\u003e sum()', + }, + body: [ + { + type: 'VariableDeclaration', + location: { + start: {line: 1, column: 1}, + end: {line: 1, column: 28}, + source: 'tele = from(db: "telegraf")', + }, + declarations: [ + { + type: 'VariableDeclarator', + id: { + type: 'Identifier', + location: { + start: {line: 1, column: 1}, + end: {line: 1, column: 5}, + source: 'tele', + }, + name: 'tele', + }, + init: { + type: 'CallExpression', + location: { + start: {line: 1, column: 8}, + end: {line: 1, column: 28}, + source: 'from(db: "telegraf")', + }, + callee: { + type: 'Identifier', + location: { + start: {line: 1, column: 8}, + end: {line: 1, column: 12}, + source: 'from', + }, + name: 'from', + }, + arguments: [ + { + type: 'ObjectExpression', + location: { + start: {line: 1, column: 13}, + end: {line: 1, column: 27}, + source: 'db: "telegraf"', + }, + properties: [ + { + type: 'Property', + location: { + start: {line: 1, column: 13}, + end: {line: 1, column: 27}, + source: 'db: "telegraf"', + }, + key: { + type: 'Identifier', + location: { + start: {line: 1, column: 13}, + end: {line: 1, column: 15}, + source: 'db', + }, + name: 'db', + }, + value: { + type: 'StringLiteral', + location: { + start: {line: 1, column: 17}, + end: {line: 1, column: 27}, + source: '"telegraf"', + }, + value: 'telegraf', + }, + }, + ], + }, + ], + }, + }, + ], + }, + { + type: 'ExpressionStatement', + location: { + start: {line: 2, column: 1}, + end: {line: 2, column: 14}, + source: 'tele |\u003e sum()', + }, + expression: { + type: 'PipeExpression', + location: { + start: {line: 2, column: 6}, + end: {line: 2, column: 14}, + source: '|\u003e sum()', + }, + argument: { + type: 'Identifier', + location: { + start: {line: 2, column: 1}, + end: {line: 2, column: 5}, + source: 'tele', + }, + name: 'tele', + }, + call: { + type: 'CallExpression', + location: { + start: {line: 2, column: 9}, + end: {line: 2, column: 14}, + source: 'sum()', + }, + callee: { + type: 'Identifier', + location: { + start: {line: 2, column: 9}, + end: {line: 2, column: 12}, + source: 'sum', + }, + name: 'sum', + }, + }, + }, + }, + ], +} diff --git a/ui/test/ifql/ast/walker.test.ts b/ui/test/ifql/ast/walker.test.ts index 8084329c4..c5f95b910 100644 --- a/ui/test/ifql/ast/walker.test.ts +++ b/ui/test/ifql/ast/walker.test.ts @@ -1,7 +1,12 @@ import Walker from 'src/ifql/ast/walker' import From from 'test/ifql/ast/from' import Complex from 'test/ifql/ast/complex' -import {StringLiteral, Expression, ArrowFunction} from 'test/ifql/ast/variable' +import { + StringLiteral, + Expression, + ArrowFunction, + Fork, +} from 'test/ifql/ast/variable' describe('IFQL.AST.Walker', () => { describe('Walker#functions', () => { @@ -80,7 +85,7 @@ describe('IFQL.AST.Walker', () => { }) }) - describe.only('a single ArrowFunction variable', () => { + describe('a single ArrowFunction variable', () => { it('returns the expected list', () => { const walker = new Walker(ArrowFunction) expect(walker.body).toEqual([ @@ -104,6 +109,45 @@ describe('IFQL.AST.Walker', () => { ]) }) }) + + describe('forking', () => { + it('return the expected list of objects', () => { + const walker = new Walker(Fork) + expect(walker.body).toEqual([ + { + type: 'VariableDeclaration', + source: 'tele = from(db: "telegraf")', + declarations: [ + { + name: 'tele', + type: 'CallExpression', + source: 'tele = from(db: "telegraf")', + funcs: [ + { + name: 'from', + source: 'from(db: "telegraf")', + args: [ + { + key: 'db', + value: 'telegraf', + }, + ], + }, + ], + }, + ], + }, + { + type: 'PipeExpression', + source: 'tele |> sum()', + funcs: [ + {args: [], name: 'tele', source: 'tele'}, + {args: [], name: 'sum', source: '|> sum()'}, + ], + }, + ]) + }) + }) }) }) }) From 774115c926da8e506e92b32081ff455200a77e23 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Tue, 15 May 2018 14:16:35 -0700 Subject: [PATCH 07/49] Remove untitled variables --- ui/src/ifql/components/BodyBuilder.tsx | 1 - ui/src/ifql/components/VariableName.tsx | 70 +------------------------ ui/src/ifql/containers/IFQLPage.tsx | 7 +-- 3 files changed, 4 insertions(+), 74 deletions(-) diff --git a/ui/src/ifql/components/BodyBuilder.tsx b/ui/src/ifql/components/BodyBuilder.tsx index 7232e4d9a..346e4f2de 100644 --- a/ui/src/ifql/components/BodyBuilder.tsx +++ b/ui/src/ifql/components/BodyBuilder.tsx @@ -45,7 +45,6 @@ class BodyBuilder extends PureComponent { return (
- { } public render() { - const {isExpanded} = this.state - - return ( -
- {this.nameElement} - {isExpanded && this.renderTooltip} -
- ) - } - - private get renderTooltip(): JSX.Element { - const {name} = this.props - - if (name.includes('=')) { - const split = name.split('=') - const varName = split[0].substring(0, split[0].length - 1) - const varValue = split[1].substring(1) - - return ( -
- - = - -
- ) - } - - return ( -
- -
- ) - } - - private handleMouseEnter = (e: MouseEvent): void => { - e.stopPropagation() - - this.setState({isExpanded: true}) - } - - private handleMouseLeave = (e: MouseEvent): void => { - e.stopPropagation() - - this.setState({isExpanded: false}) + return
{this.nameElement}
} private get nameElement(): JSX.Element { const {name} = this.props - if (!name) { - return Untitled - } - if (name.includes('=')) { return this.colorizeSyntax } diff --git a/ui/src/ifql/containers/IFQLPage.tsx b/ui/src/ifql/containers/IFQLPage.tsx index a122061c5..89de26ff8 100644 --- a/ui/src/ifql/containers/IFQLPage.tsx +++ b/ui/src/ifql/containers/IFQLPage.tsx @@ -40,11 +40,8 @@ export class IFQLPage extends PureComponent { ast: null, data: 'Hit "Get Data!" or Ctrl + Enter to run your script', suggestions: [], - script: `from(db:"telegraf") - |> filter(fn: (r) => r["_measurement"] == "cpu" AND r["_field"] == "usage_user") - |> range(start:-170h) - |> sum() - |> limit(n: 100)`, + script: `tele = from(db: "telegraf") + tele |> sum()`, } } From ad9a5ff985aa57da9dcc3f74c2ab7dbdc337349f Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Tue, 15 May 2018 14:35:20 -0700 Subject: [PATCH 08/49] Display user defined functions --- ui/src/ifql/components/VariableName.tsx | 2 +- ui/src/ifql/containers/IFQLPage.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ui/src/ifql/components/VariableName.tsx b/ui/src/ifql/components/VariableName.tsx index cd91d6baf..d07f4b3ab 100644 --- a/ui/src/ifql/components/VariableName.tsx +++ b/ui/src/ifql/components/VariableName.tsx @@ -39,7 +39,7 @@ export default class VariableName extends PureComponent { const {name} = this.props const split = name.split('=') const varName = split[0].substring(0, split[0].length - 1) - const varValue = split[1].substring(1) + const varValue = this.props.name.replace(/^[^=]+=/, '') const valueIsString = varValue.endsWith('"') diff --git a/ui/src/ifql/containers/IFQLPage.tsx b/ui/src/ifql/containers/IFQLPage.tsx index 89de26ff8..b46bfcb84 100644 --- a/ui/src/ifql/containers/IFQLPage.tsx +++ b/ui/src/ifql/containers/IFQLPage.tsx @@ -40,8 +40,8 @@ export class IFQLPage extends PureComponent { ast: null, data: 'Hit "Get Data!" or Ctrl + Enter to run your script', suggestions: [], - script: `tele = from(db: "telegraf") - tele |> sum()`, + script: `fil = (r) => r._measurement == "cpu" + tele = from(db: "telegraf") |> filter(fn: fil) |> range(start: -1m) |> sum()`, } } From cfa20426e51233ae758b0d4558aea695ba022733 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Tue, 15 May 2018 14:56:25 -0700 Subject: [PATCH 09/49] Move Threesizer to own dir --- ui/src/ifql/components/TimeMachine.tsx | 2 +- .../{ResizeDivision.tsx => Threesizer/Division.tsx} | 0 .../shared/components/{ => Threesizer}/Threesizer.tsx | 10 +++++----- 3 files changed, 6 insertions(+), 6 deletions(-) rename ui/src/shared/components/{ResizeDivision.tsx => Threesizer/Division.tsx} (100%) rename ui/src/shared/components/{ => Threesizer}/Threesizer.tsx (98%) diff --git a/ui/src/ifql/components/TimeMachine.tsx b/ui/src/ifql/components/TimeMachine.tsx index fddffe3dc..2a7890322 100644 --- a/ui/src/ifql/components/TimeMachine.tsx +++ b/ui/src/ifql/components/TimeMachine.tsx @@ -3,7 +3,7 @@ import SchemaExplorer from 'src/ifql/components/SchemaExplorer' import BodyBuilder from 'src/ifql/components/BodyBuilder' import TimeMachineEditor from 'src/ifql/components/TimeMachineEditor' import TimeMachineVis from 'src/ifql/components/TimeMachineVis' -import Threesizer from 'src/shared/components/Threesizer' +import Threesizer from 'src/shared/components/Threesizer/Threesizer' import {Suggestion, OnChangeScript, FlatBody} from 'src/types/ifql' import {ErrorHandling} from 'src/shared/decorators/errors' import {HANDLE_VERTICAL, HANDLE_HORIZONTAL} from 'src/shared/constants' diff --git a/ui/src/shared/components/ResizeDivision.tsx b/ui/src/shared/components/Threesizer/Division.tsx similarity index 100% rename from ui/src/shared/components/ResizeDivision.tsx rename to ui/src/shared/components/Threesizer/Division.tsx diff --git a/ui/src/shared/components/Threesizer.tsx b/ui/src/shared/components/Threesizer/Threesizer.tsx similarity index 98% rename from ui/src/shared/components/Threesizer.tsx rename to ui/src/shared/components/Threesizer/Threesizer.tsx index b68dbc4d2..8714248ec 100644 --- a/ui/src/shared/components/Threesizer.tsx +++ b/ui/src/shared/components/Threesizer/Threesizer.tsx @@ -3,7 +3,7 @@ import classnames from 'classnames' import uuid from 'uuid' import _ from 'lodash' -import ResizeDivision from 'src/shared/components/ResizeDivision' +import Division from 'src/shared/components/ThreeSizer/Division' import {ErrorHandling} from 'src/shared/decorators/errors' import { HANDLE_NONE, @@ -28,20 +28,20 @@ interface State { dragEvent: any } -interface Division { +interface DivisionProps { name?: string handleDisplay?: string handlePixels?: number render: (visibility?: string) => ReactElement } -interface DivisionState extends Division { +interface DivisionState extends DivisionProps { id: string size: number } interface Props { - divisions: Division[] + divisions: DivisionProps[] orientation: string containerClass?: string } @@ -129,7 +129,7 @@ class Threesizer extends Component { ref={r => (this.containerRef = r)} > {divisions.map((d, i) => ( - Date: Tue, 15 May 2018 14:59:31 -0700 Subject: [PATCH 10/49] Introduce DivisionHeader --- ui/src/shared/components/Threesizer/Division.tsx | 3 ++- ui/src/shared/components/Threesizer/DivisionHeader.tsx | 9 +++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 ui/src/shared/components/Threesizer/DivisionHeader.tsx diff --git a/ui/src/shared/components/Threesizer/Division.tsx b/ui/src/shared/components/Threesizer/Division.tsx index facf2049f..1f892c562 100644 --- a/ui/src/shared/components/Threesizer/Division.tsx +++ b/ui/src/shared/components/Threesizer/Division.tsx @@ -2,6 +2,7 @@ import React, {PureComponent, ReactElement, MouseEvent} from 'react' import classnames from 'classnames' import calculateSize from 'calculate-size' +import Header from 'src/shared/components/Threesizer/DivisionHeader' import {HANDLE_VERTICAL, HANDLE_HORIZONTAL} from 'src/shared/constants/index' const NOOP = () => {} @@ -77,7 +78,7 @@ class Division extends PureComponent {
{name}
- {name &&
} + {name &&
}
{render(this.visibility)}
diff --git a/ui/src/shared/components/Threesizer/DivisionHeader.tsx b/ui/src/shared/components/Threesizer/DivisionHeader.tsx new file mode 100644 index 000000000..0b459a568 --- /dev/null +++ b/ui/src/shared/components/Threesizer/DivisionHeader.tsx @@ -0,0 +1,9 @@ +import React, {PureComponent} from 'react' + +class DivisionHeader extends PureComponent<{}> { + public render() { + return
+ } +} + +export default DivisionHeader From c7cda660bc925afcb12973becd8e1f628f5efe2f Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Tue, 15 May 2018 15:38:53 -0700 Subject: [PATCH 11/49] Add threesizer context menus --- .../components/Threesizer/DivisionHeader.tsx | 30 ++- ui/src/style/components/threesizer.scss | 173 ++++++++++++++---- 2 files changed, 167 insertions(+), 36 deletions(-) diff --git a/ui/src/shared/components/Threesizer/DivisionHeader.tsx b/ui/src/shared/components/Threesizer/DivisionHeader.tsx index 0b459a568..a98bf294c 100644 --- a/ui/src/shared/components/Threesizer/DivisionHeader.tsx +++ b/ui/src/shared/components/Threesizer/DivisionHeader.tsx @@ -1,8 +1,34 @@ import React, {PureComponent} from 'react' +import MenuTooltipButton from 'src/shared/components/MenuTooltipButton' -class DivisionHeader extends PureComponent<{}> { +const noop = () => {} + +class DivisionHeader extends PureComponent { public render() { - return
+ return ( +
+
+ +
+
+ ) + } + + private get options() { + return [ + { + action: () => {}, + text: 'Maximize', + }, + { + action: () => {}, + text: 'Minimize', + }, + ] } } diff --git a/ui/src/style/components/threesizer.scss b/ui/src/style/components/threesizer.scss index 2bec45e63..95b7192a3 100644 --- a/ui/src/style/components/threesizer.scss +++ b/ui/src/style/components/threesizer.scss @@ -4,22 +4,18 @@ */ $threesizer-handle: 30px; - .threesizer { width: 100%; height: 100%; display: flex; align-items: stretch; - &.dragging .threesizer--division { @include no-user-select(); pointer-events: none; } - &.vertical { flex-direction: row; } - &.horizontal { flex-direction: column; } @@ -29,55 +25,45 @@ $threesizer-handle: 30px; overflow: hidden; display: flex; align-items: stretch; - transition: height 0.25s ease-in-out, width 0.25s ease-in-out; - &.dragging { transition: none; } - &.vertical { flex-direction: row; } - &.horizontal { flex-direction: column; } } /* Draggable Handle With Title */ + .threesizer--handle { @include no-user-select(); background-color: $g4-onyx; transition: background-color 0.25s ease, color 0.25s ease; - &.vertical { border-right: solid 2px $g3-castle; - &:hover, &.dragging { cursor: col-resize; } } - &.horizontal { border-bottom: solid 2px $g3-castle; - &:hover, &.dragging { cursor: row-resize; } } - &:hover { &.disabled { cursor: pointer; } - color: $g16-pearl; background-color: $g5-pepper; } - &.dragging { color: $c-laser; background-color: $g5-pepper; @@ -93,10 +79,8 @@ $threesizer-handle: 30px; color: $g11-sidewalk; z-index: 1; transition: transform 0.25s ease; - &.vertical { transform: translate(28px, 14px); - &.threesizer--collapsed { transform: translate(0, 3px) rotate(90deg); } @@ -107,22 +91,17 @@ $threesizer-shadow-size: 9px; $threesizer-z-index: 2; $threesizer-shadow-start: fade-out($g0-obsidian, 0.82); $threesizer-shadow-stop: fade-out($g0-obsidian, 1); - .threesizer--contents { display: flex; align-items: stretch; flex-wrap: nowrap; position: relative; - &.horizontal { flex-direction: row; } - &.vertical { flex-direction: column; - } - - // Bottom Shadow + } // Bottom Shadow &.horizontal:after, &.vertical:after { content: ''; @@ -131,13 +110,11 @@ $threesizer-shadow-stop: fade-out($g0-obsidian, 1); right: 0; z-index: $threesizer-z-index; } - &.horizontal:after { width: 100%; height: $threesizer-shadow-size; @include gradient-v($threesizer-shadow-stop, $threesizer-shadow-start); } - &.vertical:after { height: 100%; width: $threesizer-shadow-size; @@ -156,28 +133,156 @@ $threesizer-shadow-stop: fade-out($g0-obsidian, 1); // Header .threesizer--header { background-color: $g2-kevlar; - - .horizontal > & { + .horizontal>& { width: 50px; border-right: 2px solid $g4-onyx; } - - .vertical > & { + .vertical>& { height: 50px; border-bottom: 2px solid $g4-onyx; } } .threesizer--body { - .horizontal > &:only-child { + .horizontal>&:only-child { width: 100%; } - - .vertical > &:only-child { + .vertical>&:only-child { height: 100%; } - - .threesizer--header + & { + .threesizer--header+& { flex: 1 0 0; } } + +// Division context menus +.threesizer-context--buttons { + display: flex; + justify-content: flex-end; + margin-top: 10px; +} + +.dash-graph-context--button { + width: 24px; + height: 24px; + border-radius: 3px; + font-size: 12px; + position: relative; + color: $g11-sidewalk; + margin-right: 2px; + transition: color 0.25s ease, background-color 0.25s ease; + &:hover, + &.active { + cursor: pointer; + color: $g20-white; + background-color: $g5-pepper; + } + &:last-child { + margin-right: 0; + } + >.icon { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + } + &.active { + position: relative; + z-index: 20; + } +} + +.dash-graph-context--menu, +.dash-graph-context--menu.default { + z-index: 3; + position: absolute; + top: calc(100% + 8px); + left: 50%; + background-color: $g6-smoke; + transform: translateX(-50%); + border-radius: 3px; + display: flex; + flex-direction: column; + align-items: stretch; + justify-content: center; + &:before { + position: absolute; + content: ''; + border: 6px solid transparent; + border-bottom-color: $g6-smoke; + left: 50%; + top: 0; + transform: translate(-50%, -100%); + transition: border-color 0.25s ease; + } + .dash-graph-context--menu-item { + @include no-user-select(); + white-space: nowrap; + font-size: 12px; + font-weight: 700; + line-height: 26px; + height: 26px; + padding: 0 10px; + color: $g20-white; + transition: background-color 0.25s ease; + &:first-child { + border-top-left-radius: 3px; + border-top-right-radius: 3px; + } + &:last-child { + border-bottom-left-radius: 3px; + border-bottom-right-radius: 3px; + } + &:hover { + background-color: $g8-storm; + cursor: pointer; + } + &.disabled, + &.disabled:hover { + cursor: default; + background-color: transparent; + font-style: italic; + color: $g11-sidewalk; + } + } +} + +.dash-graph-context--menu.primary { + background-color: $c-ocean; + &:before { + border-bottom-color: $c-ocean; + } + .dash-graph-context--menu-item:hover { + background-color: $c-pool; + } +} + +.dash-graph-context--menu.warning { + background-color: $c-star; + &:before { + border-bottom-color: $c-star; + } + .dash-graph-context--menu-item:hover { + background-color: $c-comet; + } +} + +.dash-graph-context--menu.success { + background-color: $c-rainforest; + &:before { + border-bottom-color: $c-rainforest; + } + .dash-graph-context--menu-item:hover { + background-color: $c-honeydew; + } +} + +.dash-graph-context--menu.danger { + background-color: $c-curacao; + &:before { + border-bottom-color: $c-curacao; + } + .dash-graph-context--menu-item:hover { + background-color: $c-dreamsicle; + } +} \ No newline at end of file From b390e70d1c0adadc74d472150bec4bca2e32979f Mon Sep 17 00:00:00 2001 From: Alex P Date: Tue, 15 May 2018 16:17:37 -0700 Subject: [PATCH 12/49] Create & Style DivisionMenu component --- .../components/Threesizer/DivisionHeader.tsx | 16 ++-- .../components/Threesizer/DivisionMenu.tsx | 88 +++++++++++++++++++ ui/src/style/components/threesizer.scss | 23 +++-- 3 files changed, 111 insertions(+), 16 deletions(-) create mode 100644 ui/src/shared/components/Threesizer/DivisionMenu.tsx diff --git a/ui/src/shared/components/Threesizer/DivisionHeader.tsx b/ui/src/shared/components/Threesizer/DivisionHeader.tsx index a98bf294c..798bd2f1e 100644 --- a/ui/src/shared/components/Threesizer/DivisionHeader.tsx +++ b/ui/src/shared/components/Threesizer/DivisionHeader.tsx @@ -1,24 +1,18 @@ import React, {PureComponent} from 'react' -import MenuTooltipButton from 'src/shared/components/MenuTooltipButton' - -const noop = () => {} +import DivisionMenu, { + MenuItem, +} from 'src/shared/components/Threesizer/DivisionMenu' class DivisionHeader extends PureComponent { public render() { return (
-
- -
+
) } - private get options() { + private get menuItems(): MenuItem[] { return [ { action: () => {}, diff --git a/ui/src/shared/components/Threesizer/DivisionMenu.tsx b/ui/src/shared/components/Threesizer/DivisionMenu.tsx new file mode 100644 index 000000000..605bb70de --- /dev/null +++ b/ui/src/shared/components/Threesizer/DivisionMenu.tsx @@ -0,0 +1,88 @@ +import React, {PureComponent} from 'react' +import uuid from 'uuid' +import classnames from 'classnames' +import {ClickOutside} from 'src/shared/components/ClickOutside' + +export interface MenuItem { + text: string + action: () => void +} + +interface Props { + menuItems: MenuItem[] +} + +interface State { + expanded: boolean +} + +class DivisionMenu extends PureComponent { + constructor(props: Props) { + super(props) + + this.state = { + expanded: false, + } + } + + public render() { + const {expanded} = this.state + + return ( + +
+ + {expanded && this.renderMenu} +
+
+ ) + } + + private handleExpandMenu = (): void => { + this.setState({expanded: true}) + } + + private handleCollapseMenu = (): void => { + this.setState({expanded: false}) + } + + private handleMenuItemClick = action => (): void => { + this.setState({expanded: false}) + action() + } + + private get menuClass(): string { + const {expanded} = this.state + + return classnames('dropdown threesizer--menu', {open: expanded}) + } + + private get buttonClass(): string { + const {expanded} = this.state + + return classnames('btn btn-sm btn-square btn-default', { + active: expanded, + }) + } + + private get renderMenu(): JSX.Element { + const {menuItems} = this.props + return ( + + ) + } +} + +export default DivisionMenu diff --git a/ui/src/style/components/threesizer.scss b/ui/src/style/components/threesizer.scss index 95b7192a3..b2e37e4cb 100644 --- a/ui/src/style/components/threesizer.scss +++ b/ui/src/style/components/threesizer.scss @@ -4,6 +4,7 @@ */ $threesizer-handle: 30px; + .threesizer { width: 100%; height: 100%; @@ -132,25 +133,30 @@ $threesizer-shadow-stop: fade-out($g0-obsidian, 1); // Header .threesizer--header { + display: flex; + align-items: center; + justify-content: flex-end; + padding: 0 11px; background-color: $g2-kevlar; - .horizontal>& { + + .horizontal > & { width: 50px; border-right: 2px solid $g4-onyx; } - .vertical>& { + .vertical > & { height: 50px; border-bottom: 2px solid $g4-onyx; } } .threesizer--body { - .horizontal>&:only-child { + .horizontal > &:only-child { width: 100%; } - .vertical>&:only-child { + .vertical > &:only-child { height: 100%; } - .threesizer--header+& { + .threesizer--header + & { flex: 1 0 0; } } @@ -285,4 +291,11 @@ $threesizer-shadow-stop: fade-out($g0-obsidian, 1); .dash-graph-context--menu-item:hover { background-color: $c-dreamsicle; } +} + +// Header Dropdown Menu +.threesizer--menu { + .dropdown-menu { + right: 0; + } } \ No newline at end of file From eb4b656b40041ac7da361bc5c3a0fa9b92e96efa Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Tue, 15 May 2018 16:46:11 -0700 Subject: [PATCH 13/49] End merge --- ui/src/ifql/components/TimeMachine.tsx | 2 +- ui/src/ifql/components/TimeMachineEditor.tsx | 1 + ui/src/ifql/containers/IFQLPage.tsx | 2 +- .../shared/components/Threesizer/Division.tsx | 21 +++++++- .../components/Threesizer/DivisionHeader.tsx | 12 +++-- .../components/Threesizer/Threesizer.tsx | 48 ++++++++++++++++++- ui/webpack/dev.config.js | 2 +- 7 files changed, 79 insertions(+), 9 deletions(-) diff --git a/ui/src/ifql/components/TimeMachine.tsx b/ui/src/ifql/components/TimeMachine.tsx index 2a7890322..bd4ebfa6f 100644 --- a/ui/src/ifql/components/TimeMachine.tsx +++ b/ui/src/ifql/components/TimeMachine.tsx @@ -3,7 +3,7 @@ import SchemaExplorer from 'src/ifql/components/SchemaExplorer' import BodyBuilder from 'src/ifql/components/BodyBuilder' import TimeMachineEditor from 'src/ifql/components/TimeMachineEditor' import TimeMachineVis from 'src/ifql/components/TimeMachineVis' -import Threesizer from 'src/shared/components/Threesizer/Threesizer' +import Threesizer from 'src/shared/components/threesizer/Threesizer' import {Suggestion, OnChangeScript, FlatBody} from 'src/types/ifql' import {ErrorHandling} from 'src/shared/decorators/errors' import {HANDLE_VERTICAL, HANDLE_HORIZONTAL} from 'src/shared/constants' diff --git a/ui/src/ifql/components/TimeMachineEditor.tsx b/ui/src/ifql/components/TimeMachineEditor.tsx index f5caa40a2..adcd788a0 100644 --- a/ui/src/ifql/components/TimeMachineEditor.tsx +++ b/ui/src/ifql/components/TimeMachineEditor.tsx @@ -64,6 +64,7 @@ class TimeMachineEditor extends PureComponent { } private handleMount = (instance: EditorInstance) => { + instance.refresh() // required to for proper line height on mount this.editor = instance } diff --git a/ui/src/ifql/containers/IFQLPage.tsx b/ui/src/ifql/containers/IFQLPage.tsx index b46bfcb84..132f74925 100644 --- a/ui/src/ifql/containers/IFQLPage.tsx +++ b/ui/src/ifql/containers/IFQLPage.tsx @@ -63,7 +63,7 @@ export class IFQLPage extends PureComponent { return ( - +
diff --git a/ui/src/shared/components/Threesizer/Division.tsx b/ui/src/shared/components/Threesizer/Division.tsx index 1f892c562..52a9850d0 100644 --- a/ui/src/shared/components/Threesizer/Division.tsx +++ b/ui/src/shared/components/Threesizer/Division.tsx @@ -2,7 +2,7 @@ import React, {PureComponent, ReactElement, MouseEvent} from 'react' import classnames from 'classnames' import calculateSize from 'calculate-size' -import Header from 'src/shared/components/Threesizer/DivisionHeader' +import DivisionHeader from 'src/shared/components/threesizer/DivisionHeader' import {HANDLE_VERTICAL, HANDLE_HORIZONTAL} from 'src/shared/constants/index' const NOOP = () => {} @@ -20,6 +20,8 @@ interface Props { render: (visibility: string) => ReactElement onHandleStartDrag: (id: string, e: MouseEvent) => void onDoubleClick: (id: string) => void + onMaximize: (id: string) => void + onMinimize: (id: string) => void } interface Style { @@ -78,7 +80,12 @@ class Division extends PureComponent {
{name}
- {name &&
} + {name && ( + + )}
{render(this.visibility)}
@@ -224,6 +231,16 @@ class Division extends PureComponent { onDoubleClick(id) } + + private handleMinimize = (): void => { + const {id, onMinimize} = this.props + onMinimize(id) + } + + private handleMaximize = (): void => { + const {id, onMaximize} = this.props + onMaximize(id) + } } export default Division diff --git a/ui/src/shared/components/Threesizer/DivisionHeader.tsx b/ui/src/shared/components/Threesizer/DivisionHeader.tsx index 798bd2f1e..281245453 100644 --- a/ui/src/shared/components/Threesizer/DivisionHeader.tsx +++ b/ui/src/shared/components/Threesizer/DivisionHeader.tsx @@ -3,7 +3,12 @@ import DivisionMenu, { MenuItem, } from 'src/shared/components/Threesizer/DivisionMenu' -class DivisionHeader extends PureComponent { +interface Props { + onMinimize: () => void + onMaximize: () => void +} + +class DivisionHeader extends PureComponent { public render() { return (
@@ -13,13 +18,14 @@ class DivisionHeader extends PureComponent { } private get menuItems(): MenuItem[] { + const {onMaximize, onMinimize} = this.props return [ { - action: () => {}, + action: onMaximize, text: 'Maximize', }, { - action: () => {}, + action: onMinimize, text: 'Minimize', }, ] diff --git a/ui/src/shared/components/Threesizer/Threesizer.tsx b/ui/src/shared/components/Threesizer/Threesizer.tsx index 8714248ec..298ccc26b 100644 --- a/ui/src/shared/components/Threesizer/Threesizer.tsx +++ b/ui/src/shared/components/Threesizer/Threesizer.tsx @@ -3,7 +3,7 @@ import classnames from 'classnames' import uuid from 'uuid' import _ from 'lodash' -import Division from 'src/shared/components/ThreeSizer/Division' +import Division from 'src/shared/components/threesizer/Division' import {ErrorHandling} from 'src/shared/decorators/errors' import { HANDLE_NONE, @@ -140,6 +140,8 @@ class Threesizer extends Component { handlePixels={d.handlePixels} handleDisplay={d.handleDisplay} activeHandleID={activeHandleID} + onMaximize={this.handleMaximize} + onMinimize={this.handleMinimize} onDoubleClick={this.handleDoubleClick} onHandleStartDrag={this.handleStartDrag} render={this.props.divisions[i].render} @@ -209,6 +211,50 @@ class Threesizer extends Component { this.setState({divisions}) } + private handleMaximize = (id: string): void => { + const maxDiv = this.state.divisions.find(d => d.id === id) + + if (!maxDiv) { + return + } + + const divisions = this.state.divisions.map(d => { + if (d.id !== id) { + return {...d, size: 0} + } + + return {...d, size: 1} + }) + + this.setState({divisions}) + } + + private handleMinimize = (id: string): void => { + const minDiv = this.state.divisions.find(d => d.id === id) + const numDivisions = this.state.divisions.length + + if (!minDiv) { + return + } + + let size + if (numDivisions <= 1) { + size = 1 + } else { + size = 1 / (this.state.divisions.length - 1) + } + + const divisions = this.state.divisions.map(d => { + if (d.id !== id) { + return {...d, size} + } + + return {...d, size: 0} + }) + + this.setState({divisions}) + } + private equalize = () => { const denominator = this.state.divisions.length const divisions = this.state.divisions.map(d => { diff --git a/ui/webpack/dev.config.js b/ui/webpack/dev.config.js index b67bc31ed..f691c7652 100644 --- a/ui/webpack/dev.config.js +++ b/ui/webpack/dev.config.js @@ -45,7 +45,7 @@ module.exports = { }, watch: true, cache: true, - devtool: 'cheap-eval-source-map', + devtool: 'source-map', entry: { app: path.resolve(__dirname, '..', 'src', 'index.tsx'), }, From bab17e907251bc8c8b6371a6ed77ccf860f1eb87 Mon Sep 17 00:00:00 2001 From: Alex P Date: Tue, 15 May 2018 17:23:51 -0700 Subject: [PATCH 14/49] Hide children in DivisionHeader when division is collapsed --- ui/src/shared/components/Threesizer/Division.tsx | 3 +++ ui/src/style/components/threesizer.scss | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/ui/src/shared/components/Threesizer/Division.tsx b/ui/src/shared/components/Threesizer/Division.tsx index 52a9850d0..5be0e538e 100644 --- a/ui/src/shared/components/Threesizer/Division.tsx +++ b/ui/src/shared/components/Threesizer/Division.tsx @@ -170,7 +170,10 @@ class Division extends PureComponent { private get handleClass(): string { const {draggable, orientation} = this.props + const collapsed = orientation === HANDLE_VERTICAL && this.isTitleObscured + return classnames('threesizer--handle', { + 'threesizer--collapsed': collapsed, disabled: !draggable, dragging: this.isDragging, vertical: orientation === HANDLE_VERTICAL, diff --git a/ui/src/style/components/threesizer.scss b/ui/src/style/components/threesizer.scss index b2e37e4cb..436eab9a8 100644 --- a/ui/src/style/components/threesizer.scss +++ b/ui/src/style/components/threesizer.scss @@ -298,4 +298,9 @@ $threesizer-shadow-stop: fade-out($g0-obsidian, 1); .dropdown-menu { right: 0; } +} + +// Hide Header children when collapsed +.threesizer--handle.vertical.threesizer--collapsed + .threesizer--contents > .threesizer--header > * { + display: none; } \ No newline at end of file From 176613db956313eb3fe5f87c02f8e2f19fccb4c2 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Wed, 16 May 2018 09:52:44 -0700 Subject: [PATCH 15/49] Move Analyze to inside division: --- ui/src/ifql/components/TimeMachine.tsx | 21 +++++++++++++++++-- ui/src/ifql/containers/IFQLPage.tsx | 12 +++++------ .../shared/components/Threesizer/Division.tsx | 5 ++++- .../components/Threesizer/DivisionHeader.tsx | 6 ++++-- .../components/Threesizer/Threesizer.tsx | 6 +++++- ui/webpack/dev.config.js | 2 +- 6 files changed, 38 insertions(+), 14 deletions(-) diff --git a/ui/src/ifql/components/TimeMachine.tsx b/ui/src/ifql/components/TimeMachine.tsx index bd4ebfa6f..abcec6685 100644 --- a/ui/src/ifql/components/TimeMachine.tsx +++ b/ui/src/ifql/components/TimeMachine.tsx @@ -4,7 +4,12 @@ import BodyBuilder from 'src/ifql/components/BodyBuilder' import TimeMachineEditor from 'src/ifql/components/TimeMachineEditor' import TimeMachineVis from 'src/ifql/components/TimeMachineVis' import Threesizer from 'src/shared/components/threesizer/Threesizer' -import {Suggestion, OnChangeScript, FlatBody} from 'src/types/ifql' +import { + Suggestion, + OnChangeScript, + OnSubmitScript, + FlatBody, +} from 'src/types/ifql' import {ErrorHandling} from 'src/shared/decorators/errors' import {HANDLE_VERTICAL, HANDLE_HORIZONTAL} from 'src/shared/constants' @@ -14,6 +19,7 @@ interface Props { body: Body[] suggestions: Suggestion[] onChangeScript: OnChangeScript + onSubmitScript: OnSubmitScript } interface Body extends FlatBody { @@ -37,6 +43,7 @@ class TimeMachine extends PureComponent { return [ { handleDisplay: 'none', + menuOptions: [], render: () => ( { }, { handlePixels: 8, + menuOptions: [], render: () => , }, ] } private get divisions() { - const {body, suggestions, script, onChangeScript} = this.props + const { + body, + script, + suggestions, + onChangeScript, + onSubmitScript, + } = this.props return [ { name: 'Explore', + menuOptions: [], render: () => , }, { name: 'Script', + menuOptions: [{action: onSubmitScript, text: 'Analyze'}], render: visibility => ( { }, { name: 'Build', + menuOptions: [], render: () => , }, ] diff --git a/ui/src/ifql/containers/IFQLPage.tsx b/ui/src/ifql/containers/IFQLPage.tsx index 132f74925..20d179227 100644 --- a/ui/src/ifql/containers/IFQLPage.tsx +++ b/ui/src/ifql/containers/IFQLPage.tsx @@ -71,12 +71,6 @@ export class IFQLPage extends PureComponent {

Time Machine

-
@@ -345,15 +340,18 @@ export class IFQLPage extends PureComponent { } private getTimeSeries = async () => { + const {script} = this.state this.setState({data: 'fetching data...'}) try { - const {data} = await getTimeSeries(this.state.script) + const {data} = await getTimeSeries(script) this.setState({data}) } catch (error) { this.setState({data: 'Error fetching data'}) console.error('Could not get timeSeries', error) } + + this.getASTResponse(script) } } diff --git a/ui/src/shared/components/Threesizer/Division.tsx b/ui/src/shared/components/Threesizer/Division.tsx index 5be0e538e..def9f6d96 100644 --- a/ui/src/shared/components/Threesizer/Division.tsx +++ b/ui/src/shared/components/Threesizer/Division.tsx @@ -4,6 +4,7 @@ import calculateSize from 'calculate-size' import DivisionHeader from 'src/shared/components/threesizer/DivisionHeader' import {HANDLE_VERTICAL, HANDLE_HORIZONTAL} from 'src/shared/constants/index' +import {MenuItem} from 'src/shared/components/threesizer/DivisionMenu' const NOOP = () => {} @@ -22,6 +23,7 @@ interface Props { onDoubleClick: (id: string) => void onMaximize: (id: string) => void onMinimize: (id: string) => void + menuOptions?: MenuItem[] } interface Style { @@ -62,7 +64,7 @@ class Division extends PureComponent { } public render() { - const {name, render, draggable} = this.props + const {name, render, draggable, menuOptions} = this.props return (
{
{name && ( diff --git a/ui/src/shared/components/Threesizer/DivisionHeader.tsx b/ui/src/shared/components/Threesizer/DivisionHeader.tsx index 281245453..726cbc673 100644 --- a/ui/src/shared/components/Threesizer/DivisionHeader.tsx +++ b/ui/src/shared/components/Threesizer/DivisionHeader.tsx @@ -1,11 +1,12 @@ import React, {PureComponent} from 'react' import DivisionMenu, { MenuItem, -} from 'src/shared/components/Threesizer/DivisionMenu' +} from 'src/shared/components/threesizer/DivisionMenu' interface Props { onMinimize: () => void onMaximize: () => void + menuOptions?: MenuItem[] } class DivisionHeader extends PureComponent { @@ -18,8 +19,9 @@ class DivisionHeader extends PureComponent { } private get menuItems(): MenuItem[] { - const {onMaximize, onMinimize} = this.props + const {onMaximize, onMinimize, menuOptions} = this.props return [ + ...menuOptions, { action: onMaximize, text: 'Maximize', diff --git a/ui/src/shared/components/Threesizer/Threesizer.tsx b/ui/src/shared/components/Threesizer/Threesizer.tsx index 298ccc26b..657498af1 100644 --- a/ui/src/shared/components/Threesizer/Threesizer.tsx +++ b/ui/src/shared/components/Threesizer/Threesizer.tsx @@ -5,6 +5,8 @@ import _ from 'lodash' import Division from 'src/shared/components/threesizer/Division' import {ErrorHandling} from 'src/shared/decorators/errors' +import {MenuItem} from 'src/shared/components/threesizer/DivisionMenu' + import { HANDLE_NONE, HANDLE_PIXELS, @@ -32,6 +34,7 @@ interface DivisionProps { name?: string handleDisplay?: string handlePixels?: number + menuOptions: MenuItem[] render: (visibility?: string) => ReactElement } @@ -143,8 +146,9 @@ class Threesizer extends Component { onMaximize={this.handleMaximize} onMinimize={this.handleMinimize} onDoubleClick={this.handleDoubleClick} - onHandleStartDrag={this.handleStartDrag} render={this.props.divisions[i].render} + onHandleStartDrag={this.handleStartDrag} + menuOptions={this.props.divisions[i].menuOptions} /> ))}
diff --git a/ui/webpack/dev.config.js b/ui/webpack/dev.config.js index f691c7652..b67bc31ed 100644 --- a/ui/webpack/dev.config.js +++ b/ui/webpack/dev.config.js @@ -45,7 +45,7 @@ module.exports = { }, watch: true, cache: true, - devtool: 'source-map', + devtool: 'cheap-eval-source-map', entry: { app: path.resolve(__dirname, '..', 'src', 'index.tsx'), }, From 6bcf1ae58ce24024455874d756b2c37fd21485da Mon Sep 17 00:00:00 2001 From: ebb-tide Date: Mon, 14 May 2018 17:39:33 -0700 Subject: [PATCH 16/49] Update changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 01dc7f389..333be47f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -57,6 +57,9 @@ 1. [#3412](https://github.com/influxdata/chronograf/pull/3412): Limit max-width of TICKScript editor 1. [#3166](https://github.com/influxdata/chronograf/pull/3166): Fix naming of new TICKScripts 1. [#3449](https://github.com/influxdata/chronograf/pull/3449): Fix data explorer query error reporting regression +1. [#3412](https://github.com/influxdata/chronograf/pull/3412): Limit max-width of TICKScript editor. +1. [#3166](https://github.com/influxdata/chronograf/pull/3166): Fixes naming of new TICKScripts +1. [#3449](https://github.com/influxdata/chronograf/pull/3449): Fixes data explorer query error reporting regression 1. [#3453](https://github.com/influxdata/chronograf/pull/3453): Fix Kapacitor Logs fetch regression ## v1.4.4.1 [2018-04-16] From c1e6937b89f8a1be918690b6e331e86a012e35fd Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Wed, 16 May 2018 11:55:56 -0700 Subject: [PATCH 17/49] Add line specific error text --- ui/src/ifql/components/TimeMachine.tsx | 4 ++ ui/src/ifql/components/TimeMachineEditor.tsx | 37 ++++++++++++++++++- ui/src/ifql/containers/IFQLPage.tsx | 20 +++++++++- .../components/time-machine/ifql-editor.scss | 5 +++ ui/src/types/ifql.ts | 5 +++ 5 files changed, 68 insertions(+), 3 deletions(-) diff --git a/ui/src/ifql/components/TimeMachine.tsx b/ui/src/ifql/components/TimeMachine.tsx index abcec6685..ecc12012b 100644 --- a/ui/src/ifql/components/TimeMachine.tsx +++ b/ui/src/ifql/components/TimeMachine.tsx @@ -9,6 +9,7 @@ import { OnChangeScript, OnSubmitScript, FlatBody, + Status, } from 'src/types/ifql' import {ErrorHandling} from 'src/shared/decorators/errors' import {HANDLE_VERTICAL, HANDLE_HORIZONTAL} from 'src/shared/constants' @@ -17,6 +18,7 @@ interface Props { data: string script: string body: Body[] + status: Status suggestions: Suggestion[] onChangeScript: OnChangeScript onSubmitScript: OnSubmitScript @@ -63,6 +65,7 @@ class TimeMachine extends PureComponent { const { body, script, + status, suggestions, onChangeScript, onSubmitScript, @@ -78,6 +81,7 @@ class TimeMachine extends PureComponent { menuOptions: [{action: onSubmitScript, text: 'Analyze'}], render: visibility => ( { private editor: EditorInstance + private prevKey: string constructor(props) { super(props) } public componentDidUpdate(prevProps) { + if (this.props.status.type === 'error') { + this.makeError() + } + + if (this.props.status.type !== 'error') { + this.editor.clearGutter('error-gutter') + } + if (prevProps.visibility === this.props.visibility) { return } @@ -45,6 +60,7 @@ class TimeMachineEditor extends PureComponent { extraKeys: {'Ctrl-Space': 'autocomplete'}, completeSingle: false, autoRefresh: true, + gutters: ['error-gutter'], } return ( @@ -63,6 +79,25 @@ class TimeMachineEditor extends PureComponent { ) } + private makeError(): void { + const {status} = this.props + this.editor.clearGutter('error-gutter') + const span = document.createElement('span') + span.className = 'icon stop error-warning' + span.title = status.text + const lineNumber = this.statusLine + this.editor.setGutterMarker(lineNumber - 1, 'error-gutter', span) + this.editor.refresh() + } + + private get statusLine(): number { + const {status} = this.props + const numbers = status.text.split(' ')[0] + const [lineNumber] = numbers.split(':') + + return Number(lineNumber) + } + private handleMount = (instance: EditorInstance) => { instance.refresh() // required to for proper line height on mount this.editor = instance diff --git a/ui/src/ifql/containers/IFQLPage.tsx b/ui/src/ifql/containers/IFQLPage.tsx index 20d179227..8c4719d45 100644 --- a/ui/src/ifql/containers/IFQLPage.tsx +++ b/ui/src/ifql/containers/IFQLPage.tsx @@ -13,6 +13,11 @@ import {getSuggestions, getAST, getTimeSeries} from 'src/ifql/apis' import * as argTypes from 'src/ifql/constants/argumentTypes' import {ErrorHandling} from 'src/shared/decorators/errors' +interface Status { + type: string + text: string +} + interface Props { links: Links } @@ -27,6 +32,7 @@ interface State { script: string data: string suggestions: Suggestion[] + status: Status } export const IFQLContext = React.createContext() @@ -42,6 +48,10 @@ export class IFQLPage extends PureComponent { suggestions: [], script: `fil = (r) => r._measurement == "cpu" tele = from(db: "telegraf") |> filter(fn: fil) |> range(start: -1m) |> sum()`, + status: { + type: 'none', + text: '', + }, } } @@ -59,7 +69,7 @@ export class IFQLPage extends PureComponent { } public render() { - const {suggestions, script, data, body} = this.state + const {suggestions, script, data, body, status} = this.state return ( @@ -84,6 +94,7 @@ export class IFQLPage extends PureComponent { data={data} body={body} script={script} + status={status} suggestions={suggestions} onChangeScript={this.handleChangeScript} onSubmitScript={this.handleSubmitScript} @@ -333,8 +344,13 @@ export class IFQLPage extends PureComponent { try { const ast = await getAST({url: links.ast, body: script}) const body = bodyNodes(ast, this.state.suggestions) - this.setState({ast, script, body}) + const status = {type: 'success', text: ''} + this.setState({ast, script, body, status}) } catch (error) { + const s = error.data.slice(0, -5) // There is a null newline at the end of these responses + const data = JSON.parse(s) + const status = {type: 'error', text: `${data.message}`} + this.setState({status}) return console.error('Could not parse AST', error) } } diff --git a/ui/src/style/components/time-machine/ifql-editor.scss b/ui/src/style/components/time-machine/ifql-editor.scss index 843646300..0612898d8 100644 --- a/ui/src/style/components/time-machine/ifql-editor.scss +++ b/ui/src/style/components/time-machine/ifql-editor.scss @@ -19,3 +19,8 @@ width: 100%; height: 100%; } + +.error-warning { + color: $c-dreamsicle; + cursor: pointer; +} \ No newline at end of file diff --git a/ui/src/types/ifql.ts b/ui/src/types/ifql.ts index 1792b1744..f90eba4be 100644 --- a/ui/src/types/ifql.ts +++ b/ui/src/types/ifql.ts @@ -10,6 +10,11 @@ export type OnGenerateScript = (script: string) => void export type OnChangeScript = (script: string) => void export type OnSubmitScript = () => void +export interface Status { + type: string + text: string +} + export interface Handlers { onAddNode: OnAddNode onChangeArg: OnChangeArg From c68dfc9f75c06259476fe45180692171da147fce Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Wed, 16 May 2018 11:58:44 -0700 Subject: [PATCH 18/49] Prevent suggestions on paste / copy --- ui/src/ifql/components/TimeMachineEditor.tsx | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/ui/src/ifql/components/TimeMachineEditor.tsx b/ui/src/ifql/components/TimeMachineEditor.tsx index fa6cafd1f..ed9fbbf84 100644 --- a/ui/src/ifql/components/TimeMachineEditor.tsx +++ b/ui/src/ifql/components/TimeMachineEditor.tsx @@ -105,6 +105,17 @@ class TimeMachineEditor extends PureComponent { private handleKeyUp = (instance: EditorInstance, e: KeyboardEvent) => { const {key} = e + const prevKey = this.prevKey + + if (prevKey === 'Control' || prevKey === 'Meta') { + return (this.prevKey = key) + } + + if (editor.EXCLUDED_KEYS.includes(key)) { + return (this.prevKey = key) + } + + this.prevKey = key if (editor.EXCLUDED_KEYS.includes(key)) { return From f4b019993a72650a2587322c78be5bd81752c1aa Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Wed, 16 May 2018 13:01:19 -0700 Subject: [PATCH 19/49] Refine error reporting --- ui/src/ifql/components/TimeMachine.tsx | 4 +++- ui/src/ifql/components/TimeMachineEditor.tsx | 16 +++++++++++----- ui/src/ifql/constants/editor.ts | 3 +++ 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/ui/src/ifql/components/TimeMachine.tsx b/ui/src/ifql/components/TimeMachine.tsx index ecc12012b..45c6945fe 100644 --- a/ui/src/ifql/components/TimeMachine.tsx +++ b/ui/src/ifql/components/TimeMachine.tsx @@ -70,6 +70,7 @@ class TimeMachine extends PureComponent { onChangeScript, onSubmitScript, } = this.props + return [ { name: 'Explore', @@ -83,8 +84,9 @@ class TimeMachine extends PureComponent { ), }, diff --git a/ui/src/ifql/components/TimeMachineEditor.tsx b/ui/src/ifql/components/TimeMachineEditor.tsx index ed9fbbf84..528e8ab26 100644 --- a/ui/src/ifql/components/TimeMachineEditor.tsx +++ b/ui/src/ifql/components/TimeMachineEditor.tsx @@ -3,7 +3,7 @@ import {Controlled as CodeMirror, IInstance} from 'react-codemirror2' import {EditorChange} from 'codemirror' import 'src/external/codemirror' import {ErrorHandling} from 'src/shared/decorators/errors' -import {OnChangeScript} from 'src/types/ifql' +import {OnChangeScript, OnSubmitScript} from 'src/types/ifql' import {editor} from 'src/ifql/constants' interface Status { @@ -16,6 +16,7 @@ interface Props { visibility: string status: Status onChangeScript: OnChangeScript + onSubmitScript: OnSubmitScript } interface EditorInstance extends IInstance { @@ -74,11 +75,16 @@ class TimeMachineEditor extends PureComponent { onBeforeChange={this.updateCode} onTouchStart={this.onTouchStart} editorDidMount={this.handleMount} + onBlur={this.handleBlur} />
) } + private handleBlur = (): void => { + this.props.onSubmitScript() + } + private makeError(): void { const {status} = this.props this.editor.clearGutter('error-gutter') @@ -111,16 +117,16 @@ class TimeMachineEditor extends PureComponent { return (this.prevKey = key) } - if (editor.EXCLUDED_KEYS.includes(key)) { - return (this.prevKey = key) - } - this.prevKey = key if (editor.EXCLUDED_KEYS.includes(key)) { return } + if (editor.EXCLUDED_KEYS.includes(key)) { + return + } + instance.showHint({completeSingle: false}) } diff --git a/ui/src/ifql/constants/editor.ts b/ui/src/ifql/constants/editor.ts index f91858da8..6337a4467 100644 --- a/ui/src/ifql/constants/editor.ts +++ b/ui/src/ifql/constants/editor.ts @@ -30,6 +30,9 @@ export const EXCLUDED_KEYS = [ 'Subtract', 'Decimal point', 'Divide', + '>', + '|', + ')', 'F1', 'F2', 'F3', From 056c5f49adba0a2a84aefacd407b1635483061fb Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Wed, 16 May 2018 14:28:24 -0700 Subject: [PATCH 20/49] Enable multiple line error spans --- ui/src/ifql/components/TimeMachineEditor.tsx | 47 +++++++++++++++----- ui/src/ifql/containers/IFQLPage.tsx | 3 +- 2 files changed, 36 insertions(+), 14 deletions(-) diff --git a/ui/src/ifql/components/TimeMachineEditor.tsx b/ui/src/ifql/components/TimeMachineEditor.tsx index 528e8ab26..5ed261747 100644 --- a/ui/src/ifql/components/TimeMachineEditor.tsx +++ b/ui/src/ifql/components/TimeMachineEditor.tsx @@ -6,6 +6,11 @@ import {ErrorHandling} from 'src/shared/decorators/errors' import {OnChangeScript, OnSubmitScript} from 'src/types/ifql' import {editor} from 'src/ifql/constants' +interface Gutter { + line: number + text: string +} + interface Status { type: string text: string @@ -86,22 +91,36 @@ class TimeMachineEditor extends PureComponent { } private makeError(): void { - const {status} = this.props this.editor.clearGutter('error-gutter') - const span = document.createElement('span') - span.className = 'icon stop error-warning' - span.title = status.text - const lineNumber = this.statusLine - this.editor.setGutterMarker(lineNumber - 1, 'error-gutter', span) + const lineNumbers = this.statusLine + lineNumbers.forEach(({line, text}) => { + this.editor.setGutterMarker( + line - 1, + 'error-gutter', + this.errorMarker(text) + ) + }) + this.editor.refresh() } - private get statusLine(): number { - const {status} = this.props - const numbers = status.text.split(' ')[0] - const [lineNumber] = numbers.split(':') + private errorMarker(message: string): HTMLElement { + const span = document.createElement('span') + span.className = 'icon stop error-warning' + span.title = message + return span + } - return Number(lineNumber) + private get statusLine(): Gutter[] { + const {status} = this.props + const messages = status.text.split('\n') + const lineNumbers = messages.map(text => { + const [numbers] = text.split(' ') + const [lineNumber] = numbers.split(':') + return {line: Number(lineNumber), text} + }) + + return lineNumbers } private handleMount = (instance: EditorInstance) => { @@ -113,7 +132,11 @@ class TimeMachineEditor extends PureComponent { const {key} = e const prevKey = this.prevKey - if (prevKey === 'Control' || prevKey === 'Meta') { + if ( + prevKey === 'Control' || + prevKey === 'Meta' || + (prevKey === 'Shift' && key === '.') + ) { return (this.prevKey = key) } diff --git a/ui/src/ifql/containers/IFQLPage.tsx b/ui/src/ifql/containers/IFQLPage.tsx index 8c4719d45..a1d44a4a2 100644 --- a/ui/src/ifql/containers/IFQLPage.tsx +++ b/ui/src/ifql/containers/IFQLPage.tsx @@ -46,8 +46,7 @@ export class IFQLPage extends PureComponent { ast: null, data: 'Hit "Get Data!" or Ctrl + Enter to run your script', suggestions: [], - script: `fil = (r) => r._measurement == "cpu" - tele = from(db: "telegraf") |> filter(fn: fil) |> range(start: -1m) |> sum()`, + script: `"fil = (r) => r._measurement == \"cpu\"\ntele = from(db: \"telegraf\") \n\t\t|> filter(fn: fil)\n |> range(start: -1m)\n |> sum()"`, status: { type: 'none', text: '', From f7cd7407414e41fff30a44a0f2c35a99911a6eeb Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Wed, 16 May 2018 14:45:46 -0700 Subject: [PATCH 21/49] Introduce analyze button --- ui/src/ifql/components/TimeMachine.tsx | 15 ++++++++++++++- .../shared/components/Threesizer/Division.tsx | 6 ++++-- .../components/Threesizer/DivisionHeader.tsx | 2 ++ .../components/Threesizer/Threesizer.tsx | 2 ++ ui/src/style/components/threesizer.scss | 18 ++++++++++-------- 5 files changed, 32 insertions(+), 11 deletions(-) diff --git a/ui/src/ifql/components/TimeMachine.tsx b/ui/src/ifql/components/TimeMachine.tsx index 45c6945fe..39103c39a 100644 --- a/ui/src/ifql/components/TimeMachine.tsx +++ b/ui/src/ifql/components/TimeMachine.tsx @@ -46,6 +46,7 @@ class TimeMachine extends PureComponent { { handleDisplay: 'none', menuOptions: [], + headerButtons: [], render: () => ( { { handlePixels: 8, menuOptions: [], + headerButtons: [], render: () => , }, ] @@ -74,12 +76,22 @@ class TimeMachine extends PureComponent { return [ { name: 'Explore', + headerButtons: [ +
+ Analyze +
, + ], menuOptions: [], render: () => , }, { name: 'Script', - menuOptions: [{action: onSubmitScript, text: 'Analyze'}], + headerButtons: [ +
+ Analyze +
, + ], + menuOptions: [], render: visibility => ( { }, { name: 'Build', + headerButtons: [], menuOptions: [], render: () => , }, diff --git a/ui/src/shared/components/Threesizer/Division.tsx b/ui/src/shared/components/Threesizer/Division.tsx index def9f6d96..30b7e572d 100644 --- a/ui/src/shared/components/Threesizer/Division.tsx +++ b/ui/src/shared/components/Threesizer/Division.tsx @@ -11,6 +11,7 @@ const NOOP = () => {} interface Props { name?: string handleDisplay?: string + menuOptions?: MenuItem[] handlePixels: number id: string size: number @@ -23,7 +24,7 @@ interface Props { onDoubleClick: (id: string) => void onMaximize: (id: string) => void onMinimize: (id: string) => void - menuOptions?: MenuItem[] + headerButtons: JSX.Element[] } interface Style { @@ -64,7 +65,7 @@ class Division extends PureComponent { } public render() { - const {name, render, draggable, menuOptions} = this.props + const {name, render, draggable, menuOptions, headerButtons} = this.props return (
{
{name && ( void onMaximize: () => void + buttons: JSX.Element[] menuOptions?: MenuItem[] } @@ -13,6 +14,7 @@ class DivisionHeader extends PureComponent { public render() { return (
+ {this.props.buttons.map(b => b)}
) diff --git a/ui/src/shared/components/Threesizer/Threesizer.tsx b/ui/src/shared/components/Threesizer/Threesizer.tsx index 657498af1..21af18d95 100644 --- a/ui/src/shared/components/Threesizer/Threesizer.tsx +++ b/ui/src/shared/components/Threesizer/Threesizer.tsx @@ -34,6 +34,7 @@ interface DivisionProps { name?: string handleDisplay?: string handlePixels?: number + headerButtons?: JSX.Element[] menuOptions: MenuItem[] render: (visibility?: string) => ReactElement } @@ -149,6 +150,7 @@ class Threesizer extends Component { render={this.props.divisions[i].render} onHandleStartDrag={this.handleStartDrag} menuOptions={this.props.divisions[i].menuOptions} + headerButtons={this.props.divisions[i].headerButtons} /> ))}
diff --git a/ui/src/style/components/threesizer.scss b/ui/src/style/components/threesizer.scss index 436eab9a8..57acdac6f 100644 --- a/ui/src/style/components/threesizer.scss +++ b/ui/src/style/components/threesizer.scss @@ -4,7 +4,6 @@ */ $threesizer-handle: 30px; - .threesizer { width: 100%; height: 100%; @@ -138,25 +137,24 @@ $threesizer-shadow-stop: fade-out($g0-obsidian, 1); justify-content: flex-end; padding: 0 11px; background-color: $g2-kevlar; - - .horizontal > & { + .horizontal>& { width: 50px; border-right: 2px solid $g4-onyx; } - .vertical > & { + .vertical>& { height: 50px; border-bottom: 2px solid $g4-onyx; } } .threesizer--body { - .horizontal > &:only-child { + .horizontal>&:only-child { width: 100%; } - .vertical > &:only-child { + .vertical>&:only-child { height: 100%; } - .threesizer--header + & { + .threesizer--header+& { flex: 1 0 0; } } @@ -168,6 +166,10 @@ $threesizer-shadow-stop: fade-out($g0-obsidian, 1); margin-top: 10px; } +.analyze--button { + margin-right: 3px +} + .dash-graph-context--button { width: 24px; height: 24px; @@ -301,6 +303,6 @@ $threesizer-shadow-stop: fade-out($g0-obsidian, 1); } // Hide Header children when collapsed -.threesizer--handle.vertical.threesizer--collapsed + .threesizer--contents > .threesizer--header > * { +.threesizer--handle.vertical.threesizer--collapsed+.threesizer--contents>.threesizer--header>* { display: none; } \ No newline at end of file From 204fdaeb8c53815918654bece0b005f6040da35c Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Wed, 16 May 2018 15:09:45 -0700 Subject: [PATCH 22/49] Display success on analysis --- ui/src/ifql/components/TimeMachine.tsx | 8 ++++- ui/src/ifql/containers/IFQLPage.tsx | 44 +++++++++++++++++++++----- ui/src/shared/copy/notifications.js | 6 ++++ 3 files changed, 49 insertions(+), 9 deletions(-) diff --git a/ui/src/ifql/components/TimeMachine.tsx b/ui/src/ifql/components/TimeMachine.tsx index 39103c39a..7cc129101 100644 --- a/ui/src/ifql/components/TimeMachine.tsx +++ b/ui/src/ifql/components/TimeMachine.tsx @@ -22,6 +22,7 @@ interface Props { suggestions: Suggestion[] onChangeScript: OnChangeScript onSubmitScript: OnSubmitScript + onAnalyze: () => void } interface Body extends FlatBody { @@ -68,6 +69,7 @@ class TimeMachine extends PureComponent { body, script, status, + onAnalyze, suggestions, onChangeScript, onSubmitScript, @@ -87,7 +89,11 @@ class TimeMachine extends PureComponent { { name: 'Script', headerButtons: [ -
+
Analyze
, ], diff --git a/ui/src/ifql/containers/IFQLPage.tsx b/ui/src/ifql/containers/IFQLPage.tsx index a1d44a4a2..20ce643b1 100644 --- a/ui/src/ifql/containers/IFQLPage.tsx +++ b/ui/src/ifql/containers/IFQLPage.tsx @@ -1,16 +1,19 @@ import React, {PureComponent} from 'react' - +import {bindActionCreators} from 'redux' 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, Links} from 'src/types/ifql' import {InputArg, Handlers, DeleteFuncNodeArgs, Func} from 'src/types/ifql' +import {notify as notifyAction} from 'src/shared/actions/notifications' +import {analyzeSuccess} from 'src/shared/copy/notifications' import {bodyNodes} from 'src/ifql/helpers' import {getSuggestions, getAST, getTimeSeries} from 'src/ifql/apis' import * as argTypes from 'src/ifql/constants/argumentTypes' +import {Suggestion, FlatBody, Links} from 'src/types/ifql' +import {Notification} from 'src/types' import {ErrorHandling} from 'src/shared/decorators/errors' interface Status { @@ -20,6 +23,7 @@ interface Status { interface Props { links: Links + notify: (message: Notification) => void } interface Body extends FlatBody { @@ -46,7 +50,7 @@ export class IFQLPage extends PureComponent { ast: null, data: 'Hit "Get Data!" or Ctrl + Enter to run your script', suggestions: [], - script: `"fil = (r) => r._measurement == \"cpu\"\ntele = from(db: \"telegraf\") \n\t\t|> filter(fn: fil)\n |> range(start: -1m)\n |> sum()"`, + script: `fil = (r) => r._measurement == \"cpu\"\ntele = from(db: \"telegraf\") \n\t\t|> filter(fn: fil)\n |> range(start: -1m)\n |> sum()`, status: { type: 'none', text: '', @@ -95,6 +99,7 @@ export class IFQLPage extends PureComponent { script={script} status={status} suggestions={suggestions} + onAnalyze={this.handleAnalyze} onChangeScript={this.handleChangeScript} onSubmitScript={this.handleSubmitScript} /> @@ -337,6 +342,22 @@ export class IFQLPage extends PureComponent { }, '') } + private handleAnalyze = async () => { + const {links, notify} = this.props + + try { + const ast = await getAST({url: links.ast, body: this.state.script}) + const body = bodyNodes(ast, this.state.suggestions) + const status = {type: 'success', text: ''} + notify(analyzeSuccess) + + this.setState({ast, body, status}) + } catch (error) { + this.setState({status: this.parseError(error)}) + return console.error('Could not parse AST', error) + } + } + private getASTResponse = async (script: string) => { const {links} = this.props @@ -346,10 +367,7 @@ export class IFQLPage extends PureComponent { const status = {type: 'success', text: ''} this.setState({ast, script, body, status}) } catch (error) { - const s = error.data.slice(0, -5) // There is a null newline at the end of these responses - const data = JSON.parse(s) - const status = {type: 'error', text: `${data.message}`} - this.setState({status}) + this.setState({status: this.parseError(error)}) return console.error('Could not parse AST', error) } } @@ -368,10 +386,20 @@ export class IFQLPage extends PureComponent { this.getASTResponse(script) } + + private parseError = (error): Status => { + const s = error.data.slice(0, -5) // There is a 'null\n' at the end of these responses + const data = JSON.parse(s) + return {type: 'error', text: `${data.message}`} + } } const mapStateToProps = ({links}) => { return {links: links.ifql} } -export default connect(mapStateToProps, null)(IFQLPage) +const mapDispatchToProps = dispatch => ({ + notify: bindActionCreators(notifyAction, dispatch), +}) + +export default connect(mapStateToProps, mapDispatchToProps)(IFQLPage) diff --git a/ui/src/shared/copy/notifications.js b/ui/src/shared/copy/notifications.js index 419fafc4a..8f0c996b4 100644 --- a/ui/src/shared/copy/notifications.js +++ b/ui/src/shared/copy/notifications.js @@ -608,3 +608,9 @@ export const notifyKapacitorNotFound = () => ({ ...defaultErrorNotification, message: 'We could not find a Kapacitor configuration for this source.', }) + +// IFQL notifications +export const analyzeSuccess = { + ...defaultSuccessNotification, + message: 'No errors found. Happy Happy Joy Joy!', +} From d97366ec62e78a393b2f2a946a50dcc5a9c8a60a Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Wed, 16 May 2018 15:22:44 -0700 Subject: [PATCH 23/49] Fix broken tests --- ui/src/ifql/components/TimeMachine.tsx | 2 +- ui/test/ifql/components/TimeMachine.test.tsx | 2 ++ ui/test/ifql/containers/IFQLPage.test.tsx | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/ui/src/ifql/components/TimeMachine.tsx b/ui/src/ifql/components/TimeMachine.tsx index 7cc129101..2614a6b61 100644 --- a/ui/src/ifql/components/TimeMachine.tsx +++ b/ui/src/ifql/components/TimeMachine.tsx @@ -3,7 +3,7 @@ import SchemaExplorer from 'src/ifql/components/SchemaExplorer' import BodyBuilder from 'src/ifql/components/BodyBuilder' import TimeMachineEditor from 'src/ifql/components/TimeMachineEditor' import TimeMachineVis from 'src/ifql/components/TimeMachineVis' -import Threesizer from 'src/shared/components/threesizer/Threesizer' +import Threesizer from 'src/shared/components/Threesizer/Threesizer' import { Suggestion, OnChangeScript, diff --git a/ui/test/ifql/components/TimeMachine.test.tsx b/ui/test/ifql/components/TimeMachine.test.tsx index e05ecc2e3..c88306c5f 100644 --- a/ui/test/ifql/components/TimeMachine.test.tsx +++ b/ui/test/ifql/components/TimeMachine.test.tsx @@ -7,9 +7,11 @@ const setup = () => { script: '', body: [], data: '', + status: {type: '', text: ''}, suggestions: [], onSubmitScript: () => {}, onChangeScript: () => {}, + onAnalyze: () => {}, } const wrapper = shallow() diff --git a/ui/test/ifql/containers/IFQLPage.test.tsx b/ui/test/ifql/containers/IFQLPage.test.tsx index 23558838c..3fa27e054 100644 --- a/ui/test/ifql/containers/IFQLPage.test.tsx +++ b/ui/test/ifql/containers/IFQLPage.test.tsx @@ -13,6 +13,7 @@ const setup = () => { suggestions: '', ast: '', }, + notify: () => {}, } const wrapper = shallow() From afccff10861b7e65f78991bf29109115bab86fca Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Wed, 16 May 2018 16:17:51 -0700 Subject: [PATCH 24/49] Introduce appendFrom funtionality --- ui/package.json | 4 ++-- ui/src/ifql/components/BodyBuilder.tsx | 15 ++++++++------- ui/src/ifql/components/TimeMachine.tsx | 10 +++++++++- ui/src/ifql/constants/builder.ts | 1 + ui/src/ifql/constants/index.ts | 7 ++++--- ui/src/ifql/containers/IFQLPage.tsx | 17 +++++++++++++---- ui/test/ifql/components/TimeMachine.test.tsx | 1 + 7 files changed, 38 insertions(+), 17 deletions(-) create mode 100644 ui/src/ifql/constants/builder.ts diff --git a/ui/package.json b/ui/package.json index e3be12850..aee04c93c 100644 --- a/ui/package.json +++ b/ui/package.json @@ -9,7 +9,7 @@ "url": "github:influxdata/chronograf" }, "scripts": { - "build": "yarn run clean && webpack --config ./webpack/prod.config.js", + "build": "yarn run clean && webpack --config ./webpack/prod.config.js --display-error-details", "build:dev": "webpack --config ./webpack/dev.config.js", "build:vendor": "webpack --config webpack/vendor.config.js", "start": "yarn run clean && yarn run build:vendor && webpack --watch --config ./webpack/dev.config.js --progress", @@ -157,4 +157,4 @@ "rome": "^2.1.22", "uuid": "^3.2.1" } -} +} \ No newline at end of file diff --git a/ui/src/ifql/components/BodyBuilder.tsx b/ui/src/ifql/components/BodyBuilder.tsx index 346e4f2de..1ba5f1b01 100644 --- a/ui/src/ifql/components/BodyBuilder.tsx +++ b/ui/src/ifql/components/BodyBuilder.tsx @@ -4,12 +4,14 @@ import _ from 'lodash' import ExpressionNode from 'src/ifql/components/ExpressionNode' import VariableName from 'src/ifql/components/VariableName' import FuncSelector from 'src/ifql/components/FuncSelector' +import {funcNames} from 'src/ifql/constants' import {FlatBody, Suggestion} from 'src/types/ifql' interface Props { body: Body[] suggestions: Suggestion[] + onAppendFrom: () => void } interface Body extends FlatBody { @@ -61,7 +63,7 @@ class BodyBuilder extends PureComponent { @@ -72,14 +74,13 @@ class BodyBuilder extends PureComponent { private get newDeclarationFuncs(): string[] { // 'JOIN' only available if there are at least 2 named declarations - return ['from', 'join', 'variable'] + return ['from'] } - private createNewDeclaration = (bodyID, name, declarationID) => { - // Returning a string here so linter stops yelling - // TODO: write a real function - - return `${bodyID} / ${name} / ${declarationID}` + private createNewBody = name => { + if (name === funcNames.FROM) { + this.props.onAppendFrom() + } } private get funcNames() { diff --git a/ui/src/ifql/components/TimeMachine.tsx b/ui/src/ifql/components/TimeMachine.tsx index 2614a6b61..d601642c0 100644 --- a/ui/src/ifql/components/TimeMachine.tsx +++ b/ui/src/ifql/components/TimeMachine.tsx @@ -22,6 +22,7 @@ interface Props { suggestions: Suggestion[] onChangeScript: OnChangeScript onSubmitScript: OnSubmitScript + onAppendFrom: () => void onAnalyze: () => void } @@ -71,6 +72,7 @@ class TimeMachine extends PureComponent { status, onAnalyze, suggestions, + onAppendFrom, onChangeScript, onSubmitScript, } = this.props @@ -112,7 +114,13 @@ class TimeMachine extends PureComponent { name: 'Build', headerButtons: [], menuOptions: [], - render: () => , + render: () => ( + + ), }, ] } diff --git a/ui/src/ifql/constants/builder.ts b/ui/src/ifql/constants/builder.ts new file mode 100644 index 000000000..752966cb9 --- /dev/null +++ b/ui/src/ifql/constants/builder.ts @@ -0,0 +1 @@ +export const NEW_FROM = `from(db: "pick a db")\n\t|> filter(fn: (r) => r.tag == "value")\n\t|> range(start: -1m)` diff --git a/ui/src/ifql/constants/index.ts b/ui/src/ifql/constants/index.ts index d34a7234f..be7056754 100644 --- a/ui/src/ifql/constants/index.ts +++ b/ui/src/ifql/constants/index.ts @@ -1,6 +1,7 @@ -import * as funcNames from 'src/ifql/constants/funcNames' -import * as argTypes from 'src/ifql/constants/argumentTypes' import {ast} from 'src/ifql/constants/ast' import * as editor from 'src/ifql/constants/editor' +import * as argTypes from 'src/ifql/constants/argumentTypes' +import * as funcNames from 'src/ifql/constants/funcNames' +import * as builder from 'src/ifql/constants/builder' -export {ast, funcNames, argTypes, editor} +export {ast, funcNames, argTypes, editor, builder} diff --git a/ui/src/ifql/containers/IFQLPage.tsx b/ui/src/ifql/containers/IFQLPage.tsx index 20ce643b1..5981b9c89 100644 --- a/ui/src/ifql/containers/IFQLPage.tsx +++ b/ui/src/ifql/containers/IFQLPage.tsx @@ -4,6 +4,7 @@ import {connect} from 'react-redux' import _ from 'lodash' import TimeMachine from 'src/ifql/components/TimeMachine' +import {ErrorHandling} from 'src/shared/decorators/errors' import KeyboardShortcuts from 'src/shared/components/KeyboardShortcuts' import {InputArg, Handlers, DeleteFuncNodeArgs, Func} from 'src/types/ifql' import {notify as notifyAction} from 'src/shared/actions/notifications' @@ -11,10 +12,10 @@ import {analyzeSuccess} from 'src/shared/copy/notifications' import {bodyNodes} from 'src/ifql/helpers' import {getSuggestions, getAST, getTimeSeries} from 'src/ifql/apis' -import * as argTypes from 'src/ifql/constants/argumentTypes' -import {Suggestion, FlatBody, Links} from 'src/types/ifql' +import {builder, argTypes} from 'src/ifql/constants' + import {Notification} from 'src/types' -import {ErrorHandling} from 'src/shared/decorators/errors' +import {Suggestion, FlatBody, Links} from 'src/types/ifql' interface Status { type: string @@ -50,7 +51,7 @@ export class IFQLPage extends PureComponent { ast: null, data: 'Hit "Get Data!" or Ctrl + Enter to run your script', suggestions: [], - script: `fil = (r) => r._measurement == \"cpu\"\ntele = from(db: \"telegraf\") \n\t\t|> filter(fn: fil)\n |> range(start: -1m)\n |> sum()`, + script: `fil = (r) => r._measurement == \"cpu\"\ntele = from(db: \"telegraf\") \n\t\t|> filter(fn: fil)\n |> range(start: -1m)\n |> sum()\n\n`, status: { type: 'none', text: '', @@ -100,6 +101,7 @@ export class IFQLPage extends PureComponent { status={status} suggestions={suggestions} onAnalyze={this.handleAnalyze} + onAppendFrom={this.handleAppendFrom} onChangeScript={this.handleChangeScript} onSubmitScript={this.handleSubmitScript} /> @@ -240,6 +242,13 @@ export class IFQLPage extends PureComponent { .join(', ') } + private handleAppendFrom = (): void => { + const {script} = this.state + const newScript = `${script.trim()}\n\n${builder.NEW_FROM}\n\n` + + this.getASTResponse(newScript) + } + private handleChangeScript = (script: string): void => { this.setState({script}) } diff --git a/ui/test/ifql/components/TimeMachine.test.tsx b/ui/test/ifql/components/TimeMachine.test.tsx index c88306c5f..b32c918c6 100644 --- a/ui/test/ifql/components/TimeMachine.test.tsx +++ b/ui/test/ifql/components/TimeMachine.test.tsx @@ -12,6 +12,7 @@ const setup = () => { onSubmitScript: () => {}, onChangeScript: () => {}, onAnalyze: () => {}, + onAppendFrom: () => {}, } const wrapper = shallow() From 37a5acf5047533f976a61cce824b1e56fae65f7a Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Wed, 16 May 2018 16:30:14 -0700 Subject: [PATCH 25/49] Correct filename --- ui/src/ifql/components/TimeMachine.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/ifql/components/TimeMachine.tsx b/ui/src/ifql/components/TimeMachine.tsx index d601642c0..8119966a7 100644 --- a/ui/src/ifql/components/TimeMachine.tsx +++ b/ui/src/ifql/components/TimeMachine.tsx @@ -3,7 +3,7 @@ import SchemaExplorer from 'src/ifql/components/SchemaExplorer' import BodyBuilder from 'src/ifql/components/BodyBuilder' import TimeMachineEditor from 'src/ifql/components/TimeMachineEditor' import TimeMachineVis from 'src/ifql/components/TimeMachineVis' -import Threesizer from 'src/shared/components/Threesizer/Threesizer' +import Threesizer from 'src/shared/components/threesizer/Threesizer' import { Suggestion, OnChangeScript, From 963ab37fba6e8f5b9e3af4eeb2a6ae8b44c00013 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Wed, 16 May 2018 16:31:48 -0700 Subject: [PATCH 26/49] Moved files --- ui/src/shared/components/{Threesizer => }/Division.tsx | 0 ui/src/shared/components/{Threesizer => }/DivisionHeader.tsx | 0 ui/src/shared/components/{Threesizer => }/DivisionMenu.tsx | 0 ui/src/shared/components/{Threesizer => }/Threesizer.tsx | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename ui/src/shared/components/{Threesizer => }/Division.tsx (100%) rename ui/src/shared/components/{Threesizer => }/DivisionHeader.tsx (100%) rename ui/src/shared/components/{Threesizer => }/DivisionMenu.tsx (100%) rename ui/src/shared/components/{Threesizer => }/Threesizer.tsx (100%) diff --git a/ui/src/shared/components/Threesizer/Division.tsx b/ui/src/shared/components/Division.tsx similarity index 100% rename from ui/src/shared/components/Threesizer/Division.tsx rename to ui/src/shared/components/Division.tsx diff --git a/ui/src/shared/components/Threesizer/DivisionHeader.tsx b/ui/src/shared/components/DivisionHeader.tsx similarity index 100% rename from ui/src/shared/components/Threesizer/DivisionHeader.tsx rename to ui/src/shared/components/DivisionHeader.tsx diff --git a/ui/src/shared/components/Threesizer/DivisionMenu.tsx b/ui/src/shared/components/DivisionMenu.tsx similarity index 100% rename from ui/src/shared/components/Threesizer/DivisionMenu.tsx rename to ui/src/shared/components/DivisionMenu.tsx diff --git a/ui/src/shared/components/Threesizer/Threesizer.tsx b/ui/src/shared/components/Threesizer.tsx similarity index 100% rename from ui/src/shared/components/Threesizer/Threesizer.tsx rename to ui/src/shared/components/Threesizer.tsx From 6d5a123f17f376fa26363861af18079c1a2e779c Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Wed, 16 May 2018 16:33:54 -0700 Subject: [PATCH 27/49] Move threesizer into own dir --- ui/src/shared/components/{ => threesizer}/Division.tsx | 0 ui/src/shared/components/{ => threesizer}/DivisionHeader.tsx | 0 ui/src/shared/components/{ => threesizer}/DivisionMenu.tsx | 0 ui/src/shared/components/{ => threesizer}/Threesizer.tsx | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename ui/src/shared/components/{ => threesizer}/Division.tsx (100%) rename ui/src/shared/components/{ => threesizer}/DivisionHeader.tsx (100%) rename ui/src/shared/components/{ => threesizer}/DivisionMenu.tsx (100%) rename ui/src/shared/components/{ => threesizer}/Threesizer.tsx (100%) diff --git a/ui/src/shared/components/Division.tsx b/ui/src/shared/components/threesizer/Division.tsx similarity index 100% rename from ui/src/shared/components/Division.tsx rename to ui/src/shared/components/threesizer/Division.tsx diff --git a/ui/src/shared/components/DivisionHeader.tsx b/ui/src/shared/components/threesizer/DivisionHeader.tsx similarity index 100% rename from ui/src/shared/components/DivisionHeader.tsx rename to ui/src/shared/components/threesizer/DivisionHeader.tsx diff --git a/ui/src/shared/components/DivisionMenu.tsx b/ui/src/shared/components/threesizer/DivisionMenu.tsx similarity index 100% rename from ui/src/shared/components/DivisionMenu.tsx rename to ui/src/shared/components/threesizer/DivisionMenu.tsx diff --git a/ui/src/shared/components/Threesizer.tsx b/ui/src/shared/components/threesizer/Threesizer.tsx similarity index 100% rename from ui/src/shared/components/Threesizer.tsx rename to ui/src/shared/components/threesizer/Threesizer.tsx From cb3c1004317561491feaba58d68b5e853b71ddad Mon Sep 17 00:00:00 2001 From: Jade McGough Date: Fri, 11 May 2018 17:30:36 -0700 Subject: [PATCH 28/49] Convert ManageSource page and actions to ts --- ui/src/shared/actions/sources.js | 120 ---------- ui/src/shared/actions/sources.ts | 207 ++++++++++++++++++ ui/src/shared/apis/{index.js => index.ts} | 14 +- .../{ManageSources.js => ManageSources.tsx} | 104 ++++----- ui/src/types/notifications.ts | 5 +- ui/src/types/sourcesPage.ts | 0 6 files changed, 267 insertions(+), 183 deletions(-) delete mode 100644 ui/src/shared/actions/sources.js create mode 100644 ui/src/shared/actions/sources.ts rename ui/src/shared/apis/{index.js => index.ts} (95%) rename ui/src/sources/containers/{ManageSources.js => ManageSources.tsx} (58%) create mode 100644 ui/src/types/sourcesPage.ts diff --git a/ui/src/shared/actions/sources.js b/ui/src/shared/actions/sources.js deleted file mode 100644 index f218ae11c..000000000 --- a/ui/src/shared/actions/sources.js +++ /dev/null @@ -1,120 +0,0 @@ -import { - deleteSource, - getSources as getSourcesAJAX, - getKapacitors as getKapacitorsAJAX, - updateKapacitor as updateKapacitorAJAX, - deleteKapacitor as deleteKapacitorAJAX, -} from 'shared/apis' -import {notify} from './notifications' -import {errorThrown} from 'shared/actions/errors' - -import {HTTP_NOT_FOUND} from 'shared/constants' -import { - notifyServerError, - notifyCouldNotRetrieveKapacitors, - notifyCouldNotDeleteKapacitor, -} from 'shared/copy/notifications' - -export const loadSources = sources => ({ - type: 'LOAD_SOURCES', - payload: { - sources, - }, -}) - -export const updateSource = source => ({ - type: 'SOURCE_UPDATED', - payload: { - source, - }, -}) - -export const addSource = source => ({ - type: 'SOURCE_ADDED', - payload: { - source, - }, -}) - -export const fetchKapacitors = (source, kapacitors) => ({ - type: 'LOAD_KAPACITORS', - payload: { - source, - kapacitors, - }, -}) - -export const setActiveKapacitor = kapacitor => ({ - type: 'SET_ACTIVE_KAPACITOR', - payload: { - kapacitor, - }, -}) - -export const deleteKapacitor = kapacitor => ({ - type: 'DELETE_KAPACITOR', - payload: { - kapacitor, - }, -}) - -// Async action creators - -export const removeAndLoadSources = source => async dispatch => { - try { - try { - await deleteSource(source) - } catch (err) { - // A 404 means that either a concurrent write occurred or the source - // passed to this action creator doesn't exist (or is undefined) - if (err.status !== HTTP_NOT_FOUND) { - // eslint-disable-line no-magic-numbers - throw err - } - } - - const { - data: {sources: newSources}, - } = await getSourcesAJAX() - dispatch(loadSources(newSources)) - } catch (err) { - dispatch(notify(notifyServerError())) - } -} - -export const fetchKapacitorsAsync = source => async dispatch => { - try { - const {data} = await getKapacitorsAJAX(source) - dispatch(fetchKapacitors(source, data.kapacitors)) - } catch (err) { - dispatch(notify(notifyCouldNotRetrieveKapacitors(source.id))) - } -} - -export const setActiveKapacitorAsync = kapacitor => async dispatch => { - // eagerly update the redux state - dispatch(setActiveKapacitor(kapacitor)) - const kapacitorPost = {...kapacitor, active: true} - await updateKapacitorAJAX(kapacitorPost) -} - -export const deleteKapacitorAsync = kapacitor => async dispatch => { - try { - await deleteKapacitorAJAX(kapacitor) - dispatch(deleteKapacitor(kapacitor)) - } catch (err) { - dispatch(notify(notifyCouldNotDeleteKapacitor())) - } -} - -export const getSourcesAsync = () => async dispatch => { - try { - const { - data: {sources}, - } = await getSourcesAJAX() - dispatch(loadSources(sources)) - return sources - } catch (error) { - dispatch(errorThrown(error)) - } -} diff --git a/ui/src/shared/actions/sources.ts b/ui/src/shared/actions/sources.ts new file mode 100644 index 000000000..841c0a518 --- /dev/null +++ b/ui/src/shared/actions/sources.ts @@ -0,0 +1,207 @@ +import { + deleteSource, + getSources as getSourcesAJAX, + getKapacitors as getKapacitorsAJAX, + updateKapacitor as updateKapacitorAJAX, + deleteKapacitor as deleteKapacitorAJAX, +} from 'src/shared/apis' +import {notify} from './notifications' +import {errorThrown} from 'src/shared/actions/errors' + +import {HTTP_NOT_FOUND} from 'src/shared/constants' +import { + notifyServerError, + notifyCouldNotRetrieveKapacitors, + notifyCouldNotDeleteKapacitor, +} from 'src/shared/copy/notifications' + +import {Source, Kapacitor} from 'src/types' + +export type Action = ActionLoadSources | ActionUpdateSource + +export type LoadSources = (sources: Source[]) => ActionLoadSources +export interface ActionLoadSources { + type: 'LOAD_SOURCES' + payload: { + sources: Source[] + } +} + +export const loadSources = (sources: Source[]): ActionLoadSources => ({ + type: 'LOAD_SOURCES', + payload: { + sources, + }, +}) + +export type UpdateSource = (source: Source) => ActionUpdateSource +export interface ActionUpdateSource { + type: 'SOURCE_UPDATED' + payload: { + source: Source + } +} + +export const updateSource = (source: Source): ActionUpdateSource => ({ + type: 'SOURCE_UPDATED', + payload: { + source, + }, +}) + +export type AddSource = (source: Source) => ActionAddSource +export interface ActionAddSource { + type: 'SOURCE_ADDED' + payload: { + source: Source + } +} + +export const addSource = (source: Source): ActionAddSource => ({ + type: 'SOURCE_ADDED', + payload: { + source, + }, +}) + +export type FetchKapacitors = ( + source: Source, + kapacitors: Kapacitor[] +) => ActionFetchKapacitors + +export interface ActionFetchKapacitors { + type: 'LOAD_KAPACITORS' + payload: { + source: Source + kapacitors: Kapacitor[] + } +} + +export const fetchKapacitors = ( + source: Source, + kapacitors: Kapacitor[] +): ActionFetchKapacitors => ({ + type: 'LOAD_KAPACITORS', + payload: { + source, + kapacitors, + }, +}) + +export type SetActiveKapacitor = ( + kapacitor: Kapacitor +) => ActionSetActiveKapacitor + +export interface ActionSetActiveKapacitor { + type: 'SET_ACTIVE_KAPACITOR' + payload: { + kapacitor: Kapacitor + } +} + +export const setActiveKapacitor = ( + kapacitor: Kapacitor +): ActionSetActiveKapacitor => ({ + type: 'SET_ACTIVE_KAPACITOR', + payload: { + kapacitor, + }, +}) + +export type DeleteKapacitor = (kapacitor: Kapacitor) => ActionDeleteKapacitor +export interface ActionDeleteKapacitor { + type: 'DELETE_KAPACITOR' + payload: { + kapacitor: Kapacitor + } +} + +export const deleteKapacitor = (kapacitor: Kapacitor) => ({ + type: 'DELETE_KAPACITOR', + payload: { + kapacitor, + }, +}) + +export type RemoveAndLoadSources = ( + source: Source +) => (dispatch) => Promise +// Async action creators +export const removeAndLoadSources = (source: Source) => async ( + dispatch +): Promise => { + try { + try { + await deleteSource(source) + } catch (err) { + // A 404 means that either a concurrent write occurred or the source + // passed to this action creator doesn't exist (or is undefined) + if (err.status !== HTTP_NOT_FOUND) { + // eslint-disable-line no-magic-numbers + throw err + } + } + + const { + data: {sources: newSources}, + } = await getSourcesAJAX() + dispatch(loadSources(newSources)) + } catch (err) { + dispatch(notify(notifyServerError())) + } +} + +export type FetchKapacitorsAsync = ( + source: Source +) => (dispatch) => Promise + +export const fetchKapacitorsAsync = (source: Source) => async ( + dispatch +): Promise => { + try { + const {data} = await getKapacitorsAJAX(source) + dispatch(fetchKapacitors(source, data.kapacitors)) + } catch (err) { + dispatch(notify(notifyCouldNotRetrieveKapacitors(source.id))) + } +} + +export type SetActiveKapacitorAsync = ( + source: Source +) => (dispatch) => Promise + +export const setActiveKapacitorAsync = (kapacitor: Kapacitor) => async ( + dispatch +): Promise => { + // eagerly update the redux state + dispatch(setActiveKapacitor(kapacitor)) + const kapacitorPost = {...kapacitor, active: true} + await updateKapacitorAJAX(kapacitorPost) +} + +export type DeleteKapacitorAsync = ( + source: Source +) => (dispatch) => Promise + +export const deleteKapacitorAsync = (kapacitor: Kapacitor) => async ( + dispatch +): Promise => { + try { + await deleteKapacitorAJAX(kapacitor) + dispatch(deleteKapacitor(kapacitor)) + } catch (err) { + dispatch(notify(notifyCouldNotDeleteKapacitor())) + } +} + +export const getSourcesAsync = () => async (dispatch): Promise => { + try { + const { + data: {sources}, + } = await getSourcesAJAX() + dispatch(loadSources(sources)) + return sources + } catch (error) { + dispatch(errorThrown(error)) + } +} diff --git a/ui/src/shared/apis/index.js b/ui/src/shared/apis/index.ts similarity index 95% rename from ui/src/shared/apis/index.js rename to ui/src/shared/apis/index.ts index 31663aff6..94590db34 100644 --- a/ui/src/shared/apis/index.js +++ b/ui/src/shared/apis/index.ts @@ -1,14 +1,17 @@ -import AJAX from 'utils/ajax' +import AJAX from 'src/utils/ajax' import {AlertTypes} from 'src/kapacitor/constants' +import {Kapacitor} from 'src/types' export function getSources() { return AJAX({ + url: null, resource: 'sources', }) } export function getSource(id) { return AJAX({ + url: null, resource: 'sources', id, }) @@ -16,6 +19,7 @@ export function getSource(id) { export function createSource(attributes) { return AJAX({ + url: null, resource: 'sources', method: 'POST', data: attributes, @@ -123,12 +127,12 @@ export function createKapacitor( export function updateKapacitor({ links, url, - name = 'My Kapacitor', + name = 'My Kaacitor', username, password, active, insecureSkipVerify, -}) { +}: Kapacitor) { return AJAX({ url: links.self, method: 'PATCH', @@ -282,7 +286,7 @@ export function deleteKapacitorTask(kapacitor, id) { return kapacitorProxy(kapacitor, 'DELETE', `/kapacitor/v1/tasks/${id}`, '') } -export function kapacitorProxy(kapacitor, method, path, body) { +export function kapacitorProxy(kapacitor, method, path, body?) { return AJAX({ method, url: kapacitor.links.proxy, @@ -293,7 +297,7 @@ export function kapacitorProxy(kapacitor, method, path, body) { }) } -export const getQueryConfigAndStatus = (url, queries, tempVars) => +export const getQueryConfigAndStatus = (url, queries, tempVars = []) => AJAX({ url, method: 'POST', diff --git a/ui/src/sources/containers/ManageSources.js b/ui/src/sources/containers/ManageSources.tsx similarity index 58% rename from ui/src/sources/containers/ManageSources.js rename to ui/src/sources/containers/ManageSources.tsx index 598b7c109..a823c1e6f 100644 --- a/ui/src/sources/containers/ManageSources.js +++ b/ui/src/sources/containers/ManageSources.tsx @@ -1,41 +1,43 @@ -import React, {Component} from 'react' -import PropTypes from 'prop-types' +import React, {PureComponent} from 'react' import {connect} from 'react-redux' import {bindActionCreators} from 'redux' import {ErrorHandling} from 'src/shared/decorators/errors' -import { - removeAndLoadSources, - fetchKapacitorsAsync, - setActiveKapacitorAsync, - deleteKapacitorAsync, -} from 'shared/actions/sources' -import {notify as notifyAction} from 'shared/actions/notifications' +import * as actions from 'src/shared/actions/sources' +import {notify as notifyAction} from 'src/shared/actions/notifications' -import FancyScrollbar from 'shared/components/FancyScrollbar' -import SourceIndicator from 'shared/components/SourceIndicator' +import FancyScrollbar from 'src/shared/components/FancyScrollbar' +import SourceIndicator from 'src/shared/components/SourceIndicator' import InfluxTable from 'src/sources/components/InfluxTable' import { notifySourceDeleted, notifySourceDeleteFailed, -} from 'shared/copy/notifications' +} from 'src/shared/copy/notifications' -const V_NUMBER = VERSION // eslint-disable-line no-undef +import {Source, NotificationFunc} from 'src/types' + +interface Props { + source: Source + sources: Source[] + notify: NotificationFunc + deleteKapacitor: actions.DeleteKapacitorAsync + fetchKapacitors: actions.FetchKapacitorsAsync + removeAndLoadSources: actions.RemoveAndLoadSources + setActiveKapacitor: actions.SetActiveKapacitorAsync +} + +declare var VERSION: string @ErrorHandling -class ManageSources extends Component { - constructor(props) { - super(props) - } - - componentDidMount() { +class ManageSources extends PureComponent { + public componentDidMount() { this.props.sources.forEach(source => { this.props.fetchKapacitors(source) }) } - componentDidUpdate(prevProps) { + public componentDidUpdate(prevProps: Props) { if (prevProps.sources.length !== this.props.sources.length) { this.props.sources.forEach(source => { this.props.fetchKapacitors(source) @@ -43,22 +45,7 @@ class ManageSources extends Component { } } - handleDeleteSource = source => () => { - const {notify} = this.props - - try { - this.props.removeAndLoadSources(source) - notify(notifySourceDeleted(source.name)) - } catch (e) { - notify(notifySourceDeleteFailed(source.name)) - } - } - - handleSetActiveKapacitor = ({kapacitor}) => { - this.props.setActiveKapacitor(kapacitor) - } - - render() { + public render() { const {sources, source, deleteKapacitor} = this.props return ( @@ -82,30 +69,27 @@ class ManageSources extends Component { handleDeleteSource={this.handleDeleteSource} setActiveKapacitor={this.handleSetActiveKapacitor} /> -

Chronograf Version: {V_NUMBER}

+

Chronograf Version: {VERSION}

) } -} -const {array, func, shape, string} = PropTypes + private handleDeleteSource = (source: Source) => () => { + const {notify} = this.props -ManageSources.propTypes = { - source: shape({ - id: string.isRequired, - links: shape({ - proxy: string.isRequired, - self: string.isRequired, - }), - }), - sources: array, - notify: func.isRequired, - removeAndLoadSources: func.isRequired, - fetchKapacitors: func.isRequired, - setActiveKapacitor: func.isRequired, - deleteKapacitor: func.isRequired, + try { + this.props.removeAndLoadSources(source) + notify(notifySourceDeleted(source.name)) + } catch (e) { + notify(notifySourceDeleteFailed(source.name)) + } + } + + private handleSetActiveKapacitor = ({kapacitor}) => { + this.props.setActiveKapacitor(kapacitor) + } } const mapStateToProps = ({sources}) => ({ @@ -113,10 +97,16 @@ const mapStateToProps = ({sources}) => ({ }) const mapDispatchToProps = dispatch => ({ - removeAndLoadSources: bindActionCreators(removeAndLoadSources, dispatch), - fetchKapacitors: bindActionCreators(fetchKapacitorsAsync, dispatch), - setActiveKapacitor: bindActionCreators(setActiveKapacitorAsync, dispatch), - deleteKapacitor: bindActionCreators(deleteKapacitorAsync, dispatch), + removeAndLoadSources: bindActionCreators( + actions.removeAndLoadSources, + dispatch + ), + fetchKapacitors: bindActionCreators(actions.fetchKapacitorsAsync, dispatch), + setActiveKapacitor: bindActionCreators( + actions.setActiveKapacitorAsync, + dispatch + ), + deleteKapacitor: bindActionCreators(actions.deleteKapacitorAsync, dispatch), notify: bindActionCreators(notifyAction, dispatch), }) diff --git a/ui/src/types/notifications.ts b/ui/src/types/notifications.ts index 789e88924..caaa05605 100644 --- a/ui/src/types/notifications.ts +++ b/ui/src/types/notifications.ts @@ -6,4 +6,7 @@ export interface Notification { message: string } -export type NotificationFunc = () => Notification +interface AdditionalInfo { + [x: string]: string +} +export type NotificationFunc = (info?: AdditionalInfo) => Notification diff --git a/ui/src/types/sourcesPage.ts b/ui/src/types/sourcesPage.ts new file mode 100644 index 000000000..e69de29bb From 89a33a270d6ff461a8f05ad53410d29e8fd29521 Mon Sep 17 00:00:00 2001 From: ebb-tide Date: Thu, 17 May 2018 11:43:23 -0700 Subject: [PATCH 29/49] Add join to available declarationfuncs --- ui/src/ifql/components/BodyBuilder.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/ui/src/ifql/components/BodyBuilder.tsx b/ui/src/ifql/components/BodyBuilder.tsx index 1ba5f1b01..413f8476c 100644 --- a/ui/src/ifql/components/BodyBuilder.tsx +++ b/ui/src/ifql/components/BodyBuilder.tsx @@ -73,8 +73,12 @@ class BodyBuilder extends PureComponent { } private get newDeclarationFuncs(): string[] { - // 'JOIN' only available if there are at least 2 named declarations - return ['from'] + const {body} = this.props + const declarationFunctions = ['from'] + if (body.length > 1) { + declarationFunctions.push('join') + } + return declarationFunctions } private createNewBody = name => { From 77f66c2175b99ef25ab598d2a2702aafe8c56447 Mon Sep 17 00:00:00 2001 From: ebb-tide Date: Thu, 17 May 2018 12:12:35 -0700 Subject: [PATCH 30/49] Add join to funcNames, and use to build declerationFunctions --- ui/src/ifql/components/BodyBuilder.tsx | 7 +++++-- ui/src/ifql/constants/funcNames.ts | 1 + 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/ui/src/ifql/components/BodyBuilder.tsx b/ui/src/ifql/components/BodyBuilder.tsx index 413f8476c..1a6587490 100644 --- a/ui/src/ifql/components/BodyBuilder.tsx +++ b/ui/src/ifql/components/BodyBuilder.tsx @@ -74,9 +74,9 @@ class BodyBuilder extends PureComponent { private get newDeclarationFuncs(): string[] { const {body} = this.props - const declarationFunctions = ['from'] + const declarationFunctions = [funcNames.FROM] if (body.length > 1) { - declarationFunctions.push('join') + declarationFunctions.push(funcNames.JOIN) } return declarationFunctions } @@ -85,6 +85,9 @@ class BodyBuilder extends PureComponent { if (name === funcNames.FROM) { this.props.onAppendFrom() } + if (name === funcNames.JOIN) { + this.props.onAppendJoin() + } } private get funcNames() { diff --git a/ui/src/ifql/constants/funcNames.ts b/ui/src/ifql/constants/funcNames.ts index 9fe675c06..026fa66b4 100644 --- a/ui/src/ifql/constants/funcNames.ts +++ b/ui/src/ifql/constants/funcNames.ts @@ -1,2 +1,3 @@ export const FROM = 'from' export const FILTER = 'filter' +export const JOIN = 'join' From 0522767f6ae1d4ff5dc8263658f865a8b304283a Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Thu, 17 May 2018 12:22:24 -0700 Subject: [PATCH 31/49] Refactor InfluxTable and convert to ts --- ui/src/sources/components/ConnectionLink.tsx | 56 ++++ ui/src/sources/components/InfluxTable.js | 247 ------------------ ui/src/sources/components/InfluxTable.tsx | 71 +++++ ui/src/sources/components/InfluxTableHead.tsx | 28 ++ .../sources/components/InfluxTableHeader.tsx | 47 ++++ ui/src/sources/components/InfluxTableRow.tsx | 98 +++++++ .../sources/components/KapacitorDropdown.tsx | 118 +++++++++ ui/src/sources/constants/index.js | 2 - ui/src/sources/constants/index.ts | 5 + ui/src/sources/containers/ManageSources.tsx | 4 +- ui/src/sources/{index.js => index.ts} | 0 11 files changed, 425 insertions(+), 251 deletions(-) create mode 100644 ui/src/sources/components/ConnectionLink.tsx delete mode 100644 ui/src/sources/components/InfluxTable.js create mode 100644 ui/src/sources/components/InfluxTable.tsx create mode 100644 ui/src/sources/components/InfluxTableHead.tsx create mode 100644 ui/src/sources/components/InfluxTableHeader.tsx create mode 100644 ui/src/sources/components/InfluxTableRow.tsx create mode 100644 ui/src/sources/components/KapacitorDropdown.tsx delete mode 100644 ui/src/sources/constants/index.js create mode 100644 ui/src/sources/constants/index.ts rename ui/src/sources/{index.js => index.ts} (100%) diff --git a/ui/src/sources/components/ConnectionLink.tsx b/ui/src/sources/components/ConnectionLink.tsx new file mode 100644 index 000000000..3d6807e00 --- /dev/null +++ b/ui/src/sources/components/ConnectionLink.tsx @@ -0,0 +1,56 @@ +import React, {PureComponent} from 'react' +import {Link} from 'react-router' +import Authorized, {EDITOR_ROLE} from 'src/auth/Authorized' + +import {Source} from 'src/types' + +interface Props { + source: Source + currentSource: Source +} + +class ConnectionLink extends PureComponent { + public render() { + const {source} = this.props + return ( +
+ {source.name}} + > + + {source.name} + {this.default} + + +
+ ) + } + + private get className(): string { + if (this.isCurrentSource) { + return 'link-success' + } + + return '' + } + + private get default(): string { + const {source} = this.props + if (source.default) { + return ' (Default)' + } + + return '' + } + + private get isCurrentSource(): boolean { + const {source, currentSource} = this.props + return source.id === currentSource.id + } +} + +export default ConnectionLink diff --git a/ui/src/sources/components/InfluxTable.js b/ui/src/sources/components/InfluxTable.js deleted file mode 100644 index 6aa79be84..000000000 --- a/ui/src/sources/components/InfluxTable.js +++ /dev/null @@ -1,247 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' -import {Link, withRouter} from 'react-router' -import {connect} from 'react-redux' - -import Authorized, {EDITOR_ROLE} from 'src/auth/Authorized' - -import Dropdown from 'shared/components/Dropdown' -import QuestionMarkTooltip from 'shared/components/QuestionMarkTooltip' -import ConfirmButton from 'shared/components/ConfirmButton' - -const kapacitorDropdown = ( - kapacitors, - source, - router, - setActiveKapacitor, - handleDeleteKapacitor -) => { - if (!kapacitors || kapacitors.length === 0) { - return ( - - - Add Kapacitor Connection - - - ) - } - const kapacitorItems = kapacitors.map(k => { - return { - text: k.name, - resource: `/sources/${source.id}/kapacitors/${k.id}`, - kapacitor: k, - } - }) - - const activeKapacitor = kapacitors.find(k => k.active) - - let selected = '' - if (activeKapacitor) { - selected = activeKapacitor.name - } else { - selected = kapacitorItems[0].text - } - - const unauthorizedDropdown = ( -
{selected}
- ) - - return ( - - { - router.push(`${item.resource}/edit`) - }, - }, - { - icon: 'trash', - text: 'delete', - handler: item => { - handleDeleteKapacitor(item.kapacitor) - }, - confirmable: true, - }, - ]} - selected={selected} - /> - - ) -} - -const InfluxTable = ({ - source, - router, - sources, - location, - setActiveKapacitor, - handleDeleteSource, - handleDeleteKapacitor, - isUsingAuth, - me, -}) => { - return ( -
-
-
-
-

- {isUsingAuth ? ( - - Connections for {me.currentOrganization.name} - - ) : ( - Connections - )} -

- - - Add Connection - - -
-
- - - - - - - - - {sources.map(s => { - return ( - - - - - - - ) - })} - -
- InfluxDB Connection - - Kapacitor Connection{' '} - Kapacitor Connections are
scoped per InfluxDB Connection.
Only one can be active at a time.

' - } - /> -
- {s.id === source.id ? ( -
- Connected -
- ) : ( - - Connect - - )} -
-
- {s.name} - } - > - - {s.name} - {s.default ? ' (Default)' : null} - - -
- {s.url} -
- - - - - {kapacitorDropdown( - s.kapacitors, - s, - router, - setActiveKapacitor, - handleDeleteKapacitor - )} -
-
-
-
-
- ) -} - -const {array, bool, func, shape, string} = PropTypes - -InfluxTable.propTypes = { - handleDeleteSource: func.isRequired, - location: shape({ - pathname: string.isRequired, - }).isRequired, - router: PropTypes.shape({ - push: PropTypes.func.isRequired, - }).isRequired, - source: shape({ - id: string.isRequired, - links: shape({ - proxy: string.isRequired, - self: string.isRequired, - }), - }), - sources: array.isRequired, - setActiveKapacitor: func.isRequired, - handleDeleteKapacitor: func.isRequired, - me: shape({ - currentOrganization: shape({ - id: string.isRequired, - name: string.isRequired, - }), - }), - isUsingAuth: bool, -} - -const mapStateToProps = ({auth: {isUsingAuth, me}}) => ({isUsingAuth, me}) - -export default connect(mapStateToProps)(withRouter(InfluxTable)) diff --git a/ui/src/sources/components/InfluxTable.tsx b/ui/src/sources/components/InfluxTable.tsx new file mode 100644 index 000000000..09e549992 --- /dev/null +++ b/ui/src/sources/components/InfluxTable.tsx @@ -0,0 +1,71 @@ +import React, {PureComponent} from 'react' +import {connect} from 'react-redux' + +import {SetActiveKapacitor, DeleteKapacitor} from 'src/shared/actions/sources' + +import InfluxTableHead from 'src/sources/components/InfluxTableHead' +import InfluxTableHeader from 'src/sources/components/InfluxTableHeader' +import InfluxTableRow from 'src/sources/components/InfluxTableRow' + +import {Source, Me} from 'src/types' + +interface Props { + me: Me + source: Source + sources: Source[] + isUsingAuth: boolean + deleteKapacitor: DeleteKapacitor + setActiveKapacitor: SetActiveKapacitor + onDeleteSource: (source: Source) => () => void +} + +class InfluxTable extends PureComponent { + public render() { + const { + source, + sources, + setActiveKapacitor, + onDeleteSource, + deleteKapacitor, + isUsingAuth, + me, + } = this.props + + return ( +
+
+
+ +
+ + + + {sources.map(s => { + return ( + + ) + })} + +
+
+
+
+
+ ) + } +} + +const mapStateToProps = ({auth: {isUsingAuth, me}}) => ({isUsingAuth, me}) + +export default connect(mapStateToProps)(InfluxTable) diff --git a/ui/src/sources/components/InfluxTableHead.tsx b/ui/src/sources/components/InfluxTableHead.tsx new file mode 100644 index 000000000..7f80b3b03 --- /dev/null +++ b/ui/src/sources/components/InfluxTableHead.tsx @@ -0,0 +1,28 @@ +import React, {SFC, ReactElement} from 'react' + +import QuestionMarkTooltip from 'src/shared/components/QuestionMarkTooltip' + +import {KAPACITOR_TOOLTIP_COPY} from 'src/sources/constants' + +const InfluxTableHead: SFC<{}> = (): ReactElement< + HTMLTableHeaderCellElement +> => { + return ( + + + + InfluxDB Connection + + + Kapacitor Connection + + + + + ) +} + +export default InfluxTableHead diff --git a/ui/src/sources/components/InfluxTableHeader.tsx b/ui/src/sources/components/InfluxTableHeader.tsx new file mode 100644 index 000000000..ede002de0 --- /dev/null +++ b/ui/src/sources/components/InfluxTableHeader.tsx @@ -0,0 +1,47 @@ +import React, {PureComponent, ReactElement} from 'react' +import {Link} from 'react-router' + +import Authorized, {EDITOR_ROLE} from 'src/auth/Authorized' + +import {Me, Source} from 'src/types' + +interface Props { + me: Me + source: Source + isUsingAuth: boolean +} + +class InfluxTableHeader extends PureComponent { + public render() { + const {source} = this.props + + return ( +
+

{this.title}

+ + + Add Connection + + +
+ ) + } + + private get title(): ReactElement { + const {isUsingAuth, me} = this.props + if (isUsingAuth) { + return ( + + Connections for {me.currentOrganization.name} + + ) + } + + return Connections + } +} + +export default InfluxTableHeader diff --git a/ui/src/sources/components/InfluxTableRow.tsx b/ui/src/sources/components/InfluxTableRow.tsx new file mode 100644 index 000000000..c0f6a0c74 --- /dev/null +++ b/ui/src/sources/components/InfluxTableRow.tsx @@ -0,0 +1,98 @@ +import React, {PureComponent, ReactElement} from 'react' +import {Link} from 'react-router' + +import * as actions from 'src/shared/actions/sources' + +import Authorized, {EDITOR_ROLE} from 'src/auth/Authorized' +import ConfirmButton from 'src/shared/components/ConfirmButton' +import KapacitorDropdown from 'src/sources/components/KapacitorDropdown' +import ConnectionLink from 'src/sources/components/ConnectionLink' + +import {Source} from 'src/types' + +interface Props { + source: Source + currentSource: Source + onDeleteSource: (source: Source) => void + setActiveKapacitor: actions.SetActiveKapacitor + deleteKapacitor: actions.DeleteKapacitor +} + +class InfluxTableRow extends PureComponent { + public render() { + const { + source, + currentSource, + setActiveKapacitor, + deleteKapacitor, + } = this.props + + return ( + + {this.connectButton} + + + {source.url} + + + + + + + + + + + ) + } + + private handleDeleteSource = (): void => { + this.props.onDeleteSource(this.props.source) + } + + private get connectButton(): ReactElement { + const {source} = this.props + if (this.isCurrentSource) { + return ( +
+ Connected +
+ ) + } + + return ( + + Connect + + ) + } + + private get className(): string { + if (this.isCurrentSource) { + return 'hightlight' + } + + return '' + } + + private get isCurrentSource(): boolean { + const {source, currentSource} = this.props + return source.id === currentSource.id + } +} + +export default InfluxTableRow diff --git a/ui/src/sources/components/KapacitorDropdown.tsx b/ui/src/sources/components/KapacitorDropdown.tsx new file mode 100644 index 000000000..6451f0acd --- /dev/null +++ b/ui/src/sources/components/KapacitorDropdown.tsx @@ -0,0 +1,118 @@ +import React, {PureComponent, ReactElement} from 'react' +import {Link, withRouter, RouteComponentProps} from 'react-router' + +import Dropdown from 'src/shared/components/Dropdown' +import Authorized, {EDITOR_ROLE} from 'src/auth/Authorized' +import {Source, Kapacitor} from 'src/types' +import {SetActiveKapacitor} from 'src/shared/actions/sources' + +interface Props { + source: Source + kapacitors: Kapacitor[] + setActiveKapacitor: SetActiveKapacitor + deleteKapacitor: (Kapacitor: Kapacitor) => void +} + +interface KapacitorItem { + text: string + resource: string + kapacitor: Kapacitor +} + +class KapacitorDropdown extends PureComponent< + Props & RouteComponentProps +> { + public render() { + const {source, router, setActiveKapacitor, deleteKapacitor} = this.props + + if (this.isKapacitorsEmpty) { + return ( + + + Add Kapacitor Connection + + + ) + } + + return ( + + { + router.push(`${item.resource}/edit`) + }, + }, + { + icon: 'trash', + text: 'delete', + handler: item => { + deleteKapacitor(item.kapacitor) + }, + confirmable: true, + }, + ]} + selected={this.selected} + /> + + ) + } + + private get UnauthorizedDropdown(): ReactElement { + return ( +
{this.selected}
+ ) + } + + private get isKapacitorsEmpty(): boolean { + const {kapacitors} = this.props + return !kapacitors || kapacitors.length === 0 + } + + private get kapacitorItems(): KapacitorItem[] { + const {kapacitors, source} = this.props + + return kapacitors.map(k => { + return { + text: k.name, + resource: `/sources/${source.id}/kapacitors/${k.id}`, + kapacitor: k, + } + }) + } + + private get activeKapacitor(): Kapacitor { + return this.props.kapacitors.find(k => k.active) + } + + private get selected(): string { + let selected = '' + if (this.activeKapacitor) { + selected = this.activeKapacitor.name + } else { + selected = this.kapacitorItems[0].text + } + + return selected + } +} + +export default withRouter(KapacitorDropdown) diff --git a/ui/src/sources/constants/index.js b/ui/src/sources/constants/index.js deleted file mode 100644 index d98cdfbd2..000000000 --- a/ui/src/sources/constants/index.js +++ /dev/null @@ -1,2 +0,0 @@ -export const REQUIRED_ROLE_COPY = - 'The minimum Role a user must have
in order to access this source.' diff --git a/ui/src/sources/constants/index.ts b/ui/src/sources/constants/index.ts new file mode 100644 index 000000000..8e2b6e87f --- /dev/null +++ b/ui/src/sources/constants/index.ts @@ -0,0 +1,5 @@ +export const REQUIRED_ROLE_COPY = + 'The minimum Role a user must have
in order to access this source.' + +export const KAPACITOR_TOOLTIP_COPY = + '

Kapacitor Connections are
scoped per InfluxDB Connection.
Only one can be active at a time.

' diff --git a/ui/src/sources/containers/ManageSources.tsx b/ui/src/sources/containers/ManageSources.tsx index a823c1e6f..41d42ca56 100644 --- a/ui/src/sources/containers/ManageSources.tsx +++ b/ui/src/sources/containers/ManageSources.tsx @@ -65,8 +65,8 @@ class ManageSources extends PureComponent {

Chronograf Version: {VERSION}

diff --git a/ui/src/sources/index.js b/ui/src/sources/index.ts similarity index 100% rename from ui/src/sources/index.js rename to ui/src/sources/index.ts From b9e270692c2509ccbb9e09e350ce80656f81aa99 Mon Sep 17 00:00:00 2001 From: ebb-tide Date: Thu, 17 May 2018 12:46:05 -0700 Subject: [PATCH 32/49] Add append handlers for join function --- ui/src/ifql/components/BodyBuilder.tsx | 1 + ui/src/ifql/components/TimeMachine.tsx | 3 +++ ui/src/ifql/constants/builder.ts | 1 + ui/src/ifql/containers/IFQLPage.tsx | 8 ++++++++ ui/test/ifql/components/TimeMachine.test.tsx | 1 + 5 files changed, 14 insertions(+) diff --git a/ui/src/ifql/components/BodyBuilder.tsx b/ui/src/ifql/components/BodyBuilder.tsx index 1a6587490..0e733e54a 100644 --- a/ui/src/ifql/components/BodyBuilder.tsx +++ b/ui/src/ifql/components/BodyBuilder.tsx @@ -12,6 +12,7 @@ interface Props { body: Body[] suggestions: Suggestion[] onAppendFrom: () => void + onAppendJoin: () => void } interface Body extends FlatBody { diff --git a/ui/src/ifql/components/TimeMachine.tsx b/ui/src/ifql/components/TimeMachine.tsx index 8119966a7..8e56d0e40 100644 --- a/ui/src/ifql/components/TimeMachine.tsx +++ b/ui/src/ifql/components/TimeMachine.tsx @@ -23,6 +23,7 @@ interface Props { onChangeScript: OnChangeScript onSubmitScript: OnSubmitScript onAppendFrom: () => void + onAppendJoin: () => void onAnalyze: () => void } @@ -73,6 +74,7 @@ class TimeMachine extends PureComponent { onAnalyze, suggestions, onAppendFrom, + onAppendJoin, onChangeScript, onSubmitScript, } = this.props @@ -119,6 +121,7 @@ class TimeMachine extends PureComponent { body={body} suggestions={suggestions} onAppendFrom={onAppendFrom} + onAppendJoin={onAppendJoin} /> ), }, diff --git a/ui/src/ifql/constants/builder.ts b/ui/src/ifql/constants/builder.ts index 752966cb9..f57aba1c2 100644 --- a/ui/src/ifql/constants/builder.ts +++ b/ui/src/ifql/constants/builder.ts @@ -1 +1,2 @@ export const NEW_FROM = `from(db: "pick a db")\n\t|> filter(fn: (r) => r.tag == "value")\n\t|> range(start: -1m)` +export const NEW_JOIN = `join(tables: {table1:table1, table2:table2}, on:["host"], fn: (tables) => tables.table1["_value"] + tables.table2["_value"])` diff --git a/ui/src/ifql/containers/IFQLPage.tsx b/ui/src/ifql/containers/IFQLPage.tsx index 5981b9c89..0df31bcb7 100644 --- a/ui/src/ifql/containers/IFQLPage.tsx +++ b/ui/src/ifql/containers/IFQLPage.tsx @@ -102,6 +102,7 @@ export class IFQLPage extends PureComponent { suggestions={suggestions} onAnalyze={this.handleAnalyze} onAppendFrom={this.handleAppendFrom} + onAppendJoin={this.handleAppendJoin} onChangeScript={this.handleChangeScript} onSubmitScript={this.handleSubmitScript} /> @@ -249,6 +250,13 @@ export class IFQLPage extends PureComponent { this.getASTResponse(newScript) } + private handleAppendJoin = (): void => { + const {script} = this.state + const newScript = `${script.trim()}\n\n${builder.NEW_JOIN}\n\n` + + this.getASTResponse(newScript) + } + private handleChangeScript = (script: string): void => { this.setState({script}) } diff --git a/ui/test/ifql/components/TimeMachine.test.tsx b/ui/test/ifql/components/TimeMachine.test.tsx index b32c918c6..6d32cec0c 100644 --- a/ui/test/ifql/components/TimeMachine.test.tsx +++ b/ui/test/ifql/components/TimeMachine.test.tsx @@ -13,6 +13,7 @@ const setup = () => { onChangeScript: () => {}, onAnalyze: () => {}, onAppendFrom: () => {}, + onAppendJoin: () => {}, } const wrapper = shallow() From d249fd6d9bd61b4c16bfe9e267a922c1ed88e168 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Thu, 17 May 2018 13:27:45 -0700 Subject: [PATCH 33/49] WIP Introduce LOAD_SERVICES --- ui/src/shared/actions/sources.ts | 35 ++++++- .../reducers/{sources.js => sources.ts} | 27 +++++- ui/src/sources/components/InfluxTableHead.tsx | 1 + ui/src/sources/components/InfluxTableRow.tsx | 1 + ui/src/types/services.ts | 14 +++ ui/src/types/sources.ts | 1 + ui/test/resources.ts | 14 +++ ui/test/shared/reducers/sources.test.js | 58 ------------ ui/test/shared/reducers/sources.test.ts | 92 +++++++++++++++++++ 9 files changed, 179 insertions(+), 64 deletions(-) rename ui/src/shared/reducers/{sources.js => sources.ts} (73%) create mode 100644 ui/src/types/services.ts delete mode 100644 ui/test/shared/reducers/sources.test.js create mode 100644 ui/test/shared/reducers/sources.test.ts diff --git a/ui/src/shared/actions/sources.ts b/ui/src/shared/actions/sources.ts index 841c0a518..b840d7f6d 100644 --- a/ui/src/shared/actions/sources.ts +++ b/ui/src/shared/actions/sources.ts @@ -17,8 +17,41 @@ import { import {Source, Kapacitor} from 'src/types' -export type Action = ActionLoadSources | ActionUpdateSource +export type Action = + | ActionLoadSources + | ActionUpdateSource + | ActionAddSource + | ActionFetchKapacitors + | ActionSetActiveKapacitor + | ActionDeleteKapacitor + | ActionLoadServices +// Load Services +export type LoadServices = ( + source: Source, + services: Service[] +) => ActionLoadServices + +export interface ActionLoadServices { + type: 'LOAD_SERVICES' + payload: { + source: Source + services: Service[] + } +} + +export const loadServices = ( + source: Source, + services: Service[] +): ActionLoadServices => ({ + type: 'LOAD_SERVICES', + payload: { + source, + services, + }, +}) + +// Load Sources export type LoadSources = (sources: Source[]) => ActionLoadSources export interface ActionLoadSources { type: 'LOAD_SOURCES' diff --git a/ui/src/shared/reducers/sources.js b/ui/src/shared/reducers/sources.ts similarity index 73% rename from ui/src/shared/reducers/sources.js rename to ui/src/shared/reducers/sources.ts index cbed0e8c1..b616f5bda 100644 --- a/ui/src/shared/reducers/sources.js +++ b/ui/src/shared/reducers/sources.ts @@ -1,15 +1,28 @@ import _ from 'lodash' +import {Source, Kapacitor} from 'src/types' +import {Action} from 'src/shared/actions/sources' -const getInitialState = () => [] +export const initialState: Source[] = [] -const initialState = getInitialState() - -const sourcesReducer = (state = initialState, action) => { +const sourcesReducer = (state = initialState, action: Action): Source[] => { switch (action.type) { case 'LOAD_SOURCES': { return action.payload.sources } + case 'LOAD_SERVICES': { + const {services} = action.payload + const newState = state.map(source => { + if (source.id === action.payload.source.id) { + return {...source, services} + } + + return {...source} + }) + + return newState + } + case 'SOURCE_UPDATED': { const {source} = action.payload const updatedIndex = state.findIndex(s => s.id === source.id) @@ -59,7 +72,11 @@ const sourcesReducer = (state = initialState, action) => { const {kapacitor} = action.payload const updatedSources = _.cloneDeep(state) updatedSources.forEach(source => { - const index = _.findIndex(source.kapacitors, k => k.id === kapacitor.id) + const index = _.findIndex( + source.kapacitors, + k => k.id === kapacitor.id + ) + if (index >= 0) { source.kapacitors.splice(index, 1) } diff --git a/ui/src/sources/components/InfluxTableHead.tsx b/ui/src/sources/components/InfluxTableHead.tsx index 7f80b3b03..2d0624f07 100644 --- a/ui/src/sources/components/InfluxTableHead.tsx +++ b/ui/src/sources/components/InfluxTableHead.tsx @@ -13,6 +13,7 @@ const InfluxTableHead: SFC<{}> = (): ReactElement< InfluxDB Connection + Services Connection Kapacitor Connection { /> + Services Connection { - it('can correctly show default sources when adding a source', () => { - let state = [] - - state = reducer( - state, - addSource({ - id: '1', - default: true, - }) - ) - - state = reducer( - state, - addSource({ - id: '2', - default: true, - }) - ) - - expect(state.filter(s => s.default).length).toBe(1) - }) - - it('can correctly show default sources when updating a source', () => { - let state = [] - - state = reducer( - state, - addSource({ - id: '1', - default: true, - }) - ) - - state = reducer( - state, - addSource({ - id: '2', - default: true, - }) - ) - - state = reducer( - state, - updateSource({ - id: '1', - default: true, - }) - ) - - expect(state.find(({id}) => id === '1').default).toBe(true) - expect(state.find(({id}) => id === '2').default).toBe(false) - }) -}) diff --git a/ui/test/shared/reducers/sources.test.ts b/ui/test/shared/reducers/sources.test.ts new file mode 100644 index 000000000..e6325ee36 --- /dev/null +++ b/ui/test/shared/reducers/sources.test.ts @@ -0,0 +1,92 @@ +import reducer, {initialState} from 'src/shared/reducers/sources' + +import { + updateSource, + addSource, + loadServices, + loadSources, +} from 'src/shared/actions/sources' + +import {source, service} from 'test/resources' + +describe('Shared.Reducers.sources', () => { + it('can LOAD_SOURCES', () => { + const expected = [{...source, id: '1'}] + const actual = reducer(initialState, loadSources(expected)) + + expect(actual).toEqual(expected) + }) + + describe('ADD_SOURCES', () => { + it('can ADD_SOURCES', () => { + let state = [] + + state = reducer( + state, + addSource({ + ...source, + id: '1', + default: true, + }) + ) + + state = reducer( + state, + addSource({ + ...source, + id: '2', + default: true, + }) + ) + + expect(state.filter(s => s.default).length).toBe(1) + }) + + it('can correctly show default sources when updating a source', () => { + let state = [] + + state = reducer( + initialState, + addSource({ + ...source, + id: '1', + default: true, + }) + ) + + state = reducer( + state, + addSource({ + ...source, + id: '2', + default: true, + }) + ) + + state = reducer( + state, + updateSource({ + ...source, + id: '1', + default: true, + }) + ) + + expect(state.find(({id}) => id === '1').default).toBe(true) + expect(state.find(({id}) => id === '2').default).toBe(false) + }) + }) + + describe('LOAD_SERVICES', () => { + it('correctly loads the services', () => { + const s1 = {...service, id: '1'} + const s2 = {...service, id: '2'} + + const expected = [s1, s2] + const state = reducer(initialState, loadSources([source])) + const actual = reducer(state, loadServices(source, expected)) + + expect(actual[0].services).toEqual(expected) + }) + }) +}) From 06b63a70ab223a7b78f6407eca4cecac3df207f8 Mon Sep 17 00:00:00 2001 From: ebb-tide Date: Thu, 17 May 2018 14:07:22 -0700 Subject: [PATCH 34/49] Improve default join query --- ui/src/ifql/constants/builder.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/ifql/constants/builder.ts b/ui/src/ifql/constants/builder.ts index f57aba1c2..0c85dfad5 100644 --- a/ui/src/ifql/constants/builder.ts +++ b/ui/src/ifql/constants/builder.ts @@ -1,2 +1,2 @@ export const NEW_FROM = `from(db: "pick a db")\n\t|> filter(fn: (r) => r.tag == "value")\n\t|> range(start: -1m)` -export const NEW_JOIN = `join(tables: {table1:table1, table2:table2}, on:["host"], fn: (tables) => tables.table1["_value"] + tables.table2["_value"])` +export const NEW_JOIN = `join(tables: {fil:fil, tele:tele}, on:["host"], fn: (tables) => tables.fil["_value"] + tables.tele["_value"])` From a8e51b2da2c7f84d1e4fba6749faf17a04cfaeed Mon Sep 17 00:00:00 2001 From: ebb-tide Date: Thu, 17 May 2018 14:15:15 -0700 Subject: [PATCH 35/49] Add temporary overrides to suggestions --- ui/src/ifql/containers/IFQLPage.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ui/src/ifql/containers/IFQLPage.tsx b/ui/src/ifql/containers/IFQLPage.tsx index 0df31bcb7..fef4d6bf2 100644 --- a/ui/src/ifql/containers/IFQLPage.tsx +++ b/ui/src/ifql/containers/IFQLPage.tsx @@ -380,6 +380,11 @@ export class IFQLPage extends PureComponent { try { const ast = await getAST({url: links.ast, body: script}) + const suggs = this.state.suggestions + suggs[17] = { + name: 'join', + params: {tables: 'array', on: 'string', fn: 'function'}, + } const body = bodyNodes(ast, this.state.suggestions) const status = {type: 'success', text: ''} this.setState({ast, script, body, status}) From 22251a782f7ad9a88fb2fe11238128f2ad07685a Mon Sep 17 00:00:00 2001 From: ebb-tide Date: Thu, 17 May 2018 14:50:55 -0700 Subject: [PATCH 36/49] Override suggestions temporarily --- ui/src/ifql/containers/IFQLPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/ifql/containers/IFQLPage.tsx b/ui/src/ifql/containers/IFQLPage.tsx index fef4d6bf2..347e9af9e 100644 --- a/ui/src/ifql/containers/IFQLPage.tsx +++ b/ui/src/ifql/containers/IFQLPage.tsx @@ -385,7 +385,7 @@ export class IFQLPage extends PureComponent { name: 'join', params: {tables: 'array', on: 'string', fn: 'function'}, } - const body = bodyNodes(ast, this.state.suggestions) + const body = bodyNodes(ast, suggs) const status = {type: 'success', text: ''} this.setState({ast, script, body, status}) } catch (error) { From 45a981911d3782a38cc7e84c00e0e16e553a7c99 Mon Sep 17 00:00:00 2001 From: Chris Goller Date: Thu, 17 May 2018 19:10:54 -0500 Subject: [PATCH 37/49] feat(server/services): add source id and source link to all services --- integrations/server_test.go | 4 +++- server/services.go | 12 ++++++++---- server/sources_test.go | 6 ++++-- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/integrations/server_test.go b/integrations/server_test.go index e7ce7995e..77748efe3 100644 --- a/integrations/server_test.go +++ b/integrations/server_test.go @@ -108,6 +108,7 @@ func TestServer(t *testing.T) { "links": { "self": "/chronograf/v1/sources/5000", "kapacitors": "/chronograf/v1/sources/5000/kapacitors", + "services": "/chronograf/v1/sources/5000/services", "proxy": "/chronograf/v1/sources/5000/proxy", "queries": "/chronograf/v1/sources/5000/queries", "write": "/chronograf/v1/sources/5000/write", @@ -296,6 +297,7 @@ func TestServer(t *testing.T) { "links": { "self": "/chronograf/v1/sources/5000", "kapacitors": "/chronograf/v1/sources/5000/kapacitors", + "services": "/chronograf/v1/sources/5000/services", "proxy": "/chronograf/v1/sources/5000/proxy", "queries": "/chronograf/v1/sources/5000/queries", "write": "/chronograf/v1/sources/5000/write", @@ -2943,7 +2945,7 @@ func TestServer(t *testing.T) { serverURL := fmt.Sprintf("http://%v:%v%v", host, port, tt.args.path) // Wait for the server to come online - timeout := time.Now().Add(5 * time.Second) + timeout := time.Now().Add(30 * time.Second) for { _, err := http.Get(serverURL + "/swagger.json") if err == nil { diff --git a/server/services.go b/server/services.go index 62aa8d8d4..95e58eb1f 100644 --- a/server/services.go +++ b/server/services.go @@ -45,12 +45,14 @@ func (p *postServiceRequest) Valid(defaultOrgID string) error { } type serviceLinks struct { - Proxy string `json:"proxy"` // URL location of proxy endpoint for this source - Self string `json:"self"` // Self link mapping to this resource + Proxy string `json:"proxy"` // URL location of proxy endpoint for this source + Self string `json:"self"` // Self link mapping to this resource + Source string `json:"source"` // URL location of the parent source } type service struct { ID int `json:"id,string"` // Unique identifier representing a service instance. + SrcID int `json:"sourceID,string"` // SrcID of the data source Name string `json:"name"` // User facing name of service instance. URL string `json:"url"` // URL for the service backend (e.g. http://localhost:9092) Username string `json:"username,omitempty"` // Username for authentication to service @@ -118,14 +120,16 @@ func newService(srv chronograf.Server) service { httpAPISrcs := "/chronograf/v1/sources" return service{ ID: srv.ID, + SrcID: srv.SrcID, Name: srv.Name, Username: srv.Username, URL: srv.URL, InsecureSkipVerify: srv.InsecureSkipVerify, Type: srv.Type, Links: serviceLinks{ - Self: fmt.Sprintf("%s/%d/services/%d", httpAPISrcs, srv.SrcID, srv.ID), - Proxy: fmt.Sprintf("%s/%d/services/%d/proxy", httpAPISrcs, srv.SrcID, srv.ID), + Self: fmt.Sprintf("%s/%d/services/%d", httpAPISrcs, srv.SrcID, srv.ID), + Source: fmt.Sprintf("%s/%d", httpAPISrcs, srv.SrcID), + Proxy: fmt.Sprintf("%s/%d/services/%d/proxy", httpAPISrcs, srv.SrcID, srv.ID), }, } } diff --git a/server/sources_test.go b/server/sources_test.go index 3030e9965..a86f58a78 100644 --- a/server/sources_test.go +++ b/server/sources_test.go @@ -175,6 +175,7 @@ func Test_newSourceResponse(t *testing.T) { }, Links: sourceLinks{ Self: "/chronograf/v1/sources/1", + Services: "/chronograf/v1/sources/1/services", Proxy: "/chronograf/v1/sources/1/proxy", Queries: "/chronograf/v1/sources/1/queries", Write: "/chronograf/v1/sources/1/write", @@ -201,6 +202,7 @@ func Test_newSourceResponse(t *testing.T) { Links: sourceLinks{ Self: "/chronograf/v1/sources/1", Proxy: "/chronograf/v1/sources/1/proxy", + Services: "/chronograf/v1/sources/1/services", Queries: "/chronograf/v1/sources/1/queries", Write: "/chronograf/v1/sources/1/write", Kapacitors: "/chronograf/v1/sources/1/kapacitors", @@ -440,7 +442,7 @@ func TestService_SourcesID(t *testing.T) { ID: "1", wantStatusCode: 200, wantContentType: "application/json", - wantBody: `{"id":"1","name":"","url":"","default":false,"telegraf":"telegraf","organization":"","defaultRP":"","links":{"self":"/chronograf/v1/sources/1","kapacitors":"/chronograf/v1/sources/1/kapacitors","proxy":"/chronograf/v1/sources/1/proxy","queries":"/chronograf/v1/sources/1/queries","write":"/chronograf/v1/sources/1/write","permissions":"/chronograf/v1/sources/1/permissions","users":"/chronograf/v1/sources/1/users","databases":"/chronograf/v1/sources/1/dbs","annotations":"/chronograf/v1/sources/1/annotations","health":"/chronograf/v1/sources/1/health"}} + wantBody: `{"id":"1","name":"","url":"","default":false,"telegraf":"telegraf","organization":"","defaultRP":"","links":{"self":"/chronograf/v1/sources/1","kapacitors":"/chronograf/v1/sources/1/kapacitors","services":"/chronograf/v1/sources/1/services","proxy":"/chronograf/v1/sources/1/proxy","queries":"/chronograf/v1/sources/1/queries","write":"/chronograf/v1/sources/1/write","permissions":"/chronograf/v1/sources/1/permissions","users":"/chronograf/v1/sources/1/users","databases":"/chronograf/v1/sources/1/dbs","annotations":"/chronograf/v1/sources/1/annotations","health":"/chronograf/v1/sources/1/health"}} `, }, } @@ -530,7 +532,7 @@ func TestService_UpdateSource(t *testing.T) { wantStatusCode: 200, wantContentType: "application/json", wantBody: func(url string) string { - return fmt.Sprintf(`{"id":"1","name":"marty","type":"influx","username":"bob","url":"%s","metaUrl":"http://murl","default":false,"telegraf":"murlin","organization":"1337","defaultRP":"pineapple","links":{"self":"/chronograf/v1/sources/1","kapacitors":"/chronograf/v1/sources/1/kapacitors","proxy":"/chronograf/v1/sources/1/proxy","queries":"/chronograf/v1/sources/1/queries","write":"/chronograf/v1/sources/1/write","permissions":"/chronograf/v1/sources/1/permissions","users":"/chronograf/v1/sources/1/users","databases":"/chronograf/v1/sources/1/dbs","annotations":"/chronograf/v1/sources/1/annotations","health":"/chronograf/v1/sources/1/health"}} + return fmt.Sprintf(`{"id":"1","name":"marty","type":"influx","username":"bob","url":"%s","metaUrl":"http://murl","default":false,"telegraf":"murlin","organization":"1337","defaultRP":"pineapple","links":{"self":"/chronograf/v1/sources/1","kapacitors":"/chronograf/v1/sources/1/kapacitors","services":"/chronograf/v1/sources/1/services","proxy":"/chronograf/v1/sources/1/proxy","queries":"/chronograf/v1/sources/1/queries","write":"/chronograf/v1/sources/1/write","permissions":"/chronograf/v1/sources/1/permissions","users":"/chronograf/v1/sources/1/users","databases":"/chronograf/v1/sources/1/dbs","annotations":"/chronograf/v1/sources/1/annotations","health":"/chronograf/v1/sources/1/health"}} `, url) }, }, From 484a05319c92777d669ccbd4e80feb966234f17a Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Thu, 17 May 2018 17:15:48 -0700 Subject: [PATCH 38/49] Give services their own namespace in app state --- ui/src/shared/actions/services.ts | 71 ++++++++++++++++++++++++ ui/src/shared/actions/sources.ts | 27 +-------- ui/src/shared/reducers/services.ts | 40 +++++++++++++ ui/src/shared/reducers/sources.ts | 13 ----- ui/src/types/index.ts | 2 + ui/src/types/services.ts | 3 +- ui/test/resources.ts | 2 + ui/test/shared/reducers/services.test.ts | 60 ++++++++++++++++++++ ui/test/shared/reducers/sources.test.ts | 22 +------- 9 files changed, 180 insertions(+), 60 deletions(-) create mode 100644 ui/src/shared/actions/services.ts create mode 100644 ui/src/shared/reducers/services.ts create mode 100644 ui/test/shared/reducers/services.test.ts diff --git a/ui/src/shared/actions/services.ts b/ui/src/shared/actions/services.ts new file mode 100644 index 000000000..46ede7d73 --- /dev/null +++ b/ui/src/shared/actions/services.ts @@ -0,0 +1,71 @@ +import {Service} from 'src/types' + +export type Action = + | ActionLoadServices + | ActionAddService + | ActionDeleteService + | ActionUpdateService + +// Load Services +export type LoadServices = (services: Service[]) => ActionLoadServices +export interface ActionLoadServices { + type: 'LOAD_SERVICES' + payload: { + services: Service[] + } +} + +export const loadServices = (services: Service[]): ActionLoadServices => ({ + type: 'LOAD_SERVICES', + payload: { + services, + }, +}) + +// Add a Service +export type AddService = (service: Service) => ActionAddService +export interface ActionAddService { + type: 'ADD_SERVICE' + payload: { + service: Service + } +} + +export const addService = (service: Service): ActionAddService => ({ + type: 'ADD_SERVICE', + payload: { + service, + }, +}) + +// Delete Service +export type DeleteService = (service: Service) => ActionDeleteService +export interface ActionDeleteService { + type: 'DELETE_SERVICE' + payload: { + service: Service + } +} + +export const deleteService = (service: Service): ActionDeleteService => ({ + type: 'DELETE_SERVICE', + payload: { + service, + }, +}) + +// Update Service +export type UpdateService = (service: Service) => ActionUpdateService +export interface ActionUpdateService { + type: 'UPDATE_SERVICE' + payload: { + service: Service + } +} + +export const updateService = (service: Service): ActionUpdateService => ({ + type: 'UPDATE_SERVICE', + payload: { + service, + }, +}) diff --git a/ui/src/shared/actions/sources.ts b/ui/src/shared/actions/sources.ts index b840d7f6d..0bd36f66c 100644 --- a/ui/src/shared/actions/sources.ts +++ b/ui/src/shared/actions/sources.ts @@ -5,6 +5,7 @@ import { updateKapacitor as updateKapacitorAJAX, deleteKapacitor as deleteKapacitorAJAX, } from 'src/shared/apis' + import {notify} from './notifications' import {errorThrown} from 'src/shared/actions/errors' @@ -24,32 +25,6 @@ export type Action = | ActionFetchKapacitors | ActionSetActiveKapacitor | ActionDeleteKapacitor - | ActionLoadServices - -// Load Services -export type LoadServices = ( - source: Source, - services: Service[] -) => ActionLoadServices - -export interface ActionLoadServices { - type: 'LOAD_SERVICES' - payload: { - source: Source - services: Service[] - } -} - -export const loadServices = ( - source: Source, - services: Service[] -): ActionLoadServices => ({ - type: 'LOAD_SERVICES', - payload: { - source, - services, - }, -}) // Load Sources export type LoadSources = (sources: Source[]) => ActionLoadSources diff --git a/ui/src/shared/reducers/services.ts b/ui/src/shared/reducers/services.ts new file mode 100644 index 000000000..b6f539f63 --- /dev/null +++ b/ui/src/shared/reducers/services.ts @@ -0,0 +1,40 @@ +import {Action} from 'src/shared/actions/services' +import {Service} from 'src/types' + +export const initialState: Service[] = [] + +const servicesReducer = (state = initialState, action: Action): Service[] => { + switch (action.type) { + case 'LOAD_SERVICES': { + return action.payload.services + } + + case 'ADD_SERVICE': { + const {service} = action.payload + return [...state, service] + } + + case 'DELETE_SERVICE': { + const {service} = action.payload + + return state.filter(s => s.id !== service.id) + } + + case 'UPDATE_SERVICE': { + const {service} = action.payload + const newState = state.map(s => { + if (s.id === service.id) { + return {...s, ...service} + } + + return {...s} + }) + + return newState + } + } + + return state +} + +export default servicesReducer diff --git a/ui/src/shared/reducers/sources.ts b/ui/src/shared/reducers/sources.ts index b616f5bda..3dfe325c6 100644 --- a/ui/src/shared/reducers/sources.ts +++ b/ui/src/shared/reducers/sources.ts @@ -10,19 +10,6 @@ const sourcesReducer = (state = initialState, action: Action): Source[] => { return action.payload.sources } - case 'LOAD_SERVICES': { - const {services} = action.payload - const newState = state.map(source => { - if (source.id === action.payload.source.id) { - return {...source, services} - } - - return {...source} - }) - - return newState - } - case 'SOURCE_UPDATED': { const {source} = action.payload const updatedIndex = state.findIndex(s => s.id === source.id) diff --git a/ui/src/types/index.ts b/ui/src/types/index.ts index 7813be32c..91e87246b 100644 --- a/ui/src/types/index.ts +++ b/ui/src/types/index.ts @@ -1,3 +1,4 @@ +import {Service} from './services' import {AuthLinks, Organization, Role, User, Me} from './auth' import {Template, Cell, CellQuery, Legend, Axes} from './dashboard' import { @@ -53,4 +54,5 @@ export { Notification, NotificationFunc, Axes, + Service, } diff --git a/ui/src/types/services.ts b/ui/src/types/services.ts index 2021f6eb0..74e41c674 100644 --- a/ui/src/types/services.ts +++ b/ui/src/types/services.ts @@ -1,4 +1,4 @@ -interface Service { +export interface Service { id?: string url: string name: string @@ -8,6 +8,7 @@ interface Service { active: boolean insecureSkipVerify: boolean links: { + source: string self: string proxy: string } diff --git a/ui/test/resources.ts b/ui/test/resources.ts index 5b93efe71..529f1c6ab 100644 --- a/ui/test/resources.ts +++ b/ui/test/resources.ts @@ -94,6 +94,7 @@ export const kapacitor = { } export const service = { + id: '1', url: 'localhost:8082', type: 'ifql', name: 'IFQL', @@ -102,6 +103,7 @@ export const service = { active: false, insecureSkipVerify: false, links: { + source: '/chronograf/v1/sources/1', proxy: '/chronograf/v1/sources/1/services/2/proxy', self: '/chronograf/v1/sources/1/services/2', }, diff --git a/ui/test/shared/reducers/services.test.ts b/ui/test/shared/reducers/services.test.ts new file mode 100644 index 000000000..24a9c1c0c --- /dev/null +++ b/ui/test/shared/reducers/services.test.ts @@ -0,0 +1,60 @@ +import reducer, {initialState} from 'src/shared/reducers/services' + +import { + addService, + loadServices, + deleteService, + updateService, +} from 'src/shared/actions/services' + +import {Service} from 'src/types' +import {service} from 'test/resources' + +const services = (): Service[] => { + return reducer(initialState, addService(service)) +} + +describe('Shared.Reducers.services', () => { + describe('LOAD_SERVICES', () => { + it('correctly loads services', () => { + const s1 = {...service, id: '1'} + const s2 = {...service, id: '2'} + + const expected = [s1, s2] + const actual = reducer(initialState, loadServices(expected)) + + expect(actual).toEqual(expected) + }) + }) + + describe('ADD_SERVICE', () => { + it('can add a service', () => { + const expected = service + const [actual] = reducer(initialState, addService(service)) + + expect(actual).toEqual(expected) + }) + }) + + describe('DELETE_SERVICE', () => { + it('can delete a service', () => { + const state = services() + const actual = reducer(state, deleteService(service)) + + expect(actual).toEqual([]) + }) + }) + + describe('UPDATE_SERVICE', () => { + it('can update a service', () => { + const name = 'updated name' + const type = 'updated type' + const expected = {...service, name, type} + + const state = services() + const [actual] = reducer(state, updateService(expected)) + + expect(actual).toEqual(expected) + }) + }) +}) diff --git a/ui/test/shared/reducers/sources.test.ts b/ui/test/shared/reducers/sources.test.ts index e6325ee36..3b3915925 100644 --- a/ui/test/shared/reducers/sources.test.ts +++ b/ui/test/shared/reducers/sources.test.ts @@ -1,13 +1,8 @@ import reducer, {initialState} from 'src/shared/reducers/sources' -import { - updateSource, - addSource, - loadServices, - loadSources, -} from 'src/shared/actions/sources' +import {updateSource, addSource, loadSources} from 'src/shared/actions/sources' -import {source, service} from 'test/resources' +import {source} from 'test/resources' describe('Shared.Reducers.sources', () => { it('can LOAD_SOURCES', () => { @@ -76,17 +71,4 @@ describe('Shared.Reducers.sources', () => { expect(state.find(({id}) => id === '2').default).toBe(false) }) }) - - describe('LOAD_SERVICES', () => { - it('correctly loads the services', () => { - const s1 = {...service, id: '1'} - const s2 = {...service, id: '2'} - - const expected = [s1, s2] - const state = reducer(initialState, loadSources([source])) - const actual = reducer(state, loadServices(source, expected)) - - expect(actual[0].services).toEqual(expected) - }) - }) }) From f74f5a378f27f1cd00a6397a3e96bcf6437a0631 Mon Sep 17 00:00:00 2001 From: Chris Goller Date: Thu, 17 May 2018 19:35:27 -0500 Subject: [PATCH 39/49] feat(server/services): add metadata for services --- bolt/internal/internal.go | 16 +++ bolt/internal/internal.pb.go | 219 ++++++++++++++++++----------------- bolt/internal/internal.proto | 1 + chronograf.go | 21 ++-- server/services.go | 100 +++++++++------- 5 files changed, 197 insertions(+), 160 deletions(-) diff --git a/bolt/internal/internal.go b/bolt/internal/internal.go index e3a1fd55c..0190827da 100644 --- a/bolt/internal/internal.go +++ b/bolt/internal/internal.go @@ -76,6 +76,14 @@ func UnmarshalSource(data []byte, s *chronograf.Source) error { // MarshalServer encodes a server to binary protobuf format. func MarshalServer(s chronograf.Server) ([]byte, error) { + var ( + metadata []byte + err error + ) + metadata, err = json.Marshal(s.Metadata) + if err != nil { + return nil, err + } return proto.Marshal(&Server{ ID: int64(s.ID), SrcID: int64(s.SrcID), @@ -87,6 +95,7 @@ func MarshalServer(s chronograf.Server) ([]byte, error) { Organization: s.Organization, InsecureSkipVerify: s.InsecureSkipVerify, Type: s.Type, + MetadataJSON: string(metadata), }) } @@ -97,6 +106,13 @@ func UnmarshalServer(data []byte, s *chronograf.Server) error { return err } + s.Metadata = make(map[string]interface{}) + if len(pb.MetadataJSON) > 0 { + if err := json.Unmarshal([]byte(pb.MetadataJSON), s.Metadata); err != nil { + return err + } + } + s.ID = int(pb.ID) s.SrcID = int(pb.SrcID) s.Name = pb.Name diff --git a/bolt/internal/internal.pb.go b/bolt/internal/internal.pb.go index 548d80a5e..5c7cbe97f 100644 --- a/bolt/internal/internal.pb.go +++ b/bolt/internal/internal.pb.go @@ -735,6 +735,7 @@ type Server struct { Organization string `protobuf:"bytes,8,opt,name=Organization,proto3" json:"Organization,omitempty"` InsecureSkipVerify bool `protobuf:"varint,9,opt,name=InsecureSkipVerify,proto3" json:"InsecureSkipVerify,omitempty"` Type string `protobuf:"bytes,10,opt,name=Type,proto3" json:"Type,omitempty"` + MetadataJSON string `protobuf:"bytes,11,opt,name=MetadataJSON,proto3" json:"MetadataJSON,omitempty"` } func (m *Server) Reset() { *m = Server{} } @@ -812,6 +813,13 @@ func (m *Server) GetType() string { return "" } +func (m *Server) GetMetadataJSON() string { + if m != nil { + return m.MetadataJSON + } + return "" +} + type Layout struct { ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"` Application string `protobuf:"bytes,2,opt,name=Application,proto3" json:"Application,omitempty"` @@ -1380,110 +1388,111 @@ func init() { func init() { proto.RegisterFile("internal.proto", fileDescriptorInternal) } var fileDescriptorInternal = []byte{ - // 1673 bytes of a gzipped FileDescriptorProto + // 1685 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x58, 0x5b, 0x8f, 0xe3, 0x48, - 0x15, 0x96, 0x13, 0x3b, 0x89, 0x4f, 0xba, 0x7b, 0x5b, 0x66, 0xb4, 0x6b, 0x16, 0x84, 0x82, 0xc5, - 0xa5, 0xb9, 0xec, 0xb0, 0xea, 0x15, 0x12, 0x5a, 0xed, 0xae, 0xd4, 0x97, 0x9d, 0xa1, 0xe7, 0xda, - 0x53, 0xe9, 0x1e, 0x9e, 0xd0, 0xaa, 0x62, 0x57, 0x92, 0xd2, 0x3a, 0xb6, 0x29, 0xdb, 0xdd, 0x6d, - 0x9e, 0x79, 0xe4, 0x37, 0x20, 0x21, 0xc1, 0x1f, 0x40, 0x3c, 0x22, 0xf1, 0xce, 0x0f, 0xe0, 0xaf, - 0xf0, 0x8a, 0x4e, 0x5d, 0xec, 0x72, 0x27, 0x33, 0x1a, 0x24, 0xb4, 0x6f, 0xf5, 0x9d, 0x73, 0x72, - 0xaa, 0xea, 0x5c, 0xbe, 0x3a, 0x0e, 0x1c, 0xf0, 0xac, 0x62, 0x22, 0xa3, 0xe9, 0xc3, 0x42, 0xe4, - 0x55, 0x1e, 0x4c, 0x0c, 0x8e, 0xfe, 0x30, 0x84, 0xd1, 0x3c, 0xaf, 0x45, 0xcc, 0x82, 0x03, 0x18, - 0x5c, 0x9c, 0x87, 0xce, 0xcc, 0x39, 0x1a, 0x92, 0xc1, 0xc5, 0x79, 0x10, 0x80, 0xfb, 0x82, 0x6e, - 0x58, 0x38, 0x98, 0x39, 0x47, 0x3e, 0x91, 0x6b, 0x94, 0x5d, 0x35, 0x05, 0x0b, 0x87, 0x4a, 0x86, - 0xeb, 0xe0, 0x43, 0x98, 0x5c, 0x97, 0xe8, 0x6d, 0xc3, 0x42, 0x57, 0xca, 0x5b, 0x8c, 0xba, 0x4b, - 0x5a, 0x96, 0xb7, 0xb9, 0x48, 0x42, 0x4f, 0xe9, 0x0c, 0x0e, 0x0e, 0x61, 0x78, 0x4d, 0x9e, 0x85, - 0x23, 0x29, 0xc6, 0x65, 0x10, 0xc2, 0xf8, 0x9c, 0x2d, 0x69, 0x9d, 0x56, 0xe1, 0x78, 0xe6, 0x1c, - 0x4d, 0x88, 0x81, 0xe8, 0xe7, 0x8a, 0xa5, 0x6c, 0x25, 0xe8, 0x32, 0x9c, 0x28, 0x3f, 0x06, 0x07, - 0x0f, 0x21, 0xb8, 0xc8, 0x4a, 0x16, 0xd7, 0x82, 0xcd, 0xbf, 0xe6, 0xc5, 0x6b, 0x26, 0xf8, 0xb2, - 0x09, 0x7d, 0xe9, 0x60, 0x87, 0x06, 0x77, 0x79, 0xce, 0x2a, 0x8a, 0x7b, 0x83, 0x74, 0x65, 0x60, - 0x10, 0xc1, 0xde, 0x7c, 0x4d, 0x05, 0x4b, 0xe6, 0x2c, 0x16, 0xac, 0x0a, 0xa7, 0x52, 0xdd, 0x93, - 0xa1, 0xcd, 0x4b, 0xb1, 0xa2, 0x19, 0xff, 0x3d, 0xad, 0x78, 0x9e, 0x85, 0x7b, 0xca, 0xc6, 0x96, - 0x61, 0x94, 0x48, 0x9e, 0xb2, 0x70, 0x5f, 0x45, 0x09, 0xd7, 0xc1, 0x77, 0xc1, 0xd7, 0x97, 0x21, - 0x97, 0xe1, 0x81, 0x54, 0x74, 0x82, 0xe8, 0xef, 0x0e, 0xf8, 0xe7, 0xb4, 0x5c, 0x2f, 0x72, 0x2a, - 0x92, 0x77, 0xca, 0xc4, 0x47, 0xe0, 0xc5, 0x2c, 0x4d, 0xcb, 0x70, 0x38, 0x1b, 0x1e, 0x4d, 0x8f, - 0x3f, 0x78, 0xd8, 0xa6, 0xb8, 0xf5, 0x73, 0xc6, 0xd2, 0x94, 0x28, 0xab, 0xe0, 0x63, 0xf0, 0x2b, - 0xb6, 0x29, 0x52, 0x5a, 0xb1, 0x32, 0x74, 0xe5, 0x4f, 0x82, 0xee, 0x27, 0x57, 0x5a, 0x45, 0x3a, - 0xa3, 0xad, 0x8b, 0x7a, 0xdb, 0x17, 0x8d, 0xfe, 0xed, 0xc2, 0x7e, 0x6f, 0xbb, 0x60, 0x0f, 0x9c, - 0x3b, 0x79, 0x72, 0x8f, 0x38, 0x77, 0x88, 0x1a, 0x79, 0x6a, 0x8f, 0x38, 0x0d, 0xa2, 0x5b, 0x59, - 0x39, 0x1e, 0x71, 0x6e, 0x11, 0xad, 0x65, 0xbd, 0x78, 0xc4, 0x59, 0x07, 0x3f, 0x81, 0xf1, 0xef, - 0x6a, 0x26, 0x38, 0x2b, 0x43, 0x4f, 0x9e, 0xee, 0xbd, 0xee, 0x74, 0xaf, 0x6a, 0x26, 0x1a, 0x62, - 0xf4, 0x18, 0x0d, 0x59, 0x6b, 0xaa, 0x70, 0xe4, 0x1a, 0x65, 0x15, 0xd6, 0xe5, 0x58, 0xc9, 0x70, - 0xad, 0xa3, 0xa8, 0xaa, 0x05, 0xa3, 0xf8, 0x4b, 0x70, 0xe9, 0x1d, 0x2b, 0x43, 0x5f, 0xfa, 0xff, - 0xfe, 0x1b, 0x02, 0xf6, 0xf0, 0xe4, 0x8e, 0x95, 0x5f, 0x66, 0x95, 0x68, 0x88, 0x34, 0x0f, 0x7e, - 0x0c, 0xa3, 0x38, 0x4f, 0x73, 0x51, 0x86, 0x70, 0xff, 0x60, 0x67, 0x28, 0x27, 0x5a, 0x1d, 0x1c, - 0xc1, 0x28, 0x65, 0x2b, 0x96, 0x25, 0xb2, 0x6e, 0xa6, 0xc7, 0x87, 0x9d, 0xe1, 0x33, 0x29, 0x27, - 0x5a, 0x1f, 0x7c, 0x0a, 0x7b, 0x15, 0x5d, 0xa4, 0xec, 0x65, 0x81, 0x51, 0x2c, 0x65, 0x0d, 0x4d, - 0x8f, 0xdf, 0xb7, 0xf2, 0x61, 0x69, 0x49, 0xcf, 0x36, 0xf8, 0x0c, 0xf6, 0x96, 0x9c, 0xa5, 0x89, - 0xf9, 0xed, 0xbe, 0x3c, 0x54, 0xd8, 0xfd, 0x96, 0xb0, 0x8c, 0x6e, 0xf0, 0x17, 0x8f, 0xd0, 0x8c, - 0xf4, 0xac, 0x83, 0xef, 0x01, 0x54, 0x7c, 0xc3, 0x1e, 0xe5, 0x62, 0x43, 0x2b, 0x5d, 0x86, 0x96, - 0x24, 0xf8, 0x1c, 0xf6, 0x13, 0x16, 0xf3, 0x0d, 0x4d, 0x2f, 0x53, 0x1a, 0xb3, 0x32, 0x7c, 0x4f, - 0x1e, 0xcd, 0xae, 0x2e, 0x5b, 0x4d, 0xfa, 0xd6, 0x1f, 0x3e, 0x06, 0xbf, 0x0d, 0x1f, 0xf6, 0xf7, - 0xd7, 0xac, 0x91, 0xc5, 0xe0, 0x13, 0x5c, 0x06, 0x3f, 0x00, 0xef, 0x86, 0xa6, 0xb5, 0x2a, 0xe4, - 0xe9, 0xf1, 0x41, 0xe7, 0xf5, 0xe4, 0x8e, 0x97, 0x44, 0x29, 0x3f, 0x1d, 0xfc, 0xca, 0x89, 0x1e, - 0xc3, 0x7e, 0x6f, 0x23, 0x3c, 0x38, 0x2f, 0xbf, 0xcc, 0x96, 0xb9, 0x88, 0x59, 0x22, 0x7d, 0x4e, - 0x88, 0x25, 0x09, 0xde, 0x87, 0x51, 0xc2, 0x57, 0xbc, 0x2a, 0x75, 0xb9, 0x69, 0x14, 0xfd, 0xc3, - 0x81, 0x3d, 0x3b, 0x9a, 0xc1, 0x4f, 0xe1, 0xf0, 0x86, 0x89, 0x8a, 0xc7, 0x34, 0xbd, 0xe2, 0x1b, - 0x86, 0x1b, 0xcb, 0x9f, 0x4c, 0xc8, 0x96, 0x3c, 0xf8, 0x18, 0x46, 0x65, 0x2e, 0xaa, 0xd3, 0x46, - 0x56, 0xed, 0xdb, 0xa2, 0xac, 0xed, 0x90, 0xa7, 0x6e, 0x05, 0x2d, 0x0a, 0x9e, 0xad, 0x0c, 0x17, - 0x1a, 0x1c, 0xfc, 0x08, 0x0e, 0x96, 0xfc, 0xee, 0x11, 0x17, 0x65, 0x75, 0x96, 0xa7, 0xf5, 0x26, - 0x93, 0x15, 0x3c, 0x21, 0xf7, 0xa4, 0x4f, 0xdc, 0x89, 0x73, 0x38, 0x78, 0xe2, 0x4e, 0xbc, 0xc3, - 0x51, 0x54, 0xc0, 0x41, 0x7f, 0x27, 0x6c, 0x4b, 0x73, 0x08, 0xc9, 0x09, 0x2a, 0xbc, 0x3d, 0x59, - 0x30, 0x83, 0x69, 0xc2, 0xcb, 0x22, 0xa5, 0x8d, 0x45, 0x1b, 0xb6, 0x08, 0x39, 0xf0, 0x86, 0x97, - 0x7c, 0x91, 0x2a, 0x2a, 0x9f, 0x10, 0x03, 0xa3, 0x15, 0x78, 0xb2, 0xac, 0x2d, 0x12, 0xf2, 0x0d, - 0x09, 0x49, 0xea, 0x1f, 0x58, 0xd4, 0x7f, 0x08, 0xc3, 0x5f, 0xb3, 0x3b, 0xfd, 0x1a, 0xe0, 0xb2, - 0xa5, 0x2a, 0xd7, 0xa2, 0xaa, 0x07, 0xe0, 0xbd, 0x96, 0x69, 0x57, 0x14, 0xa2, 0x40, 0xf4, 0x05, - 0x8c, 0x54, 0x5b, 0xb4, 0x9e, 0x1d, 0xcb, 0xf3, 0x0c, 0xa6, 0x2f, 0x05, 0x67, 0x59, 0xa5, 0xc8, - 0x47, 0x5f, 0xc1, 0x12, 0x45, 0x7f, 0x73, 0xc0, 0x95, 0x59, 0x8a, 0x60, 0x2f, 0x65, 0x2b, 0x1a, - 0x37, 0xa7, 0x79, 0x9d, 0x25, 0x65, 0xe8, 0xcc, 0x86, 0x47, 0x43, 0xd2, 0x93, 0x61, 0x79, 0x2c, - 0x94, 0x76, 0x30, 0x1b, 0x1e, 0xf9, 0x44, 0x23, 0x3c, 0x5a, 0x4a, 0x17, 0x2c, 0xd5, 0x57, 0x50, - 0x00, 0xad, 0x0b, 0xc1, 0x96, 0xfc, 0x4e, 0x5f, 0x43, 0x23, 0x94, 0x97, 0xf5, 0x12, 0xe5, 0xea, - 0x26, 0x1a, 0xe1, 0x05, 0x16, 0xb4, 0x6c, 0x19, 0x09, 0xd7, 0xe8, 0xb9, 0x8c, 0x69, 0x6a, 0x28, - 0x49, 0x81, 0xe8, 0x9f, 0x0e, 0x3e, 0x64, 0x8a, 0x62, 0xb7, 0x22, 0xfc, 0x6d, 0x98, 0x20, 0xfd, - 0x7e, 0x75, 0x43, 0x85, 0xbe, 0xf0, 0x18, 0xf1, 0x6b, 0x2a, 0x82, 0x5f, 0xc0, 0x48, 0x36, 0xc7, - 0x0e, 0xba, 0x37, 0xee, 0x64, 0x54, 0x89, 0x36, 0x6b, 0x09, 0xd1, 0xb5, 0x08, 0xb1, 0xbd, 0xac, - 0x67, 0x5f, 0xf6, 0x23, 0xf0, 0x90, 0x59, 0x1b, 0x79, 0xfa, 0x9d, 0x9e, 0x15, 0xff, 0x2a, 0xab, - 0xe8, 0x1a, 0xf6, 0x7b, 0x3b, 0xb6, 0x3b, 0x39, 0xfd, 0x9d, 0xba, 0x46, 0xf7, 0x75, 0x63, 0x63, - 0x73, 0x94, 0x2c, 0x65, 0x71, 0xc5, 0x12, 0x5d, 0x75, 0x2d, 0x8e, 0xfe, 0xec, 0x74, 0x7e, 0xe5, - 0x7e, 0x58, 0xa2, 0x71, 0xbe, 0xd9, 0xd0, 0x2c, 0xd1, 0xae, 0x0d, 0xc4, 0xb8, 0x25, 0x0b, 0xed, - 0x7a, 0x90, 0x2c, 0x10, 0x8b, 0x42, 0x67, 0x70, 0x20, 0x0a, 0xac, 0x9d, 0x0d, 0xa3, 0x65, 0x2d, - 0xd8, 0x86, 0x65, 0x95, 0x0e, 0x81, 0x2d, 0x0a, 0x3e, 0x80, 0x71, 0x45, 0x57, 0x5f, 0x21, 0x3d, - 0xe9, 0x4c, 0x56, 0x74, 0xf5, 0x94, 0x35, 0xc1, 0x77, 0xc0, 0x97, 0x7c, 0x29, 0x55, 0x2a, 0x9d, - 0x13, 0x29, 0x78, 0xca, 0x9a, 0xe8, 0x8f, 0x03, 0x18, 0xcd, 0x99, 0xb8, 0x61, 0xe2, 0x9d, 0x5e, - 0x68, 0x7b, 0x2e, 0x1a, 0xbe, 0x65, 0x2e, 0x72, 0x77, 0xcf, 0x45, 0x5e, 0x37, 0x17, 0x3d, 0x00, - 0x6f, 0x2e, 0xe2, 0x8b, 0x73, 0x79, 0xa2, 0x21, 0x51, 0x00, 0xab, 0xf1, 0x24, 0xae, 0xf8, 0x0d, - 0xd3, 0xc3, 0x92, 0x46, 0x5b, 0x0f, 0xf7, 0x64, 0xc7, 0x84, 0xf2, 0xbf, 0xce, 0x4c, 0xa6, 0x45, - 0xa1, 0x6b, 0xd1, 0xe8, 0x4f, 0x0e, 0x8c, 0x9e, 0xd1, 0x26, 0xaf, 0xab, 0xad, 0x4a, 0x9e, 0xc1, - 0xf4, 0xa4, 0x28, 0x52, 0x1e, 0xf7, 0xba, 0xd7, 0x12, 0xa1, 0xc5, 0x73, 0x2b, 0x47, 0x2a, 0x3e, - 0xb6, 0x08, 0x1f, 0x8b, 0x33, 0x39, 0xe0, 0xa8, 0x69, 0xc5, 0x7a, 0x2c, 0xd4, 0x5c, 0x23, 0x95, - 0x18, 0xc8, 0x93, 0xba, 0xca, 0x97, 0x69, 0x7e, 0x2b, 0x23, 0x36, 0x21, 0x2d, 0x8e, 0xfe, 0x35, - 0x00, 0xf7, 0x9b, 0x1a, 0x4a, 0xf6, 0xc0, 0xe1, 0xba, 0x60, 0x1c, 0xde, 0x8e, 0x28, 0x63, 0x6b, - 0x44, 0x09, 0x61, 0xdc, 0x08, 0x9a, 0xad, 0x58, 0x19, 0x4e, 0x24, 0x43, 0x19, 0x28, 0x35, 0xb2, - 0x17, 0xd5, 0x6c, 0xe2, 0x13, 0x03, 0xdb, 0xde, 0x02, 0xab, 0xb7, 0x7e, 0xae, 0xc7, 0x98, 0xe9, - 0xfd, 0x87, 0x7f, 0xd7, 0xf4, 0xf2, 0xff, 0x7b, 0x91, 0xff, 0xe3, 0x80, 0xd7, 0x36, 0xe6, 0x59, - 0xbf, 0x31, 0xcf, 0xba, 0xc6, 0x3c, 0x3f, 0x35, 0x8d, 0x79, 0x7e, 0x8a, 0x98, 0x5c, 0x9a, 0xc6, - 0x24, 0x97, 0x98, 0xac, 0xc7, 0x22, 0xaf, 0x8b, 0xd3, 0x46, 0x65, 0xd5, 0x27, 0x2d, 0xc6, 0x6a, - 0xfe, 0xcd, 0x9a, 0x09, 0x1d, 0x6a, 0x9f, 0x68, 0x84, 0xb5, 0xff, 0x4c, 0x92, 0x96, 0x0a, 0xae, - 0x02, 0xc1, 0x0f, 0xc1, 0x23, 0x18, 0x3c, 0x19, 0xe1, 0x5e, 0x5e, 0xa4, 0x98, 0x28, 0x2d, 0x3a, - 0x55, 0x1f, 0x37, 0xba, 0x09, 0xcc, 0xa7, 0xce, 0xcf, 0x60, 0x34, 0x5f, 0xf3, 0x65, 0x65, 0x86, - 0xc1, 0x6f, 0x59, 0xa4, 0xc7, 0x37, 0x4c, 0xea, 0x88, 0x36, 0x89, 0x5e, 0x81, 0xdf, 0x0a, 0xbb, - 0xe3, 0x38, 0xf6, 0x71, 0x02, 0x70, 0xaf, 0x33, 0x5e, 0x99, 0xf6, 0xc7, 0x35, 0x5e, 0xf6, 0x55, - 0x4d, 0xb3, 0x8a, 0x57, 0x8d, 0x69, 0x7f, 0x83, 0xa3, 0x4f, 0xf4, 0xf1, 0xd1, 0xdd, 0x75, 0x51, - 0x30, 0xa1, 0xa9, 0x44, 0x01, 0xb9, 0x49, 0x7e, 0xcb, 0xd4, 0x2b, 0x30, 0x24, 0x0a, 0x44, 0xbf, - 0x05, 0xff, 0x24, 0x65, 0xa2, 0x22, 0x75, 0xca, 0x76, 0xbd, 0xce, 0x4f, 0xe6, 0x2f, 0x5f, 0x98, - 0x13, 0xe0, 0xba, 0xa3, 0x8d, 0xe1, 0x3d, 0xda, 0x78, 0x4a, 0x0b, 0x7a, 0x71, 0x2e, 0xeb, 0x7c, - 0x48, 0x34, 0x8a, 0xfe, 0xe2, 0x80, 0x8b, 0xfc, 0x64, 0xb9, 0x76, 0xdf, 0xc6, 0x6d, 0x97, 0x22, - 0xbf, 0xe1, 0x09, 0x13, 0xe6, 0x72, 0x06, 0xcb, 0xa0, 0xc7, 0x6b, 0xd6, 0x0e, 0x01, 0x1a, 0x61, - 0xad, 0xe1, 0x97, 0x90, 0xe9, 0x25, 0xab, 0xd6, 0x50, 0x4c, 0x94, 0x12, 0x07, 0xbd, 0x79, 0x5d, - 0x30, 0x71, 0x92, 0x6c, 0xb8, 0x99, 0x90, 0x2c, 0x49, 0xf4, 0x85, 0xfa, 0xb6, 0xda, 0x62, 0x39, - 0x67, 0xf7, 0x77, 0xd8, 0xfd, 0x93, 0x47, 0x7f, 0x75, 0x60, 0xfc, 0x5c, 0x4f, 0x64, 0xf6, 0x2d, - 0x9c, 0x37, 0xde, 0x62, 0xd0, 0xbb, 0xc5, 0x31, 0x3c, 0x30, 0x36, 0xbd, 0xfd, 0x55, 0x14, 0x76, - 0xea, 0x74, 0x44, 0xdd, 0x36, 0x59, 0xef, 0xf2, 0x69, 0x75, 0xd5, 0xb7, 0xd9, 0x95, 0xf0, 0xad, - 0xac, 0xcc, 0x60, 0x6a, 0x3e, 0x29, 0xf3, 0xd4, 0x3c, 0x3a, 0xb6, 0x28, 0x3a, 0x86, 0xd1, 0x59, - 0x9e, 0x2d, 0xf9, 0x2a, 0x38, 0x02, 0xf7, 0xa4, 0xae, 0xd6, 0xd2, 0xe3, 0xf4, 0xf8, 0x81, 0xd5, - 0xf8, 0x75, 0xb5, 0x56, 0x36, 0x44, 0x5a, 0x44, 0x9f, 0x01, 0x74, 0x32, 0x7c, 0x39, 0xba, 0x6c, - 0xbc, 0x60, 0xb7, 0x58, 0x32, 0xa5, 0x1e, 0xc8, 0x77, 0x68, 0xa2, 0xcf, 0xc1, 0x3f, 0xad, 0x79, - 0x9a, 0x5c, 0x64, 0xcb, 0x1c, 0xa9, 0xe3, 0x35, 0x13, 0x65, 0x97, 0x2f, 0x03, 0x31, 0xdc, 0xc8, - 0x22, 0x6d, 0x0f, 0x69, 0xb4, 0x18, 0xc9, 0x3f, 0x2c, 0x3e, 0xf9, 0x6f, 0x00, 0x00, 0x00, 0xff, - 0xff, 0x59, 0xb8, 0x9d, 0x3e, 0xc2, 0x10, 0x00, 0x00, + 0x15, 0x96, 0x93, 0x38, 0x89, 0x4f, 0xd2, 0xbd, 0x2d, 0x33, 0xda, 0x35, 0x0b, 0x42, 0xc1, 0xe2, + 0xd2, 0x5c, 0x76, 0x58, 0xf5, 0x0a, 0x09, 0xad, 0x76, 0x57, 0xea, 0xcb, 0xce, 0xd0, 0x73, 0xed, + 0xa9, 0x74, 0x0f, 0x4f, 0x68, 0x55, 0xb1, 0x2b, 0x49, 0x69, 0x1d, 0xdb, 0x94, 0xcb, 0xdd, 0x6d, + 0x9e, 0xf9, 0x1d, 0x48, 0x48, 0xf0, 0x8e, 0x10, 0x8f, 0x48, 0xbc, 0xf3, 0x03, 0xf8, 0x2b, 0xbc, + 0xa2, 0x53, 0x17, 0xa7, 0xdc, 0x9d, 0x19, 0x0d, 0x12, 0xda, 0xb7, 0xfa, 0xce, 0x39, 0x39, 0x55, + 0x75, 0x2e, 0x5f, 0x1d, 0x07, 0xf6, 0x79, 0x2e, 0x99, 0xc8, 0x69, 0xf6, 0xb0, 0x14, 0x85, 0x2c, + 0xc2, 0xb1, 0xc5, 0xf1, 0x1f, 0xfa, 0x30, 0x9c, 0x17, 0xb5, 0x48, 0x58, 0xb8, 0x0f, 0xbd, 0xf3, + 0xb3, 0xc8, 0x9b, 0x79, 0x87, 0x7d, 0xd2, 0x3b, 0x3f, 0x0b, 0x43, 0x18, 0xbc, 0xa0, 0x1b, 0x16, + 0xf5, 0x66, 0xde, 0x61, 0x40, 0xd4, 0x1a, 0x65, 0x97, 0x4d, 0xc9, 0xa2, 0xbe, 0x96, 0xe1, 0x3a, + 0xfc, 0x10, 0xc6, 0x57, 0x15, 0x7a, 0xdb, 0xb0, 0x68, 0xa0, 0xe4, 0x2d, 0x46, 0xdd, 0x05, 0xad, + 0xaa, 0x9b, 0x42, 0xa4, 0x91, 0xaf, 0x75, 0x16, 0x87, 0x07, 0xd0, 0xbf, 0x22, 0xcf, 0xa2, 0xa1, + 0x12, 0xe3, 0x32, 0x8c, 0x60, 0x74, 0xc6, 0x96, 0xb4, 0xce, 0x64, 0x34, 0x9a, 0x79, 0x87, 0x63, + 0x62, 0x21, 0xfa, 0xb9, 0x64, 0x19, 0x5b, 0x09, 0xba, 0x8c, 0xc6, 0xda, 0x8f, 0xc5, 0xe1, 0x43, + 0x08, 0xcf, 0xf3, 0x8a, 0x25, 0xb5, 0x60, 0xf3, 0xaf, 0x79, 0xf9, 0x9a, 0x09, 0xbe, 0x6c, 0xa2, + 0x40, 0x39, 0xd8, 0xa1, 0xc1, 0x5d, 0x9e, 0x33, 0x49, 0x71, 0x6f, 0x50, 0xae, 0x2c, 0x0c, 0x63, + 0x98, 0xce, 0xd7, 0x54, 0xb0, 0x74, 0xce, 0x12, 0xc1, 0x64, 0x34, 0x51, 0xea, 0x8e, 0x0c, 0x6d, + 0x5e, 0x8a, 0x15, 0xcd, 0xf9, 0xef, 0xa9, 0xe4, 0x45, 0x1e, 0x4d, 0xb5, 0x8d, 0x2b, 0xc3, 0x28, + 0x91, 0x22, 0x63, 0xd1, 0x9e, 0x8e, 0x12, 0xae, 0xc3, 0xef, 0x42, 0x60, 0x2e, 0x43, 0x2e, 0xa2, + 0x7d, 0xa5, 0xd8, 0x0a, 0xe2, 0xbf, 0x7b, 0x10, 0x9c, 0xd1, 0x6a, 0xbd, 0x28, 0xa8, 0x48, 0xdf, + 0x29, 0x13, 0x1f, 0x81, 0x9f, 0xb0, 0x2c, 0xab, 0xa2, 0xfe, 0xac, 0x7f, 0x38, 0x39, 0xfa, 0xe0, + 0x61, 0x9b, 0xe2, 0xd6, 0xcf, 0x29, 0xcb, 0x32, 0xa2, 0xad, 0xc2, 0x8f, 0x21, 0x90, 0x6c, 0x53, + 0x66, 0x54, 0xb2, 0x2a, 0x1a, 0xa8, 0x9f, 0x84, 0xdb, 0x9f, 0x5c, 0x1a, 0x15, 0xd9, 0x1a, 0xdd, + 0xbb, 0xa8, 0x7f, 0xff, 0xa2, 0xf1, 0xbf, 0x07, 0xb0, 0xd7, 0xd9, 0x2e, 0x9c, 0x82, 0x77, 0xab, + 0x4e, 0xee, 0x13, 0xef, 0x16, 0x51, 0xa3, 0x4e, 0xed, 0x13, 0xaf, 0x41, 0x74, 0xa3, 0x2a, 0xc7, + 0x27, 0xde, 0x0d, 0xa2, 0xb5, 0xaa, 0x17, 0x9f, 0x78, 0xeb, 0xf0, 0x27, 0x30, 0xfa, 0x5d, 0xcd, + 0x04, 0x67, 0x55, 0xe4, 0xab, 0xd3, 0xbd, 0xb7, 0x3d, 0xdd, 0xab, 0x9a, 0x89, 0x86, 0x58, 0x3d, + 0x46, 0x43, 0xd5, 0x9a, 0x2e, 0x1c, 0xb5, 0x46, 0x99, 0xc4, 0xba, 0x1c, 0x69, 0x19, 0xae, 0x4d, + 0x14, 0x75, 0xb5, 0x60, 0x14, 0x7f, 0x09, 0x03, 0x7a, 0xcb, 0xaa, 0x28, 0x50, 0xfe, 0xbf, 0xff, + 0x86, 0x80, 0x3d, 0x3c, 0xbe, 0x65, 0xd5, 0x97, 0xb9, 0x14, 0x0d, 0x51, 0xe6, 0xe1, 0x8f, 0x61, + 0x98, 0x14, 0x59, 0x21, 0xaa, 0x08, 0xee, 0x1e, 0xec, 0x14, 0xe5, 0xc4, 0xa8, 0xc3, 0x43, 0x18, + 0x66, 0x6c, 0xc5, 0xf2, 0x54, 0xd5, 0xcd, 0xe4, 0xe8, 0x60, 0x6b, 0xf8, 0x4c, 0xc9, 0x89, 0xd1, + 0x87, 0x9f, 0xc2, 0x54, 0xd2, 0x45, 0xc6, 0x5e, 0x96, 0x18, 0xc5, 0x4a, 0xd5, 0xd0, 0xe4, 0xe8, + 0x7d, 0x27, 0x1f, 0x8e, 0x96, 0x74, 0x6c, 0xc3, 0xcf, 0x60, 0xba, 0xe4, 0x2c, 0x4b, 0xed, 0x6f, + 0xf7, 0xd4, 0xa1, 0xa2, 0xed, 0x6f, 0x09, 0xcb, 0xe9, 0x06, 0x7f, 0xf1, 0x08, 0xcd, 0x48, 0xc7, + 0x3a, 0xfc, 0x1e, 0x80, 0xe4, 0x1b, 0xf6, 0xa8, 0x10, 0x1b, 0x2a, 0x4d, 0x19, 0x3a, 0x92, 0xf0, + 0x73, 0xd8, 0x4b, 0x59, 0xc2, 0x37, 0x34, 0xbb, 0xc8, 0x68, 0xc2, 0xaa, 0xe8, 0x3d, 0x75, 0x34, + 0xb7, 0xba, 0x5c, 0x35, 0xe9, 0x5a, 0x7f, 0xf8, 0x18, 0x82, 0x36, 0x7c, 0xd8, 0xdf, 0x5f, 0xb3, + 0x46, 0x15, 0x43, 0x40, 0x70, 0x19, 0xfe, 0x00, 0xfc, 0x6b, 0x9a, 0xd5, 0xba, 0x90, 0x27, 0x47, + 0xfb, 0x5b, 0xaf, 0xc7, 0xb7, 0xbc, 0x22, 0x5a, 0xf9, 0x69, 0xef, 0x57, 0x5e, 0xfc, 0x18, 0xf6, + 0x3a, 0x1b, 0xe1, 0xc1, 0x79, 0xf5, 0x65, 0xbe, 0x2c, 0x44, 0xc2, 0x52, 0xe5, 0x73, 0x4c, 0x1c, + 0x49, 0xf8, 0x3e, 0x0c, 0x53, 0xbe, 0xe2, 0xb2, 0x32, 0xe5, 0x66, 0x50, 0xfc, 0x0f, 0x0f, 0xa6, + 0x6e, 0x34, 0xc3, 0x9f, 0xc2, 0xc1, 0x35, 0x13, 0x92, 0x27, 0x34, 0xbb, 0xe4, 0x1b, 0x86, 0x1b, + 0xab, 0x9f, 0x8c, 0xc9, 0x3d, 0x79, 0xf8, 0x31, 0x0c, 0xab, 0x42, 0xc8, 0x93, 0x46, 0x55, 0xed, + 0xdb, 0xa2, 0x6c, 0xec, 0x90, 0xa7, 0x6e, 0x04, 0x2d, 0x4b, 0x9e, 0xaf, 0x2c, 0x17, 0x5a, 0x1c, + 0xfe, 0x08, 0xf6, 0x97, 0xfc, 0xf6, 0x11, 0x17, 0x95, 0x3c, 0x2d, 0xb2, 0x7a, 0x93, 0xab, 0x0a, + 0x1e, 0x93, 0x3b, 0xd2, 0x27, 0x83, 0xb1, 0x77, 0xd0, 0x7b, 0x32, 0x18, 0xfb, 0x07, 0xc3, 0xb8, + 0x84, 0xfd, 0xee, 0x4e, 0xd8, 0x96, 0xf6, 0x10, 0x8a, 0x13, 0x74, 0x78, 0x3b, 0xb2, 0x70, 0x06, + 0x93, 0x94, 0x57, 0x65, 0x46, 0x1b, 0x87, 0x36, 0x5c, 0x11, 0x72, 0xe0, 0x35, 0xaf, 0xf8, 0x22, + 0xd3, 0x54, 0x3e, 0x26, 0x16, 0xc6, 0x2b, 0xf0, 0x55, 0x59, 0x3b, 0x24, 0x14, 0x58, 0x12, 0x52, + 0xd4, 0xdf, 0x73, 0xa8, 0xff, 0x00, 0xfa, 0xbf, 0x66, 0xb7, 0xe6, 0x35, 0xc0, 0x65, 0x4b, 0x55, + 0x03, 0x87, 0xaa, 0x1e, 0x80, 0xff, 0x5a, 0xa5, 0x5d, 0x53, 0x88, 0x06, 0xf1, 0x17, 0x30, 0xd4, + 0x6d, 0xd1, 0x7a, 0xf6, 0x1c, 0xcf, 0x33, 0x98, 0xbc, 0x14, 0x9c, 0xe5, 0x52, 0x93, 0x8f, 0xb9, + 0x82, 0x23, 0x8a, 0xff, 0xe6, 0xc1, 0x40, 0x65, 0x29, 0x86, 0x69, 0xc6, 0x56, 0x34, 0x69, 0x4e, + 0x8a, 0x3a, 0x4f, 0xab, 0xc8, 0x9b, 0xf5, 0x0f, 0xfb, 0xa4, 0x23, 0xc3, 0xf2, 0x58, 0x68, 0x6d, + 0x6f, 0xd6, 0x3f, 0x0c, 0x88, 0x41, 0x78, 0xb4, 0x8c, 0x2e, 0x58, 0x66, 0xae, 0xa0, 0x01, 0x5a, + 0x97, 0x82, 0x2d, 0xf9, 0xad, 0xb9, 0x86, 0x41, 0x28, 0xaf, 0xea, 0x25, 0xca, 0xf5, 0x4d, 0x0c, + 0xc2, 0x0b, 0x2c, 0x68, 0xd5, 0x32, 0x12, 0xae, 0xd1, 0x73, 0x95, 0xd0, 0xcc, 0x52, 0x92, 0x06, + 0xf1, 0x3f, 0x3d, 0x7c, 0xc8, 0x34, 0xc5, 0xde, 0x8b, 0xf0, 0xb7, 0x61, 0x8c, 0xf4, 0xfb, 0xd5, + 0x35, 0x15, 0xe6, 0xc2, 0x23, 0xc4, 0xaf, 0xa9, 0x08, 0x7f, 0x01, 0x43, 0xd5, 0x1c, 0x3b, 0xe8, + 0xde, 0xba, 0x53, 0x51, 0x25, 0xc6, 0xac, 0x25, 0xc4, 0x81, 0x43, 0x88, 0xed, 0x65, 0x7d, 0xf7, + 0xb2, 0x1f, 0x81, 0x8f, 0xcc, 0xda, 0xa8, 0xd3, 0xef, 0xf4, 0xac, 0xf9, 0x57, 0x5b, 0xc5, 0x57, + 0xb0, 0xd7, 0xd9, 0xb1, 0xdd, 0xc9, 0xeb, 0xee, 0xb4, 0x6d, 0xf4, 0xc0, 0x34, 0x36, 0x36, 0x47, + 0xc5, 0x32, 0x96, 0x48, 0x96, 0x9a, 0xaa, 0x6b, 0x71, 0xfc, 0x27, 0x6f, 0xeb, 0x57, 0xed, 0x87, + 0x25, 0x9a, 0x14, 0x9b, 0x0d, 0xcd, 0x53, 0xe3, 0xda, 0x42, 0x8c, 0x5b, 0xba, 0x30, 0xae, 0x7b, + 0xe9, 0x02, 0xb1, 0x28, 0x4d, 0x06, 0x7b, 0xa2, 0xc4, 0xda, 0xd9, 0x30, 0x5a, 0xd5, 0x82, 0x6d, + 0x58, 0x2e, 0x4d, 0x08, 0x5c, 0x51, 0xf8, 0x01, 0x8c, 0x24, 0x5d, 0x7d, 0x85, 0xf4, 0x64, 0x32, + 0x29, 0xe9, 0xea, 0x29, 0x6b, 0xc2, 0xef, 0x40, 0xa0, 0xf8, 0x52, 0xa9, 0x74, 0x3a, 0xc7, 0x4a, + 0xf0, 0x94, 0x35, 0xf1, 0x5f, 0x7b, 0x30, 0x9c, 0x33, 0x71, 0xcd, 0xc4, 0x3b, 0xbd, 0xd0, 0xee, + 0x5c, 0xd4, 0x7f, 0xcb, 0x5c, 0x34, 0xd8, 0x3d, 0x17, 0xf9, 0xdb, 0xb9, 0xe8, 0x01, 0xf8, 0x73, + 0x91, 0x9c, 0x9f, 0xa9, 0x13, 0xf5, 0x89, 0x06, 0x58, 0x8d, 0xc7, 0x89, 0xe4, 0xd7, 0xcc, 0x0c, + 0x4b, 0x06, 0xdd, 0x7b, 0xb8, 0xc7, 0x3b, 0x26, 0x94, 0xff, 0x75, 0x66, 0xb2, 0x2d, 0x0a, 0x4e, + 0x8b, 0xc6, 0x30, 0xc5, 0xc1, 0x29, 0xa5, 0x92, 0x3e, 0x99, 0xbf, 0x7c, 0x61, 0xa7, 0x25, 0x57, + 0x16, 0xff, 0xd1, 0x83, 0xe1, 0x33, 0xda, 0x14, 0xb5, 0xbc, 0x57, 0xed, 0x33, 0x98, 0x1c, 0x97, + 0x65, 0xc6, 0x93, 0x4e, 0x87, 0x3b, 0x22, 0xb4, 0x78, 0xee, 0xe4, 0x51, 0xc7, 0xd0, 0x15, 0xe1, + 0x83, 0x72, 0xaa, 0x86, 0x20, 0x3d, 0xd1, 0x38, 0x0f, 0x8a, 0x9e, 0x7d, 0x94, 0x12, 0x83, 0x7d, + 0x5c, 0xcb, 0x62, 0x99, 0x15, 0x37, 0x2a, 0xaa, 0x63, 0xd2, 0xe2, 0xf8, 0x5f, 0x3d, 0x18, 0x7c, + 0x53, 0x83, 0xcb, 0x14, 0x3c, 0x6e, 0x8a, 0xca, 0xe3, 0xed, 0x18, 0x33, 0x72, 0xc6, 0x98, 0x08, + 0x46, 0x8d, 0xa0, 0xf9, 0x8a, 0x55, 0xd1, 0x58, 0xb1, 0x98, 0x85, 0x4a, 0xa3, 0xfa, 0x55, 0xcf, + 0x2f, 0x01, 0xb1, 0xb0, 0xed, 0x3f, 0x70, 0xfa, 0xef, 0xe7, 0x66, 0xd4, 0x99, 0xdc, 0x1d, 0x0e, + 0x76, 0x4d, 0x38, 0xff, 0xbf, 0x57, 0xfb, 0x3f, 0x1e, 0xf8, 0x6d, 0xf3, 0x9e, 0x76, 0x9b, 0xf7, + 0x74, 0xdb, 0xbc, 0x67, 0x27, 0xb6, 0x79, 0xcf, 0x4e, 0x10, 0x93, 0x0b, 0xdb, 0xbc, 0xe4, 0x02, + 0x93, 0xf5, 0x58, 0x14, 0x75, 0x79, 0xd2, 0xe8, 0xac, 0x06, 0xa4, 0xc5, 0x58, 0xf1, 0xbf, 0x59, + 0x33, 0x61, 0x42, 0x1d, 0x10, 0x83, 0xb0, 0x3f, 0x9e, 0x29, 0x62, 0xd3, 0xc1, 0xd5, 0x20, 0xfc, + 0x21, 0xf8, 0x04, 0x83, 0xa7, 0x22, 0xdc, 0xc9, 0x8b, 0x12, 0x13, 0xad, 0x45, 0xa7, 0xfa, 0x03, + 0xc8, 0x34, 0x8a, 0xfd, 0x1c, 0xfa, 0x19, 0x0c, 0xe7, 0x6b, 0xbe, 0x94, 0x76, 0x60, 0xfc, 0x96, + 0x43, 0x8c, 0x7c, 0xc3, 0x94, 0x8e, 0x18, 0x93, 0xf8, 0x15, 0x04, 0xad, 0x70, 0x7b, 0x1c, 0xcf, + 0x3d, 0x4e, 0x08, 0x83, 0xab, 0x9c, 0x4b, 0x4b, 0x11, 0xb8, 0xc6, 0xcb, 0xbe, 0xaa, 0x69, 0x2e, + 0xb9, 0x6c, 0x2c, 0x45, 0x58, 0x1c, 0x7f, 0x62, 0x8e, 0x8f, 0xee, 0xae, 0xca, 0x92, 0x09, 0x43, + 0x37, 0x1a, 0xa8, 0x4d, 0x8a, 0x1b, 0xa6, 0x5f, 0x8a, 0x3e, 0xd1, 0x20, 0xfe, 0x2d, 0x04, 0xc7, + 0x19, 0x13, 0x92, 0xd4, 0x19, 0xdb, 0xf5, 0x82, 0xab, 0x46, 0x35, 0x27, 0xc0, 0xf5, 0x96, 0x5a, + 0xfa, 0x77, 0xa8, 0xe5, 0x29, 0x2d, 0xe9, 0xf9, 0x99, 0xaa, 0xf3, 0x3e, 0x31, 0x28, 0xfe, 0xb3, + 0x07, 0x03, 0xe4, 0x30, 0xc7, 0xf5, 0xe0, 0x6d, 0xfc, 0x77, 0x21, 0x8a, 0x6b, 0x9e, 0x32, 0x61, + 0x2f, 0x67, 0xb1, 0x0a, 0x7a, 0xb2, 0x66, 0xed, 0xa0, 0x60, 0x10, 0xd6, 0x1a, 0x7e, 0x2d, 0xd9, + 0x5e, 0x72, 0x6a, 0x0d, 0xc5, 0x44, 0x2b, 0x71, 0x18, 0x9c, 0xd7, 0x25, 0x13, 0xc7, 0xe9, 0x86, + 0xdb, 0x29, 0xca, 0x91, 0xc4, 0x5f, 0xe8, 0xef, 0xaf, 0x7b, 0x4c, 0xe8, 0xed, 0xfe, 0x56, 0xbb, + 0x7b, 0xf2, 0xf8, 0x2f, 0x1e, 0x8c, 0x9e, 0x9b, 0xa9, 0xcd, 0xbd, 0x85, 0xf7, 0xc6, 0x5b, 0xf4, + 0x3a, 0xb7, 0x38, 0x82, 0x07, 0xd6, 0xa6, 0xb3, 0xbf, 0x8e, 0xc2, 0x4e, 0x9d, 0x89, 0xe8, 0xa0, + 0x4d, 0xd6, 0xbb, 0x7c, 0x7e, 0x5d, 0x76, 0x6d, 0x76, 0x25, 0xfc, 0x5e, 0x56, 0x66, 0x30, 0xb1, + 0x9f, 0x9d, 0x45, 0x66, 0x1f, 0x26, 0x57, 0x14, 0x1f, 0xc1, 0xf0, 0xb4, 0xc8, 0x97, 0x7c, 0x15, + 0x1e, 0xc2, 0xe0, 0xb8, 0x96, 0x6b, 0xe5, 0x71, 0x72, 0xf4, 0xc0, 0x69, 0xfc, 0x5a, 0xae, 0xb5, + 0x0d, 0x51, 0x16, 0xf1, 0x67, 0x00, 0x5b, 0x19, 0xbe, 0x2e, 0xdb, 0x6c, 0xbc, 0x60, 0x37, 0x58, + 0x32, 0x95, 0x19, 0xda, 0x77, 0x68, 0xe2, 0xcf, 0x21, 0x38, 0xa9, 0x79, 0x96, 0x9e, 0xe7, 0xcb, + 0x02, 0xa9, 0xe3, 0x35, 0x13, 0xd5, 0x36, 0x5f, 0x16, 0x62, 0xb8, 0x91, 0x45, 0xda, 0x1e, 0x32, + 0x68, 0x31, 0x54, 0x7f, 0x6a, 0x7c, 0xf2, 0xdf, 0x00, 0x00, 0x00, 0xff, 0xff, 0xd3, 0xfb, 0xbd, + 0x7b, 0xe6, 0x10, 0x00, 0x00, } diff --git a/bolt/internal/internal.proto b/bolt/internal/internal.proto index 99b887453..6070dbdb3 100644 --- a/bolt/internal/internal.proto +++ b/bolt/internal/internal.proto @@ -122,6 +122,7 @@ message Server { string Organization = 8; // Organization is the organization ID that resource belongs to bool InsecureSkipVerify = 9; // InsecureSkipVerify accepts any certificate from the client string Type = 10; // Type is the kind of the server (e.g. ifql) + string MetadataJSON = 11; // JSON byte representation of the metadata } message Layout { diff --git a/chronograf.go b/chronograf.go index a001310aa..7f48fb2b9 100644 --- a/chronograf.go +++ b/chronograf.go @@ -359,16 +359,17 @@ type KapacitorProperty struct { // Server represents a proxy connection to an HTTP server type Server struct { - ID int `json:"id,string"` // ID is the unique ID of the server - SrcID int `json:"srcId,string"` // SrcID of the data source - Name string `json:"name"` // Name is the user-defined name for the server - Username string `json:"username"` // Username is the username to connect to the server - Password string `json:"password"` // Password is in CLEARTEXT - URL string `json:"url"` // URL are the connections to the server - InsecureSkipVerify bool `json:"insecureSkipVerify"` // InsecureSkipVerify as true means any certificate presented by the server is accepted. - Active bool `json:"active"` // Is this the active server for the source? - Organization string `json:"organization"` // Organization is the organization ID that resource belongs to - Type string `json:"type"` // Type is the kind of service (e.g. kapacitor or ifql) + ID int `json:"id,string"` // ID is the unique ID of the server + SrcID int `json:"srcId,string"` // SrcID of the data source + Name string `json:"name"` // Name is the user-defined name for the server + Username string `json:"username"` // Username is the username to connect to the server + Password string `json:"password"` // Password is in CLEARTEXT + URL string `json:"url"` // URL are the connections to the server + InsecureSkipVerify bool `json:"insecureSkipVerify"` // InsecureSkipVerify as true means any certificate presented by the server is accepted. + Active bool `json:"active"` // Is this the active server for the source? + Organization string `json:"organization"` // Organization is the organization ID that resource belongs to + Type string `json:"type"` // Type is the kind of service (e.g. kapacitor or ifql) + Metadata map[string]interface{} `json:"metadata"` // Metadata is any other data that the frontend wants to store about this service } // ServersStore stores connection information for a `Server` diff --git a/server/services.go b/server/services.go index 95e58eb1f..f2cb6c9c0 100644 --- a/server/services.go +++ b/server/services.go @@ -10,14 +10,14 @@ import ( ) type postServiceRequest struct { - Name *string `json:"name"` // User facing name of service instance.; Required: true - URL *string `json:"url"` // URL for the service backend (e.g. http://localhost:9092);/ Required: true - Type *string `json:"type"` // Type is the kind of service (e.g. ifql); Required - Username string `json:"username,omitempty"` // Username for authentication to service - Password string `json:"password,omitempty"` - InsecureSkipVerify bool `json:"insecureSkipVerify"` // InsecureSkipVerify as true means any certificate presented by the service is accepted. - Organization string `json:"organization"` // Organization is the organization ID that resource belongs to - + Name *string `json:"name"` // User facing name of service instance.; Required: true + URL *string `json:"url"` // URL for the service backend (e.g. http://localhost:9092);/ Required: true + Type *string `json:"type"` // Type is the kind of service (e.g. ifql); Required + Username string `json:"username,omitempty"` // Username for authentication to service + Password string `json:"password,omitempty"` + InsecureSkipVerify bool `json:"insecureSkipVerify"` // InsecureSkipVerify as true means any certificate presented by the service is accepted. + Organization string `json:"organization"` // Organization is the organization ID that resource belongs to + Metadata map[string]interface{} `json:"metadata"` // Metadata is any other data that the frontend wants to store about this service } func (p *postServiceRequest) Valid(defaultOrgID string) error { @@ -51,15 +51,42 @@ type serviceLinks struct { } type service struct { - ID int `json:"id,string"` // Unique identifier representing a service instance. - SrcID int `json:"sourceID,string"` // SrcID of the data source - Name string `json:"name"` // User facing name of service instance. - URL string `json:"url"` // URL for the service backend (e.g. http://localhost:9092) - Username string `json:"username,omitempty"` // Username for authentication to service - Password string `json:"password,omitempty"` - InsecureSkipVerify bool `json:"insecureSkipVerify"` // InsecureSkipVerify as true means any certificate presented by the service is accepted. - Type string `json:"type"` // Type is the kind of service (e.g. ifql) - Links serviceLinks `json:"links"` // Links are URI locations related to service + ID int `json:"id,string"` // Unique identifier representing a service instance. + SrcID int `json:"sourceID,string"` // SrcID of the data source + Name string `json:"name"` // User facing name of service instance. + URL string `json:"url"` // URL for the service backend (e.g. http://localhost:9092) + Username string `json:"username,omitempty"` // Username for authentication to service + Password string `json:"password,omitempty"` + InsecureSkipVerify bool `json:"insecureSkipVerify"` // InsecureSkipVerify as true means any certificate presented by the service is accepted. + Type string `json:"type"` // Type is the kind of service (e.g. ifql) + Metadata map[string]interface{} `json:"metadata"` // Metadata is any other data that the frontend wants to store about this service + Links serviceLinks `json:"links"` // Links are URI locations related to service +} + +func newService(srv chronograf.Server) service { + if srv.Metadata == nil { + srv.Metadata = make(map[string]interface{}) + } + httpAPISrcs := "/chronograf/v1/sources" + return service{ + ID: srv.ID, + SrcID: srv.SrcID, + Name: srv.Name, + Username: srv.Username, + URL: srv.URL, + InsecureSkipVerify: srv.InsecureSkipVerify, + Type: srv.Type, + Metadata: srv.Metadata, + Links: serviceLinks{ + Self: fmt.Sprintf("%s/%d/services/%d", httpAPISrcs, srv.SrcID, srv.ID), + Source: fmt.Sprintf("%s/%d", httpAPISrcs, srv.SrcID), + Proxy: fmt.Sprintf("%s/%d/services/%d/proxy", httpAPISrcs, srv.SrcID, srv.ID), + }, + } +} + +type services struct { + Services []service `json:"services"` } // NewService adds valid service store store. @@ -103,6 +130,7 @@ func (s *Service) NewService(w http.ResponseWriter, r *http.Request) { URL: *req.URL, Organization: req.Organization, Type: *req.Type, + Metadata: req.Metadata, } if srv, err = s.Store.Servers(ctx).Add(ctx, srv); err != nil { @@ -116,28 +144,6 @@ func (s *Service) NewService(w http.ResponseWriter, r *http.Request) { encodeJSON(w, http.StatusCreated, res, s.Logger) } -func newService(srv chronograf.Server) service { - httpAPISrcs := "/chronograf/v1/sources" - return service{ - ID: srv.ID, - SrcID: srv.SrcID, - Name: srv.Name, - Username: srv.Username, - URL: srv.URL, - InsecureSkipVerify: srv.InsecureSkipVerify, - Type: srv.Type, - Links: serviceLinks{ - Self: fmt.Sprintf("%s/%d/services/%d", httpAPISrcs, srv.SrcID, srv.ID), - Source: fmt.Sprintf("%s/%d", httpAPISrcs, srv.SrcID), - Proxy: fmt.Sprintf("%s/%d/services/%d/proxy", httpAPISrcs, srv.SrcID, srv.ID), - }, - } -} - -type services struct { - Services []service `json:"services"` -} - // Services retrieves all services from store. func (s *Service) Services(w http.ResponseWriter, r *http.Request) { srcID, err := paramID("id", r) @@ -222,12 +228,13 @@ func (s *Service) RemoveService(w http.ResponseWriter, r *http.Request) { } type patchServiceRequest struct { - Name *string `json:"name,omitempty"` // User facing name of service instance. - Type *string `json:"type,omitempty"` // Type is the kind of service (e.g. ifql) - URL *string `json:"url,omitempty"` // URL for the service - Username *string `json:"username,omitempty"` // Username for service auth - Password *string `json:"password,omitempty"` - InsecureSkipVerify *bool `json:"insecureSkipVerify"` // InsecureSkipVerify as true means any certificate presented by the service is accepted. + Name *string `json:"name,omitempty"` // User facing name of service instance. + Type *string `json:"type,omitempty"` // Type is the kind of service (e.g. ifql) + URL *string `json:"url,omitempty"` // URL for the service + Username *string `json:"username,omitempty"` // Username for service auth + Password *string `json:"password,omitempty"` + InsecureSkipVerify *bool `json:"insecureSkipVerify"` // InsecureSkipVerify as true means any certificate presented by the service is accepted. + Metadata *map[string]interface{} `json:"metadata"` // Metadata is any other data that the frontend wants to store about this service } func (p *patchServiceRequest) Valid() error { @@ -298,6 +305,9 @@ func (s *Service) UpdateService(w http.ResponseWriter, r *http.Request) { if req.InsecureSkipVerify != nil { srv.InsecureSkipVerify = *req.InsecureSkipVerify } + if req.Metadata != nil { + srv.Metadata = *req.Metadata + } if err := s.Store.Servers(ctx).Update(ctx, srv); err != nil { msg := fmt.Sprintf("Error updating service ID %d", id) From 19b1dfc7d548621b9da0991b8df238ce8580a8df Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Fri, 18 May 2018 09:19:51 -0700 Subject: [PATCH 40/49] Import Service type --- ui/src/types/sources.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/types/sources.ts b/ui/src/types/sources.ts index 4e78076e6..22d5ef9ea 100644 --- a/ui/src/types/sources.ts +++ b/ui/src/types/sources.ts @@ -1,4 +1,4 @@ -import {Kapacitor} from './' +import {Kapacitor, Service} from './' export interface Source { id: string From c8c62f1de31805f8321523bfa687bdc4f0b95787 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Fri, 18 May 2018 10:42:45 -0700 Subject: [PATCH 41/49] Remove services from ifql table --- ui/src/shared/actions/services.ts | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/ui/src/shared/actions/services.ts b/ui/src/shared/actions/services.ts index 46ede7d73..51e0c69a5 100644 --- a/ui/src/shared/actions/services.ts +++ b/ui/src/shared/actions/services.ts @@ -1,10 +1,11 @@ -import {Service} from 'src/types' +import {Source, Service} from 'src/types' export type Action = | ActionLoadServices | ActionAddService | ActionDeleteService | ActionUpdateService + | ActionSetActiveService // Load Services export type LoadServices = (services: Service[]) => ActionLoadServices @@ -69,3 +70,27 @@ export const updateService = (service: Service): ActionUpdateService => ({ service, }, }) + +// Set Active Service +export type SetActiveService = ( + source: Source, + service: Service +) => ActionSetActiveService +export interface ActionSetActiveService { + type: 'SET_ACTIVE_SERVICE' + payload: { + source: Source + service: Service + } +} + +export const setActiveService = ( + source: Source, + service: Service +): ActionSetActiveService => ({ + type: 'SET_ACTIVE_SERVICE', + payload: { + source, + service, + }, +}) From 623664696e9014493d2ae8ed64df3fc9d490806a Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Fri, 18 May 2018 12:34:40 -0700 Subject: [PATCH 42/49] Introduce CheckServices component --- ui/src/ifql/components/TimeMachine.tsx | 6 +- ui/src/ifql/containers/CheckServices.tsx | 38 ++++++ ui/src/ifql/containers/IFQLPage.tsx | 71 ++++++----- ui/src/ifql/index.ts | 3 +- ui/src/index.tsx | 2 +- ui/src/shared/actions/services.ts | 16 +++ ui/src/shared/apis/index.ts | 16 ++- ui/src/shared/copy/notifications.js | 6 + ui/src/shared/reducers/services.ts | 3 + ui/src/sources/components/InfluxTableHead.tsx | 1 - ui/src/sources/components/InfluxTableRow.tsx | 1 - ui/src/sources/components/ServiceDropdown.tsx | 119 ++++++++++++++++++ ui/src/store/configureStore.js | 2 + ui/src/types/sources.ts | 1 + 14 files changed, 242 insertions(+), 43 deletions(-) create mode 100644 ui/src/ifql/containers/CheckServices.tsx create mode 100644 ui/src/sources/components/ServiceDropdown.tsx diff --git a/ui/src/ifql/components/TimeMachine.tsx b/ui/src/ifql/components/TimeMachine.tsx index 8e56d0e40..fe53c9ee5 100644 --- a/ui/src/ifql/components/TimeMachine.tsx +++ b/ui/src/ifql/components/TimeMachine.tsx @@ -82,11 +82,7 @@ class TimeMachine extends PureComponent { return [ { name: 'Explore', - headerButtons: [ -
- Analyze -
, - ], + headerButtons: [], menuOptions: [], render: () => , }, diff --git a/ui/src/ifql/containers/CheckServices.tsx b/ui/src/ifql/containers/CheckServices.tsx new file mode 100644 index 000000000..4db734238 --- /dev/null +++ b/ui/src/ifql/containers/CheckServices.tsx @@ -0,0 +1,38 @@ +import {PureComponent, ReactChildren} from 'react' +import {connect} from 'react-redux' +import {withRouter, WithRouterProps} from 'react-router' +import {Source} from 'src/types' + +import * as actions from 'src/shared/actions/services' + +interface Props { + sources: Source[] + children: ReactChildren + fetchServicesAsync: actions.FetchServicesAsync +} + +export class CheckServices extends PureComponent { + public async componentDidMount() { + const source = this.props.sources.find( + s => s.id === this.props.params.sourceID + ) + + if (!source) { + return + } + + await this.props.fetchServicesAsync(source) + } + + public render() { + return this.props.children + } +} + +const mdtp = { + fetchServicesAsync: actions.fetchServicesAsync, +} + +const mstp = ({sources}) => ({sources}) + +export default connect(mstp, mdtp)(withRouter(CheckServices)) diff --git a/ui/src/ifql/containers/IFQLPage.tsx b/ui/src/ifql/containers/IFQLPage.tsx index 347e9af9e..b7491425d 100644 --- a/ui/src/ifql/containers/IFQLPage.tsx +++ b/ui/src/ifql/containers/IFQLPage.tsx @@ -3,8 +3,9 @@ import {bindActionCreators} from 'redux' import {connect} from 'react-redux' import _ from 'lodash' -import TimeMachine from 'src/ifql/components/TimeMachine' import {ErrorHandling} from 'src/shared/decorators/errors' +import CheckServices from 'src/ifql/containers/CheckServices' +import TimeMachine from 'src/ifql/components/TimeMachine' import KeyboardShortcuts from 'src/shared/components/KeyboardShortcuts' import {InputArg, Handlers, DeleteFuncNodeArgs, Func} from 'src/types/ifql' import {notify as notifyAction} from 'src/shared/actions/notifications' @@ -16,6 +17,7 @@ import {builder, argTypes} from 'src/ifql/constants' import {Notification} from 'src/types' import {Suggestion, FlatBody, Links} from 'src/types/ifql' +import {Service} from 'src/types' interface Status { type: string @@ -24,6 +26,7 @@ interface Status { interface Props { links: Links + services: Service[] notify: (message: Notification) => void } @@ -76,39 +79,41 @@ export class IFQLPage extends PureComponent { const {suggestions, script, data, body, status} = this.state return ( - - -
-
-
-
-

Time Machine

-
-
- + + + +
+
+
+
+

Time Machine

+
+
+ +
+
- -
- - + + + ) } @@ -416,8 +421,8 @@ export class IFQLPage extends PureComponent { } } -const mapStateToProps = ({links}) => { - return {links: links.ifql} +const mapStateToProps = ({links, services}) => { + return {links: links.ifql, services} } const mapDispatchToProps = dispatch => ({ diff --git a/ui/src/ifql/index.ts b/ui/src/ifql/index.ts index b2166a88b..8ad04201b 100644 --- a/ui/src/ifql/index.ts +++ b/ui/src/ifql/index.ts @@ -1,3 +1,4 @@ import IFQLPage from 'src/ifql/containers/IFQLPage' +import CheckServices from 'src/ifql/containers/CheckServices' -export {IFQLPage} +export {IFQLPage, CheckServices} diff --git a/ui/src/index.tsx b/ui/src/index.tsx index 9d8b0d651..743e781c1 100644 --- a/ui/src/index.tsx +++ b/ui/src/index.tsx @@ -35,7 +35,7 @@ import { } from 'src/kapacitor' import {AdminChronografPage, AdminInfluxDBPage} from 'src/admin' import {SourcePage, ManageSources} from 'src/sources' -import {IFQLPage} from 'src/ifql/index' +import {IFQLPage} from 'src/ifql' import NotFound from 'src/shared/components/NotFound' import {getLinksAsync} from 'src/shared/actions/links' diff --git a/ui/src/shared/actions/services.ts b/ui/src/shared/actions/services.ts index 51e0c69a5..059ac0938 100644 --- a/ui/src/shared/actions/services.ts +++ b/ui/src/shared/actions/services.ts @@ -1,4 +1,7 @@ import {Source, Service} from 'src/types' +import {getServices as getServicesAJAX} from 'src/shared/apis' +import {notify} from './notifications' +import {couldNotGetServices} from 'src/shared/copy/notifications' export type Action = | ActionLoadServices @@ -94,3 +97,16 @@ export const setActiveService = ( service, }, }) + +export type FetchServicesAsync = (source: Source) => (dispatch) => Promise + +export const fetchServicesAsync = (source: Source) => async ( + dispatch +): Promise => { + try { + const services = await getServicesAJAX(source.links.services) + dispatch(loadServices(services)) + } catch (err) { + dispatch(notify(couldNotGetServices)) + } +} diff --git a/ui/src/shared/apis/index.ts b/ui/src/shared/apis/index.ts index 94590db34..a74772153 100644 --- a/ui/src/shared/apis/index.ts +++ b/ui/src/shared/apis/index.ts @@ -1,6 +1,6 @@ import AJAX from 'src/utils/ajax' import {AlertTypes} from 'src/kapacitor/constants' -import {Kapacitor} from 'src/types' +import {Kapacitor, Service} from 'src/types' export function getSources() { return AJAX({ @@ -303,3 +303,17 @@ export const getQueryConfigAndStatus = (url, queries, tempVars = []) => method: 'POST', data: {queries, tempVars}, }) + +export const getServices = async (url: string): Promise => { + try { + const {data} = await AJAX({ + url, + method: 'GET', + }) + + return data + } catch (error) { + console.error(error) + throw error + } +} diff --git a/ui/src/shared/copy/notifications.js b/ui/src/shared/copy/notifications.js index 8f0c996b4..99f237b3f 100644 --- a/ui/src/shared/copy/notifications.js +++ b/ui/src/shared/copy/notifications.js @@ -614,3 +614,9 @@ export const analyzeSuccess = { ...defaultSuccessNotification, message: 'No errors found. Happy Happy Joy Joy!', } + +// Service notifications +export const couldNotGetServices = { + ...defaultErrorNotification, + message: 'We could not get services', +} diff --git a/ui/src/shared/reducers/services.ts b/ui/src/shared/reducers/services.ts index b6f539f63..d1c7df29d 100644 --- a/ui/src/shared/reducers/services.ts +++ b/ui/src/shared/reducers/services.ts @@ -32,6 +32,9 @@ const servicesReducer = (state = initialState, action: Action): Service[] => { return newState } + + case 'SET_ACTIVE_SERVICE': { + } } return state diff --git a/ui/src/sources/components/InfluxTableHead.tsx b/ui/src/sources/components/InfluxTableHead.tsx index 2d0624f07..7f80b3b03 100644 --- a/ui/src/sources/components/InfluxTableHead.tsx +++ b/ui/src/sources/components/InfluxTableHead.tsx @@ -13,7 +13,6 @@ const InfluxTableHead: SFC<{}> = (): ReactElement< InfluxDB Connection - Services Connection Kapacitor Connection { /> - Services Connection void +} + +interface ServiceItem { + text: string + resource: string + service: Service +} + +class ServiceDropdown extends PureComponent< + Props & RouteComponentProps +> { + public render() { + const {source, router, setActiveService, deleteService} = this.props + + if (this.isServicesEmpty) { + return ( + + + Add Service Connection + + + ) + } + + return ( + + { + router.push(`${item.resource}/edit`) + }, + }, + { + icon: 'trash', + text: 'delete', + handler: item => { + deleteService(item.service) + }, + confirmable: true, + }, + ]} + selected={this.selected} + /> + + ) + } + + private get UnauthorizedDropdown(): ReactElement { + return ( +
{this.selected}
+ ) + } + + private get isServicesEmpty(): boolean { + const {services} = this.props + return !services || services.length === 0 + } + + private get serviceItems(): ServiceItem[] { + const {services, source} = this.props + + return services.map(service => { + return { + text: service.name, + resource: `/sources/${source.id}/services/${service.id}`, + service, + } + }) + } + + private get activeService(): Service { + return this.props.services.find(s => s.active) + } + + private get selected(): string { + let selected = '' + if (this.activeService) { + selected = this.activeService.name + } else { + selected = this.serviceItems[0].text + } + + return selected + } +} + +export default withRouter(ServiceDropdown) diff --git a/ui/src/store/configureStore.js b/ui/src/store/configureStore.js index e710b6ef5..f4322b61d 100644 --- a/ui/src/store/configureStore.js +++ b/ui/src/store/configureStore.js @@ -16,6 +16,7 @@ import cellEditorOverlay from 'src/dashboards/reducers/cellEditorOverlay' import overlayTechnology from 'src/shared/reducers/overlayTechnology' import dashTimeV1 from 'src/dashboards/reducers/dashTimeV1' import persistStateEnhancer from './persistStateEnhancer' +import servicesReducer from 'src/shared/reducers/services' const rootReducer = combineReducers({ ...statusReducers, @@ -28,6 +29,7 @@ const rootReducer = combineReducers({ overlayTechnology, dashTimeV1, routing: routerReducer, + services: servicesReducer, }) const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose diff --git a/ui/src/types/sources.ts b/ui/src/types/sources.ts index 22d5ef9ea..c3bc3f3eb 100644 --- a/ui/src/types/sources.ts +++ b/ui/src/types/sources.ts @@ -32,4 +32,5 @@ export interface SourceLinks { databases: string annotations: string health: string + services: string } From 1690639c0b625762dc385c44366deb84bce2db97 Mon Sep 17 00:00:00 2001 From: ebb-tide Date: Fri, 18 May 2018 14:42:30 -0700 Subject: [PATCH 43/49] Improve temporary amendment of suggestions to include join --- ui/src/ifql/containers/IFQLPage.tsx | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/ui/src/ifql/containers/IFQLPage.tsx b/ui/src/ifql/containers/IFQLPage.tsx index b7491425d..6bcc52473 100644 --- a/ui/src/ifql/containers/IFQLPage.tsx +++ b/ui/src/ifql/containers/IFQLPage.tsx @@ -14,6 +14,7 @@ import {analyzeSuccess} from 'src/shared/copy/notifications' import {bodyNodes} from 'src/ifql/helpers' import {getSuggestions, getAST, getTimeSeries} from 'src/ifql/apis' import {builder, argTypes} from 'src/ifql/constants' +import {funcNames} from 'src/ifql/constants' import {Notification} from 'src/types' import {Suggestion, FlatBody, Links} from 'src/types/ifql' @@ -385,12 +386,20 @@ export class IFQLPage extends PureComponent { try { const ast = await getAST({url: links.ast, body: script}) - const suggs = this.state.suggestions - suggs[17] = { - name: 'join', - params: {tables: 'array', on: 'string', fn: 'function'}, - } - const body = bodyNodes(ast, suggs) + const suggestions = this.state.suggestions.map(s => { + if (s.name === funcNames.JOIN) { + return { + ...s, + params: { + tables: 'object', + on: 'array', + fn: 'function', + }, + } + } + return s + }) + const body = bodyNodes(ast, suggestions) const status = {type: 'success', text: ''} this.setState({ast, script, body, status}) } catch (error) { From 100809af4863f776b839948b531601e7026e10f2 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Fri, 18 May 2018 16:00:04 -0700 Subject: [PATCH 44/49] Introduce ability to add an IFQL connection --- ui/src/ifql/components/IFQLForm.tsx | 80 ++++++++++++++++ ui/src/ifql/components/IFQLOverlay.tsx | 95 +++++++++++++++++++ ui/src/ifql/containers/CheckServices.tsx | 40 +++++++- ui/src/ifql/containers/IFQLPage.tsx | 33 ++++--- .../components/KapacitorFormInput.tsx | 7 +- ui/src/kapacitor/containers/KapacitorPage.tsx | 2 +- ui/src/shared/actions/overlayTechnology.ts | 13 +++ ui/src/shared/actions/services.ts | 26 ++++- ui/src/shared/apis/index.ts | 27 +++++- .../{notifications.js => notifications.ts} | 12 ++- .../components/time-machine/ifql-overlay.scss | 4 + ui/src/style/pages/time-machine.scss | 1 + ui/src/types/index.ts | 3 +- ui/src/types/services.ts | 10 ++ 14 files changed, 328 insertions(+), 25 deletions(-) create mode 100644 ui/src/ifql/components/IFQLForm.tsx create mode 100644 ui/src/ifql/components/IFQLOverlay.tsx rename ui/src/shared/copy/{notifications.js => notifications.ts} (98%) create mode 100644 ui/src/style/components/time-machine/ifql-overlay.scss diff --git a/ui/src/ifql/components/IFQLForm.tsx b/ui/src/ifql/components/IFQLForm.tsx new file mode 100644 index 000000000..1042b007d --- /dev/null +++ b/ui/src/ifql/components/IFQLForm.tsx @@ -0,0 +1,80 @@ +import React, {ChangeEvent, PureComponent} from 'react' + +import Input from 'src/kapacitor/components/KapacitorFormInput' + +import {NewService} from 'src/types' + +interface Props { + service: NewService + exists: boolean + onSubmit: (e: ChangeEvent) => void + onInputChange: (e: ChangeEvent) => void +} + +class IFQLForm extends PureComponent { + public render() { + const {service, onSubmit, onInputChange} = this.props + + return ( +
+
+
+

Connect to IFQL

+
+
+
+
+
+ + +
+ +
+
+
+
+ ) + } + + private get buttonText(): string { + const {exists} = this.props + + if (exists) { + return 'Update' + } + + return 'Connect' + } + + private get url(): string { + const { + service: {url}, + } = this.props + if (url) { + return url + } + + return '' + } +} + +export default IFQLForm diff --git a/ui/src/ifql/components/IFQLOverlay.tsx b/ui/src/ifql/components/IFQLOverlay.tsx new file mode 100644 index 000000000..6c3587574 --- /dev/null +++ b/ui/src/ifql/components/IFQLOverlay.tsx @@ -0,0 +1,95 @@ +import React, {PureComponent, ChangeEvent} from 'react' +import {connect} from 'react-redux' + +import IFQLForm from 'src/ifql/components/IFQLForm' + +import {Source, NewService, Notification} from 'src/types' + +import {notify as notifyAction} from 'src/shared/actions/notifications' +import { + createServiceAsync, + CreateServiceAsync, +} from 'src/shared/actions/services' +import {ifqlCreated, ifqlNotCreated} from 'src/shared/copy/notifications' + +interface Props { + source: Source + onDismiss: () => void + notify: (message: Notification) => void + createService: CreateServiceAsync +} + +interface State { + service: NewService +} + +const port = 8093 + +class IFQLOverlay extends PureComponent { + constructor(props) { + super(props) + this.state = { + service: this.defaultService, + } + } + + public render() { + return ( + + ) + } + + private get defaultService(): NewService { + return { + name: 'IFQL', + url: this.url, + username: '', + insecureSkipVerify: false, + type: 'ifql', + active: true, + } + } + + private handleInputChange = (e: ChangeEvent): void => { + const {value, name} = e.target + const update = {[name]: value} + + this.setState({service: {...this.state.service, ...update}}) + } + + private handleSubmit = async e => { + e.preventDefault() + const {notify, source, onDismiss, createService} = this.props + + const {service} = this.state + + try { + await createService(source, service) + } catch (error) { + notify(ifqlNotCreated(error.message)) + return + } + + notify(ifqlCreated) + onDismiss() + } + + private get url(): string { + const parser = document.createElement('a') + parser.href = this.props.source.url + + return `${parser.protocol}//${parser.hostname}:${port}` + } +} + +const mdtp = { + notify: notifyAction, + createService: createServiceAsync, +} + +export default connect(null, mdtp)(IFQLOverlay) diff --git a/ui/src/ifql/containers/CheckServices.tsx b/ui/src/ifql/containers/CheckServices.tsx index 4db734238..d452a2563 100644 --- a/ui/src/ifql/containers/CheckServices.tsx +++ b/ui/src/ifql/containers/CheckServices.tsx @@ -1,14 +1,21 @@ -import {PureComponent, ReactChildren} from 'react' +import React, {PureComponent, ReactChildren} from 'react' import {connect} from 'react-redux' import {withRouter, WithRouterProps} from 'react-router' -import {Source} from 'src/types' -import * as actions from 'src/shared/actions/services' +import IFQLOverlay from 'src/ifql/components/IFQLOverlay' +import {OverlayContext} from 'src/shared/components/OverlayTechnology' +import {Source, Service} from 'src/types' +import * as a from 'src/shared/actions/overlayTechnology' +import * as b from 'src/shared/actions/services' + +const actions = {...a, ...b} interface Props { sources: Source[] + services: Service[] children: ReactChildren - fetchServicesAsync: actions.FetchServicesAsync + showOverlay: a.ShowOverlay + fetchServicesAsync: b.FetchServicesAsync } export class CheckServices extends PureComponent { @@ -22,17 +29,40 @@ export class CheckServices extends PureComponent { } await this.props.fetchServicesAsync(source) + + if (!this.props.services.length) { + this.overlay() + } } public render() { return this.props.children } + + private overlay() { + const {showOverlay, services, sources, params} = this.props + const source = sources.find(s => s.id === params.sourceID) + + if (services.length) { + return + } + + showOverlay( + + {({onDismissOverlay}) => ( + + )} + , + {} + ) + } } const mdtp = { fetchServicesAsync: actions.fetchServicesAsync, + showOverlay: actions.showOverlay, } -const mstp = ({sources}) => ({sources}) +const mstp = ({sources, services}) => ({sources, services}) export default connect(mstp, mdtp)(withRouter(CheckServices)) diff --git a/ui/src/ifql/containers/IFQLPage.tsx b/ui/src/ifql/containers/IFQLPage.tsx index 6bcc52473..7adbba3dd 100644 --- a/ui/src/ifql/containers/IFQLPage.tsx +++ b/ui/src/ifql/containers/IFQLPage.tsx @@ -1,5 +1,5 @@ import React, {PureComponent} from 'react' -import {bindActionCreators} from 'redux' +import {RouteComponentProps} from 'react-router' import {connect} from 'react-redux' import _ from 'lodash' @@ -7,7 +7,7 @@ import {ErrorHandling} from 'src/shared/decorators/errors' import CheckServices from 'src/ifql/containers/CheckServices' import TimeMachine from 'src/ifql/components/TimeMachine' import KeyboardShortcuts from 'src/shared/components/KeyboardShortcuts' -import {InputArg, Handlers, DeleteFuncNodeArgs, Func} from 'src/types/ifql' + import {notify as notifyAction} from 'src/shared/actions/notifications' import {analyzeSuccess} from 'src/shared/copy/notifications' @@ -16,9 +16,16 @@ import {getSuggestions, getAST, getTimeSeries} from 'src/ifql/apis' import {builder, argTypes} from 'src/ifql/constants' import {funcNames} from 'src/ifql/constants' -import {Notification} from 'src/types' -import {Suggestion, FlatBody, Links} from 'src/types/ifql' -import {Service} from 'src/types' +import {Source, Service, Notification} from 'src/types' +import { + Suggestion, + FlatBody, + Links, + InputArg, + Handlers, + DeleteFuncNodeArgs, + Func, +} from 'src/types/ifql' interface Status { type: string @@ -28,6 +35,7 @@ interface Status { interface Props { links: Links services: Service[] + sources: Source[] notify: (message: Notification) => void } @@ -47,7 +55,10 @@ interface State { export const IFQLContext = React.createContext() @ErrorHandling -export class IFQLPage extends PureComponent { +export class IFQLPage extends PureComponent< + Props & RouteComponentProps, + State +> { constructor(props) { super(props) this.state = { @@ -430,12 +441,12 @@ export class IFQLPage extends PureComponent { } } -const mapStateToProps = ({links, services}) => { - return {links: links.ifql, services} +const mapStateToProps = ({links, services, sources}) => { + return {links: links.ifql, services, sources} } -const mapDispatchToProps = dispatch => ({ - notify: bindActionCreators(notifyAction, dispatch), -}) +const mapDispatchToProps = { + notify: notifyAction, +} export default connect(mapStateToProps, mapDispatchToProps)(IFQLPage) diff --git a/ui/src/kapacitor/components/KapacitorFormInput.tsx b/ui/src/kapacitor/components/KapacitorFormInput.tsx index fe5439a83..29619a1b2 100644 --- a/ui/src/kapacitor/components/KapacitorFormInput.tsx +++ b/ui/src/kapacitor/components/KapacitorFormInput.tsx @@ -5,9 +5,10 @@ interface Props { label: string value: string placeholder: string - onChange: (e: ChangeEvent) => void maxLength?: number inputType?: string + customClass?: string + onChange: (e: ChangeEvent) => void } const KapacitorFormInput: SFC = ({ @@ -18,8 +19,9 @@ const KapacitorFormInput: SFC = ({ onChange, maxLength, inputType, + customClass, }) => ( -
+
= ({ KapacitorFormInput.defaultProps = { inputType: '', + customClass: 'col-sm-6', } export default KapacitorFormInput diff --git a/ui/src/kapacitor/containers/KapacitorPage.tsx b/ui/src/kapacitor/containers/KapacitorPage.tsx index a2f73e7cc..3084965a4 100644 --- a/ui/src/kapacitor/containers/KapacitorPage.tsx +++ b/ui/src/kapacitor/containers/KapacitorPage.tsx @@ -169,6 +169,7 @@ export class KapacitorPage extends PureComponent { return ( { onChangeUrl={this.handleChangeUrl} onReset={this.handleResetToDefaults} onInputChange={this.handleInputChange} - notify={notify} onCheckboxChange={this.handleCheckboxChange} /> ) diff --git a/ui/src/shared/actions/overlayTechnology.ts b/ui/src/shared/actions/overlayTechnology.ts index 69fb9eac0..ec19ed442 100644 --- a/ui/src/shared/actions/overlayTechnology.ts +++ b/ui/src/shared/actions/overlayTechnology.ts @@ -8,6 +8,19 @@ interface Options { transitionTime?: number } +export type ShowOverlay = ( + OverlayNode: OverlayNodeType, + options: Options +) => ActionOverlayNode + +export interface ActionOverlayNode { + type: 'SHOW_OVERLAY' + payload: { + OverlayNode + options + } +} + export const showOverlay = ( OverlayNode: OverlayNodeType, options: Options diff --git a/ui/src/shared/actions/services.ts b/ui/src/shared/actions/services.ts index 059ac0938..1c56e22f0 100644 --- a/ui/src/shared/actions/services.ts +++ b/ui/src/shared/actions/services.ts @@ -1,5 +1,8 @@ -import {Source, Service} from 'src/types' -import {getServices as getServicesAJAX} from 'src/shared/apis' +import {Source, Service, NewService} from 'src/types' +import { + getServices as getServicesAJAX, + createService as createServiceAJAX, +} from 'src/shared/apis' import {notify} from './notifications' import {couldNotGetServices} from 'src/shared/copy/notifications' @@ -99,7 +102,6 @@ export const setActiveService = ( }) export type FetchServicesAsync = (source: Source) => (dispatch) => Promise - export const fetchServicesAsync = (source: Source) => async ( dispatch ): Promise => { @@ -110,3 +112,21 @@ export const fetchServicesAsync = (source: Source) => async ( dispatch(notify(couldNotGetServices)) } } + +export type CreateServiceAsync = ( + source: Source, + service: NewService +) => (dispatch) => Promise + +export const createServiceAsync = ( + source: Source, + service: NewService +) => async (dispatch): Promise => { + try { + const s = await createServiceAJAX(source, service) + dispatch(addService(s)) + } catch (err) { + console.error(err.data) + throw err.data + } +} diff --git a/ui/src/shared/apis/index.ts b/ui/src/shared/apis/index.ts index a74772153..d81f9fd7a 100644 --- a/ui/src/shared/apis/index.ts +++ b/ui/src/shared/apis/index.ts @@ -1,6 +1,6 @@ import AJAX from 'src/utils/ajax' import {AlertTypes} from 'src/kapacitor/constants' -import {Kapacitor, Service} from 'src/types' +import {Kapacitor, Source, Service, NewService} from 'src/types' export function getSources() { return AJAX({ @@ -311,6 +311,31 @@ export const getServices = async (url: string): Promise => { method: 'GET', }) + return data.services + } catch (error) { + console.error(error) + throw error + } +} + +export const createService = async ( + source: Source, + { + url, + name = 'My IFQLD', + type, + username, + password, + insecureSkipVerify, + }: NewService +): Promise => { + try { + const {data} = await AJAX({ + url: source.links.services, + method: 'POST', + data: {url, name, type, username, password, insecureSkipVerify}, + }) + return data } catch (error) { console.error(error) diff --git a/ui/src/shared/copy/notifications.js b/ui/src/shared/copy/notifications.ts similarity index 98% rename from ui/src/shared/copy/notifications.js rename to ui/src/shared/copy/notifications.ts index 99f237b3f..16c912a9b 100644 --- a/ui/src/shared/copy/notifications.js +++ b/ui/src/shared/copy/notifications.ts @@ -1,7 +1,7 @@ // All copy for notifications should be stored here for easy editing // and ensuring stylistic consistency -import {FIVE_SECONDS, TEN_SECONDS, INFINITE} from 'shared/constants/index' +import {FIVE_SECONDS, TEN_SECONDS, INFINITE} from 'src/shared/constants/index' const defaultErrorNotification = { type: 'error', @@ -620,3 +620,13 @@ export const couldNotGetServices = { ...defaultErrorNotification, message: 'We could not get services', } + +export const ifqlCreated = { + ...defaultSuccessNotification, + message: 'IFQL Connection Created. Script your heart out!', +} + +export const ifqlNotCreated = (message: string) => ({ + ...defaultErrorNotification, + message, +}) diff --git a/ui/src/style/components/time-machine/ifql-overlay.scss b/ui/src/style/components/time-machine/ifql-overlay.scss new file mode 100644 index 000000000..0da27b6bb --- /dev/null +++ b/ui/src/style/components/time-machine/ifql-overlay.scss @@ -0,0 +1,4 @@ +.ifql-overlay { + max-width: 500px; + margin: 0 auto; +} \ No newline at end of file diff --git a/ui/src/style/pages/time-machine.scss b/ui/src/style/pages/time-machine.scss index 2b6c49f5b..b379f22c8 100644 --- a/ui/src/style/pages/time-machine.scss +++ b/ui/src/style/pages/time-machine.scss @@ -3,6 +3,7 @@ ---------------------------------------------------------------------------- */ +@import '../components/time-machine/ifql-overlay'; @import '../components/time-machine/ifql-editor'; @import '../components/time-machine/ifql-builder'; @import '../components/time-machine/ifql-explorer'; diff --git a/ui/src/types/index.ts b/ui/src/types/index.ts index 91e87246b..6804f009d 100644 --- a/ui/src/types/index.ts +++ b/ui/src/types/index.ts @@ -1,4 +1,4 @@ -import {Service} from './services' +import {Service, NewService} from './services' import {AuthLinks, Organization, Role, User, Me} from './auth' import {Template, Cell, CellQuery, Legend, Axes} from './dashboard' import { @@ -55,4 +55,5 @@ export { NotificationFunc, Axes, Service, + NewService, } diff --git a/ui/src/types/services.ts b/ui/src/types/services.ts index 74e41c674..19115df1e 100644 --- a/ui/src/types/services.ts +++ b/ui/src/types/services.ts @@ -1,3 +1,13 @@ +export interface NewService { + url: string + name: string + type: string + username?: string + password?: string + active: boolean + insecureSkipVerify: boolean +} + export interface Service { id?: string url: string From 2dc26ad81a433993ca4a3b7f4b26a5a9736a0d45 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Fri, 18 May 2018 16:08:58 -0700 Subject: [PATCH 45/49] Fix type errors --- ui/src/ifql/containers/IFQLPage.tsx | 6 +----- ui/src/shared/copy/notifications.ts | 4 ++-- ui/src/types/notifications.ts | 5 +---- ui/test/fixtures/index.ts | 1 + ui/test/ifql/containers/IFQLPage.test.tsx | 2 ++ ui/test/resources.ts | 1 + 6 files changed, 8 insertions(+), 11 deletions(-) diff --git a/ui/src/ifql/containers/IFQLPage.tsx b/ui/src/ifql/containers/IFQLPage.tsx index 7adbba3dd..07a6170d7 100644 --- a/ui/src/ifql/containers/IFQLPage.tsx +++ b/ui/src/ifql/containers/IFQLPage.tsx @@ -1,5 +1,4 @@ import React, {PureComponent} from 'react' -import {RouteComponentProps} from 'react-router' import {connect} from 'react-redux' import _ from 'lodash' @@ -55,10 +54,7 @@ interface State { export const IFQLContext = React.createContext() @ErrorHandling -export class IFQLPage extends PureComponent< - Props & RouteComponentProps, - State -> { +export class IFQLPage extends PureComponent { constructor(props) { super(props) this.state = { diff --git a/ui/src/shared/copy/notifications.ts b/ui/src/shared/copy/notifications.ts index 16c912a9b..0f7091e06 100644 --- a/ui/src/shared/copy/notifications.ts +++ b/ui/src/shared/copy/notifications.ts @@ -131,7 +131,7 @@ export const notifySourceUdpateFailed = (sourceName, errorMessage) => ({ message: `Failed to update InfluxDB ${sourceName} Connection: ${errorMessage}`, }) -export const notifySourceDeleted = sourceName => ({ +export const notifySourceDeleted = (sourceName: string) => ({ ...defaultSuccessNotification, icon: 'server2', message: `${sourceName} deleted successfully.`, @@ -536,7 +536,7 @@ export const notifyTestAlertSent = endpoint => ({ message: `Test Alert sent to ${endpoint}. If the Alert does not reach its destination, please check your endpoint configuration settings.`, }) -export const notifyTestAlertFailed = (endpoint, errorMessage) => ({ +export const notifyTestAlertFailed = (endpoint, errorMessage?) => ({ ...defaultErrorNotification, message: `There was an error sending a Test Alert to ${endpoint}${ errorMessage ? `: ${errorMessage}` : '.' diff --git a/ui/src/types/notifications.ts b/ui/src/types/notifications.ts index caaa05605..6107a58e2 100644 --- a/ui/src/types/notifications.ts +++ b/ui/src/types/notifications.ts @@ -6,7 +6,4 @@ export interface Notification { message: string } -interface AdditionalInfo { - [x: string]: string -} -export type NotificationFunc = (info?: AdditionalInfo) => Notification +export type NotificationFunc = (message: any) => Notification diff --git a/ui/test/fixtures/index.ts b/ui/test/fixtures/index.ts index d9d8c5df1..4be00d3d6 100644 --- a/ui/test/fixtures/index.ts +++ b/ui/test/fixtures/index.ts @@ -18,6 +18,7 @@ import {ColorString, ColorNumber} from 'src/types/colors' import {CellType} from 'src/types/dashboard' export const sourceLinks: SourceLinks = { + services: '/chronograf/v1/sources/4', self: '/chronograf/v1/sources/4', kapacitors: '/chronograf/v1/sources/4/kapacitors', proxy: '/chronograf/v1/sources/4/proxy', diff --git a/ui/test/ifql/containers/IFQLPage.test.tsx b/ui/test/ifql/containers/IFQLPage.test.tsx index 3fa27e054..17b1d005e 100644 --- a/ui/test/ifql/containers/IFQLPage.test.tsx +++ b/ui/test/ifql/containers/IFQLPage.test.tsx @@ -13,6 +13,8 @@ const setup = () => { suggestions: '', ast: '', }, + services: [], + sources: [], notify: () => {}, } diff --git a/ui/test/resources.ts b/ui/test/resources.ts index 529f1c6ab..5b96e30a3 100644 --- a/ui/test/resources.ts +++ b/ui/test/resources.ts @@ -21,6 +21,7 @@ export const me = { } export const sourceLinks: SourceLinks = { + services: '/chronograf/v1/sources/16/services', self: '/chronograf/v1/sources/16', kapacitors: '/chronograf/v1/sources/16/kapacitors', proxy: '/chronograf/v1/sources/16/proxy', From e6eef932f6915b38091c5fe25dbed3d36702e0c8 Mon Sep 17 00:00:00 2001 From: Chris Goller Date: Fri, 18 May 2018 18:55:12 -0500 Subject: [PATCH 46/49] fix(bolt/services): unmarshal metadata into pointer --- bolt/internal/internal.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bolt/internal/internal.go b/bolt/internal/internal.go index 0190827da..07dc5adca 100644 --- a/bolt/internal/internal.go +++ b/bolt/internal/internal.go @@ -108,7 +108,7 @@ func UnmarshalServer(data []byte, s *chronograf.Server) error { s.Metadata = make(map[string]interface{}) if len(pb.MetadataJSON) > 0 { - if err := json.Unmarshal([]byte(pb.MetadataJSON), s.Metadata); err != nil { + if err := json.Unmarshal([]byte(pb.MetadataJSON), &s.Metadata); err != nil { return err } } From 008b5c9b37a8290d1280a9ec87d69f8f5fdb4f5a Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Fri, 18 May 2018 17:06:02 -0700 Subject: [PATCH 47/49] WIP edit ifql service --- ui/src/ifql/components/IFQLHeader.tsx | 65 ++++++++++++++++++++++++ ui/src/ifql/components/IFQLOverlay.tsx | 49 ++++++++++++++++-- ui/src/ifql/containers/CheckServices.tsx | 6 ++- ui/src/ifql/containers/IFQLPage.tsx | 26 ++++------ ui/src/shared/actions/services.ts | 16 ++++++ ui/src/shared/apis/index.ts | 15 ++++++ ui/src/shared/copy/notifications.ts | 5 ++ 7 files changed, 160 insertions(+), 22 deletions(-) create mode 100644 ui/src/ifql/components/IFQLHeader.tsx diff --git a/ui/src/ifql/components/IFQLHeader.tsx b/ui/src/ifql/components/IFQLHeader.tsx new file mode 100644 index 000000000..33310ce98 --- /dev/null +++ b/ui/src/ifql/components/IFQLHeader.tsx @@ -0,0 +1,65 @@ +import React, {PureComponent} from 'react' +import {connect} from 'react-redux' + +import IFQLOverlay from 'src/ifql/components/IFQLOverlay' +import {OverlayContext} from 'src/shared/components/OverlayTechnology' +import { + showOverlay as showOverlayAction, + ShowOverlay, +} from 'src/shared/actions/overlayTechnology' + +import {Service} from 'src/types' + +interface Props { + showOverlay: ShowOverlay + service: Service + onGetTimeSeries: () => void +} + +class IFQLHeader extends PureComponent { + public render() { + const {onGetTimeSeries, service} = this.props + + return ( +
+
+
+

Time Machine

+
+ +
+
+ ) + } + + private overlay() { + const {showOverlay, service} = this.props + + showOverlay( + + {({onDismissOverlay}) => ( + + )} + , + {} + ) + } +} + +const mdtp = { + showOverlay: showOverlayAction, +} + +export default connect(null, mdtp)(IFQLHeader) diff --git a/ui/src/ifql/components/IFQLOverlay.tsx b/ui/src/ifql/components/IFQLOverlay.tsx index 6c3587574..ec65fe875 100644 --- a/ui/src/ifql/components/IFQLOverlay.tsx +++ b/ui/src/ifql/components/IFQLOverlay.tsx @@ -3,20 +3,30 @@ import {connect} from 'react-redux' import IFQLForm from 'src/ifql/components/IFQLForm' -import {Source, NewService, Notification} from 'src/types' +import {Service, Source, NewService, Notification} from 'src/types' import {notify as notifyAction} from 'src/shared/actions/notifications' import { + updateServiceAsync, + UpdateServiceAsync, createServiceAsync, CreateServiceAsync, } from 'src/shared/actions/services' -import {ifqlCreated, ifqlNotCreated} from 'src/shared/copy/notifications' + +import { + ifqlCreated, + ifqlNotCreated, + ifqlNotUpdated, +} from 'src/shared/copy/notifications' interface Props { - source: Source + mode: string + source?: Source + service?: Service onDismiss: () => void notify: (message: Notification) => void createService: CreateServiceAsync + updateService: UpdateServiceAsync } interface State { @@ -44,7 +54,11 @@ class IFQLOverlay extends PureComponent { ) } - private get defaultService(): NewService { + private get defaultService(): NewService | Service { + if (this.props.mode === 'edit') { + return this.props.service + } + return { name: 'IFQL', url: this.url, @@ -62,8 +76,32 @@ class IFQLOverlay extends PureComponent { this.setState({service: {...this.state.service, ...update}}) } - private handleSubmit = async e => { + private handleSubmit = (e): void => { e.preventDefault() + if (this.props.mode === 'edit') { + this.handleEdit() + return + } + + this.handleCreate() + } + + private handleEdit = async (): Promise => { + const {notify, onDismiss, updateService} = this.props + const {service} = this.state + + try { + await updateService(service) + } catch (error) { + notify(ifqlNotUpdated(error.message)) + return + } + + notify(ifqlCreated) + onDismiss() + } + + private handleCreate = async (): Promise => { const {notify, source, onDismiss, createService} = this.props const {service} = this.state @@ -90,6 +128,7 @@ class IFQLOverlay extends PureComponent { const mdtp = { notify: notifyAction, createService: createServiceAsync, + updateService: updateServiceAsync, } export default connect(null, mdtp)(IFQLOverlay) diff --git a/ui/src/ifql/containers/CheckServices.tsx b/ui/src/ifql/containers/CheckServices.tsx index d452a2563..e7951749c 100644 --- a/ui/src/ifql/containers/CheckServices.tsx +++ b/ui/src/ifql/containers/CheckServices.tsx @@ -50,7 +50,11 @@ export class CheckServices extends PureComponent { showOverlay( {({onDismissOverlay}) => ( - + )} , {} diff --git a/ui/src/ifql/containers/IFQLPage.tsx b/ui/src/ifql/containers/IFQLPage.tsx index 07a6170d7..d63c38c22 100644 --- a/ui/src/ifql/containers/IFQLPage.tsx +++ b/ui/src/ifql/containers/IFQLPage.tsx @@ -2,9 +2,10 @@ import React, {PureComponent} from 'react' import {connect} from 'react-redux' import _ from 'lodash' -import {ErrorHandling} from 'src/shared/decorators/errors' import CheckServices from 'src/ifql/containers/CheckServices' import TimeMachine from 'src/ifql/components/TimeMachine' +import IFQLHeader from 'src/ifql/components/IFQLHeader' +import {ErrorHandling} from 'src/shared/decorators/errors' import KeyboardShortcuts from 'src/shared/components/KeyboardShortcuts' import {notify as notifyAction} from 'src/shared/actions/notifications' @@ -91,21 +92,10 @@ export class IFQLPage extends PureComponent {
-
-
-
-

Time Machine

-
-
- -
-
-
+ { ) } + private get service(): Service { + return this.props.services[0] + } + private get handlers(): Handlers { return { onAddNode: this.handleAddNode, diff --git a/ui/src/shared/actions/services.ts b/ui/src/shared/actions/services.ts index 1c56e22f0..847d29e1b 100644 --- a/ui/src/shared/actions/services.ts +++ b/ui/src/shared/actions/services.ts @@ -1,5 +1,6 @@ import {Source, Service, NewService} from 'src/types' import { + updateService as updateServiceAJAX, getServices as getServicesAJAX, createService as createServiceAJAX, } from 'src/shared/apis' @@ -130,3 +131,18 @@ export const createServiceAsync = ( throw err.data } } + +export type UpdateServiceAsync = ( + service: Service +) => (dispatch) => Promise +export const updateServiceAsync = (service: Service) => async ( + dispatch +): Promise => { + try { + const s = await updateServiceAJAX(service) + dispatch(updateService(s)) + } catch (err) { + console.error(err.data) + throw err.data + } +} diff --git a/ui/src/shared/apis/index.ts b/ui/src/shared/apis/index.ts index d81f9fd7a..58434bf4d 100644 --- a/ui/src/shared/apis/index.ts +++ b/ui/src/shared/apis/index.ts @@ -342,3 +342,18 @@ export const createService = async ( throw error } } + +export const updateService = async (service: Service): Promise => { + try { + const {data} = await AJAX({ + url: service.links.self, + method: 'PATCH', + data: service, + }) + + return data + } catch (error) { + console.error(error) + throw error + } +} diff --git a/ui/src/shared/copy/notifications.ts b/ui/src/shared/copy/notifications.ts index 0f7091e06..5b1f76422 100644 --- a/ui/src/shared/copy/notifications.ts +++ b/ui/src/shared/copy/notifications.ts @@ -630,3 +630,8 @@ export const ifqlNotCreated = (message: string) => ({ ...defaultErrorNotification, message, }) + +export const ifqlNotUpdated = (message: string) => ({ + ...defaultErrorNotification, + message, +}) From d084ca29b918ecd55bbb462550fc29289f94a310 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Fri, 18 May 2018 17:35:52 -0700 Subject: [PATCH 48/49] Add abiility to edit existing service --- ui/src/ifql/components/IFQLForm.tsx | 60 +++++++++++--------------- ui/src/ifql/components/IFQLHeader.tsx | 8 ++-- ui/src/ifql/components/IFQLOverlay.tsx | 29 +++++++++---- ui/src/ifql/containers/IFQLPage.tsx | 17 ++++++-- ui/src/shared/copy/notifications.ts | 5 +++ ui/src/types/services.ts | 5 +++ 6 files changed, 75 insertions(+), 49 deletions(-) diff --git a/ui/src/ifql/components/IFQLForm.tsx b/ui/src/ifql/components/IFQLForm.tsx index 1042b007d..1c357b1e6 100644 --- a/ui/src/ifql/components/IFQLForm.tsx +++ b/ui/src/ifql/components/IFQLForm.tsx @@ -16,41 +16,33 @@ class IFQLForm extends PureComponent { const {service, onSubmit, onInputChange} = this.props return ( -
-
-
-

Connect to IFQL

+
+
+ + +
+
-
-
-
- - - -
- -
- -
+
) } diff --git a/ui/src/ifql/components/IFQLHeader.tsx b/ui/src/ifql/components/IFQLHeader.tsx index 33310ce98..5ba854b29 100644 --- a/ui/src/ifql/components/IFQLHeader.tsx +++ b/ui/src/ifql/components/IFQLHeader.tsx @@ -18,7 +18,7 @@ interface Props { class IFQLHeader extends PureComponent { public render() { - const {onGetTimeSeries, service} = this.props + const {onGetTimeSeries} = this.props return (
@@ -27,7 +27,9 @@ class IFQLHeader extends PureComponent {

Time Machine

- +
) } - private get defaultService(): NewService { - if (this.props.mode === 'edit') { - return this.props.service + private get form(): JSX.Element { + const { + mode, + source, + service, + notify, + onDismiss, + createService, + updateService, + } = this.props + + if (mode === 'new') { + return ( + + ) } - return { - name: 'IFQL', - url: this.url, - username: '', - insecureSkipVerify: false, - type: 'ifql', - active: true, - } - } - - private handleInputChange = (e: ChangeEvent): void => { - const {value, name} = e.target - const update = {[name]: value} - - this.setState({service: {...this.state.service, ...update}}) - } - - private handleSubmit = (e): void => { - e.preventDefault() - if (this.props.mode === 'edit') { - this.handleEdit() - return - } - - this.handleCreate() - } - - private handleEdit = async (): Promise => { - const {notify, onDismiss, updateService} = this.props - const {service} = this.state - - try { - await updateService(service) - } catch (error) { - notify(ifqlNotUpdated(error.message)) - return - } - - notify(ifqlCreated) - onDismiss() - } - - private handleCreate = async (): Promise => { - const {notify, source, onDismiss, createService} = this.props - - const {service} = this.state - - try { - await createService(source, service) - } catch (error) { - notify(ifqlNotCreated(error.message)) - return - } - - notify(ifqlCreated) - onDismiss() - } - - private get url(): string { - const parser = document.createElement('a') - parser.href = this.props.source.url - - return `${parser.protocol}//${parser.hostname}:${port}` + return ( + + ) } } diff --git a/ui/src/types/services.ts b/ui/src/types/services.ts index aa568a856..19115df1e 100644 --- a/ui/src/types/services.ts +++ b/ui/src/types/services.ts @@ -6,11 +6,6 @@ export interface NewService { password?: string active: boolean insecureSkipVerify: boolean - links?: { - source: string - self: string - proxy: string - } } export interface Service {