From 2eb68a3b368c2716542a25c7faf0c9258580b8f1 Mon Sep 17 00:00:00 2001 From: Michael Desa Date: Tue, 2 Jan 2018 15:48:10 -0800 Subject: [PATCH 01/98] Add mappings to organizations Add links to organization response on me --- bolt/internal/internal.go | 25 +++ bolt/internal/internal.pb.go | 240 +++++++++++++--------- bolt/internal/internal.proto | 24 ++- bolt/organizations.go | 11 + bolt/organizations_test.go | 74 ++++++- chronograf.go | 27 ++- integrations/server_test.go | 70 +++++-- server/me.go | 16 +- server/me_test.go | 17 +- server/organizations.go | 50 ++++- server/organizations_test.go | 384 ++++++++++++++++++++++++++++++++++- 11 files changed, 785 insertions(+), 153 deletions(-) diff --git a/bolt/internal/internal.go b/bolt/internal/internal.go index d7e6985da..436739e71 100644 --- a/bolt/internal/internal.go +++ b/bolt/internal/internal.go @@ -578,11 +578,23 @@ func UnmarshalRolePB(data []byte, r *Role) error { // MarshalOrganization encodes a organization to binary protobuf format. func MarshalOrganization(o *chronograf.Organization) ([]byte, error) { + mappings := make([]*Mapping, len(o.Mappings)) + + for i, m := range o.Mappings { + mappings[i] = &Mapping{ + Provider: m.Provider, + Scheme: m.Scheme, + Group: m.Group, + GrantedRole: m.GrantedRole, + } + } + return MarshalOrganizationPB(&Organization{ ID: o.ID, Name: o.Name, DefaultRole: o.DefaultRole, Public: o.Public, + Mappings: mappings, }) } @@ -602,6 +614,19 @@ func UnmarshalOrganization(data []byte, o *chronograf.Organization) error { o.DefaultRole = pb.DefaultRole o.Public = pb.Public + mappings := make([]chronograf.Mapping, len(pb.Mappings)) + + for i, m := range pb.Mappings { + mappings[i] = chronograf.Mapping{ + Provider: m.Provider, + Scheme: m.Scheme, + Group: m.Group, + GrantedRole: m.GrantedRole, + } + } + + o.Mappings = mappings + return nil } diff --git a/bolt/internal/internal.pb.go b/bolt/internal/internal.pb.go index 8864e63fa..e23676351 100644 --- a/bolt/internal/internal.pb.go +++ b/bolt/internal/internal.pb.go @@ -26,6 +26,7 @@ It has these top-level messages: AlertRule User Role + Mapping Organization Config AuthConfig @@ -1024,17 +1025,58 @@ func (m *Role) GetName() string { return "" } +type Mapping struct { + Provider string `protobuf:"bytes,1,opt,name=Provider,proto3" json:"Provider,omitempty"` + Scheme string `protobuf:"bytes,2,opt,name=Scheme,proto3" json:"Scheme,omitempty"` + Group string `protobuf:"bytes,3,opt,name=Group,proto3" json:"Group,omitempty"` + GrantedRole string `protobuf:"bytes,4,opt,name=GrantedRole,proto3" json:"GrantedRole,omitempty"` +} + +func (m *Mapping) Reset() { *m = Mapping{} } +func (m *Mapping) String() string { return proto.CompactTextString(m) } +func (*Mapping) ProtoMessage() {} +func (*Mapping) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{17} } + +func (m *Mapping) GetProvider() string { + if m != nil { + return m.Provider + } + return "" +} + +func (m *Mapping) GetScheme() string { + if m != nil { + return m.Scheme + } + return "" +} + +func (m *Mapping) GetGroup() string { + if m != nil { + return m.Group + } + return "" +} + +func (m *Mapping) GetGrantedRole() string { + if m != nil { + return m.GrantedRole + } + return "" +} + type Organization struct { - ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"` - Name string `protobuf:"bytes,2,opt,name=Name,proto3" json:"Name,omitempty"` - DefaultRole string `protobuf:"bytes,3,opt,name=DefaultRole,proto3" json:"DefaultRole,omitempty"` - Public bool `protobuf:"varint,4,opt,name=Public,proto3" json:"Public,omitempty"` + ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"` + Name string `protobuf:"bytes,2,opt,name=Name,proto3" json:"Name,omitempty"` + DefaultRole string `protobuf:"bytes,3,opt,name=DefaultRole,proto3" json:"DefaultRole,omitempty"` + Public bool `protobuf:"varint,4,opt,name=Public,proto3" json:"Public,omitempty"` + Mappings []*Mapping `protobuf:"bytes,5,rep,name=Mappings" json:"Mappings,omitempty"` } func (m *Organization) Reset() { *m = Organization{} } func (m *Organization) String() string { return proto.CompactTextString(m) } func (*Organization) ProtoMessage() {} -func (*Organization) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{17} } +func (*Organization) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{18} } func (m *Organization) GetID() string { if m != nil { @@ -1064,6 +1106,13 @@ func (m *Organization) GetPublic() bool { return false } +func (m *Organization) GetMappings() []*Mapping { + if m != nil { + return m.Mappings + } + return nil +} + type Config struct { Auth *AuthConfig `protobuf:"bytes,1,opt,name=Auth" json:"Auth,omitempty"` } @@ -1071,7 +1120,7 @@ type Config struct { func (m *Config) Reset() { *m = Config{} } func (m *Config) String() string { return proto.CompactTextString(m) } func (*Config) ProtoMessage() {} -func (*Config) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{18} } +func (*Config) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{19} } func (m *Config) GetAuth() *AuthConfig { if m != nil { @@ -1087,7 +1136,7 @@ type AuthConfig struct { func (m *AuthConfig) Reset() { *m = AuthConfig{} } func (m *AuthConfig) String() string { return proto.CompactTextString(m) } func (*AuthConfig) ProtoMessage() {} -func (*AuthConfig) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{19} } +func (*AuthConfig) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{20} } func (m *AuthConfig) GetSuperAdminNewUsers() bool { if m != nil { @@ -1104,7 +1153,7 @@ type BuildInfo struct { func (m *BuildInfo) Reset() { *m = BuildInfo{} } func (m *BuildInfo) String() string { return proto.CompactTextString(m) } func (*BuildInfo) ProtoMessage() {} -func (*BuildInfo) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{20} } +func (*BuildInfo) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{21} } func (m *BuildInfo) GetVersion() string { if m != nil { @@ -1138,6 +1187,7 @@ func init() { proto.RegisterType((*AlertRule)(nil), "internal.AlertRule") proto.RegisterType((*User)(nil), "internal.User") proto.RegisterType((*Role)(nil), "internal.Role") + proto.RegisterType((*Mapping)(nil), "internal.Mapping") proto.RegisterType((*Organization)(nil), "internal.Organization") proto.RegisterType((*Config)(nil), "internal.Config") proto.RegisterType((*AuthConfig)(nil), "internal.AuthConfig") @@ -1147,89 +1197,93 @@ func init() { func init() { proto.RegisterFile("internal.proto", fileDescriptorInternal) } var fileDescriptorInternal = []byte{ - // 1342 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x57, 0xdd, 0x8e, 0xe3, 0xc4, - 0x12, 0x96, 0xe3, 0x38, 0xb1, 0x2b, 0xb3, 0x7b, 0x56, 0x3e, 0xab, 0xb3, 0x3e, 0x7b, 0xa4, 0xa3, - 0x60, 0x81, 0x08, 0x82, 0x1d, 0xd0, 0xac, 0x90, 0x10, 0x02, 0xa4, 0xcc, 0x04, 0x2d, 0xc3, 0xfe, - 0xcd, 0x76, 0x76, 0x86, 0x2b, 0xb4, 0xea, 0x38, 0x95, 0xc4, 0x5a, 0xc7, 0x36, 0x6d, 0x7b, 0x26, - 0xe6, 0x61, 0x90, 0x90, 0x78, 0x02, 0xc4, 0x3d, 0xb7, 0x88, 0x5b, 0xde, 0x81, 0x57, 0xe0, 0x16, - 0x55, 0x77, 0xfb, 0x27, 0x93, 0xb0, 0xda, 0x0b, 0xc4, 0x5d, 0x7f, 0x55, 0xed, 0xea, 0xfa, 0xf9, - 0xaa, 0xba, 0x0d, 0x37, 0xc3, 0x38, 0x47, 0x11, 0xf3, 0xe8, 0x30, 0x15, 0x49, 0x9e, 0xb8, 0x76, - 0x85, 0xfd, 0xdf, 0x3b, 0xd0, 0x9b, 0x26, 0x85, 0x08, 0xd0, 0xbd, 0x09, 0x9d, 0xd3, 0x89, 0x67, - 0x0c, 0x8d, 0x91, 0xc9, 0x3a, 0xa7, 0x13, 0xd7, 0x85, 0xee, 0x13, 0xbe, 0x46, 0xaf, 0x33, 0x34, - 0x46, 0x0e, 0x93, 0x6b, 0x92, 0x3d, 0x2f, 0x53, 0xf4, 0x4c, 0x25, 0xa3, 0xb5, 0x7b, 0x17, 0xec, - 0xf3, 0x8c, 0xac, 0xad, 0xd1, 0xeb, 0x4a, 0x79, 0x8d, 0x49, 0x77, 0xc6, 0xb3, 0xec, 0x2a, 0x11, - 0x73, 0xcf, 0x52, 0xba, 0x0a, 0xbb, 0xb7, 0xc0, 0x3c, 0x67, 0x8f, 0xbc, 0x9e, 0x14, 0xd3, 0xd2, - 0xf5, 0xa0, 0x3f, 0xc1, 0x05, 0x2f, 0xa2, 0xdc, 0xeb, 0x0f, 0x8d, 0x91, 0xcd, 0x2a, 0x48, 0x76, - 0x9e, 0x63, 0x84, 0x4b, 0xc1, 0x17, 0x9e, 0xad, 0xec, 0x54, 0xd8, 0x3d, 0x04, 0xf7, 0x34, 0xce, - 0x30, 0x28, 0x04, 0x4e, 0x5f, 0x86, 0xe9, 0x05, 0x8a, 0x70, 0x51, 0x7a, 0x8e, 0x34, 0xb0, 0x47, - 0x43, 0xa7, 0x3c, 0xc6, 0x9c, 0xd3, 0xd9, 0x20, 0x4d, 0x55, 0xd0, 0xf5, 0xe1, 0x60, 0xba, 0xe2, - 0x02, 0xe7, 0x53, 0x0c, 0x04, 0xe6, 0xde, 0x40, 0xaa, 0xb7, 0x64, 0xb4, 0xe7, 0xa9, 0x58, 0xf2, - 0x38, 0xfc, 0x96, 0xe7, 0x61, 0x12, 0x7b, 0x07, 0x6a, 0x4f, 0x5b, 0x46, 0x59, 0x62, 0x49, 0x84, - 0xde, 0x0d, 0x95, 0x25, 0x5a, 0xfb, 0x3f, 0x19, 0xe0, 0x4c, 0x78, 0xb6, 0x9a, 0x25, 0x5c, 0xcc, - 0x5f, 0x2b, 0xd7, 0xf7, 0xc0, 0x0a, 0x30, 0x8a, 0x32, 0xcf, 0x1c, 0x9a, 0xa3, 0xc1, 0xd1, 0x9d, - 0xc3, 0xba, 0x88, 0xb5, 0x9d, 0x13, 0x8c, 0x22, 0xa6, 0x76, 0xb9, 0x1f, 0x80, 0x93, 0xe3, 0x3a, - 0x8d, 0x78, 0x8e, 0x99, 0xd7, 0x95, 0x9f, 0xb8, 0xcd, 0x27, 0xcf, 0xb5, 0x8a, 0x35, 0x9b, 0x76, - 0x42, 0xb1, 0x76, 0x43, 0xf1, 0x7f, 0xeb, 0xc0, 0x8d, 0xad, 0xe3, 0xdc, 0x03, 0x30, 0x36, 0xd2, - 0x73, 0x8b, 0x19, 0x1b, 0x42, 0xa5, 0xf4, 0xda, 0x62, 0x46, 0x49, 0xe8, 0x4a, 0x72, 0xc3, 0x62, - 0xc6, 0x15, 0xa1, 0x95, 0x64, 0x84, 0xc5, 0x8c, 0x95, 0xfb, 0x0e, 0xf4, 0xbf, 0x29, 0x50, 0x84, - 0x98, 0x79, 0x96, 0xf4, 0xee, 0x5f, 0x8d, 0x77, 0xcf, 0x0a, 0x14, 0x25, 0xab, 0xf4, 0x94, 0x0d, - 0xc9, 0x26, 0x45, 0x0d, 0xb9, 0x26, 0x59, 0x4e, 0xcc, 0xeb, 0x2b, 0x19, 0xad, 0x75, 0x16, 0x15, - 0x1f, 0x28, 0x8b, 0x1f, 0x42, 0x97, 0x6f, 0x30, 0xf3, 0x1c, 0x69, 0xff, 0x8d, 0xbf, 0x48, 0xd8, - 0xe1, 0x78, 0x83, 0xd9, 0xe7, 0x71, 0x2e, 0x4a, 0x26, 0xb7, 0xbb, 0x6f, 0x43, 0x2f, 0x48, 0xa2, - 0x44, 0x64, 0x1e, 0x5c, 0x77, 0xec, 0x84, 0xe4, 0x4c, 0xab, 0xef, 0x3e, 0x00, 0xa7, 0xfe, 0x96, - 0xe8, 0xfb, 0x12, 0x4b, 0x99, 0x09, 0x87, 0xd1, 0xd2, 0x7d, 0x13, 0xac, 0x4b, 0x1e, 0x15, 0xaa, - 0x8a, 0x83, 0xa3, 0x9b, 0x8d, 0x99, 0xf1, 0x26, 0xcc, 0x98, 0x52, 0x7e, 0xdc, 0xf9, 0xc8, 0xf0, - 0x97, 0x60, 0x49, 0xcb, 0x2d, 0x1e, 0x38, 0x15, 0x0f, 0x64, 0x7f, 0x75, 0x5a, 0xfd, 0x75, 0x0b, - 0xcc, 0x2f, 0x70, 0xa3, 0x5b, 0x8e, 0x96, 0x35, 0x5b, 0xba, 0x2d, 0xb6, 0xdc, 0x06, 0xeb, 0x42, - 0x1e, 0xae, 0xaa, 0xa8, 0x80, 0xff, 0xa3, 0x01, 0x5d, 0x3a, 0x9c, 0x6a, 0x1d, 0xe1, 0x92, 0x07, - 0xe5, 0x71, 0x52, 0xc4, 0xf3, 0xcc, 0x33, 0x86, 0xe6, 0xc8, 0x64, 0x5b, 0x32, 0xf7, 0x3f, 0xd0, - 0x9b, 0x29, 0x6d, 0x67, 0x68, 0x8e, 0x1c, 0xa6, 0x11, 0x99, 0x8e, 0xf8, 0x0c, 0x23, 0xed, 0x82, - 0x02, 0xb4, 0x3b, 0x15, 0xb8, 0x08, 0x37, 0xda, 0x0d, 0x8d, 0x48, 0x9e, 0x15, 0x0b, 0x92, 0x2b, - 0x4f, 0x34, 0x22, 0xa7, 0x67, 0x3c, 0xab, 0x8b, 0x4a, 0x6b, 0xb2, 0x9c, 0x05, 0x3c, 0xaa, 0xaa, - 0xaa, 0x80, 0xff, 0xb3, 0x41, 0xdd, 0xae, 0x58, 0xba, 0x93, 0xa1, 0xff, 0x82, 0x4d, 0x0c, 0x7e, - 0x71, 0xc9, 0x85, 0xce, 0x52, 0x9f, 0xf0, 0x05, 0x17, 0xee, 0xfb, 0xd0, 0x93, 0x29, 0xde, 0xd3, - 0x31, 0x95, 0x39, 0x99, 0x15, 0xa6, 0xb7, 0xd5, 0x9c, 0xea, 0xb6, 0x38, 0x55, 0x07, 0x6b, 0xb5, - 0x83, 0xbd, 0x07, 0x16, 0x91, 0xb3, 0x94, 0xde, 0xef, 0xb5, 0xac, 0x28, 0xac, 0x76, 0xf9, 0xe7, - 0x70, 0x63, 0xeb, 0xc4, 0xfa, 0x24, 0x63, 0xfb, 0xa4, 0x86, 0x2e, 0x8e, 0xa6, 0x07, 0x4d, 0xba, - 0x0c, 0x23, 0x0c, 0x72, 0x9c, 0xcb, 0x7c, 0xdb, 0xac, 0xc6, 0xfe, 0xf7, 0x46, 0x63, 0x57, 0x9e, - 0x47, 0xb3, 0x2c, 0x48, 0xd6, 0x6b, 0x1e, 0xcf, 0xb5, 0xe9, 0x0a, 0x52, 0xde, 0xe6, 0x33, 0x6d, - 0xba, 0x33, 0x9f, 0x11, 0x16, 0xa9, 0xae, 0x60, 0x47, 0xa4, 0xee, 0x10, 0x06, 0x6b, 0xe4, 0x59, - 0x21, 0x70, 0x8d, 0x71, 0xae, 0x53, 0xd0, 0x16, 0xb9, 0x77, 0xa0, 0x9f, 0xf3, 0xe5, 0x0b, 0x22, - 0xb9, 0xae, 0x64, 0xce, 0x97, 0x0f, 0xb1, 0x74, 0xff, 0x07, 0xce, 0x22, 0xc4, 0x68, 0x2e, 0x55, - 0xaa, 0x9c, 0xb6, 0x14, 0x3c, 0xc4, 0xd2, 0xff, 0xc5, 0x80, 0xde, 0x14, 0xc5, 0x25, 0x8a, 0xd7, - 0x1a, 0x72, 0xed, 0xcb, 0xc3, 0x7c, 0xc5, 0xe5, 0xd1, 0xdd, 0x7f, 0x79, 0x58, 0xcd, 0xe5, 0x71, - 0x1b, 0xac, 0xa9, 0x08, 0x4e, 0x27, 0xd2, 0x23, 0x93, 0x29, 0x40, 0x6c, 0x1c, 0x07, 0x79, 0x78, - 0x89, 0xfa, 0x46, 0xd1, 0x68, 0x67, 0xf6, 0xd9, 0x7b, 0x66, 0xdf, 0x77, 0x06, 0xf4, 0x1e, 0xf1, - 0x32, 0x29, 0xf2, 0x1d, 0x16, 0x0e, 0x61, 0x30, 0x4e, 0xd3, 0x28, 0x0c, 0xd4, 0xd7, 0x2a, 0xa2, - 0xb6, 0x88, 0x76, 0x3c, 0x6e, 0xe5, 0x57, 0xc5, 0xd6, 0x16, 0xd1, 0xb8, 0x38, 0x91, 0xf3, 0x5d, - 0x0d, 0xeb, 0xd6, 0xb8, 0x50, 0x63, 0x5d, 0x2a, 0x29, 0x09, 0xe3, 0x22, 0x4f, 0x16, 0x51, 0x72, - 0x25, 0xa3, 0xb5, 0x59, 0x8d, 0xfd, 0x5f, 0x3b, 0xd0, 0xfd, 0xa7, 0x66, 0xf2, 0x01, 0x18, 0xa1, - 0x2e, 0xb6, 0x11, 0xd6, 0x13, 0xba, 0xdf, 0x9a, 0xd0, 0x1e, 0xf4, 0x4b, 0xc1, 0xe3, 0x25, 0x66, - 0x9e, 0x2d, 0xa7, 0x4b, 0x05, 0xa5, 0x46, 0xf6, 0x91, 0x1a, 0xcd, 0x0e, 0xab, 0x60, 0xdd, 0x17, - 0xd0, 0xea, 0x8b, 0xf7, 0xf4, 0x14, 0x1f, 0x48, 0x8f, 0xbc, 0xed, 0xb4, 0x5c, 0x1f, 0xde, 0x7f, - 0xdf, 0x4c, 0xfe, 0xc3, 0x00, 0xab, 0x6e, 0xaa, 0x93, 0xed, 0xa6, 0x3a, 0x69, 0x9a, 0x6a, 0x72, - 0x5c, 0x35, 0xd5, 0xe4, 0x98, 0x30, 0x3b, 0xab, 0x9a, 0x8a, 0x9d, 0x51, 0xb1, 0x1e, 0x88, 0xa4, - 0x48, 0x8f, 0x4b, 0x55, 0x55, 0x87, 0xd5, 0x98, 0x98, 0xf8, 0xd5, 0x0a, 0x85, 0x4e, 0xb5, 0xc3, - 0x34, 0x22, 0xde, 0x3e, 0x92, 0x03, 0x47, 0x25, 0x57, 0x01, 0xf7, 0x2d, 0xb0, 0x18, 0x25, 0x4f, - 0x66, 0x78, 0xab, 0x2e, 0x52, 0xcc, 0x94, 0x96, 0x8c, 0xaa, 0xd7, 0x9b, 0x26, 0x70, 0xf5, 0x96, - 0x7b, 0x17, 0x7a, 0xd3, 0x55, 0xb8, 0xc8, 0xab, 0xbb, 0xf0, 0xdf, 0xad, 0x81, 0x15, 0xae, 0x51, - 0xea, 0x98, 0xde, 0xe2, 0x3f, 0x03, 0xa7, 0x16, 0x36, 0xee, 0x18, 0x6d, 0x77, 0x5c, 0xe8, 0x9e, - 0xc7, 0x61, 0x5e, 0xb5, 0x2e, 0xad, 0x29, 0xd8, 0x67, 0x05, 0x8f, 0xf3, 0x30, 0x2f, 0xab, 0xd6, - 0xad, 0xb0, 0x7f, 0x5f, 0xbb, 0x4f, 0xe6, 0xce, 0xd3, 0x14, 0x85, 0x1e, 0x03, 0x0a, 0xc8, 0x43, - 0x92, 0x2b, 0x54, 0x13, 0xdc, 0x64, 0x0a, 0xf8, 0x5f, 0x83, 0x33, 0x8e, 0x50, 0xe4, 0xac, 0x88, - 0x70, 0xdf, 0xcd, 0xf8, 0xe5, 0xf4, 0xe9, 0x93, 0xca, 0x03, 0x5a, 0x37, 0x2d, 0x6f, 0x5e, 0x6b, - 0xf9, 0x87, 0x3c, 0xe5, 0xa7, 0x13, 0xc9, 0x73, 0x93, 0x69, 0xe4, 0xff, 0x60, 0x40, 0x97, 0x66, - 0x4b, 0xcb, 0x74, 0xf7, 0x55, 0x73, 0xe9, 0x4c, 0x24, 0x97, 0xe1, 0x1c, 0x45, 0x15, 0x5c, 0x85, - 0x65, 0xd2, 0x83, 0x15, 0xd6, 0x17, 0xb0, 0x46, 0xc4, 0x35, 0x7a, 0xea, 0x55, 0xbd, 0xd4, 0xe2, - 0x1a, 0x89, 0x99, 0x52, 0xba, 0xff, 0x07, 0x98, 0x16, 0x29, 0x8a, 0xf1, 0x7c, 0x1d, 0xc6, 0xb2, - 0xe8, 0x36, 0x6b, 0x49, 0xfc, 0xcf, 0xd4, 0xe3, 0x71, 0x67, 0x42, 0x19, 0xfb, 0x1f, 0x9a, 0xd7, - 0x3d, 0xf7, 0xa3, 0xed, 0xef, 0xf6, 0x25, 0x72, 0x27, 0xda, 0x21, 0x0c, 0xf4, 0x4b, 0x5b, 0xbe, - 0x5b, 0xf5, 0xb0, 0x6a, 0x89, 0x28, 0xe6, 0xb3, 0x62, 0x16, 0x85, 0x81, 0x8c, 0xd9, 0x66, 0x1a, - 0xf9, 0x47, 0xd0, 0x3b, 0x49, 0xe2, 0x45, 0xb8, 0x74, 0x47, 0xd0, 0x1d, 0x17, 0xf9, 0x4a, 0x9e, - 0x34, 0x38, 0xba, 0xdd, 0x6a, 0xb4, 0x22, 0x5f, 0xa9, 0x3d, 0x4c, 0xee, 0xf0, 0x3f, 0x01, 0x68, - 0x64, 0xf4, 0x7c, 0x6f, 0xa2, 0x7f, 0x82, 0x57, 0x54, 0xa2, 0x4c, 0x5a, 0xb1, 0xd9, 0x1e, 0x8d, - 0xff, 0x29, 0x38, 0xc7, 0x45, 0x18, 0xcd, 0x4f, 0xe3, 0x45, 0x42, 0xad, 0x7a, 0x81, 0x22, 0x6b, - 0xf2, 0x53, 0x41, 0x72, 0x98, 0xba, 0xb6, 0xe6, 0xac, 0x46, 0xb3, 0x9e, 0xfc, 0x03, 0xba, 0xff, - 0x67, 0x00, 0x00, 0x00, 0xff, 0xff, 0xbe, 0x23, 0x76, 0x8f, 0x13, 0x0d, 0x00, 0x00, + // 1396 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x57, 0x5f, 0x8f, 0xdb, 0x44, + 0x10, 0x97, 0xe3, 0x38, 0xb1, 0x27, 0xd7, 0x52, 0xcc, 0x89, 0x9a, 0x22, 0xa1, 0x60, 0x81, 0x38, + 0x04, 0x3d, 0xd0, 0x55, 0x48, 0x08, 0x01, 0x52, 0xee, 0x82, 0xca, 0xd1, 0x7f, 0xd7, 0x4d, 0xef, + 0x78, 0x42, 0xd5, 0xc6, 0x99, 0x24, 0x56, 0x1d, 0xdb, 0xac, 0xed, 0xbb, 0x33, 0x5f, 0x05, 0x09, + 0x09, 0x89, 0x4f, 0x80, 0x78, 0xe7, 0x15, 0xf1, 0xca, 0x77, 0xe0, 0x2b, 0xf0, 0x8a, 0x66, 0x77, + 0xfd, 0x27, 0x97, 0xb4, 0xea, 0x03, 0xe2, 0x6d, 0x7f, 0x33, 0x9b, 0xd9, 0xd9, 0x99, 0xdf, 0xfc, + 0xd6, 0x81, 0xeb, 0x61, 0x9c, 0xa3, 0x88, 0x79, 0xb4, 0x9f, 0x8a, 0x24, 0x4f, 0x5c, 0xbb, 0xc2, + 0xfe, 0xdf, 0x1d, 0xe8, 0x4d, 0x92, 0x42, 0x04, 0xe8, 0x5e, 0x87, 0xce, 0xf1, 0xd8, 0x33, 0x86, + 0xc6, 0x9e, 0xc9, 0x3a, 0xc7, 0x63, 0xd7, 0x85, 0xee, 0x43, 0xbe, 0x42, 0xaf, 0x33, 0x34, 0xf6, + 0x1c, 0x26, 0xd7, 0x64, 0x7b, 0x52, 0xa6, 0xe8, 0x99, 0xca, 0x46, 0x6b, 0xf7, 0x16, 0xd8, 0xa7, + 0x19, 0x45, 0x5b, 0xa1, 0xd7, 0x95, 0xf6, 0x1a, 0x93, 0xef, 0x84, 0x67, 0xd9, 0x45, 0x22, 0x66, + 0x9e, 0xa5, 0x7c, 0x15, 0x76, 0x6f, 0x80, 0x79, 0xca, 0xee, 0x7b, 0x3d, 0x69, 0xa6, 0xa5, 0xeb, + 0x41, 0x7f, 0x8c, 0x73, 0x5e, 0x44, 0xb9, 0xd7, 0x1f, 0x1a, 0x7b, 0x36, 0xab, 0x20, 0xc5, 0x79, + 0x82, 0x11, 0x2e, 0x04, 0x9f, 0x7b, 0xb6, 0x8a, 0x53, 0x61, 0x77, 0x1f, 0xdc, 0xe3, 0x38, 0xc3, + 0xa0, 0x10, 0x38, 0x79, 0x16, 0xa6, 0x67, 0x28, 0xc2, 0x79, 0xe9, 0x39, 0x32, 0xc0, 0x16, 0x0f, + 0x9d, 0xf2, 0x00, 0x73, 0x4e, 0x67, 0x83, 0x0c, 0x55, 0x41, 0xd7, 0x87, 0x9d, 0xc9, 0x92, 0x0b, + 0x9c, 0x4d, 0x30, 0x10, 0x98, 0x7b, 0x03, 0xe9, 0x5e, 0xb3, 0xd1, 0x9e, 0x47, 0x62, 0xc1, 0xe3, + 0xf0, 0x07, 0x9e, 0x87, 0x49, 0xec, 0xed, 0xa8, 0x3d, 0x6d, 0x1b, 0x55, 0x89, 0x25, 0x11, 0x7a, + 0xd7, 0x54, 0x95, 0x68, 0xed, 0xff, 0x66, 0x80, 0x33, 0xe6, 0xd9, 0x72, 0x9a, 0x70, 0x31, 0x7b, + 0xa9, 0x5a, 0xdf, 0x06, 0x2b, 0xc0, 0x28, 0xca, 0x3c, 0x73, 0x68, 0xee, 0x0d, 0x0e, 0x6e, 0xee, + 0xd7, 0x4d, 0xac, 0xe3, 0x1c, 0x61, 0x14, 0x31, 0xb5, 0xcb, 0xfd, 0x18, 0x9c, 0x1c, 0x57, 0x69, + 0xc4, 0x73, 0xcc, 0xbc, 0xae, 0xfc, 0x89, 0xdb, 0xfc, 0xe4, 0x89, 0x76, 0xb1, 0x66, 0xd3, 0xc6, + 0x55, 0xac, 0xcd, 0xab, 0xf8, 0x7f, 0x75, 0xe0, 0xda, 0xda, 0x71, 0xee, 0x0e, 0x18, 0x97, 0x32, + 0x73, 0x8b, 0x19, 0x97, 0x84, 0x4a, 0x99, 0xb5, 0xc5, 0x8c, 0x92, 0xd0, 0x85, 0xe4, 0x86, 0xc5, + 0x8c, 0x0b, 0x42, 0x4b, 0xc9, 0x08, 0x8b, 0x19, 0x4b, 0xf7, 0x7d, 0xe8, 0x7f, 0x5f, 0xa0, 0x08, + 0x31, 0xf3, 0x2c, 0x99, 0xdd, 0x2b, 0x4d, 0x76, 0x8f, 0x0b, 0x14, 0x25, 0xab, 0xfc, 0x54, 0x0d, + 0xc9, 0x26, 0x45, 0x0d, 0xb9, 0x26, 0x5b, 0x4e, 0xcc, 0xeb, 0x2b, 0x1b, 0xad, 0x75, 0x15, 0x15, + 0x1f, 0xa8, 0x8a, 0x9f, 0x40, 0x97, 0x5f, 0x62, 0xe6, 0x39, 0x32, 0xfe, 0xdb, 0xcf, 0x29, 0xd8, + 0xfe, 0xe8, 0x12, 0xb3, 0xaf, 0xe2, 0x5c, 0x94, 0x4c, 0x6e, 0x77, 0xdf, 0x83, 0x5e, 0x90, 0x44, + 0x89, 0xc8, 0x3c, 0xb8, 0x9a, 0xd8, 0x11, 0xd9, 0x99, 0x76, 0xdf, 0xba, 0x0b, 0x4e, 0xfd, 0x5b, + 0xa2, 0xef, 0x33, 0x2c, 0x65, 0x25, 0x1c, 0x46, 0x4b, 0xf7, 0x1d, 0xb0, 0xce, 0x79, 0x54, 0xa8, + 0x2e, 0x0e, 0x0e, 0xae, 0x37, 0x61, 0x46, 0x97, 0x61, 0xc6, 0x94, 0xf3, 0xb3, 0xce, 0xa7, 0x86, + 0xbf, 0x00, 0x4b, 0x46, 0x6e, 0xf1, 0xc0, 0xa9, 0x78, 0x20, 0xe7, 0xab, 0xd3, 0x9a, 0xaf, 0x1b, + 0x60, 0x7e, 0x8d, 0x97, 0x7a, 0xe4, 0x68, 0x59, 0xb3, 0xa5, 0xdb, 0x62, 0xcb, 0x2e, 0x58, 0x67, + 0xf2, 0x70, 0xd5, 0x45, 0x05, 0xfc, 0x5f, 0x0d, 0xe8, 0xd2, 0xe1, 0xd4, 0xeb, 0x08, 0x17, 0x3c, + 0x28, 0x0f, 0x93, 0x22, 0x9e, 0x65, 0x9e, 0x31, 0x34, 0xf7, 0x4c, 0xb6, 0x66, 0x73, 0x5f, 0x87, + 0xde, 0x54, 0x79, 0x3b, 0x43, 0x73, 0xcf, 0x61, 0x1a, 0x51, 0xe8, 0x88, 0x4f, 0x31, 0xd2, 0x29, + 0x28, 0x40, 0xbb, 0x53, 0x81, 0xf3, 0xf0, 0x52, 0xa7, 0xa1, 0x11, 0xd9, 0xb3, 0x62, 0x4e, 0x76, + 0x95, 0x89, 0x46, 0x94, 0xf4, 0x94, 0x67, 0x75, 0x53, 0x69, 0x4d, 0x91, 0xb3, 0x80, 0x47, 0x55, + 0x57, 0x15, 0xf0, 0x7f, 0x37, 0x68, 0xda, 0x15, 0x4b, 0x37, 0x2a, 0xf4, 0x06, 0xd8, 0xc4, 0xe0, + 0xa7, 0xe7, 0x5c, 0xe8, 0x2a, 0xf5, 0x09, 0x9f, 0x71, 0xe1, 0x7e, 0x04, 0x3d, 0x59, 0xe2, 0x2d, + 0x13, 0x53, 0x85, 0x93, 0x55, 0x61, 0x7a, 0x5b, 0xcd, 0xa9, 0x6e, 0x8b, 0x53, 0xf5, 0x65, 0xad, + 0xf6, 0x65, 0x6f, 0x83, 0x45, 0xe4, 0x2c, 0x65, 0xf6, 0x5b, 0x23, 0x2b, 0x0a, 0xab, 0x5d, 0xfe, + 0x29, 0x5c, 0x5b, 0x3b, 0xb1, 0x3e, 0xc9, 0x58, 0x3f, 0xa9, 0xa1, 0x8b, 0xa3, 0xe9, 0x41, 0x4a, + 0x97, 0x61, 0x84, 0x41, 0x8e, 0x33, 0x59, 0x6f, 0x9b, 0xd5, 0xd8, 0xff, 0xd9, 0x68, 0xe2, 0xca, + 0xf3, 0x48, 0xcb, 0x82, 0x64, 0xb5, 0xe2, 0xf1, 0x4c, 0x87, 0xae, 0x20, 0xd5, 0x6d, 0x36, 0xd5, + 0xa1, 0x3b, 0xb3, 0x29, 0x61, 0x91, 0xea, 0x0e, 0x76, 0x44, 0xea, 0x0e, 0x61, 0xb0, 0x42, 0x9e, + 0x15, 0x02, 0x57, 0x18, 0xe7, 0xba, 0x04, 0x6d, 0x93, 0x7b, 0x13, 0xfa, 0x39, 0x5f, 0x3c, 0x25, + 0x92, 0xeb, 0x4e, 0xe6, 0x7c, 0x71, 0x0f, 0x4b, 0xf7, 0x4d, 0x70, 0xe6, 0x21, 0x46, 0x33, 0xe9, + 0x52, 0xed, 0xb4, 0xa5, 0xe1, 0x1e, 0x96, 0xfe, 0x1f, 0x06, 0xf4, 0x26, 0x28, 0xce, 0x51, 0xbc, + 0x94, 0xc8, 0xb5, 0x1f, 0x0f, 0xf3, 0x05, 0x8f, 0x47, 0x77, 0xfb, 0xe3, 0x61, 0x35, 0x8f, 0xc7, + 0x2e, 0x58, 0x13, 0x11, 0x1c, 0x8f, 0x65, 0x46, 0x26, 0x53, 0x80, 0xd8, 0x38, 0x0a, 0xf2, 0xf0, + 0x1c, 0xf5, 0x8b, 0xa2, 0xd1, 0x86, 0xf6, 0xd9, 0x5b, 0xb4, 0xef, 0x27, 0x03, 0x7a, 0xf7, 0x79, + 0x99, 0x14, 0xf9, 0x06, 0x0b, 0x87, 0x30, 0x18, 0xa5, 0x69, 0x14, 0x06, 0xea, 0xd7, 0xea, 0x46, + 0x6d, 0x13, 0xed, 0x78, 0xd0, 0xaa, 0xaf, 0xba, 0x5b, 0xdb, 0x44, 0x72, 0x71, 0x24, 0xf5, 0x5d, + 0x89, 0x75, 0x4b, 0x2e, 0x94, 0xac, 0x4b, 0x27, 0x15, 0x61, 0x54, 0xe4, 0xc9, 0x3c, 0x4a, 0x2e, + 0xe4, 0x6d, 0x6d, 0x56, 0x63, 0xff, 0xcf, 0x0e, 0x74, 0xff, 0x2f, 0x4d, 0xde, 0x01, 0x23, 0xd4, + 0xcd, 0x36, 0xc2, 0x5a, 0xa1, 0xfb, 0x2d, 0x85, 0xf6, 0xa0, 0x5f, 0x0a, 0x1e, 0x2f, 0x30, 0xf3, + 0x6c, 0xa9, 0x2e, 0x15, 0x94, 0x1e, 0x39, 0x47, 0x4a, 0x9a, 0x1d, 0x56, 0xc1, 0x7a, 0x2e, 0xa0, + 0x35, 0x17, 0x1f, 0x6a, 0x15, 0x1f, 0xc8, 0x8c, 0xbc, 0xf5, 0xb2, 0x5c, 0x15, 0xef, 0xff, 0x4e, + 0x93, 0xff, 0x31, 0xc0, 0xaa, 0x87, 0xea, 0x68, 0x7d, 0xa8, 0x8e, 0x9a, 0xa1, 0x1a, 0x1f, 0x56, + 0x43, 0x35, 0x3e, 0x24, 0xcc, 0x4e, 0xaa, 0xa1, 0x62, 0x27, 0xd4, 0xac, 0xbb, 0x22, 0x29, 0xd2, + 0xc3, 0x52, 0x75, 0xd5, 0x61, 0x35, 0x26, 0x26, 0x7e, 0xbb, 0x44, 0xa1, 0x4b, 0xed, 0x30, 0x8d, + 0x88, 0xb7, 0xf7, 0xa5, 0xe0, 0xa8, 0xe2, 0x2a, 0xe0, 0xbe, 0x0b, 0x16, 0xa3, 0xe2, 0xc9, 0x0a, + 0xaf, 0xf5, 0x45, 0x9a, 0x99, 0xf2, 0x52, 0x50, 0xf5, 0xf5, 0xa6, 0x09, 0x5c, 0x7d, 0xcb, 0x7d, + 0x00, 0xbd, 0xc9, 0x32, 0x9c, 0xe7, 0xd5, 0x5b, 0xf8, 0x5a, 0x4b, 0xb0, 0xc2, 0x15, 0x4a, 0x1f, + 0xd3, 0x5b, 0xfc, 0xc7, 0xe0, 0xd4, 0xc6, 0x26, 0x1d, 0xa3, 0x9d, 0x8e, 0x0b, 0xdd, 0xd3, 0x38, + 0xcc, 0xab, 0xd1, 0xa5, 0x35, 0x5d, 0xf6, 0x71, 0xc1, 0xe3, 0x3c, 0xcc, 0xcb, 0x6a, 0x74, 0x2b, + 0xec, 0xdf, 0xd1, 0xe9, 0x53, 0xb8, 0xd3, 0x34, 0x45, 0xa1, 0x65, 0x40, 0x01, 0x79, 0x48, 0x72, + 0x81, 0x4a, 0xc1, 0x4d, 0xa6, 0x80, 0xff, 0x1d, 0x38, 0xa3, 0x08, 0x45, 0xce, 0x8a, 0x08, 0xb7, + 0xbd, 0x8c, 0xdf, 0x4c, 0x1e, 0x3d, 0xac, 0x32, 0xa0, 0x75, 0x33, 0xf2, 0xe6, 0x95, 0x91, 0xbf, + 0xc7, 0x53, 0x7e, 0x3c, 0x96, 0x3c, 0x37, 0x99, 0x46, 0xfe, 0x2f, 0x06, 0x74, 0x49, 0x5b, 0x5a, + 0xa1, 0xbb, 0x2f, 0xd2, 0xa5, 0x13, 0x91, 0x9c, 0x87, 0x33, 0x14, 0xd5, 0xe5, 0x2a, 0x2c, 0x8b, + 0x1e, 0x2c, 0xb1, 0x7e, 0x80, 0x35, 0x22, 0xae, 0xd1, 0xa7, 0x5e, 0x35, 0x4b, 0x2d, 0xae, 0x91, + 0x99, 0x29, 0xa7, 0xfb, 0x16, 0xc0, 0xa4, 0x48, 0x51, 0x8c, 0x66, 0xab, 0x30, 0x96, 0x4d, 0xb7, + 0x59, 0xcb, 0xe2, 0x7f, 0xa9, 0x3e, 0x1e, 0x37, 0x14, 0xca, 0xd8, 0xfe, 0xa1, 0x79, 0x35, 0x73, + 0xbf, 0x80, 0xfe, 0x03, 0x9e, 0xa6, 0x61, 0xbc, 0x58, 0xbb, 0x84, 0xf1, 0xdc, 0x4b, 0x74, 0xd6, + 0x2e, 0xb1, 0x0b, 0x96, 0xa4, 0x6c, 0xf5, 0xd8, 0x4b, 0x40, 0x6a, 0x76, 0x57, 0xf0, 0x38, 0xc7, + 0x99, 0xfc, 0xb0, 0xd5, 0xaf, 0x45, 0xcb, 0xe4, 0xff, 0x68, 0xac, 0xe7, 0xbb, 0xad, 0x81, 0x1b, + 0x55, 0x1e, 0xc2, 0x40, 0x7f, 0xe1, 0xcb, 0xb0, 0x5a, 0x24, 0x5b, 0x26, 0x4a, 0xf3, 0xa4, 0x98, + 0x46, 0x61, 0x20, 0xcf, 0xb4, 0x99, 0x46, 0xee, 0x6d, 0xb0, 0xf5, 0x2d, 0xab, 0x72, 0xbf, 0xda, + 0x94, 0x5b, 0x7b, 0x58, 0xbd, 0xc5, 0x3f, 0x80, 0xde, 0x51, 0x12, 0xcf, 0xc3, 0x85, 0xbb, 0x07, + 0xdd, 0x51, 0x91, 0x2f, 0x65, 0x62, 0x83, 0x83, 0xdd, 0x96, 0x1e, 0x14, 0xf9, 0x52, 0xed, 0x61, + 0x72, 0x87, 0xff, 0x39, 0x40, 0x63, 0xa3, 0x7f, 0x19, 0x4d, 0x93, 0x1e, 0xe2, 0x05, 0x31, 0x29, + 0x93, 0x51, 0x6c, 0xb6, 0xc5, 0xe3, 0x7f, 0x01, 0xce, 0x61, 0x11, 0x46, 0xb3, 0xe3, 0x78, 0x9e, + 0x90, 0xa2, 0x9c, 0xa1, 0xc8, 0x9a, 0x36, 0x56, 0x90, 0xee, 0x47, 0xe2, 0x52, 0x8f, 0x96, 0x46, + 0xd3, 0x9e, 0xfc, 0xa3, 0x76, 0xe7, 0xdf, 0x00, 0x00, 0x00, 0xff, 0xff, 0x3e, 0x6a, 0xf4, 0x02, + 0xba, 0x0d, 0x00, 0x00, } diff --git a/bolt/internal/internal.proto b/bolt/internal/internal.proto index 87ea5a7f3..6cfd6723f 100644 --- a/bolt/internal/internal.proto +++ b/bolt/internal/internal.proto @@ -153,15 +153,23 @@ message User { } message Role { - string Organization = 1; // Organization is the ID of the organization that this user has a role in - string Name = 2; // Name is the name of the role of this user in the respective organization + string Organization = 1; // Organization is the ID of the organization that this user has a role in + string Name = 2; // Name is the name of the role of this user in the respective organization +} + +message Mapping { + string Provider = 1; // Provider is the provider that certifies and issues this user's authentication, e.g. GitHub + string Scheme = 2; // Scheme is the scheme used to perform this user's authentication, e.g. OAuth2 or LDAP + string Group = 3; // Group is the group or organizations that you are a part of in an auth provider + string GrantedRole = 4; // GrantedRole is the name of the role that you will be granted if you match the mapping } message Organization { - string ID = 1; // ID is the unique ID of the organization - string Name = 2; // Name is the organization's name - string DefaultRole = 3; // DefaultRole is the name of the role that is the default for any users added to the organization - bool Public = 4; // Public specifies that users must be explicitly added to the organization + string ID = 1; // ID is the unique ID of the organization + string Name = 2; // Name is the organization's name + string DefaultRole = 3; // DefaultRole is the name of the role that is the default for any users added to the organization + bool Public = 4; // Public specifies that users must be explicitly added to the organization + repeated Mapping Mappings = 5; // Mappings is set of mappings a organization has } message Config { @@ -173,8 +181,8 @@ message AuthConfig { } message BuildInfo { - string Version = 1; // Version is a descriptive git SHA identifier - string Commit = 2; // Commit is an abbreviated SHA + string Version = 1; // Version is a descriptive git SHA identifier + string Commit = 2; // Commit is an abbreviated SHA } // The following is a vim modeline, it autoconfigures vim to have the diff --git a/bolt/organizations.go b/bolt/organizations.go index 84ee78792..8f0103759 100644 --- a/bolt/organizations.go +++ b/bolt/organizations.go @@ -29,6 +29,14 @@ const ( DefaultOrganizationPublic bool = true ) +// DefaultOrganizationMapping is the mapping for the default organization +var DefaultOrganizationMapping = chronograf.Mapping{ + Provider: chronograf.MappingWildcard, + Scheme: chronograf.MappingWildcard, + Group: chronograf.MappingWildcard, + GrantedRole: DefaultOrganizationRole, +} + // OrganizationsStore uses bolt to store and retrieve Organizations type OrganizationsStore struct { client *Client @@ -46,6 +54,9 @@ func (s *OrganizationsStore) CreateDefault(ctx context.Context) error { Name: DefaultOrganizationName, DefaultRole: DefaultOrganizationRole, Public: DefaultOrganizationPublic, + Mappings: []chronograf.Mapping{ + DefaultOrganizationMapping, + }, } return s.client.db.Update(func(tx *bolt.Tx) error { b := tx.Bucket(OrganizationsBucket) diff --git a/bolt/organizations_test.go b/bolt/organizations_test.go index eb30968d3..abf1fcddf 100644 --- a/bolt/organizations_test.go +++ b/bolt/organizations_test.go @@ -42,10 +42,26 @@ func TestOrganizationsStore_GetWithName(t *testing.T) { ctx: context.Background(), org: &chronograf.Organization{ Name: "EE - Evil Empire", + Mappings: []chronograf.Mapping{ + chronograf.Mapping{ + Provider: chronograf.MappingWildcard, + Scheme: chronograf.MappingWildcard, + Group: chronograf.MappingWildcard, + GrantedRole: roles.ViewerRoleName, + }, + }, }, }, want: &chronograf.Organization{ Name: "EE - Evil Empire", + Mappings: []chronograf.Mapping{ + chronograf.Mapping{ + Provider: chronograf.MappingWildcard, + Scheme: chronograf.MappingWildcard, + Group: chronograf.MappingWildcard, + GrantedRole: roles.ViewerRoleName, + }, + }, }, addFirst: true, }, @@ -171,6 +187,9 @@ func TestOrganizationsStore_All(t *testing.T) { Name: "EE - Evil Empire", DefaultRole: roles.MemberRoleName, Public: true, + Mappings: []chronograf.Mapping{ + bolt.DefaultOrganizationMapping, + }, }, { Name: "The Good Place", @@ -184,6 +203,9 @@ func TestOrganizationsStore_All(t *testing.T) { Name: "EE - Evil Empire", DefaultRole: roles.MemberRoleName, Public: true, + Mappings: []chronograf.Mapping{ + bolt.DefaultOrganizationMapping, + }, }, { Name: "The Good Place", @@ -194,6 +216,9 @@ func TestOrganizationsStore_All(t *testing.T) { Name: bolt.DefaultOrganizationName, DefaultRole: bolt.DefaultOrganizationRole, Public: bolt.DefaultOrganizationPublic, + Mappings: []chronograf.Mapping{ + bolt.DefaultOrganizationMapping, + }, }, }, addFirst: true, @@ -235,9 +260,10 @@ func TestOrganizationsStore_Update(t *testing.T) { orgs []chronograf.Organization } type args struct { - ctx context.Context - initial *chronograf.Organization - updates *chronograf.Organization + ctx context.Context + initial *chronograf.Organization + updates *chronograf.Organization + mappings []chronograf.Mapping } tests := []struct { name string @@ -361,7 +387,41 @@ func TestOrganizationsStore_Update(t *testing.T) { addFirst: true, }, { - name: "Update organization name - organization already exists", + name: "Update organization name and mappings", + fields: fields{}, + args: args{ + ctx: context.Background(), + initial: &chronograf.Organization{ + Name: "The Good Place", + Public: false, + }, + updates: &chronograf.Organization{ + Name: "The Bad Place", + Mappings: []chronograf.Mapping{ + chronograf.Mapping{ + Provider: chronograf.MappingWildcard, + Scheme: chronograf.MappingWildcard, + Group: chronograf.MappingWildcard, + GrantedRole: roles.ViewerRoleName, + }, + }, + }, + }, + want: &chronograf.Organization{ + Name: "The Bad Place", + Mappings: []chronograf.Mapping{ + chronograf.Mapping{ + Provider: chronograf.MappingWildcard, + Scheme: chronograf.MappingWildcard, + Group: chronograf.MappingWildcard, + GrantedRole: roles.ViewerRoleName, + }, + }, + }, + addFirst: true, + }, + { + name: "Update organization name - name already taken", fields: fields{ orgs: []chronograf.Organization{ { @@ -408,6 +468,9 @@ func TestOrganizationsStore_Update(t *testing.T) { if tt.args.updates.DefaultRole != "" { tt.args.initial.DefaultRole = tt.args.updates.DefaultRole } + if tt.args.updates.Mappings != nil { + tt.args.initial.Mappings = tt.args.updates.Mappings + } if tt.args.updates.Public != tt.args.initial.Public { tt.args.initial.Public = tt.args.updates.Public @@ -619,6 +682,9 @@ func TestOrganizationsStore_DefaultOrganization(t *testing.T) { Name: bolt.DefaultOrganizationName, DefaultRole: bolt.DefaultOrganizationRole, Public: bolt.DefaultOrganizationPublic, + Mappings: []chronograf.Mapping{ + bolt.DefaultOrganizationMapping, + }, }, wantErr: false, }, diff --git a/chronograf.go b/chronograf.go index 29785e3fd..724a62696 100644 --- a/chronograf.go +++ b/chronograf.go @@ -571,7 +571,32 @@ type Organization struct { DefaultRole string `json:"defaultRole,omitempty"` // Public specifies whether users must be explicitly added to the organization. // It is currently only used by the default organization, but that may change in the future. - Public bool `json:"public"` + Public bool `json:"public"` + Mappings []Mapping `json:"mappings"` +} + +// MappingWildcard is the wildcard value for mappings +const MappingWildcard string = "*" + +// A Mapping is the structure that is used to determine a users +// role within an organization. The high level idea is to grant +// certain roles to certain users without them having to be given +// explicit role within the organization. +// +// One can think of a mapping like so: +// Provider:Scheme:Group -> GrantedRole +// github:oauth2:influxdata -> Viewer +// beyondcorp:ldap:influxdata -> Admin +// +// Any of Provider, Scheme, or Group may be provided as a wildcard * +// github:oauth2:* -> Editor +// *:*:* -> Member +type Mapping struct { + Provider string `json:"provider"` + Scheme string `json:"scheme"` + Group string `json:"group"` + + GrantedRole string `json:"grantedRole"` } // OrganizationQuery represents the attributes that a organization may be retrieved by. diff --git a/integrations/server_test.go b/integrations/server_test.go index d985d8296..ba6465ea8 100644 --- a/integrations/server_test.go +++ b/integrations/server_test.go @@ -351,7 +351,15 @@ func TestServer(t *testing.T) { "id": "default", "name": "Default", "defaultRole": "member", - "public": true + "public": true, + "mappings": [ + { + "provider": "*", + "scheme": "*", + "group": "*", + "grantedRole": "member" + } + ] }, { "links": { @@ -360,7 +368,9 @@ func TestServer(t *testing.T) { "id": "howdy", "name": "An Organization", "defaultRole": "viewer", - "public": false + "public": false, + "mappings": [ + ] } ] }`, @@ -409,7 +419,9 @@ func TestServer(t *testing.T) { "id": "howdy", "name": "An Organization", "defaultRole": "viewer", - "public": false + "public": false, + "mappings": [ + ] }`, }, }, @@ -1364,25 +1376,49 @@ func TestServer(t *testing.T) { "links": { "self": "/chronograf/v1/users/1" }, - "organizations": [ - { - "id": "1", - "name": "Sweet", - "defaultRole": "viewer", - "public": false + "organizations": { + "links": { + "self": "/chronograf/v1/organizations" }, - { - "id": "default", - "name": "Default", - "defaultRole": "member", - "public": true - } - ], + "organizations": [ + { + "links": { + "self": "/chronograf/v1/organizations/1" + }, + "id": "1", + "name": "Sweet", + "defaultRole": "viewer", + "public": false, + "mappings": [] + }, + { + "links": { + "self": "/chronograf/v1/organizations/default" + }, + "id": "default", + "name": "Default", + "defaultRole": "member", + "public": true, + "mappings": [ + { + "provider": "*", + "scheme": "*", + "group": "*", + "grantedRole": "member" + } + ] + } + ] + }, "currentOrganization": { + "links": { + "self": "/chronograf/v1/organizations/1" + }, "id": "1", "name": "Sweet", "defaultRole": "viewer", - "public": false + "public": false, + "mappings": [] } }`, }, diff --git a/server/me.go b/server/me.go index 52a36c6ff..eadd6c0ee 100644 --- a/server/me.go +++ b/server/me.go @@ -19,9 +19,9 @@ type meLinks struct { type meResponse struct { *chronograf.User - Links meLinks `json:"links"` - Organizations []chronograf.Organization `json:"organizations,omitempty"` - CurrentOrganization *chronograf.Organization `json:"currentOrganization,omitempty"` + Links meLinks `json:"links"` + Organizations *organizationsResponse `json:"organizations,omitempty"` + CurrentOrganization *organizationResponse `json:"currentOrganization,omitempty"` } // If new user response is nil, return an empty meResponse because it @@ -224,7 +224,6 @@ func (s *Service) Me(w http.ResponseWriter, r *http.Request) { unknownErrorWithMessage(w, err, s.Logger) return } - if usr != nil { if defaultOrg.Public || usr.SuperAdmin == true { @@ -265,8 +264,8 @@ func (s *Service) Me(w http.ResponseWriter, r *http.Request) { return } res := newMeResponse(usr) - res.Organizations = orgs - res.CurrentOrganization = currentOrg + res.Organizations = newOrganizationsResponse(orgs) + res.CurrentOrganization = newOrganizationResponse(currentOrg) encodeJSON(w, http.StatusOK, res, s.Logger) return } @@ -277,7 +276,6 @@ func (s *Service) Me(w http.ResponseWriter, r *http.Request) { Error(w, http.StatusForbidden, "This organization is private. To gain access, you must be explicitly added by an administrator.", s.Logger) return } - // Because we didnt find a user, making a new one user := &chronograf.User{ Name: p.Subject, @@ -315,8 +313,8 @@ func (s *Service) Me(w http.ResponseWriter, r *http.Request) { return } res := newMeResponse(newUser) - res.Organizations = orgs - res.CurrentOrganization = currentOrg + res.Organizations = newOrganizationsResponse(orgs) + res.CurrentOrganization = newOrganizationResponse(currentOrg) encodeJSON(w, http.StatusOK, res, s.Logger) } diff --git a/server/me_test.go b/server/me_test.go index 6f90e7d32..d9cce6fb5 100644 --- a/server/me_test.go +++ b/server/me_test.go @@ -176,7 +176,7 @@ func TestService_Me(t *testing.T) { }, wantStatus: http.StatusOK, wantContentType: "application/json", - wantBody: `{"name":"me","roles":[{"name":"viewer","organization":"0"}],"provider":"github","scheme":"oauth2","superAdmin":true,"links":{"self":"/chronograf/v1/users/0"},"organizations":[{"id":"0","name":"Default","defaultRole":"viewer","public":true}],"currentOrganization":{"id":"0","name":"Default","defaultRole":"viewer","public":true}}`, + wantBody: `{"name":"me","roles":[{"name":"viewer","organization":"0"}],"provider":"github","scheme":"oauth2","superAdmin":true,"links":{"self":"/chronograf/v1/users/0"},"organizations":{"links":{"self":"/chronograf/v1/organizations"},"organizations":[{"links":{"self":"/chronograf/v1/organizations/0"},"id":"0","name":"Default","defaultRole":"viewer","public":true,"mappings":[]}]},"currentOrganization":{"links":{"self":"/chronograf/v1/organizations/0"},"id":"0","name":"Default","defaultRole":"viewer","public":true,"mappings":[]}}`, }, { name: "Existing user - private default org", @@ -306,7 +306,7 @@ func TestService_Me(t *testing.T) { }, wantStatus: http.StatusOK, wantContentType: "application/json", - wantBody: `{"name":"me","roles":[{"name":"viewer","organization":"0"}],"provider":"github","scheme":"oauth2","links":{"self":"/chronograf/v1/users/0"},"organizations":[{"id":"0","name":"Default","defaultRole":"viewer","public":true}],"currentOrganization":{"id":"0","name":"Default","defaultRole":"viewer","public":true}}`, + wantBody: `{"name":"me","roles":[{"name":"viewer","organization":"0"}],"provider":"github","scheme":"oauth2","links":{"self":"/chronograf/v1/users/0"},"organizations":{"links":{"self":"/chronograf/v1/organizations"},"organizations":[{"links":{"self":"/chronograf/v1/organizations/0"},"id":"0","name":"Default","defaultRole":"viewer","public":true,"mappings":[]}]},"currentOrganization":{"links":{"self":"/chronograf/v1/organizations/0"},"id":"0","name":"Default","defaultRole":"viewer","public":true,"mappings":[]}}`, }, { name: "Existing user - organization doesn't exist", @@ -423,8 +423,7 @@ func TestService_Me(t *testing.T) { }, wantStatus: http.StatusOK, wantContentType: "application/json", - wantBody: `{"name":"secret","superAdmin":true,"roles":[{"name":"viewer","organization":"0"}],"provider":"auth0","scheme":"oauth2","links":{"self":"/chronograf/v1/users/0"},"organizations":[{"id":"0","name":"The Gnarly Default","defaultRole":"viewer","public":true}],"currentOrganization":{"id":"0","name":"The Gnarly Default","defaultRole":"viewer","public":true}} -`, + wantBody: `{"name":"secret","roles":[{"name":"viewer","organization":"0"}],"provider":"auth0","scheme":"oauth2","superAdmin":true,"links":{"self":"/chronograf/v1/users/0"},"organizations":{"links":{"self":"/chronograf/v1/organizations"},"organizations":[{"links":{"self":"/chronograf/v1/organizations/0"},"id":"0","name":"The Gnarly Default","defaultRole":"viewer","public":true,"mappings":[]}]},"currentOrganization":{"links":{"self":"/chronograf/v1/organizations/0"},"id":"0","name":"The Gnarly Default","defaultRole":"viewer","public":true,"mappings":[]}}`, }, { name: "New user - New users not super admin, not first user", @@ -485,8 +484,7 @@ func TestService_Me(t *testing.T) { }, wantStatus: http.StatusOK, wantContentType: "application/json", - wantBody: `{"name":"secret","roles":[{"name":"viewer","organization":"0"}],"provider":"auth0","scheme":"oauth2","links":{"self":"/chronograf/v1/users/0"},"organizations":[{"id":"0","name":"The Gnarly Default","public":true,"defaultRole":"viewer"}],"currentOrganization":{"id":"0","name":"The Gnarly Default","public":true,"defaultRole":"viewer"}} -`, + wantBody: `{"name":"secret","roles":[{"name":"viewer","organization":"0"}],"provider":"auth0","scheme":"oauth2","links":{"self":"/chronograf/v1/users/0"},"organizations":{"links":{"self":"/chronograf/v1/organizations"},"organizations":[{"links":{"self":"/chronograf/v1/organizations/0"},"id":"0","name":"The Gnarly Default","defaultRole":"viewer","public":true,"mappings":[]}]},"currentOrganization":{"links":{"self":"/chronograf/v1/organizations/0"},"id":"0","name":"The Gnarly Default","defaultRole":"viewer","public":true,"mappings":[]}}`, }, { name: "New user - New users not super admin, first user", @@ -547,8 +545,7 @@ func TestService_Me(t *testing.T) { }, wantStatus: http.StatusOK, wantContentType: "application/json", - wantBody: `{"name":"secret","superAdmin":true,"roles":[{"name":"viewer","organization":"0"}],"provider":"auth0","scheme":"oauth2","links":{"self":"/chronograf/v1/users/0"},"organizations":[{"id":"0","name":"The Gnarly Default","public":true,"defaultRole":"viewer"}],"currentOrganization":{"id":"0","name":"The Gnarly Default","public":true,"defaultRole":"viewer"}} -`, + wantBody: `{"name":"secret","roles":[{"name":"viewer","organization":"0"}],"provider":"auth0","scheme":"oauth2","superAdmin":true,"links":{"self":"/chronograf/v1/users/0"},"organizations":{"links":{"self":"/chronograf/v1/organizations"},"organizations":[{"links":{"self":"/chronograf/v1/organizations/0"},"id":"0","name":"The Gnarly Default","defaultRole":"viewer","public":true,"mappings":[]}]},"currentOrganization":{"links":{"self":"/chronograf/v1/organizations/0"},"id":"0","name":"The Gnarly Default","defaultRole":"viewer","public":true,"mappings":[]}}`, }, { name: "Error adding user", @@ -824,7 +821,7 @@ func TestService_UpdateMe(t *testing.T) { }, wantStatus: http.StatusOK, wantContentType: "application/json", - wantBody: `{"name":"me","roles":[{"name":"admin","organization":"1337"},{"name":"admin","organization":"0"}],"provider":"github","scheme":"oauth2","links":{"self":"/chronograf/v1/users/0"},"organizations":[{"id":"0","name":"Default","defaultRole":"admin","public":true},{"id":"1337","name":"The ShillBillThrilliettas","public":true}],"currentOrganization":{"id":"1337","name":"The ShillBillThrilliettas","public":true}}`, + wantBody: `{"name":"me","roles":[{"name":"admin","organization":"1337"},{"name":"admin","organization":"0"}],"provider":"github","scheme":"oauth2","links":{"self":"/chronograf/v1/users/0"},"organizations":{"links":{"self":"/chronograf/v1/organizations"},"organizations":[{"links":{"self":"/chronograf/v1/organizations/0"},"id":"0","name":"Default","defaultRole":"admin","public":true,"mappings":[]},{"links":{"self":"/chronograf/v1/organizations/1337"},"id":"1337","name":"The ShillBillThrilliettas","public":true,"mappings":[]}]},"currentOrganization":{"links":{"self":"/chronograf/v1/organizations/1337"},"id":"1337","name":"The ShillBillThrilliettas","public":true,"mappings":[]}}`, }, { name: "Change the current User's organization", @@ -899,7 +896,7 @@ func TestService_UpdateMe(t *testing.T) { }, wantStatus: http.StatusOK, wantContentType: "application/json", - wantBody: `{"name":"me","roles":[{"name":"admin","organization":"1337"},{"name":"editor","organization":"0"}],"provider":"github","scheme":"oauth2","links":{"self":"/chronograf/v1/users/0"},"organizations":[{"id":"0","name":"Default","defaultRole":"editor","public":true},{"id":"1337","name":"The ThrillShilliettos","public":false}],"currentOrganization":{"id":"1337","name":"The ThrillShilliettos","public":false}}`, + wantBody: `{"name":"me","roles":[{"name":"admin","organization":"1337"},{"name":"editor","organization":"0"}],"provider":"github","scheme":"oauth2","links":{"self":"/chronograf/v1/users/0"},"organizations":{"links":{"self":"/chronograf/v1/organizations"},"organizations":[{"links":{"self":"/chronograf/v1/organizations/0"},"id":"0","name":"Default","defaultRole":"editor","public":true,"mappings":[]},{"links":{"self":"/chronograf/v1/organizations/1337"},"id":"1337","name":"The ThrillShilliettos","public":false,"mappings":[]}]},"currentOrganization":{"links":{"self":"/chronograf/v1/organizations/1337"},"id":"1337","name":"The ThrillShilliettos","public":false,"mappings":[]}}`, }, { name: "Unable to find requested user in valid organization", diff --git a/server/organizations.go b/server/organizations.go index 5b2227953..6f27bbba4 100644 --- a/server/organizations.go +++ b/server/organizations.go @@ -13,9 +13,10 @@ import ( ) type organizationRequest struct { - Name string `json:"name"` - DefaultRole string `json:"defaultRole"` - Public *bool `json:"public"` + Name string `json:"name"` + DefaultRole string `json:"defaultRole"` + Public *bool `json:"public"` + Mappings []chronograf.Mapping `json:"mappings"` } func (r *organizationRequest) ValidCreate() error { @@ -23,11 +24,36 @@ func (r *organizationRequest) ValidCreate() error { return fmt.Errorf("Name required on Chronograf Organization request body") } + if len(r.Mappings) > 0 { + if err := r.ValidMappings(); err != nil { + return err + } + } + return r.ValidDefaultRole() } +func (r *organizationRequest) ValidMappings() error { + for _, m := range r.Mappings { + if m.Provider == "" { + return fmt.Errorf("mapping must specify provider") + } + if m.Scheme == "" { + return fmt.Errorf("mapping must specify scheme") + } + if m.Group == "" { + return fmt.Errorf("mapping must specify group") + } + if m.GrantedRole == "" { + return fmt.Errorf("mapping must specify grantedRole") + } + } + + return nil +} + func (r *organizationRequest) ValidUpdate() error { - if r.Name == "" && r.DefaultRole == "" && r.Public == nil { + if r.Name == "" && r.DefaultRole == "" && r.Public == nil && len(r.Mappings) == 0 { return fmt.Errorf("No fields to update") } @@ -35,6 +61,12 @@ func (r *organizationRequest) ValidUpdate() error { return r.ValidDefaultRole() } + if len(r.Mappings) > 0 { + if err := r.ValidMappings(); err != nil { + return err + } + } + return nil } @@ -60,6 +92,12 @@ func newOrganizationResponse(o *chronograf.Organization) *organizationResponse { if o == nil { o = &chronograf.Organization{} } + // This ensures that any user response with no roles returns an empty array instead of + // null when marshaled into JSON. That way, JavaScript doesn't need any guard on the + // key existing and it can simply be iterated over. + if o.Mappings == nil { + o.Mappings = []chronograf.Mapping{} + } return &organizationResponse{ Organization: *o, Links: selfLinks{ @@ -211,6 +249,10 @@ func (s *Service) UpdateOrganization(w http.ResponseWriter, r *http.Request) { org.Public = *req.Public } + if req.Mappings != nil { + org.Mappings = req.Mappings + } + err = s.Store.Organizations(ctx).Update(ctx, org) if err != nil { Error(w, http.StatusBadRequest, err.Error(), s.Logger) diff --git a/server/organizations_test.go b/server/organizations_test.go index 3315d981b..515ce7256 100644 --- a/server/organizations_test.go +++ b/server/organizations_test.go @@ -65,7 +65,46 @@ func TestService_OrganizationID(t *testing.T) { id: "1337", wantStatus: http.StatusOK, wantContentType: "application/json", - wantBody: `{"links":{"self":"/chronograf/v1/organizations/1337"},"id":"1337","name":"The Good Place","public":false}`, + wantBody: `{"links":{"self":"/chronograf/v1/organizations/1337"},"id":"1337","name":"The Good Place","public":false,"mappings":[]}`, + }, + { + name: "Get Single Organization - with mappings", + args: args{ + w: httptest.NewRecorder(), + r: httptest.NewRequest( + "GET", + "http://any.url", // can be any valid URL as we are bypassing mux + nil, + ), + }, + fields: fields{ + Logger: log.New(log.DebugLevel), + OrganizationsStore: &mocks.OrganizationsStore{ + GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { + switch *q.ID { + case "1337": + return &chronograf.Organization{ + ID: "1337", + Name: "The Good Place", + Mappings: []chronograf.Mapping{ + { + Provider: chronograf.MappingWildcard, + Scheme: chronograf.MappingWildcard, + Group: chronograf.MappingWildcard, + GrantedRole: roles.ViewerRoleName, + }, + }, + }, nil + default: + return nil, fmt.Errorf("Organization with ID %s not found", *q.ID) + } + }, + }, + }, + id: "1337", + wantStatus: http.StatusOK, + wantContentType: "application/json", + wantBody: `{"id":"1337","name":"The Good Place","public":false,"mappings":[{"provider":"*","scheme":"*","group":"*","grantedRole":"viewer"}],"links":{"self":"/chronograf/v1/organizations/1337"}}`, }, } @@ -124,7 +163,7 @@ func TestService_Organizations(t *testing.T) { wantBody string }{ { - name: "Get Single Organization", + name: "Get Organizations", args: args{ w: httptest.NewRecorder(), r: httptest.NewRequest( @@ -147,6 +186,14 @@ func TestService_Organizations(t *testing.T) { ID: "100", Name: "The Bad Place", Public: false, + Mappings: []chronograf.Mapping{ + { + Provider: chronograf.MappingWildcard, + Scheme: chronograf.MappingWildcard, + Group: chronograf.MappingWildcard, + GrantedRole: roles.ViewerRoleName, + }, + }, }, }, nil }, @@ -154,7 +201,7 @@ func TestService_Organizations(t *testing.T) { }, wantStatus: http.StatusOK, wantContentType: "application/json", - wantBody: `{"links":{"self":"/chronograf/v1/organizations"},"organizations":[{"links":{"self":"/chronograf/v1/organizations/1337"},"id":"1337","name":"The Good Place","public":false},{"links":{"self":"/chronograf/v1/organizations/100"},"id":"100","name":"The Bad Place","public":false}]}`, + wantBody: `{"links":{"self":"/chronograf/v1/organizations"},"organizations":[{"links":{"self":"/chronograf/v1/organizations/1337"},"mappings":[],"id":"1337","name":"The Good Place","public":false},{"links":{"self":"/chronograf/v1/organizations/100"},"id":"100","name":"The Bad Place","public":false,"mappings":[{"provider":"*","scheme":"*","group":"*","grantedRole":"viewer"}]}]}`, }, } @@ -239,7 +286,243 @@ func TestService_UpdateOrganization(t *testing.T) { id: "1337", wantStatus: http.StatusOK, wantContentType: "application/json", - wantBody: `{"id":"1337","name":"The Bad Place","defaultRole":"viewer","links":{"self":"/chronograf/v1/organizations/1337"},"public":false}`, + wantBody: `{"id":"1337","mappings":[],"name":"The Bad Place","defaultRole":"viewer","links":{"self":"/chronograf/v1/organizations/1337"},"public":false}`, + }, + { + name: "Update Organization mappings", + args: args{ + w: httptest.NewRecorder(), + r: httptest.NewRequest( + "GET", + "http://any.url", // can be any valid URL as we are bypassing mux + nil, + ), + org: &organizationRequest{ + Mappings: []chronograf.Mapping{ + { + Provider: chronograf.MappingWildcard, + Scheme: chronograf.MappingWildcard, + Group: chronograf.MappingWildcard, + GrantedRole: roles.AdminRoleName, + }, + }, + }, + }, + fields: fields{ + Logger: log.New(log.DebugLevel), + OrganizationsStore: &mocks.OrganizationsStore{ + UpdateF: func(ctx context.Context, o *chronograf.Organization) error { + return nil + }, + GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { + return &chronograf.Organization{ + ID: "1337", + Name: "The Good Place", + DefaultRole: roles.ViewerRoleName, + Mappings: []chronograf.Mapping{ + { + Provider: chronograf.MappingWildcard, + Scheme: chronograf.MappingWildcard, + Group: chronograf.MappingWildcard, + GrantedRole: roles.ViewerRoleName, + }, + }, + }, nil + }, + }, + }, + id: "1337", + wantStatus: http.StatusOK, + wantContentType: "application/json", + wantBody: `{"id":"1337","public":false,"mappings":[{"provider":"*","scheme":"*","group":"*","grantedRole":"admin"}],"name":"The Good Place","defaultRole":"viewer","links":{"self":"/chronograf/v1/organizations/1337"}}`, + }, + { + name: "Fail to update Organization mappings - no provider", + args: args{ + w: httptest.NewRecorder(), + r: httptest.NewRequest( + "GET", + "http://any.url", // can be any valid URL as we are bypassing mux + nil, + ), + org: &organizationRequest{ + Mappings: []chronograf.Mapping{ + { + Scheme: chronograf.MappingWildcard, + Group: chronograf.MappingWildcard, + GrantedRole: roles.AdminRoleName, + }, + }, + }, + }, + fields: fields{ + Logger: log.New(log.DebugLevel), + OrganizationsStore: &mocks.OrganizationsStore{ + UpdateF: func(ctx context.Context, o *chronograf.Organization) error { + return nil + }, + GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { + return &chronograf.Organization{ + ID: "1337", + Name: "The Good Place", + DefaultRole: roles.ViewerRoleName, + Mappings: []chronograf.Mapping{ + { + Provider: chronograf.MappingWildcard, + Scheme: chronograf.MappingWildcard, + Group: chronograf.MappingWildcard, + GrantedRole: roles.ViewerRoleName, + }, + }, + }, nil + }, + }, + }, + id: "1337", + wantStatus: http.StatusUnprocessableEntity, + wantContentType: "application/json", + wantBody: `{"code":422,"message":"mapping must specify provider"}`, + }, + { + name: "Fail to update Organization mappings - no scheme", + args: args{ + w: httptest.NewRecorder(), + r: httptest.NewRequest( + "GET", + "http://any.url", // can be any valid URL as we are bypassing mux + nil, + ), + org: &organizationRequest{ + Mappings: []chronograf.Mapping{ + { + Provider: chronograf.MappingWildcard, + Group: chronograf.MappingWildcard, + GrantedRole: roles.AdminRoleName, + }, + }, + }, + }, + fields: fields{ + Logger: log.New(log.DebugLevel), + OrganizationsStore: &mocks.OrganizationsStore{ + UpdateF: func(ctx context.Context, o *chronograf.Organization) error { + return nil + }, + GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { + return &chronograf.Organization{ + ID: "1337", + Name: "The Good Place", + DefaultRole: roles.ViewerRoleName, + Mappings: []chronograf.Mapping{ + { + Provider: chronograf.MappingWildcard, + Scheme: chronograf.MappingWildcard, + Group: chronograf.MappingWildcard, + GrantedRole: roles.ViewerRoleName, + }, + }, + }, nil + }, + }, + }, + id: "1337", + wantStatus: http.StatusUnprocessableEntity, + wantContentType: "application/json", + wantBody: `{"code":422,"message":"mapping must specify scheme"}`, + }, + { + name: "Fail to update Organization mappings - no group", + args: args{ + w: httptest.NewRecorder(), + r: httptest.NewRequest( + "GET", + "http://any.url", // can be any valid URL as we are bypassing mux + nil, + ), + org: &organizationRequest{ + Mappings: []chronograf.Mapping{ + { + Provider: chronograf.MappingWildcard, + Scheme: chronograf.MappingWildcard, + GrantedRole: roles.AdminRoleName, + }, + }, + }, + }, + fields: fields{ + Logger: log.New(log.DebugLevel), + OrganizationsStore: &mocks.OrganizationsStore{ + UpdateF: func(ctx context.Context, o *chronograf.Organization) error { + return nil + }, + GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { + return &chronograf.Organization{ + ID: "1337", + Name: "The Good Place", + DefaultRole: roles.ViewerRoleName, + Mappings: []chronograf.Mapping{ + { + Provider: chronograf.MappingWildcard, + Scheme: chronograf.MappingWildcard, + Group: chronograf.MappingWildcard, + GrantedRole: roles.ViewerRoleName, + }, + }, + }, nil + }, + }, + }, + id: "1337", + wantStatus: http.StatusUnprocessableEntity, + wantContentType: "application/json", + wantBody: `{"code":422,"message":"mapping must specify group"}`, + }, + { + name: "Fail to update Organization mappings - no granted role", + args: args{ + w: httptest.NewRecorder(), + r: httptest.NewRequest( + "GET", + "http://any.url", // can be any valid URL as we are bypassing mux + nil, + ), + org: &organizationRequest{ + Mappings: []chronograf.Mapping{ + { + Provider: chronograf.MappingWildcard, + Scheme: chronograf.MappingWildcard, + Group: chronograf.MappingWildcard, + }, + }, + }, + }, + fields: fields{ + Logger: log.New(log.DebugLevel), + OrganizationsStore: &mocks.OrganizationsStore{ + UpdateF: func(ctx context.Context, o *chronograf.Organization) error { + return nil + }, + GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { + return &chronograf.Organization{ + ID: "1337", + Name: "The Good Place", + DefaultRole: roles.ViewerRoleName, + Mappings: []chronograf.Mapping{ + { + Provider: chronograf.MappingWildcard, + Scheme: chronograf.MappingWildcard, + Group: chronograf.MappingWildcard, + GrantedRole: roles.ViewerRoleName, + }, + }, + }, nil + }, + }, + }, + id: "1337", + wantStatus: http.StatusUnprocessableEntity, + wantContentType: "application/json", + wantBody: `{"code":422,"message":"mapping must specify grantedRole"}`, }, { name: "Update Organization public", @@ -273,7 +556,7 @@ func TestService_UpdateOrganization(t *testing.T) { id: "0", wantStatus: http.StatusOK, wantContentType: "application/json", - wantBody: `{"id":"0","name":"The Good Place","defaultRole":"viewer","public":false,"links":{"self":"/chronograf/v1/organizations/0"}}`, + wantBody: `{"id":"0","mappings":[],"name":"The Good Place","defaultRole":"viewer","public":false,"links":{"self":"/chronograf/v1/organizations/0"}}`, }, { name: "Update Organization - nothing to update", @@ -339,7 +622,7 @@ func TestService_UpdateOrganization(t *testing.T) { id: "1337", wantStatus: http.StatusOK, wantContentType: "application/json", - wantBody: `{"links":{"self":"/chronograf/v1/organizations/1337"},"id":"1337","name":"The Good Place","defaultRole":"viewer","public":false}`, + wantBody: `{"links":{"self":"/chronograf/v1/organizations/1337"},"mappings":[],"id":"1337","name":"The Good Place","defaultRole":"viewer","public":false}`, }, { name: "Update Organization - invalid update", @@ -582,7 +865,94 @@ func TestService_NewOrganization(t *testing.T) { }, wantStatus: http.StatusCreated, wantContentType: "application/json", - wantBody: `{"id":"1337","public":false,"name":"The Good Place","links":{"self":"/chronograf/v1/organizations/1337"}}`, + wantBody: `{"id":"1337","mappings":[],"public":false,"name":"The Good Place","links":{"self":"/chronograf/v1/organizations/1337"}}`, + }, + { + name: "Fail to create Organization - no org name", + args: args{ + w: httptest.NewRecorder(), + r: httptest.NewRequest( + "GET", + "http://any.url", // can be any valid URL as we are bypassing mux + nil, + ), + user: &chronograf.User{ + ID: 1, + Name: "bobetta", + Provider: "github", + Scheme: "oauth2", + }, + org: &organizationRequest{}, + }, + fields: fields{ + Logger: log.New(log.DebugLevel), + UsersStore: &mocks.UsersStore{ + AddF: func(ctx context.Context, u *chronograf.User) (*chronograf.User, error) { + return &chronograf.User{ + ID: 1, + Name: "bobetta", + Provider: "github", + Scheme: "oauth2", + }, nil + }, + }, + OrganizationsStore: &mocks.OrganizationsStore{ + AddF: func(ctx context.Context, o *chronograf.Organization) (*chronograf.Organization, error) { + return nil, nil + }, + }, + }, + wantStatus: http.StatusUnprocessableEntity, + wantContentType: "application/json", + wantBody: `{"code":422,"message":"Name required on Chronograf Organization request body"}`, + }, + { + name: "Fail to create Organization - mappings missing provider", + args: args{ + w: httptest.NewRecorder(), + r: httptest.NewRequest( + "GET", + "http://any.url", // can be any valid URL as we are bypassing mux + nil, + ), + user: &chronograf.User{ + ID: 1, + Name: "bobetta", + Provider: "github", + Scheme: "oauth2", + }, + org: &organizationRequest{ + Name: "The Good Place", + Mappings: []chronograf.Mapping{ + { + Scheme: chronograf.MappingWildcard, + Group: chronograf.MappingWildcard, + GrantedRole: roles.ViewerRoleName, + }, + }, + }, + }, + fields: fields{ + Logger: log.New(log.DebugLevel), + UsersStore: &mocks.UsersStore{ + AddF: func(ctx context.Context, u *chronograf.User) (*chronograf.User, error) { + return &chronograf.User{ + ID: 1, + Name: "bobetta", + Provider: "github", + Scheme: "oauth2", + }, nil + }, + }, + OrganizationsStore: &mocks.OrganizationsStore{ + AddF: func(ctx context.Context, o *chronograf.Organization) (*chronograf.Organization, error) { + return nil, nil + }, + }, + }, + wantStatus: http.StatusUnprocessableEntity, + wantContentType: "application/json", + wantBody: `{"code":422,"message":"mapping must specify provider"}`, }, { name: "Create Organization - no user on context", From 8dc60c21ff3a4042b7874d5b97ee32aad7850151 Mon Sep 17 00:00:00 2001 From: Michael Desa Date: Tue, 2 Jan 2018 15:48:58 -0800 Subject: [PATCH 02/98] Add group to oauth2.Principal --- oauth2/auth0.go | 22 ++++++++++++++++++++++ oauth2/generic.go | 25 +++++++++++++++++++++++++ oauth2/github.go | 35 ++++++++++++++++++++++++++++++++--- oauth2/google.go | 16 ++++++++++++++++ oauth2/heroku.go | 28 ++++++++++++++++++++++++++++ oauth2/jwt.go | 13 ++++++++++++- oauth2/mux.go | 12 ++++++++++-- oauth2/mux_test.go | 6 +++++- oauth2/oauth2.go | 5 +++++ oauth2/oauth2_test.go | 5 +++++ 10 files changed, 160 insertions(+), 7 deletions(-) diff --git a/oauth2/auth0.go b/oauth2/auth0.go index 9c827b680..1d5c1f4a9 100644 --- a/oauth2/auth0.go +++ b/oauth2/auth0.go @@ -8,6 +8,8 @@ import ( "github.com/influxdata/chronograf" ) +var _ Provider = &Auth0{} + type Auth0 struct { Generic Organizations map[string]bool // the set of allowed organizations users may belong to @@ -41,6 +43,26 @@ func (a *Auth0) PrincipalID(provider *http.Client) (string, error) { return act.Email, nil } +func (a *Auth0) Group(provider *http.Client) (string, error) { + type Account struct { + Email string `json:"email"` + Organization string `json:"organization"` + } + + resp, err := provider.Get(a.Generic.APIURL) + if err != nil { + return "", err + } + + defer resp.Body.Close() + act := Account{} + if err = json.NewDecoder(resp.Body).Decode(&act); err != nil { + return "", err + } + + return act.Organization, nil +} + func NewAuth0(auth0Domain, clientID, clientSecret, redirectURL string, organizations []string, logger chronograf.Logger) (Auth0, error) { domain, err := url.Parse(auth0Domain) if err != nil { diff --git a/oauth2/generic.go b/oauth2/generic.go index 0172c70af..1606c8bf2 100644 --- a/oauth2/generic.go +++ b/oauth2/generic.go @@ -111,6 +111,31 @@ func (g *Generic) PrincipalID(provider *http.Client) (string, error) { return email, nil } +// Group returns the domain that a user belongs to in the +// the generic OAuth. +func (g *Generic) Group(provider *http.Client) (string, error) { + res := struct { + Email string `json:"email"` + }{} + + r, err := provider.Get(g.APIURL) + if err != nil { + return "", err + } + + defer r.Body.Close() + if err = json.NewDecoder(r.Body).Decode(&res); err != nil { + return "", err + } + + email := strings.Split(res.Email, "@") + if len(email) != 2 { + return "", fmt.Errorf("malformed email address, expected %q to contain @ symbol", res.Email) + } + + return email[1], nil +} + // UserEmail represents user's email address type UserEmail struct { Email *string `json:"email,omitempty"` diff --git a/oauth2/github.go b/oauth2/github.go index 0f3e0e932..d31db87db 100644 --- a/oauth2/github.go +++ b/oauth2/github.go @@ -6,6 +6,7 @@ import ( "errors" "io" "net/http" + "strings" "github.com/google/go-github/github" "github.com/influxdata/chronograf" @@ -44,9 +45,9 @@ func (g *Github) Secret() string { // we are filtering by organizations. func (g *Github) Scopes() []string { scopes := []string{"user:email"} - if len(g.Orgs) > 0 { - scopes = append(scopes, "read:org") - } + // In order to access a users orgs, we need the "read:org" scope + // even if g.Orgs == 0 + scopes = append(scopes, "read:org") return scopes } @@ -84,6 +85,34 @@ func (g *Github) PrincipalID(provider *http.Client) (string, error) { return email, nil } +// Group returns a comma delimited string of Github organizations +// that a user belongs to in Github +func (g *Github) Group(provider *http.Client) (string, error) { + client := github.NewClient(provider) + orgs, err := getOrganizations(client, g.Logger) + if err != nil { + return "", err + } + + groups := []string{} + for _, org := range orgs { + // Prefer the organization name over the login. + // It is common for organizations to not have a name + // In the case that they do not, use the org login + if org.Name != nil { + groups = append(groups, *org.Name) + continue + } + + if org.Login != nil { + groups = append(groups, *org.Login) + continue + } + } + + return strings.Join(groups, ","), nil +} + func randomString(length int) string { k := make([]byte, length) if _, err := io.ReadFull(rand.Reader, k); err != nil { diff --git a/oauth2/google.go b/oauth2/google.go index ee5ff3bb1..59a7ec56a 100644 --- a/oauth2/google.go +++ b/oauth2/google.go @@ -88,3 +88,19 @@ func (g *Google) PrincipalID(provider *http.Client) (string, error) { g.Logger.Error("Domain '", info.Hd, "' is not a member of required Google domain(s): ", g.Domains) return "", fmt.Errorf("Not in required domain") } + +// Group returns the string of domain a user belongs to in Google +func (g *Google) Group(provider *http.Client) (string, error) { + srv, err := goauth2.New(provider) + if err != nil { + g.Logger.Error("Unable to communicate with Google ", err.Error()) + return "", err + } + info, err := srv.Userinfo.Get().Do() + if err != nil { + g.Logger.Error("Unable to retrieve Google email ", err.Error()) + return "", err + } + + return info.Hd, nil +} diff --git a/oauth2/heroku.go b/oauth2/heroku.go index 831b095df..9c6f81416 100644 --- a/oauth2/heroku.go +++ b/oauth2/heroku.go @@ -88,6 +88,34 @@ func (h *Heroku) PrincipalID(provider *http.Client) (string, error) { return account.Email, nil } +// Group returns the Heroku organization that user belongs to. +func (h *Heroku) Group(provider *http.Client) (string, error) { + type DefaultOrg struct { + ID string `json:"id"` + Name string `json:"name"` + } + type Account struct { + Email string `json:"email"` + DefaultOrganization DefaultOrg `json:"default_organization"` + } + + resp, err := provider.Get(HerokuAccountRoute) + if err != nil { + h.Logger.Error("Unable to communicate with Heroku. err:", err) + return "", err + } + defer resp.Body.Close() + d := json.NewDecoder(resp.Body) + + var account Account + if err := d.Decode(&account); err != nil { + h.Logger.Error("Unable to decode response from Heroku. err:", err) + return "", err + } + + return account.DefaultOrganization.Name, nil +} + // Scopes for heroku is "identity" which grants access to user account // information. This will grant us access to the user's email address which is // used as the Principal's identifier. diff --git a/oauth2/jwt.go b/oauth2/jwt.go index 794f44a23..b45a623bf 100644 --- a/oauth2/jwt.go +++ b/oauth2/jwt.go @@ -31,9 +31,18 @@ var _ gojwt.Claims = &Claims{} // Claims extends jwt.StandardClaims' Valid to make sure claims has a subject. type Claims struct { gojwt.StandardClaims - // We were unable to find a standard claim at https://www.iana.org/assignments/jwt/jwt.xhtmldd + // We were unable to find a standard claim at https://www.iana.org/assignments/jwt/jwt.xhtml // that felt appropriate for Organization. As a result, we added a custom `org` field. Organization string `json:"org,omitempty"` + // We were unable to find a standard claim at https://www.iana.org/assignments/jwt/jwt.xhtml + // that felt appropriate for a users Group(s). As a result we added a custom `grp` field. + // Multiple groups may be specified by comma delimiting the various group. + // + // The singlular `grp` was chosen over the `grps` to keep consistent with the JWT naming + // convention (it is common for singlularly named values to actually be arrays, see `given_name`, + // `family_name`, and `middle_name` in the iana link provided above). I should add the discalimer + // I'm currently sick, so this thought process might be off. + Group string `json:"grp,omitempty"` } // Valid adds an empty subject test to the StandardClaims checks. @@ -99,6 +108,7 @@ func (j *JWT) ValidClaims(jwtToken Token, lifespan time.Duration, alg gojwt.Keyf Subject: claims.Subject, Issuer: claims.Issuer, Organization: claims.Organization, + Group: claims.Group, ExpiresAt: exp, IssuedAt: iat, }, nil @@ -117,6 +127,7 @@ func (j *JWT) Create(ctx context.Context, user Principal) (Token, error) { NotBefore: user.IssuedAt.Unix(), }, Organization: user.Organization, + Group: user.Group, } token := gojwt.NewWithClaims(gojwt.SigningMethodHS256, claims) // Sign and get the complete encoded token as a string using the secret diff --git a/oauth2/mux.go b/oauth2/mux.go index f9881cbe6..88e04b2e8 100644 --- a/oauth2/mux.go +++ b/oauth2/mux.go @@ -23,8 +23,8 @@ func NewAuthMux(p Provider, a Authenticator, t Tokenizer, basepath string, l chr Tokens: t, SuccessURL: path.Join(basepath, "/"), FailureURL: path.Join(basepath, "/login"), - Now: DefaultNowTime, - Logger: l, + Now: DefaultNowTime, + Logger: l, } } @@ -125,9 +125,17 @@ func (j *AuthMux) Callback() http.Handler { return } + group, err := j.Provider.Group(oauthClient) + if err != nil { + log.Error("Unable to get OAuth Group", err.Error()) + http.Redirect(w, r, j.FailureURL, http.StatusTemporaryRedirect) + return + } + p := Principal{ Subject: id, Issuer: j.Provider.Name(), + Group: group, } ctx := r.Context() err = j.Auth.Authorize(ctx, w, p) diff --git a/oauth2/mux_test.go b/oauth2/mux_test.go index 7cc13ba34..a7dcecdb5 100644 --- a/oauth2/mux_test.go +++ b/oauth2/mux_test.go @@ -27,7 +27,11 @@ func setupMuxTest(selector func(*AuthMux) http.Handler) (*http.Client, *httptest now := func() time.Time { return testTime } - mp := &MockProvider{"biff@example.com", provider.URL} + mp := &MockProvider{ + Email: "biff@example.com", + ProviderURL: provider.URL, + Orgs: "", + } mt := &YesManTokenizer{} auth := &cookie{ Name: DefaultCookieName, diff --git a/oauth2/oauth2.go b/oauth2/oauth2.go index ef0c44b51..6ed19761d 100644 --- a/oauth2/oauth2.go +++ b/oauth2/oauth2.go @@ -33,6 +33,7 @@ type Principal struct { Subject string Issuer string Organization string + Group string ExpiresAt time.Time IssuedAt time.Time } @@ -53,6 +54,10 @@ type Provider interface { PrincipalID(provider *http.Client) (string, error) // Name is the name of the Provider Name() string + // Group is a comma delimited list of groups and organizations for a provider + // TODO: This will break if there are any group names that contain commas. + // I think this is okay, but I'm not 100% certain. + Group(provider *http.Client) (string, error) } // Mux is a collection of handlers responsible for servicing an Oauth2 interaction between a browser and a provider diff --git a/oauth2/oauth2_test.go b/oauth2/oauth2_test.go index 48e4929ce..f3cb1685e 100644 --- a/oauth2/oauth2_test.go +++ b/oauth2/oauth2_test.go @@ -16,6 +16,7 @@ var _ Provider = &MockProvider{} type MockProvider struct { Email string + Orgs string ProviderURL string } @@ -44,6 +45,10 @@ func (mp *MockProvider) PrincipalID(provider *http.Client) (string, error) { return mp.Email, nil } +func (mp *MockProvider) Group(provider *http.Client) (string, error) { + return mp.Orgs, nil +} + func (mp *MockProvider) Scopes() []string { return []string{} } From de61afc8500779f537bbf086e9afac03546619ce Mon Sep 17 00:00:00 2001 From: Michael Desa Date: Wed, 10 Jan 2018 16:34:04 -0500 Subject: [PATCH 03/98] Fix mappings on me response --- integrations/server_test.go | 24 +++------ server/me.go | 14 ++--- server/me_test.go | 102 +++++++++++++++++++++++------------- 3 files changed, 80 insertions(+), 60 deletions(-) diff --git a/integrations/server_test.go b/integrations/server_test.go index ba6465ea8..9e6265ba5 100644 --- a/integrations/server_test.go +++ b/integrations/server_test.go @@ -1376,25 +1376,16 @@ func TestServer(t *testing.T) { "links": { "self": "/chronograf/v1/users/1" }, - "organizations": { - "links": { - "self": "/chronograf/v1/organizations" - }, - "organizations": [ + "organizations": [ { - "links": { - "self": "/chronograf/v1/organizations/1" - }, "id": "1", "name": "Sweet", "defaultRole": "viewer", "public": false, - "mappings": [] + "mappings": [ + ] }, { - "links": { - "self": "/chronograf/v1/organizations/default" - }, "id": "default", "name": "Default", "defaultRole": "member", @@ -1408,17 +1399,14 @@ func TestServer(t *testing.T) { } ] } - ] - }, + ], "currentOrganization": { - "links": { - "self": "/chronograf/v1/organizations/1" - }, "id": "1", "name": "Sweet", "defaultRole": "viewer", "public": false, - "mappings": [] + "mappings": [ + ] } }`, }, diff --git a/server/me.go b/server/me.go index eadd6c0ee..d2a4bfb1c 100644 --- a/server/me.go +++ b/server/me.go @@ -19,9 +19,9 @@ type meLinks struct { type meResponse struct { *chronograf.User - Links meLinks `json:"links"` - Organizations *organizationsResponse `json:"organizations,omitempty"` - CurrentOrganization *organizationResponse `json:"currentOrganization,omitempty"` + Links meLinks `json:"links"` + Organizations []chronograf.Organization `json:"organizations,omitempty"` + CurrentOrganization *chronograf.Organization `json:"currentOrganization,omitempty"` } // If new user response is nil, return an empty meResponse because it @@ -264,8 +264,8 @@ func (s *Service) Me(w http.ResponseWriter, r *http.Request) { return } res := newMeResponse(usr) - res.Organizations = newOrganizationsResponse(orgs) - res.CurrentOrganization = newOrganizationResponse(currentOrg) + res.Organizations = orgs + res.CurrentOrganization = currentOrg encodeJSON(w, http.StatusOK, res, s.Logger) return } @@ -313,8 +313,8 @@ func (s *Service) Me(w http.ResponseWriter, r *http.Request) { return } res := newMeResponse(newUser) - res.Organizations = newOrganizationsResponse(orgs) - res.CurrentOrganization = newOrganizationResponse(currentOrg) + res.Organizations = orgs + res.CurrentOrganization = currentOrg encodeJSON(w, http.StatusOK, res, s.Logger) } diff --git a/server/me_test.go b/server/me_test.go index d9cce6fb5..35df19ba2 100644 --- a/server/me_test.go +++ b/server/me_test.go @@ -63,6 +63,7 @@ func TestService_Me(t *testing.T) { Name: "Default", DefaultRole: roles.ViewerRoleName, Public: false, + Mappings: []chronograf.Mapping{}, }, nil }, GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { @@ -73,12 +74,14 @@ func TestService_Me(t *testing.T) { Name: "Default", DefaultRole: roles.ViewerRoleName, Public: false, + Mappings: []chronograf.Mapping{}, }, nil case "1": return &chronograf.Organization{ - ID: "1", - Name: "The Bad Place", - Public: false, + ID: "1", + Name: "The Bad Place", + Public: false, + Mappings: []chronograf.Mapping{}, }, nil } return nil, nil @@ -128,6 +131,7 @@ func TestService_Me(t *testing.T) { Name: "Default", DefaultRole: roles.ViewerRoleName, Public: false, + Mappings: []chronograf.Mapping{}, }, nil }, GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { @@ -138,12 +142,14 @@ func TestService_Me(t *testing.T) { Name: "Default", DefaultRole: roles.ViewerRoleName, Public: true, + Mappings: []chronograf.Mapping{}, }, nil case "1": return &chronograf.Organization{ - ID: "1", - Name: "The Bad Place", - Public: true, + ID: "1", + Name: "The Bad Place", + Public: true, + Mappings: []chronograf.Mapping{}, }, nil } return nil, nil @@ -176,7 +182,7 @@ func TestService_Me(t *testing.T) { }, wantStatus: http.StatusOK, wantContentType: "application/json", - wantBody: `{"name":"me","roles":[{"name":"viewer","organization":"0"}],"provider":"github","scheme":"oauth2","superAdmin":true,"links":{"self":"/chronograf/v1/users/0"},"organizations":{"links":{"self":"/chronograf/v1/organizations"},"organizations":[{"links":{"self":"/chronograf/v1/organizations/0"},"id":"0","name":"Default","defaultRole":"viewer","public":true,"mappings":[]}]},"currentOrganization":{"links":{"self":"/chronograf/v1/organizations/0"},"id":"0","name":"Default","defaultRole":"viewer","public":true,"mappings":[]}}`, + wantBody: `{"name":"me","roles":[{"name":"viewer","organization":"0"}],"provider":"github","scheme":"oauth2","superAdmin":true,"links":{"self":"/chronograf/v1/users/0"},"organizations":[{"id":"0","name":"Default","defaultRole":"viewer","public":true,"mappings":[]}],"currentOrganization":{"id":"0","name":"Default","defaultRole":"viewer","public":true,"mappings":[]}}`, }, { name: "Existing user - private default org", @@ -194,6 +200,7 @@ func TestService_Me(t *testing.T) { Name: "Default", DefaultRole: roles.ViewerRoleName, Public: false, + Mappings: []chronograf.Mapping{}, }, nil }, GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { @@ -204,12 +211,14 @@ func TestService_Me(t *testing.T) { Name: "Default", DefaultRole: roles.ViewerRoleName, Public: true, + Mappings: []chronograf.Mapping{}, }, nil case "1": return &chronograf.Organization{ - ID: "1", - Name: "The Bad Place", - Public: true, + ID: "1", + Name: "The Bad Place", + Public: true, + Mappings: []chronograf.Mapping{}, }, nil } return nil, nil @@ -259,6 +268,7 @@ func TestService_Me(t *testing.T) { Name: "Default", DefaultRole: roles.ViewerRoleName, Public: true, + Mappings: []chronograf.Mapping{}, }, nil }, GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { @@ -269,12 +279,14 @@ func TestService_Me(t *testing.T) { Name: "Default", DefaultRole: roles.ViewerRoleName, Public: true, + Mappings: []chronograf.Mapping{}, }, nil case "1": return &chronograf.Organization{ - ID: "1", - Name: "The Bad Place", - Public: true, + ID: "1", + Name: "The Bad Place", + Public: true, + Mappings: []chronograf.Mapping{}, }, nil } return nil, nil @@ -306,7 +318,7 @@ func TestService_Me(t *testing.T) { }, wantStatus: http.StatusOK, wantContentType: "application/json", - wantBody: `{"name":"me","roles":[{"name":"viewer","organization":"0"}],"provider":"github","scheme":"oauth2","links":{"self":"/chronograf/v1/users/0"},"organizations":{"links":{"self":"/chronograf/v1/organizations"},"organizations":[{"links":{"self":"/chronograf/v1/organizations/0"},"id":"0","name":"Default","defaultRole":"viewer","public":true,"mappings":[]}]},"currentOrganization":{"links":{"self":"/chronograf/v1/organizations/0"},"id":"0","name":"Default","defaultRole":"viewer","public":true,"mappings":[]}}`, + wantBody: `{"name":"me","roles":[{"name":"viewer","organization":"0"}],"provider":"github","scheme":"oauth2","links":{"self":"/chronograf/v1/users/0"},"organizations":[{"id":"0","name":"Default","defaultRole":"viewer","public":true,"mappings":[]}],"currentOrganization":{"id":"0","name":"Default","defaultRole":"viewer","public":true,"mappings":[]}}`, }, { name: "Existing user - organization doesn't exist", @@ -324,6 +336,7 @@ func TestService_Me(t *testing.T) { Name: "Default", DefaultRole: roles.ViewerRoleName, Public: true, + Mappings: []chronograf.Mapping{}, }, nil }, GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { @@ -334,6 +347,7 @@ func TestService_Me(t *testing.T) { Name: "Default", DefaultRole: roles.ViewerRoleName, Public: true, + Mappings: []chronograf.Mapping{}, }, nil } return nil, chronograf.ErrOrganizationNotFound @@ -387,6 +401,7 @@ func TestService_Me(t *testing.T) { Name: "The Gnarly Default", DefaultRole: roles.ViewerRoleName, Public: true, + Mappings: []chronograf.Mapping{}, }, nil }, GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { @@ -395,6 +410,7 @@ func TestService_Me(t *testing.T) { Name: "The Gnarly Default", DefaultRole: roles.ViewerRoleName, Public: true, + Mappings: []chronograf.Mapping{}, }, nil }, }, @@ -423,7 +439,7 @@ func TestService_Me(t *testing.T) { }, wantStatus: http.StatusOK, wantContentType: "application/json", - wantBody: `{"name":"secret","roles":[{"name":"viewer","organization":"0"}],"provider":"auth0","scheme":"oauth2","superAdmin":true,"links":{"self":"/chronograf/v1/users/0"},"organizations":{"links":{"self":"/chronograf/v1/organizations"},"organizations":[{"links":{"self":"/chronograf/v1/organizations/0"},"id":"0","name":"The Gnarly Default","defaultRole":"viewer","public":true,"mappings":[]}]},"currentOrganization":{"links":{"self":"/chronograf/v1/organizations/0"},"id":"0","name":"The Gnarly Default","defaultRole":"viewer","public":true,"mappings":[]}}`, + wantBody: `{"name":"secret","roles":[{"name":"viewer","organization":"0"}],"provider":"auth0","scheme":"oauth2","superAdmin":true,"links":{"self":"/chronograf/v1/users/0"},"organizations":[{"id":"0","name":"The Gnarly Default","defaultRole":"viewer","public":true,"mappings":[]}],"currentOrganization":{"id":"0","name":"The Gnarly Default","defaultRole":"viewer","public":true,"mappings":[]}}`, }, { name: "New user - New users not super admin, not first user", @@ -448,6 +464,7 @@ func TestService_Me(t *testing.T) { Name: "The Gnarly Default", DefaultRole: roles.ViewerRoleName, Public: true, + Mappings: []chronograf.Mapping{}, }, nil }, GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { @@ -456,6 +473,7 @@ func TestService_Me(t *testing.T) { Name: "The Gnarly Default", DefaultRole: roles.ViewerRoleName, Public: true, + Mappings: []chronograf.Mapping{}, }, nil }, }, @@ -484,7 +502,7 @@ func TestService_Me(t *testing.T) { }, wantStatus: http.StatusOK, wantContentType: "application/json", - wantBody: `{"name":"secret","roles":[{"name":"viewer","organization":"0"}],"provider":"auth0","scheme":"oauth2","links":{"self":"/chronograf/v1/users/0"},"organizations":{"links":{"self":"/chronograf/v1/organizations"},"organizations":[{"links":{"self":"/chronograf/v1/organizations/0"},"id":"0","name":"The Gnarly Default","defaultRole":"viewer","public":true,"mappings":[]}]},"currentOrganization":{"links":{"self":"/chronograf/v1/organizations/0"},"id":"0","name":"The Gnarly Default","defaultRole":"viewer","public":true,"mappings":[]}}`, + wantBody: `{"name":"secret","roles":[{"name":"viewer","organization":"0"}],"provider":"auth0","scheme":"oauth2","links":{"self":"/chronograf/v1/users/0"},"organizations":[{"id":"0","name":"The Gnarly Default","defaultRole":"viewer","public":true,"mappings":[]}],"currentOrganization":{"id":"0","name":"The Gnarly Default","defaultRole":"viewer","public":true,"mappings":[]}}`, }, { name: "New user - New users not super admin, first user", @@ -509,6 +527,7 @@ func TestService_Me(t *testing.T) { Name: "The Gnarly Default", DefaultRole: roles.ViewerRoleName, Public: true, + Mappings: []chronograf.Mapping{}, }, nil }, GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { @@ -517,6 +536,7 @@ func TestService_Me(t *testing.T) { Name: "The Gnarly Default", DefaultRole: roles.ViewerRoleName, Public: true, + Mappings: []chronograf.Mapping{}, }, nil }, }, @@ -545,7 +565,7 @@ func TestService_Me(t *testing.T) { }, wantStatus: http.StatusOK, wantContentType: "application/json", - wantBody: `{"name":"secret","roles":[{"name":"viewer","organization":"0"}],"provider":"auth0","scheme":"oauth2","superAdmin":true,"links":{"self":"/chronograf/v1/users/0"},"organizations":{"links":{"self":"/chronograf/v1/organizations"},"organizations":[{"links":{"self":"/chronograf/v1/organizations/0"},"id":"0","name":"The Gnarly Default","defaultRole":"viewer","public":true,"mappings":[]}]},"currentOrganization":{"links":{"self":"/chronograf/v1/organizations/0"},"id":"0","name":"The Gnarly Default","defaultRole":"viewer","public":true,"mappings":[]}}`, + wantBody: `{"name":"secret","roles":[{"name":"viewer","organization":"0"}],"provider":"auth0","scheme":"oauth2","superAdmin":true,"links":{"self":"/chronograf/v1/users/0"},"organizations":[{"id":"0","name":"The Gnarly Default","defaultRole":"viewer","public":true,"mappings":[]}],"currentOrganization":{"id":"0","name":"The Gnarly Default","defaultRole":"viewer","public":true,"mappings":[]}}`, }, { name: "Error adding user", @@ -565,15 +585,17 @@ func TestService_Me(t *testing.T) { OrganizationsStore: &mocks.OrganizationsStore{ DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) { return &chronograf.Organization{ - ID: "0", - Public: true, + ID: "0", + Public: true, + Mappings: []chronograf.Mapping{}, }, nil }, GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { return &chronograf.Organization{ - ID: "0", - Name: "The Bad Place", - Public: true, + ID: "0", + Name: "The Bad Place", + Public: true, + Mappings: []chronograf.Mapping{}, }, nil }, }, @@ -663,6 +685,7 @@ func TestService_Me(t *testing.T) { Name: "The Bad Place", DefaultRole: roles.MemberRoleName, Public: false, + Mappings: []chronograf.Mapping{}, }, nil }, }, @@ -790,6 +813,7 @@ func TestService_UpdateMe(t *testing.T) { Name: "Default", DefaultRole: roles.AdminRoleName, Public: true, + Mappings: []chronograf.Mapping{}, }, nil }, GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { @@ -803,12 +827,14 @@ func TestService_UpdateMe(t *testing.T) { Name: "Default", DefaultRole: roles.AdminRoleName, Public: true, + Mappings: []chronograf.Mapping{}, }, nil case "1337": return &chronograf.Organization{ - ID: "1337", - Name: "The ShillBillThrilliettas", - Public: true, + ID: "1337", + Name: "The ShillBillThrilliettas", + Public: true, + Mappings: []chronograf.Mapping{}, }, nil } return nil, nil @@ -821,7 +847,7 @@ func TestService_UpdateMe(t *testing.T) { }, wantStatus: http.StatusOK, wantContentType: "application/json", - wantBody: `{"name":"me","roles":[{"name":"admin","organization":"1337"},{"name":"admin","organization":"0"}],"provider":"github","scheme":"oauth2","links":{"self":"/chronograf/v1/users/0"},"organizations":{"links":{"self":"/chronograf/v1/organizations"},"organizations":[{"links":{"self":"/chronograf/v1/organizations/0"},"id":"0","name":"Default","defaultRole":"admin","public":true,"mappings":[]},{"links":{"self":"/chronograf/v1/organizations/1337"},"id":"1337","name":"The ShillBillThrilliettas","public":true,"mappings":[]}]},"currentOrganization":{"links":{"self":"/chronograf/v1/organizations/1337"},"id":"1337","name":"The ShillBillThrilliettas","public":true,"mappings":[]}}`, + wantBody: `{"name":"me","roles":[{"name":"admin","organization":"1337"},{"name":"admin","organization":"0"}],"provider":"github","scheme":"oauth2","links":{"self":"/chronograf/v1/users/0"},"organizations":[{"id":"0","name":"Default","defaultRole":"admin","public":true,"mappings":[]},{"id":"1337","name":"The ShillBillThrilliettas","public":true,"mappings":[]}],"currentOrganization":{"id":"1337","name":"The ShillBillThrilliettas","public":true,"mappings":[]}}`, }, { name: "Change the current User's organization", @@ -864,6 +890,7 @@ func TestService_UpdateMe(t *testing.T) { Name: "Default", DefaultRole: roles.EditorRoleName, Public: true, + Mappings: []chronograf.Mapping{}, }, nil }, GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { @@ -873,9 +900,10 @@ func TestService_UpdateMe(t *testing.T) { switch *q.ID { case "1337": return &chronograf.Organization{ - ID: "1337", - Name: "The ThrillShilliettos", - Public: false, + ID: "1337", + Name: "The ThrillShilliettos", + Public: false, + Mappings: []chronograf.Mapping{}, }, nil case "0": return &chronograf.Organization{ @@ -883,6 +911,7 @@ func TestService_UpdateMe(t *testing.T) { Name: "Default", DefaultRole: roles.EditorRoleName, Public: true, + Mappings: []chronograf.Mapping{}, }, nil } return nil, nil @@ -896,7 +925,7 @@ func TestService_UpdateMe(t *testing.T) { }, wantStatus: http.StatusOK, wantContentType: "application/json", - wantBody: `{"name":"me","roles":[{"name":"admin","organization":"1337"},{"name":"editor","organization":"0"}],"provider":"github","scheme":"oauth2","links":{"self":"/chronograf/v1/users/0"},"organizations":{"links":{"self":"/chronograf/v1/organizations"},"organizations":[{"links":{"self":"/chronograf/v1/organizations/0"},"id":"0","name":"Default","defaultRole":"editor","public":true,"mappings":[]},{"links":{"self":"/chronograf/v1/organizations/1337"},"id":"1337","name":"The ThrillShilliettos","public":false,"mappings":[]}]},"currentOrganization":{"links":{"self":"/chronograf/v1/organizations/1337"},"id":"1337","name":"The ThrillShilliettos","public":false,"mappings":[]}}`, + wantBody: `{"name":"me","roles":[{"name":"admin","organization":"1337"},{"name":"editor","organization":"0"}],"provider":"github","scheme":"oauth2","links":{"self":"/chronograf/v1/users/0"},"organizations":[{"id":"0","name":"Default","defaultRole":"editor","public":true,"mappings":[]},{"id":"1337","name":"The ThrillShilliettos","public":false,"mappings":[]}],"currentOrganization":{"id":"1337","name":"The ThrillShilliettos","public":false,"mappings":[]}}`, }, { name: "Unable to find requested user in valid organization", @@ -935,7 +964,8 @@ func TestService_UpdateMe(t *testing.T) { OrganizationsStore: &mocks.OrganizationsStore{ DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) { return &chronograf.Organization{ - ID: "0", + ID: "0", + Mappings: []chronograf.Mapping{}, }, nil }, GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { @@ -943,9 +973,10 @@ func TestService_UpdateMe(t *testing.T) { return nil, fmt.Errorf("Invalid organization query: missing ID") } return &chronograf.Organization{ - ID: "1337", - Name: "The ShillBillThrilliettas", - Public: true, + ID: "1337", + Name: "The ShillBillThrilliettas", + Public: true, + Mappings: []chronograf.Mapping{}, }, nil }, }, @@ -996,7 +1027,8 @@ func TestService_UpdateMe(t *testing.T) { OrganizationsStore: &mocks.OrganizationsStore{ DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) { return &chronograf.Organization{ - ID: "0", + ID: "0", + Mappings: []chronograf.Mapping{}, }, nil }, GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { From 2e694aee4c3e548296dba276531fc7b6d23a9c91 Mon Sep 17 00:00:00 2001 From: Michael Desa Date: Wed, 10 Jan 2018 16:38:55 -0500 Subject: [PATCH 04/98] Use github group login for mapping --- oauth2/github.go | 8 -------- 1 file changed, 8 deletions(-) diff --git a/oauth2/github.go b/oauth2/github.go index d31db87db..d3a50c7c3 100644 --- a/oauth2/github.go +++ b/oauth2/github.go @@ -96,14 +96,6 @@ func (g *Github) Group(provider *http.Client) (string, error) { groups := []string{} for _, org := range orgs { - // Prefer the organization name over the login. - // It is common for organizations to not have a name - // In the case that they do not, use the org login - if org.Name != nil { - groups = append(groups, *org.Name) - continue - } - if org.Login != nil { groups = append(groups, *org.Login) continue From c833020944e0bbef878e7c8baefb62029e5e99fd Mon Sep 17 00:00:00 2001 From: Michael Desa Date: Thu, 11 Jan 2018 12:36:13 -0500 Subject: [PATCH 05/98] Add logic mapping applying a mapping --- server/mapping.go | 111 ++++++++++++ server/mapping_test.go | 391 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 502 insertions(+) create mode 100644 server/mapping.go create mode 100644 server/mapping_test.go diff --git a/server/mapping.go b/server/mapping.go new file mode 100644 index 000000000..e7534e6af --- /dev/null +++ b/server/mapping.go @@ -0,0 +1,111 @@ +package server + +import ( + "strings" + + "github.com/influxdata/chronograf" + "github.com/influxdata/chronograf/oauth2" + "github.com/influxdata/chronograf/roles" +) + +func MappedRole(o *chronograf.Organization, p oauth2.Principal) *chronograf.Role { + roles := []*chronograf.Role{} + for _, mapping := range o.Mappings { + role := applyMapping(mapping, p) + if role != nil { + role.Organization = o.ID + roles = append(roles, role) + } + } + + return maxRole(roles) +} + +func applyMapping(m chronograf.Mapping, p oauth2.Principal) *chronograf.Role { + switch m.Provider { + case chronograf.MappingWildcard, p.Issuer: + default: + return nil + } + + switch m.Scheme { + case chronograf.MappingWildcard, "oauth2": + default: + return nil + } + + if m.Group == chronograf.MappingWildcard { + return &chronograf.Role{ + Name: m.GrantedRole, + } + } + + groups := strings.Split(p.Group, ",") + + match := matchGroup(m.Group, groups) + + if match { + return &chronograf.Role{ + Name: m.GrantedRole, + } + } + + return nil +} + +func matchGroup(match string, groups []string) bool { + for _, group := range groups { + if match == group { + return true + } + } + + return false +} + +func maxRole(roles []*chronograf.Role) *chronograf.Role { + var max *chronograf.Role + for _, role := range roles { + max = maximumRole(max, role) + } + + return max +} + +func maximumRole(r1, r2 *chronograf.Role) *chronograf.Role { + if r1 == nil { + return r2 + } + if r2 == nil { + return r2 + } + if r1.Name == roles.AdminRoleName { + return r1 + } + if r2.Name == roles.AdminRoleName { + return r2 + } + + if r1.Name == roles.EditorRoleName { + return r1 + } + if r2.Name == roles.EditorRoleName { + return r2 + } + + if r1.Name == roles.ViewerRoleName { + return r1 + } + if r2.Name == roles.ViewerRoleName { + return r2 + } + + if r1.Name == roles.MemberRoleName { + return r1 + } + if r2.Name == roles.MemberRoleName { + return r2 + } + + return nil +} diff --git a/server/mapping_test.go b/server/mapping_test.go new file mode 100644 index 000000000..199e8ef20 --- /dev/null +++ b/server/mapping_test.go @@ -0,0 +1,391 @@ +package server_test + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/influxdata/chronograf" + "github.com/influxdata/chronograf/oauth2" + "github.com/influxdata/chronograf/roles" + "github.com/influxdata/chronograf/server" +) + +func TestMappedRole(t *testing.T) { + type args struct { + org *chronograf.Organization + principal oauth2.Principal + } + type wants struct { + role *chronograf.Role + } + + tests := []struct { + name string + args args + wants wants + }{ + { + name: "single mapping all wildcards", + args: args{ + org: &chronograf.Organization{ + ID: "cool", + Name: "Cool Org", + Mappings: []chronograf.Mapping{ + chronograf.Mapping{ + Provider: chronograf.MappingWildcard, + Scheme: chronograf.MappingWildcard, + Group: chronograf.MappingWildcard, + GrantedRole: roles.ViewerRoleName, + }, + }, + }, + }, + wants: wants{ + role: &chronograf.Role{ + Name: roles.ViewerRoleName, + Organization: "cool", + }, + }, + }, + { + name: "two mapping all wildcards", + args: args{ + org: &chronograf.Organization{ + ID: "cool", + Name: "Cool Org", + Mappings: []chronograf.Mapping{ + chronograf.Mapping{ + Provider: chronograf.MappingWildcard, + Scheme: chronograf.MappingWildcard, + Group: chronograf.MappingWildcard, + GrantedRole: roles.ViewerRoleName, + }, + chronograf.Mapping{ + Provider: chronograf.MappingWildcard, + Scheme: chronograf.MappingWildcard, + Group: chronograf.MappingWildcard, + GrantedRole: roles.EditorRoleName, + }, + }, + }, + }, + wants: wants{ + role: &chronograf.Role{ + Name: roles.EditorRoleName, + Organization: "cool", + }, + }, + }, + { + name: "two mapping all wildcards, different order", + args: args{ + org: &chronograf.Organization{ + ID: "cool", + Name: "Cool Org", + Mappings: []chronograf.Mapping{ + chronograf.Mapping{ + Provider: chronograf.MappingWildcard, + Scheme: chronograf.MappingWildcard, + Group: chronograf.MappingWildcard, + GrantedRole: roles.EditorRoleName, + }, + chronograf.Mapping{ + Provider: chronograf.MappingWildcard, + Scheme: chronograf.MappingWildcard, + Group: chronograf.MappingWildcard, + GrantedRole: roles.ViewerRoleName, + }, + }, + }, + }, + wants: wants{ + role: &chronograf.Role{ + Name: roles.EditorRoleName, + Organization: "cool", + }, + }, + }, + { + name: "two mappings with explicit principal", + args: args{ + principal: oauth2.Principal{ + Subject: "billieta@influxdata.com", + Issuer: "google", + Group: "influxdata.com", + }, + org: &chronograf.Organization{ + ID: "cool", + Name: "Cool Org", + Mappings: []chronograf.Mapping{ + chronograf.Mapping{ + Provider: chronograf.MappingWildcard, + Scheme: "oauth2", + Group: chronograf.MappingWildcard, + GrantedRole: roles.MemberRoleName, + }, + chronograf.Mapping{ + Provider: chronograf.MappingWildcard, + Scheme: chronograf.MappingWildcard, + Group: chronograf.MappingWildcard, + GrantedRole: roles.MemberRoleName, + }, + }, + }, + }, + wants: wants{ + role: &chronograf.Role{ + Name: roles.MemberRoleName, + Organization: "cool", + }, + }, + }, + { + name: "different two mapping all wildcards", + args: args{ + org: &chronograf.Organization{ + ID: "cool", + Name: "Cool Org", + Mappings: []chronograf.Mapping{ + chronograf.Mapping{ + Provider: chronograf.MappingWildcard, + Scheme: chronograf.MappingWildcard, + Group: chronograf.MappingWildcard, + GrantedRole: roles.ViewerRoleName, + }, + chronograf.Mapping{ + Provider: chronograf.MappingWildcard, + Scheme: chronograf.MappingWildcard, + Group: chronograf.MappingWildcard, + GrantedRole: roles.MemberRoleName, + }, + }, + }, + }, + wants: wants{ + role: &chronograf.Role{ + Name: roles.ViewerRoleName, + Organization: "cool", + }, + }, + }, + { + name: "different two mapping all wildcards, different ordering", + args: args{ + org: &chronograf.Organization{ + ID: "cool", + Name: "Cool Org", + Mappings: []chronograf.Mapping{ + chronograf.Mapping{ + Provider: chronograf.MappingWildcard, + Scheme: chronograf.MappingWildcard, + Group: chronograf.MappingWildcard, + GrantedRole: roles.MemberRoleName, + }, + chronograf.Mapping{ + Provider: chronograf.MappingWildcard, + Scheme: chronograf.MappingWildcard, + Group: chronograf.MappingWildcard, + GrantedRole: roles.ViewerRoleName, + }, + }, + }, + }, + wants: wants{ + role: &chronograf.Role{ + Name: roles.ViewerRoleName, + Organization: "cool", + }, + }, + }, + { + name: "three mapping all wildcards", + args: args{ + org: &chronograf.Organization{ + ID: "cool", + Name: "Cool Org", + Mappings: []chronograf.Mapping{ + chronograf.Mapping{ + Provider: chronograf.MappingWildcard, + Scheme: chronograf.MappingWildcard, + Group: chronograf.MappingWildcard, + GrantedRole: roles.EditorRoleName, + }, + chronograf.Mapping{ + Provider: chronograf.MappingWildcard, + Scheme: chronograf.MappingWildcard, + Group: chronograf.MappingWildcard, + GrantedRole: roles.ViewerRoleName, + }, + chronograf.Mapping{ + Provider: chronograf.MappingWildcard, + Scheme: chronograf.MappingWildcard, + Group: chronograf.MappingWildcard, + GrantedRole: roles.AdminRoleName, + }, + }, + }, + }, + wants: wants{ + role: &chronograf.Role{ + Name: roles.AdminRoleName, + Organization: "cool", + }, + }, + }, + { + name: "three mapping only one match", + args: args{ + principal: oauth2.Principal{ + Subject: "billieta@influxdata.com", + Issuer: "google", + Group: "influxdata.com", + }, + org: &chronograf.Organization{ + ID: "cool", + Name: "Cool Org", + Mappings: []chronograf.Mapping{ + chronograf.Mapping{ + Provider: "google", + Scheme: chronograf.MappingWildcard, + Group: "influxdata.com", + GrantedRole: roles.EditorRoleName, + }, + chronograf.Mapping{ + Provider: "google", + Scheme: chronograf.MappingWildcard, + Group: "not_influxdata", + GrantedRole: roles.AdminRoleName, + }, + chronograf.Mapping{ + Provider: chronograf.MappingWildcard, + Scheme: "ldap", + Group: chronograf.MappingWildcard, + GrantedRole: roles.AdminRoleName, + }, + }, + }, + }, + wants: wants{ + role: &chronograf.Role{ + Name: roles.EditorRoleName, + Organization: "cool", + }, + }, + }, + { + name: "three mapping only two matches", + args: args{ + principal: oauth2.Principal{ + Subject: "billieta@influxdata.com", + Issuer: "google", + Group: "influxdata.com", + }, + org: &chronograf.Organization{ + ID: "cool", + Name: "Cool Org", + Mappings: []chronograf.Mapping{ + chronograf.Mapping{ + Provider: "google", + Scheme: chronograf.MappingWildcard, + Group: "influxdata.com", + GrantedRole: roles.AdminRoleName, + }, + chronograf.Mapping{ + Provider: "google", + Scheme: chronograf.MappingWildcard, + Group: chronograf.MappingWildcard, + GrantedRole: roles.EditorRoleName, + }, + chronograf.Mapping{ + Provider: chronograf.MappingWildcard, + Scheme: "ldap", + Group: chronograf.MappingWildcard, + GrantedRole: roles.AdminRoleName, + }, + }, + }, + }, + wants: wants{ + role: &chronograf.Role{ + Name: roles.AdminRoleName, + Organization: "cool", + }, + }, + }, + { + name: "missing provider", + args: args{ + principal: oauth2.Principal{ + Subject: "billieta@influxdata.com", + Issuer: "google", + Group: "influxdata.com", + }, + org: &chronograf.Organization{ + ID: "cool", + Name: "Cool Org", + Mappings: []chronograf.Mapping{ + chronograf.Mapping{ + Provider: "", + Scheme: "ldap", + Group: chronograf.MappingWildcard, + GrantedRole: roles.AdminRoleName, + }, + }, + }, + }, + wants: wants{ + role: nil, + }, + }, + { + name: "user is in multiple github groups", + args: args{ + principal: oauth2.Principal{ + Subject: "billieta@influxdata.com", + Issuer: "github", + Group: "influxdata,another,mimi", + }, + org: &chronograf.Organization{ + ID: "cool", + Name: "Cool Org", + Mappings: []chronograf.Mapping{ + chronograf.Mapping{ + Provider: "github", + Scheme: chronograf.MappingWildcard, + Group: "influxdata", + GrantedRole: roles.MemberRoleName, + }, + chronograf.Mapping{ + Provider: "github", + Scheme: chronograf.MappingWildcard, + Group: "mimi", + GrantedRole: roles.EditorRoleName, + }, + chronograf.Mapping{ + Provider: "github", + Scheme: chronograf.MappingWildcard, + Group: "another", + GrantedRole: roles.AdminRoleName, + }, + }, + }, + }, + wants: wants{ + role: &chronograf.Role{ + Name: roles.AdminRoleName, + Organization: "cool", + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + role := server.MappedRole(tt.args.org, tt.args.principal) + + if diff := cmp.Diff(role, tt.wants.role); diff != "" { + t.Errorf("%q. MappedRole():\n-got/+want\ndiff %s", tt.name, diff) + } + }) + } +} From 0b89623db8e188e4b38b81f5d27606276ac8de66 Mon Sep 17 00:00:00 2001 From: Michael Desa Date: Thu, 11 Jan 2018 16:10:27 -0500 Subject: [PATCH 06/98] Apply mapping to user in /me --- integrations/server_test.go | 195 ++++++++++++++++++++++++++++++++++++ server/mapping.go | 2 +- server/mapping_test.go | 24 ++--- server/me.go | 22 ++-- server/me_test.go | 168 +++++++++++++++++++++++++++++++ 5 files changed, 391 insertions(+), 20 deletions(-) diff --git a/integrations/server_test.go b/integrations/server_test.go index 9e6265ba5..0eb028bd9 100644 --- a/integrations/server_test.go +++ b/integrations/server_test.go @@ -1303,6 +1303,201 @@ func TestServer(t *testing.T) { "code": 401, "message": "user cannot modify their own SuperAdmin status" } +`, + }, + }, + { + name: "GET /me", + subName: "New user hits me for the first time", + fields: fields{ + Config: &chronograf.Config{ + Auth: chronograf.AuthConfig{ + SuperAdminNewUsers: false, + }, + }, + Organizations: []chronograf.Organization{ + { + ID: "1", + Name: "Sweet", + DefaultRole: roles.ViewerRoleName, + Mappings: []chronograf.Mapping{ + chronograf.Mapping{ + Provider: chronograf.MappingWildcard, + Scheme: chronograf.MappingWildcard, + Group: chronograf.MappingWildcard, + GrantedRole: roles.EditorRoleName, + }, + chronograf.Mapping{ + Provider: "github", + Scheme: chronograf.MappingWildcard, + Group: "influxdata", + GrantedRole: roles.AdminRoleName, + }, + chronograf.Mapping{ + Provider: "github", + Scheme: chronograf.MappingWildcard, + Group: "mimi", + GrantedRole: roles.ViewerRoleName, + }, + }, + }, + { + ID: "2", + Name: "What", + DefaultRole: roles.ViewerRoleName, + Mappings: []chronograf.Mapping{ + chronograf.Mapping{ + Provider: chronograf.MappingWildcard, + Scheme: chronograf.MappingWildcard, + Group: chronograf.MappingWildcard, + GrantedRole: roles.MemberRoleName, + }, + chronograf.Mapping{ + Provider: "github", + Scheme: chronograf.MappingWildcard, + Group: "mimi", + GrantedRole: roles.ViewerRoleName, + }, + }, + }, + { + ID: "3", + Name: "Okay", + DefaultRole: roles.ViewerRoleName, + Mappings: []chronograf.Mapping{}, + }, + }, + Users: []chronograf.User{ + { + ID: 1, // This is artificial, but should be reflective of the users actual ID + Name: "billibob", + Provider: "github", + Scheme: "oauth2", + SuperAdmin: true, + Roles: []chronograf.Role{ + { + Name: "admin", + Organization: "default", + }, + }, + }, + }, + }, + args: args{ + server: &server.Server{ + GithubClientID: "not empty", + GithubClientSecret: "not empty", + }, + method: "GET", + path: "/chronograf/v1/me", + principal: oauth2.Principal{ + Subject: "billietta", + Issuer: "github", + Group: "influxdata,idk,mimi", + }, + }, + wants: wants{ + statusCode: 200, + body: ` +{ + "id": "2", + "name": "billietta", + "roles": [ + { + "name": "admin", + "organization": "1" + }, + { + "name": "viewer", + "organization": "2" + }, + { + "name": "member", + "organization": "default" + } + ], + "provider": "github", + "scheme": "oauth2", + "links": { + "self": "/chronograf/v1/users/2" + }, + "organizations": [ + { + "id": "1", + "name": "Sweet", + "defaultRole": "viewer", + "public": false, + "mappings": [ + { + "provider": "*", + "scheme": "*", + "group": "*", + "grantedRole": "editor" + }, + { + "provider": "github", + "scheme": "*", + "group": "influxdata", + "grantedRole": "admin" + }, + { + "provider": "github", + "scheme": "*", + "group": "mimi", + "grantedRole": "viewer" + } + ] + }, + { + "id": "2", + "name": "What", + "defaultRole": "viewer", + "public": false, + "mappings": [ + { + "provider": "*", + "scheme": "*", + "group": "*", + "grantedRole": "member" + }, + { + "provider": "github", + "scheme": "*", + "group": "mimi", + "grantedRole": "viewer" + } + ] + }, + { + "id": "default", + "name": "Default", + "defaultRole": "member", + "public": true, + "mappings": [ + { + "provider": "*", + "scheme": "*", + "group": "*", + "grantedRole": "member" + } + ] + } + ], + "currentOrganization": { + "id": "default", + "name": "Default", + "defaultRole": "member", + "public": true, + "mappings": [ + { + "provider": "*", + "scheme": "*", + "group": "*", + "grantedRole": "member" + } + ] + } +} `, }, }, diff --git a/server/mapping.go b/server/mapping.go index e7534e6af..07b675188 100644 --- a/server/mapping.go +++ b/server/mapping.go @@ -8,7 +8,7 @@ import ( "github.com/influxdata/chronograf/roles" ) -func MappedRole(o *chronograf.Organization, p oauth2.Principal) *chronograf.Role { +func MappedRole(o chronograf.Organization, p oauth2.Principal) *chronograf.Role { roles := []*chronograf.Role{} for _, mapping := range o.Mappings { role := applyMapping(mapping, p) diff --git a/server/mapping_test.go b/server/mapping_test.go index 199e8ef20..084b0e77a 100644 --- a/server/mapping_test.go +++ b/server/mapping_test.go @@ -12,7 +12,7 @@ import ( func TestMappedRole(t *testing.T) { type args struct { - org *chronograf.Organization + org chronograf.Organization principal oauth2.Principal } type wants struct { @@ -27,7 +27,7 @@ func TestMappedRole(t *testing.T) { { name: "single mapping all wildcards", args: args{ - org: &chronograf.Organization{ + org: chronograf.Organization{ ID: "cool", Name: "Cool Org", Mappings: []chronograf.Mapping{ @@ -50,7 +50,7 @@ func TestMappedRole(t *testing.T) { { name: "two mapping all wildcards", args: args{ - org: &chronograf.Organization{ + org: chronograf.Organization{ ID: "cool", Name: "Cool Org", Mappings: []chronograf.Mapping{ @@ -79,7 +79,7 @@ func TestMappedRole(t *testing.T) { { name: "two mapping all wildcards, different order", args: args{ - org: &chronograf.Organization{ + org: chronograf.Organization{ ID: "cool", Name: "Cool Org", Mappings: []chronograf.Mapping{ @@ -113,7 +113,7 @@ func TestMappedRole(t *testing.T) { Issuer: "google", Group: "influxdata.com", }, - org: &chronograf.Organization{ + org: chronograf.Organization{ ID: "cool", Name: "Cool Org", Mappings: []chronograf.Mapping{ @@ -142,7 +142,7 @@ func TestMappedRole(t *testing.T) { { name: "different two mapping all wildcards", args: args{ - org: &chronograf.Organization{ + org: chronograf.Organization{ ID: "cool", Name: "Cool Org", Mappings: []chronograf.Mapping{ @@ -171,7 +171,7 @@ func TestMappedRole(t *testing.T) { { name: "different two mapping all wildcards, different ordering", args: args{ - org: &chronograf.Organization{ + org: chronograf.Organization{ ID: "cool", Name: "Cool Org", Mappings: []chronograf.Mapping{ @@ -200,7 +200,7 @@ func TestMappedRole(t *testing.T) { { name: "three mapping all wildcards", args: args{ - org: &chronograf.Organization{ + org: chronograf.Organization{ ID: "cool", Name: "Cool Org", Mappings: []chronograf.Mapping{ @@ -240,7 +240,7 @@ func TestMappedRole(t *testing.T) { Issuer: "google", Group: "influxdata.com", }, - org: &chronograf.Organization{ + org: chronograf.Organization{ ID: "cool", Name: "Cool Org", Mappings: []chronograf.Mapping{ @@ -280,7 +280,7 @@ func TestMappedRole(t *testing.T) { Issuer: "google", Group: "influxdata.com", }, - org: &chronograf.Organization{ + org: chronograf.Organization{ ID: "cool", Name: "Cool Org", Mappings: []chronograf.Mapping{ @@ -320,7 +320,7 @@ func TestMappedRole(t *testing.T) { Issuer: "google", Group: "influxdata.com", }, - org: &chronograf.Organization{ + org: chronograf.Organization{ ID: "cool", Name: "Cool Org", Mappings: []chronograf.Mapping{ @@ -345,7 +345,7 @@ func TestMappedRole(t *testing.T) { Issuer: "github", Group: "influxdata,another,mimi", }, - org: &chronograf.Organization{ + org: chronograf.Organization{ ID: "cool", Name: "Cool Org", Mappings: []chronograf.Mapping{ diff --git a/server/me.go b/server/me.go index d2a4bfb1c..3b53b9dc8 100644 --- a/server/me.go +++ b/server/me.go @@ -284,17 +284,25 @@ func (s *Service) Me(w http.ResponseWriter, r *http.Request) { // support OAuth2. This hard-coding should be removed whenever we add // support for other authentication schemes. Scheme: scheme, - Roles: []chronograf.Role{ - { - Name: defaultOrg.DefaultRole, - // This is the ID of the default organization - Organization: defaultOrg.ID, - }, - }, // TODO(desa): this needs a better name SuperAdmin: s.newUsersAreSuperAdmin(), } + allOrgs, err := s.Store.Organizations(serverCtx).All(serverCtx) + if err != nil { + Error(w, http.StatusInternalServerError, err.Error(), s.Logger) + return + } + roles := []chronograf.Role{} + for _, org := range allOrgs { + role := MappedRole(org, p) + if role != nil { + roles = append(roles, *role) + } + } + + user.Roles = roles + newUser, err := s.Store.Users(serverCtx).Add(serverCtx, user) if err != nil { msg := fmt.Errorf("error storing user %s: %v", user.Name, err) diff --git a/server/me_test.go b/server/me_test.go index 35df19ba2..f52a9725c 100644 --- a/server/me_test.go +++ b/server/me_test.go @@ -413,6 +413,24 @@ func TestService_Me(t *testing.T) { Mappings: []chronograf.Mapping{}, }, nil }, + AllF: func(ctx context.Context) ([]chronograf.Organization, error) { + return []chronograf.Organization{ + chronograf.Organization{ + ID: "0", + Name: "The Gnarly Default", + DefaultRole: roles.ViewerRoleName, + Public: true, + Mappings: []chronograf.Mapping{ + chronograf.Mapping{ + Provider: chronograf.MappingWildcard, + Scheme: chronograf.MappingWildcard, + Group: chronograf.MappingWildcard, + GrantedRole: roles.ViewerRoleName, + }, + }, + }, + }, nil + }, }, UsersStore: &mocks.UsersStore{ NumF: func(ctx context.Context) (int, error) { @@ -476,6 +494,24 @@ func TestService_Me(t *testing.T) { Mappings: []chronograf.Mapping{}, }, nil }, + AllF: func(ctx context.Context) ([]chronograf.Organization, error) { + return []chronograf.Organization{ + chronograf.Organization{ + ID: "0", + Name: "The Gnarly Default", + DefaultRole: roles.ViewerRoleName, + Public: true, + Mappings: []chronograf.Mapping{ + chronograf.Mapping{ + Provider: chronograf.MappingWildcard, + Scheme: chronograf.MappingWildcard, + Group: chronograf.MappingWildcard, + GrantedRole: roles.ViewerRoleName, + }, + }, + }, + }, nil + }, }, UsersStore: &mocks.UsersStore{ NumF: func(ctx context.Context) (int, error) { @@ -539,6 +575,24 @@ func TestService_Me(t *testing.T) { Mappings: []chronograf.Mapping{}, }, nil }, + AllF: func(ctx context.Context) ([]chronograf.Organization, error) { + return []chronograf.Organization{ + chronograf.Organization{ + ID: "0", + Name: "The Gnarly Default", + DefaultRole: roles.ViewerRoleName, + Public: true, + Mappings: []chronograf.Mapping{ + chronograf.Mapping{ + Provider: chronograf.MappingWildcard, + Scheme: chronograf.MappingWildcard, + Group: chronograf.MappingWildcard, + GrantedRole: roles.ViewerRoleName, + }, + }, + }, + }, nil + }, }, UsersStore: &mocks.UsersStore{ NumF: func(ctx context.Context) (int, error) { @@ -586,6 +640,7 @@ func TestService_Me(t *testing.T) { DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) { return &chronograf.Organization{ ID: "0", + Name: "The Bad Place", Public: true, Mappings: []chronograf.Mapping{}, }, nil @@ -598,6 +653,24 @@ func TestService_Me(t *testing.T) { Mappings: []chronograf.Mapping{}, }, nil }, + AllF: func(ctx context.Context) ([]chronograf.Organization, error) { + return []chronograf.Organization{ + chronograf.Organization{ + ID: "0", + Name: "The Bad Place", + DefaultRole: roles.ViewerRoleName, + Public: true, + Mappings: []chronograf.Mapping{ + chronograf.Mapping{ + Provider: chronograf.MappingWildcard, + Scheme: chronograf.MappingWildcard, + Group: chronograf.MappingWildcard, + GrantedRole: roles.ViewerRoleName, + }, + }, + }, + }, nil + }, }, UsersStore: &mocks.UsersStore{ NumF: func(ctx context.Context) (int, error) { @@ -716,6 +789,101 @@ func TestService_Me(t *testing.T) { wantContentType: "application/json", wantBody: `{"code":403,"message":"This organization is private. To gain access, you must be explicitly added by an administrator."}`, }, + { + name: "New user multiple mappings", + args: args{ + w: httptest.NewRecorder(), + r: httptest.NewRequest("GET", "http://example.com/foo", nil), + }, + fields: fields{ + UseAuth: true, + Logger: log.New(log.DebugLevel), + ConfigStore: &mocks.ConfigStore{ + Config: &chronograf.Config{ + Auth: chronograf.AuthConfig{ + SuperAdminNewUsers: false, + }, + }, + }, + OrganizationsStore: &mocks.OrganizationsStore{ + DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) { + return &chronograf.Organization{ + ID: "0", + Name: "The Gnarly Default", + DefaultRole: roles.ViewerRoleName, + Public: true, + Mappings: []chronograf.Mapping{}, + }, nil + }, + GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { + return &chronograf.Organization{ + ID: "0", + Name: "The Gnarly Default", + DefaultRole: roles.ViewerRoleName, + Public: true, + Mappings: []chronograf.Mapping{}, + }, nil + }, + AllF: func(ctx context.Context) ([]chronograf.Organization, error) { + return []chronograf.Organization{ + chronograf.Organization{ + ID: "0", + Name: "The Gnarly Default", + DefaultRole: roles.ViewerRoleName, + Public: true, + Mappings: []chronograf.Mapping{ + chronograf.Mapping{ + Provider: chronograf.MappingWildcard, + Scheme: chronograf.MappingWildcard, + Group: chronograf.MappingWildcard, + GrantedRole: roles.ViewerRoleName, + }, + }, + }, + chronograf.Organization{ + ID: "1", + Name: "another org", + DefaultRole: roles.ViewerRoleName, + Public: true, + Mappings: []chronograf.Mapping{ + chronograf.Mapping{ + Provider: chronograf.MappingWildcard, + Scheme: chronograf.MappingWildcard, + Group: chronograf.MappingWildcard, + GrantedRole: roles.EditorRoleName, + }, + }, + }, + }, nil + }, + }, + UsersStore: &mocks.UsersStore{ + NumF: func(ctx context.Context) (int, error) { + // This function gets to verify that there is at least one first user + return 1, nil + }, + GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { + if q.Name == nil || q.Provider == nil || q.Scheme == nil { + return nil, fmt.Errorf("Invalid user query: missing Name, Provider, and/or Scheme") + } + return nil, chronograf.ErrUserNotFound + }, + AddF: func(ctx context.Context, u *chronograf.User) (*chronograf.User, error) { + return u, nil + }, + UpdateF: func(ctx context.Context, u *chronograf.User) error { + return nil + }, + }, + }, + principal: oauth2.Principal{ + Subject: "secret", + Issuer: "auth0", + }, + wantStatus: http.StatusOK, + wantContentType: "application/json", + wantBody: `{"name":"secret","roles":[{"name":"viewer","organization":"0"},{"name":"editor","organization":"1"}],"provider":"auth0","scheme":"oauth2","links":{"self":"/chronograf/v1/users/0"},"organizations":[{"id":"0","name":"The Gnarly Default","defaultRole":"viewer","public":true,"mappings":[]},{"id":"0","name":"The Gnarly Default","defaultRole":"viewer","public":true,"mappings":[]}],"currentOrganization":{"id":"0","name":"The Gnarly Default","defaultRole":"viewer","public":true,"mappings":[]}}`, + }, } for _, tt := range tests { tt.args.r = tt.args.r.WithContext(context.WithValue(context.Background(), oauth2.PrincipalKey, tt.principal)) From 4551f4ff2ef393181dea3efadf112a48c99c2c28 Mon Sep 17 00:00:00 2001 From: Alex P Date: Thu, 9 Nov 2017 18:35:01 -0800 Subject: [PATCH 07/98] Introduce InputClickToEdit component Now is a decent time to make this pattern a component for easier re-use and consistency --- ui/src/shared/components/InputClickToEdit.js | 82 +++++++++++++++++++ ui/src/style/chronograf.scss | 1 + .../style/components/input-click-to-edit.scss | 53 ++++++++++++ 3 files changed, 136 insertions(+) create mode 100644 ui/src/shared/components/InputClickToEdit.js create mode 100644 ui/src/style/components/input-click-to-edit.scss diff --git a/ui/src/shared/components/InputClickToEdit.js b/ui/src/shared/components/InputClickToEdit.js new file mode 100644 index 000000000..c82b59da6 --- /dev/null +++ b/ui/src/shared/components/InputClickToEdit.js @@ -0,0 +1,82 @@ +import React, {Component, PropTypes} from 'react' + +class InputClickToEdit extends Component { + constructor(props) { + super(props) + + this.state = { + reset: false, + isEditing: null, + value: this.props.value, + } + } + + handleInputClick = () => { + this.setState({isEditing: true}) + } + + handleInputBlur = reset => e => { + const {onUpdate, value} = this.props + + if (!reset && value !== e.target.value) { + onUpdate(e.target.value) + } + + this.setState({reset: false, isEditing: false}) + } + + handleKeyDown = e => { + if (e.key === 'Enter') { + this.inputRef.blur() + } + if (e.key === 'Escape') { + this.setState({reset: true, value: this.props.value}, () => + this.inputRef.blur() + ) + } + } + + handleFocus = e => { + e.target.select() + } + + render() { + const {reset, isEditing, value} = this.state + const {wrapperClass, disabled} = this.props + + return disabled + ?
+
+ {value} +
+
+ :
+ {isEditing + ? (this.inputRef = r)} + /> + :
+ {value} + +
} +
+ } +} + +const {func, bool, string} = PropTypes + +InputClickToEdit.propTypes = { + wrapperClass: string.isRequired, + value: string, + onUpdate: func.isRequired, + disabled: bool, +} + +export default InputClickToEdit diff --git a/ui/src/style/chronograf.scss b/ui/src/style/chronograf.scss index 5a377d739..72483285e 100644 --- a/ui/src/style/chronograf.scss +++ b/ui/src/style/chronograf.scss @@ -39,6 +39,7 @@ @import 'components/function-selector'; @import 'components/graph-tips'; @import 'components/graph'; +@import 'components/input-click-to-edit'; @import 'components/input-tag-list'; @import 'components/newsfeed'; @import 'components/opt-in'; diff --git a/ui/src/style/components/input-click-to-edit.scss b/ui/src/style/components/input-click-to-edit.scss new file mode 100644 index 000000000..3e036347f --- /dev/null +++ b/ui/src/style/components/input-click-to-edit.scss @@ -0,0 +1,53 @@ +/* + Click to Edit Input Styles + ------------------------------------------------------------------------------ +*/ +.input-cte, +.input-cte__disabled { + @include no-user-select(); + height: 30px; + width: 100%; + font-weight: 600; + font-size: 13px; + line-height: 28px; + padding: 0 11px; + border-radius: 4px; + border-style: solid; + border-width: 2px; +} + +.input-cte { + border-color: $g2-kevlar; + background-color: $g2-kevlar; + color: $g13-mist; + position: relative; + transition: + color 0.4s ease, + background-color 0.4s ease, + border-color 0.4s ease; + + > span.icon { + position: absolute; + top: 50%; + right: 11px; + transform: translateY(-50%); + color: $g8-storm; + opacity: 0; + transition: opacity 0.25s ease; + } + + &:hover { + color: $g20-white; + background-color: $g5-pepper; + border-color: $g5-pepper; + cursor: text; + + > span.icon {opacity: 1;} + } +} +.input-cte__disabled { + border-color: $g4-onyx; + background-color: $g4-onyx; + font-style: italic; + color: $g9-mountain; +} From 8065621dfb088810b06c42d25a0b619844ce3ba9 Mon Sep 17 00:00:00 2001 From: Alex P Date: Thu, 9 Nov 2017 18:35:43 -0800 Subject: [PATCH 08/98] Create page for managing mappings between providers and chronograf organizations --- .../components/chronograf/ProviderMap.js | 167 ++++++++++++++++++ .../components/chronograf/ProvidersTable.js | 112 ++++++++++++ ui/src/admin/constants/dummyProviderMaps.js | 19 ++ ui/src/admin/containers/ProvidersPage.js | 100 +++++++++++ ui/src/style/chronograf.scss | 1 + ui/src/style/pages/manage-providers.scss | 163 +++++++++++++++++ 6 files changed, 562 insertions(+) create mode 100644 ui/src/admin/components/chronograf/ProviderMap.js create mode 100644 ui/src/admin/components/chronograf/ProvidersTable.js create mode 100644 ui/src/admin/constants/dummyProviderMaps.js create mode 100644 ui/src/admin/containers/ProvidersPage.js create mode 100644 ui/src/style/pages/manage-providers.scss diff --git a/ui/src/admin/components/chronograf/ProviderMap.js b/ui/src/admin/components/chronograf/ProviderMap.js new file mode 100644 index 000000000..24143705f --- /dev/null +++ b/ui/src/admin/components/chronograf/ProviderMap.js @@ -0,0 +1,167 @@ +import React, {Component, PropTypes} from 'react' + +import ConfirmButtons from 'shared/components/ConfirmButtons' +import Dropdown from 'shared/components/Dropdown' +import InputClickToEdit from 'shared/components/InputClickToEdit' + +import {DEFAULT_PROVIDER_MAP_ID} from 'src/admin/constants/dummyProviderMaps' + +class ProviderMap extends Component { + constructor(props) { + super(props) + + this.state = { + scheme: this.props.providerMap.scheme, + provider: this.props.providerMap.provider, + providerOrganization: this.props.providerMap.providerOrganization, + redirectOrg: this.props.providerMap.redirectOrg, + isDeleting: false, + } + } + + handleDeleteClick = () => { + this.setState({isDeleting: true}) + } + + handleDismissDeleteConfirmation = () => { + this.setState({isDeleting: false}) + } + + handleDeleteMap = providerMap => { + const {onDelete} = this.props + + this.setState({isDeleting: false}) + onDelete(providerMap) + } + + handleChangeScheme = scheme => { + this.setState({scheme}) + this.handleUpdateProviderMap() + } + + handleChangeProvider = provider => { + this.setState({provider}) + this.handleUpdateProviderMap() + } + + handleChangeProviderOrg = providerOrganization => { + this.setState({providerOrganization}) + this.handleUpdateProviderMap() + } + + handleChooseOrganization = org => { + this.setState({redirectOrg: org}) + this.handleUpdateProviderMap() + } + + handleUpdateProviderMap = () => { + const {onUpdate, providerMap: {id}} = this.props + const {scheme, provider, providerOrganization, redirectOrg} = this.state + + const updatedMap = { + id, + scheme, + provider, + providerOrganization, + redirectOrg, + } + onUpdate(updatedMap) + } + + render() { + const { + scheme, + provider, + providerOrganization, + redirectOrg, + isDeleting, + } = this.state + const {organizations, providerMap} = this.props + + const dropdownItems = organizations.map(role => ({ + ...role, + text: role.name, + })) + + const redirectOrgClassName = isDeleting + ? 'provider--redirect deleting' + : 'provider--redirect' + + const isDefaultProviderMap = DEFAULT_PROVIDER_MAP_ID === providerMap.id + + return ( +
+
+ {providerMap.id} +
+ + + +
+ +
+
+ +
+ {isDeleting + ? + : } +
+ ) + } +} + +const {arrayOf, func, shape, string} = PropTypes + +ProviderMap.propTypes = { + providerMap: shape({ + id: string, + scheme: string, + provider: string, + providerOrganization: string, + redirectOrg: shape({ + id: string.isRequired, + name: string.isRequired, + }), + }), + organizations: arrayOf( + shape({ + id: string.isRequired, + name: string.isRequired, + }) + ), + onDelete: func.isRequired, + onUpdate: func.isRequired, +} + +export default ProviderMap diff --git a/ui/src/admin/components/chronograf/ProvidersTable.js b/ui/src/admin/components/chronograf/ProvidersTable.js new file mode 100644 index 000000000..00515215b --- /dev/null +++ b/ui/src/admin/components/chronograf/ProvidersTable.js @@ -0,0 +1,112 @@ +import React, {Component, PropTypes} from 'react' + +import ProviderMap from 'src/admin/components/chronograf/ProviderMap' +// import NewProviderMap from 'src/admin/components/chronograf/NewProviderMap' + +class ProvidersTable extends Component { + constructor(props) { + super(props) + + this.state = { + isCreatingMap: false, + } + } + handleClickCreateMap = () => { + this.setState({isCreatingMap: true}) + } + + handleDismissCreateMap = () => { + this.setState({isCreatingMap: false}) + } + + handleCreateMap = newMap => { + const {onCreateMap} = this.props + onCreateMap(newMap) + } + + render() { + const {providerMaps, organizations, onUpdateMap, onDeleteMap} = this.props + const {isCreatingMap} = this.state + + const tableTitle = + providerMaps.length === 1 ? '1 Map' : `${providerMaps.length} Maps` + + return ( +
+
+
+
+
+

+ {tableTitle} +

+ +
+
+
+
ID
+
Scheme
+
Provider
+
+ Provider Org +
+
+
Organization
+
+
+ {/* {isCreatingMap + ? + : null} */} + {providerMaps.map(providerMap => + + )} +
+
+
+
+
+ ) + } +} + +const {arrayOf, func, shape, string} = PropTypes + +ProvidersTable.propTypes = { + providerMaps: arrayOf( + shape({ + id: string, + scheme: string, + provider: string, + providerOrganization: string, + redirectOrg: shape({ + id: string.isRequired, + name: string.isRequired, + }), + }) + ).isRequired, + organizations: arrayOf( + shape({ + id: string, // when optimistically created, organization will not have an id + name: string.isRequired, + }) + ).isRequired, + onCreateMap: func.isRequired, + onUpdateMap: func.isRequired, + onDeleteMap: func.isRequired, +} +export default ProvidersTable diff --git a/ui/src/admin/constants/dummyProviderMaps.js b/ui/src/admin/constants/dummyProviderMaps.js new file mode 100644 index 000000000..b56a6a958 --- /dev/null +++ b/ui/src/admin/constants/dummyProviderMaps.js @@ -0,0 +1,19 @@ +import {DEFAULT_ORG} from 'src/admin/constants/dummyUsers' + +export const DEFAULT_PROVIDER_MAP_ID = '0' +export const PROVIDER_MAPS = [ + { + id: DEFAULT_PROVIDER_MAP_ID, + scheme: '*', + provider: '*', + providerOrganization: '*', + redirectOrg: {id: DEFAULT_ORG.id, name: DEFAULT_ORG.name}, + }, + { + id: '1', + scheme: 'oauth2', + provider: 'github', + providerOrganization: null, + redirectOrg: {id: '5', name: 'moarOrg'}, + }, +] diff --git a/ui/src/admin/containers/ProvidersPage.js b/ui/src/admin/containers/ProvidersPage.js new file mode 100644 index 000000000..4a1748e41 --- /dev/null +++ b/ui/src/admin/containers/ProvidersPage.js @@ -0,0 +1,100 @@ +import React, {Component, PropTypes} from 'react' +import {connect} from 'react-redux' +import {bindActionCreators} from 'redux' + +import * as adminChronografActionCreators from 'src/admin/actions/chronograf' +import {publishAutoDismissingNotification} from 'shared/dispatchers' + +import ProvidersTable from 'src/admin/components/chronograf/ProvidersTable' +import FancyScrollbar from 'shared/components/FancyScrollbar' + +import {PROVIDER_MAPS} from 'src/admin/constants/dummyProviderMaps' + +class ProvidersPage extends Component { + constructor(props) { + super(props) + } + + componentDidMount() { + const {links, actions: {loadOrganizationsAsync}} = this.props + + loadOrganizationsAsync(links.organizations) + } + + handleCreateMap = () => {} + + handleUpdateMap = updatedMap => { + console.log(updatedMap) + } + + handleDeleteMap = () => {} + + render() { + const {organizations, providerMaps} = this.props + + return ( +
+
+
+
+

Manage Providers

+
+
+
+ + {organizations + ? + :
} + +
+ ) + } +} + +const {arrayOf, func, shape, string} = PropTypes + +ProvidersPage.propTypes = { + links: shape({ + organizations: string.isRequired, + }), + organizations: arrayOf( + shape({ + id: string.isRequired, + name: string.isRequired, + }) + ), + providerMaps: arrayOf( + shape({ + id: string, + scheme: string, + provider: string, + providerOrganization: string, + redirectOrg: shape({ + id: string.isRequired, + name: string.isRequired, + }), + }) + ), + actions: shape({ + loadOrganizationsAsync: func.isRequired, + }), + notify: func.isRequired, +} + +const mapStateToProps = ({links, adminChronograf: {organizations}}) => ({ + links, + organizations, +}) + +const mapDispatchToProps = dispatch => ({ + actions: bindActionCreators(adminChronografActionCreators, dispatch), + notify: bindActionCreators(publishAutoDismissingNotification, dispatch), +}) + +export default connect(mapStateToProps, mapDispatchToProps)(ProvidersPage) diff --git a/ui/src/style/chronograf.scss b/ui/src/style/chronograf.scss index 72483285e..e6b5fc42e 100644 --- a/ui/src/style/chronograf.scss +++ b/ui/src/style/chronograf.scss @@ -67,6 +67,7 @@ @import 'pages/admin'; @import 'pages/users'; @import 'pages/tickscript-editor'; +@import 'pages/manage-providers'; // TODO @import 'unsorted'; diff --git a/ui/src/style/pages/manage-providers.scss b/ui/src/style/pages/manage-providers.scss new file mode 100644 index 000000000..f058c36f2 --- /dev/null +++ b/ui/src/style/pages/manage-providers.scss @@ -0,0 +1,163 @@ +/* + Styles for the Manage Poviders Page + ------------------------------------------------------------------------------ +*/ + +$provider--id-width: 60px; +$provider--scheme-width: 170px; +$provider--provider-width: 170px; +$provider--providerorg-width: 220px; +$provider--redirect-width: 220px; +$provider--delete-width: 30px; + +$provider--margin: 4px; + +.provider, +.providers-labels { + width: 100%; + display: flex; + flex-wrap: nowrap; + align-items: center; + + > div:not(.confirm-buttons) { + height: 30px; + margin-right: $provider--margin; + } +} +.provider { + margin-bottom: 8px; + position: relative; + + &:last-of-type { + margin-bottom: 0; + } +} +.providers-labels { + border-bottom: 2px solid $g5-pepper; + margin-bottom: 10px; + @include no-user-select(); + + > div { + color: $g15-platinum; + font-weight: 700; + line-height: 30px; + font-size: 13px; + padding: 0 11px; + + &:last-of-type { + margin-right: 0; + } + } +} + +.providers-labels--id, +.provider--id {width: $provider--id-width;} +.providers-labels--scheme, +.provider--scheme {width: $provider--scheme-width;} +.providers-labels--provider, +.provider--provider {width: $provider--provider-width;} +.providers-labels--providerorg, +.provider--providerorg {width: $provider--providerorg-width;} +.providers-labels--redirect, +.provider--redirect {width: $provider--redirect-width;} +.providers-labels--delete {width: $provider--delete-width;} +.providers-labels--arrow, +.provider--arrow {flex: 1 0 0;} + +.provider--redirect.deleting { + width: ($provider--redirect-width - $provider--margin - $provider--delete-width); +} + + +.provider--id { + padding: 0 11px; + line-height: 30px; + font-size: 13px; + color: $g13-mist; + font-weight: 500; +} + + +.provider--arrow { + position: relative; + + > span, + &:before, + &:after { + content: ''; + position: absolute; + top: 50%; + transform: translateY(-50%); + } + > span { + height: 2px; + @include gradient-h($c-pool,$c-star); + width: calc(100% - 22px); + left: 11px; + } + &:before { + width: 6px; + height: 6px; + border-radius: 50%; + background-color: $c-pool; + left: 11px; + } + &:after { + right: 5px; + border-style: solid; + border-color: transparent; + border-width: 6px; + border-left-color: $c-star; + } +} +.provider--editable, +input[type="text"].form-control.provider--input { + width: 100%; + font-weight: 600; + font-size: 13px; +} +.provider--editable, +.provider--editable-disabled { + @include no-user-select(); + height: 30px; + padding: 0 11px; + border-radius: 4px; + line-height: 28px; + border-style: solid; + border-width: 2px; +} +.provider--editable { + border-color: $g2-kevlar; + background-color: $g2-kevlar; + color: $g13-mist; + position: relative; + transition: + color 0.4s ease, + background-color 0.4s ease, + border-color 0.4s ease; + + > span.icon { + position: absolute; + top: 50%; + right: 11px; + transform: translateY(-50%); + color: $g8-storm; + opacity: 0; + transition: opacity 0.25s ease; + } + + &:hover { + color: $g20-white; + background-color: $g5-pepper; + border-color: $g5-pepper; + cursor: text; + + > span.icon {opacity: 1;} + } +} +.provider--editable-disabled { + border-color: $g4-onyx; + background-color: $g4-onyx; + font-style: italic; + color: $g9-mountain; +} From 1be71de790c15527ff49ef176a33721af093b751 Mon Sep 17 00:00:00 2001 From: Alex P Date: Tue, 14 Nov 2017 10:19:36 -0800 Subject: [PATCH 09/98] Update providers page to fit into admin tabs UI --- .../admin/components/chronograf/AdminTabs.js | 7 ++ .../components/chronograf/ProvidersTable.js | 74 +++++++++---------- ui/src/admin/containers/ProvidersPage.js | 36 +++------ 3 files changed, 51 insertions(+), 66 deletions(-) diff --git a/ui/src/admin/components/chronograf/AdminTabs.js b/ui/src/admin/components/chronograf/AdminTabs.js index 8d4be7f9f..38e28ad0e 100644 --- a/ui/src/admin/components/chronograf/AdminTabs.js +++ b/ui/src/admin/components/chronograf/AdminTabs.js @@ -9,9 +9,11 @@ import { import {Tab, Tabs, TabPanel, TabPanels, TabList} from 'shared/components/Tabs' import OrganizationsPage from 'src/admin/containers/chronograf/OrganizationsPage' import UsersPage from 'src/admin/containers/chronograf/UsersPage' +import ProvidersPage from 'src/admin/containers/ProvidersPage' const ORGANIZATIONS_TAB_NAME = 'Organizations' const USERS_TAB_NAME = 'Users' +const PROVIDERS_TAB_NAME = 'Providers' const AdminTabs = ({ me: {currentOrganization: meCurrentOrganization, role: meRole, id: meID}, @@ -31,6 +33,11 @@ const AdminTabs = ({ ), }, + { + requiredRole: SUPERADMIN_ROLE, + type: PROVIDERS_TAB_NAME, + component: , + }, ].filter(t => isUserAuthorized(meRole, t.requiredRole)) return ( diff --git a/ui/src/admin/components/chronograf/ProvidersTable.js b/ui/src/admin/components/chronograf/ProvidersTable.js index 00515215b..44140b576 100644 --- a/ui/src/admin/components/chronograf/ProvidersTable.js +++ b/ui/src/admin/components/chronograf/ProvidersTable.js @@ -32,52 +32,44 @@ class ProvidersTable extends Component { providerMaps.length === 1 ? '1 Map' : `${providerMaps.length} Maps` return ( -
-
-
-
-
-

- {tableTitle} -

- -
-
-
-
ID
-
Scheme
-
Provider
-
- Provider Org -
-
-
Organization
-
-
- {/* {isCreatingMap +
+
+

+ {tableTitle} +

+ +
+
+
+
ID
+
Scheme
+
Provider
+
Provider Org
+
+
Organization
+
+
+ {/* {isCreatingMap ? : null} */} - {providerMaps.map(providerMap => - - )} -
-
-
+ {providerMaps.map(providerMap => + + )}
) diff --git a/ui/src/admin/containers/ProvidersPage.js b/ui/src/admin/containers/ProvidersPage.js index 4a1748e41..287e4b2d9 100644 --- a/ui/src/admin/containers/ProvidersPage.js +++ b/ui/src/admin/containers/ProvidersPage.js @@ -6,7 +6,6 @@ import * as adminChronografActionCreators from 'src/admin/actions/chronograf' import {publishAutoDismissingNotification} from 'shared/dispatchers' import ProvidersTable from 'src/admin/components/chronograf/ProvidersTable' -import FancyScrollbar from 'shared/components/FancyScrollbar' import {PROVIDER_MAPS} from 'src/admin/constants/dummyProviderMaps' @@ -23,8 +22,8 @@ class ProvidersPage extends Component { handleCreateMap = () => {} - handleUpdateMap = updatedMap => { - console.log(updatedMap) + handleUpdateMap = _updatedMap => { + // console.log(_updatedMap) } handleDeleteMap = () => {} @@ -32,28 +31,15 @@ class ProvidersPage extends Component { render() { const {organizations, providerMaps} = this.props - return ( -
-
-
-
-

Manage Providers

-
-
-
- - {organizations - ? - :
} - -
- ) + return organizations + ? + :
} } From 83970f21078a832748231ee86f66a983cf1e920b Mon Sep 17 00:00:00 2001 From: Alex P Date: Tue, 14 Nov 2017 11:03:39 -0800 Subject: [PATCH 10/98] Introduce common set of styles for non-table tables --- ui/src/style/chronograf.scss | 1 + ui/src/style/components/fancy-table.scss | 46 ++++++++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 ui/src/style/components/fancy-table.scss diff --git a/ui/src/style/chronograf.scss b/ui/src/style/chronograf.scss index e6b5fc42e..712efb86a 100644 --- a/ui/src/style/chronograf.scss +++ b/ui/src/style/chronograf.scss @@ -34,6 +34,7 @@ @import 'components/custom-time-range'; @import 'components/dygraphs'; @import 'components/fancy-scrollbars'; +@import 'components/fancy-table'; @import 'components/fill-query'; @import 'components/flip-toggle'; @import 'components/function-selector'; diff --git a/ui/src/style/components/fancy-table.scss b/ui/src/style/components/fancy-table.scss new file mode 100644 index 000000000..77d560e1b --- /dev/null +++ b/ui/src/style/components/fancy-table.scss @@ -0,0 +1,46 @@ +/* + Styles for Fancy Tables + ------------------------------------------------------------------------------ +*/ + +$fancytable--table--margin: 4px; + +.fancytable--row, +.fancytable--labels { + width: 100%; + display: flex; + flex-wrap: nowrap; + align-items: center; + + > div:not(.confirm-buttons) { + margin-right: $fancytable--table--margin; + } +} +.fancytable--row { + margin-bottom: 8px; + position: relative; + + &:last-of-type { + margin-bottom: 0; + } +} +.fancytable--td { + height: 30px; +} +.fancytable--labels { + border-bottom: 2px solid $g5-pepper; + margin-bottom: 10px; + @include no-user-select(); +} +.fancytable--th { + color: $g17-whisper; + font-weight: 500; + height: 34px; + line-height: 34px; + font-size: 13px; + padding: 0 11px; + + &:last-of-type { + margin-right: 0; + } +} From 71d43a0eb301dd5b92d399b341249174842ee0b7 Mon Sep 17 00:00:00 2001 From: Alex P Date: Tue, 14 Nov 2017 11:04:01 -0800 Subject: [PATCH 11/98] Cleanup redundant styles --- .../components/chronograf/ProviderMap.js | 16 +- .../components/chronograf/ProvidersTable.js | 20 ++- ui/src/style/pages/manage-providers.scss | 164 ++++-------------- 3 files changed, 54 insertions(+), 146 deletions(-) diff --git a/ui/src/admin/components/chronograf/ProviderMap.js b/ui/src/admin/components/chronograf/ProviderMap.js index 24143705f..4b87c07de 100644 --- a/ui/src/admin/components/chronograf/ProviderMap.js +++ b/ui/src/admin/components/chronograf/ProviderMap.js @@ -84,35 +84,35 @@ class ProviderMap extends Component { })) const redirectOrgClassName = isDeleting - ? 'provider--redirect deleting' - : 'provider--redirect' + ? 'fancytable--td provider--redirect deleting' + : 'fancytable--td provider--redirect' const isDefaultProviderMap = DEFAULT_PROVIDER_MAP_ID === providerMap.id return ( -
-
+
+
{providerMap.id}
-
+
diff --git a/ui/src/admin/components/chronograf/ProvidersTable.js b/ui/src/admin/components/chronograf/ProvidersTable.js index 44140b576..061e5b0f4 100644 --- a/ui/src/admin/components/chronograf/ProvidersTable.js +++ b/ui/src/admin/components/chronograf/ProvidersTable.js @@ -46,14 +46,18 @@ class ProvidersTable extends Component {
-
-
ID
-
Scheme
-
Provider
-
Provider Org
-
-
Organization
-
+
+
ID
+
Scheme
+
Provider
+
+ Provider Org +
+
+
+ Organization +
+
{/* {isCreatingMap ? div:not(.confirm-buttons) { - height: 30px; - margin-right: $provider--margin; - } -} -.provider { - margin-bottom: 8px; - position: relative; - - &:last-of-type { - margin-bottom: 0; - } -} -.providers-labels { - border-bottom: 2px solid $g5-pepper; - margin-bottom: 10px; - @include no-user-select(); - - > div { - color: $g15-platinum; - font-weight: 700; - line-height: 30px; - font-size: 13px; - padding: 0 11px; - - &:last-of-type { - margin-right: 0; - } - } +.fancytable--td.provider--id, +.fancytable--th.provider--id { + padding: 0 8px; } -.providers-labels--id, .provider--id {width: $provider--id-width;} -.providers-labels--scheme, .provider--scheme {width: $provider--scheme-width;} -.providers-labels--provider, .provider--provider {width: $provider--provider-width;} -.providers-labels--providerorg, .provider--providerorg {width: $provider--providerorg-width;} -.providers-labels--redirect, .provider--redirect {width: $provider--redirect-width;} -.providers-labels--delete {width: $provider--delete-width;} -.providers-labels--arrow, +.provider--delete {width: $provider--delete-width;} .provider--arrow {flex: 1 0 0;} .provider--redirect.deleting { - width: ($provider--redirect-width - $provider--margin - $provider--delete-width); + width: ($provider--redirect-width - $fancytable--table--margin - $provider--delete-width); } - -.provider--id { - padding: 0 11px; - line-height: 30px; - font-size: 13px; - color: $g13-mist; - font-weight: 500; -} - - .provider--arrow { - position: relative; + display: flex; + align-items: center; + min-width: 36px; - > span, - &:before, - &:after { - content: ''; - position: absolute; - top: 50%; - transform: translateY(-50%); + &.fancytable--td { + padding: 0 8px; } + > span { + position: relative; height: 2px; + width: 100%; @include gradient-h($c-pool,$c-star); - width: calc(100% - 22px); - left: 11px; - } - &:before { - width: 6px; - height: 6px; - border-radius: 50%; - background-color: $c-pool; - left: 11px; - } - &:after { - right: 5px; - border-style: solid; - border-color: transparent; - border-width: 6px; - border-left-color: $c-star; - } -} -.provider--editable, -input[type="text"].form-control.provider--input { - width: 100%; - font-weight: 600; - font-size: 13px; -} -.provider--editable, -.provider--editable-disabled { - @include no-user-select(); - height: 30px; - padding: 0 11px; - border-radius: 4px; - line-height: 28px; - border-style: solid; - border-width: 2px; -} -.provider--editable { - border-color: $g2-kevlar; - background-color: $g2-kevlar; - color: $g13-mist; - position: relative; - transition: - color 0.4s ease, - background-color 0.4s ease, - border-color 0.4s ease; - > span.icon { - position: absolute; - top: 50%; - right: 11px; - transform: translateY(-50%); - color: $g8-storm; - opacity: 0; - transition: opacity 0.25s ease; - } - - &:hover { - color: $g20-white; - background-color: $g5-pepper; - border-color: $g5-pepper; - cursor: text; - - > span.icon {opacity: 1;} + &:before, + &:after { + content: ''; + position: absolute; + top: 50%; + transform: translateY(-50%); + } + &:before { + width: 6px; + height: 6px; + border-radius: 50%; + background-color: $c-pool; + left: 0px; + } + &:after { + right: -6px; + border-style: solid; + border-color: transparent; + border-width: 6px; + border-left-color: $c-star; + } } } -.provider--editable-disabled { - border-color: $g4-onyx; - background-color: $g4-onyx; - font-style: italic; - color: $g9-mountain; -} From c8bd7b2d73bb721843806fc12ead4efd6268715b Mon Sep 17 00:00:00 2001 From: Alex P Date: Tue, 14 Nov 2017 11:52:40 -0800 Subject: [PATCH 12/98] Refactor organizations table to use fancy-table styles and InputClickToEdit --- .../chronograf/OrganizationsTable.js | 14 +- .../chronograf/OrganizationsTableRow.js | 106 +++-------- .../OrganizationsTableRowDefault.js | 75 ++++++++ .../chronograf/OrganizationsTableRowNew.js | 30 +-- ui/src/admin/constants/dummyProviderMaps.js | 4 +- ui/src/style/components/fancy-table.scss | 17 +- .../style/components/organizations-table.scss | 176 +++--------------- ui/src/style/pages/manage-providers.scss | 19 +- 8 files changed, 178 insertions(+), 263 deletions(-) create mode 100644 ui/src/admin/components/chronograf/OrganizationsTableRowDefault.js diff --git a/ui/src/admin/components/chronograf/OrganizationsTable.js b/ui/src/admin/components/chronograf/OrganizationsTable.js index 272d57487..d897d6214 100644 --- a/ui/src/admin/components/chronograf/OrganizationsTable.js +++ b/ui/src/admin/components/chronograf/OrganizationsTable.js @@ -67,15 +67,17 @@ class OrganizationsTable extends Component {
-
-
-
Name
-
+
+
+
Name
+
Public{' '}
-
Default Role
-
+
+ Default Role +
+
{isCreatingOrganization ? { - this.setState({isEditing: true}) + handleUpdateOrgName = newName => { + const {organization, onRename} = this.props + onRename(organization, newName) } - - handleConfirmRename = () => { - const {onRename, organization} = this.props - const {workingName} = this.state - - onRename(organization, workingName) - this.setState({workingName, isEditing: false}) - } - - handleCancelRename = () => { - const {organization} = this.props - - this.setState({ - workingName: organization.name, - isEditing: false, - }) - } - - handleInputChange = e => { - this.setState({workingName: e.target.value}) - } - - handleInputBlur = () => { - const {organization} = this.props - const {workingName} = this.state - - if (organization.name === workingName) { - this.handleCancelRename() - } else { - this.handleConfirmRename() - } - } - - handleKeyDown = e => { - if (e.key === 'Enter') { - this.handleInputBlur() - } else if (e.key === 'Escape') { - this.handleCancelRename() - } - } - - handleFocus = e => { - e.target.select() - } - handleDeleteClick = () => { this.setState({isDeleting: true}) } @@ -117,7 +71,7 @@ class OrganizationsTableRow extends Component { } render() { - const {workingName, isEditing, isDeleting} = this.state + const {isDeleting} = this.state const {organization, currentOrganization} = this.props const dropdownRolesItems = USER_ROLES.map(role => ({ @@ -126,12 +80,12 @@ class OrganizationsTableRow extends Component { })) const defaultRoleClassName = isDeleting - ? 'orgs-table--default-role editing' - : 'orgs-table--default-role' + ? 'fancytable--td orgs-table--default-role deleting' + : 'fancytable--td orgs-table--default-role' return ( -
-
+
+
{organization.id === currentOrganization.id ? }
- {isEditing - ? (this.inputRef = r)} - /> - :
- {workingName} - -
} - {organization.id === DEFAULT_ORG_ID - ?
- -
- :
} + +
+ {organization.id === DEFAULT_ORG_ID + ?
+ +
+ :
} +
{ + const {organization, onTogglePublic} = this.props + onTogglePublic(organization) + } + + handleChooseDefaultRole = role => { + const {organization, onChooseDefaultRole} = this.props + onChooseDefaultRole(organization, role.name) + } + + render() { + const {organization} = this.props + + const dropdownRolesItems = USER_ROLES.map(role => ({ + ...role, + text: role.name, + })) + + return ( +
+
+ {organization.id} +
+
+ {organization.name} +
+
+
+ +
+
+
+ +
+ +
+ ) + } +} + +const {func, shape, string} = PropTypes + +OrganizationsTableRowDefault.propTypes = { + organization: shape({ + id: string, + name: string.isRequired, + }).isRequired, + onTogglePublic: func.isRequired, + onChooseDefaultRole: func.isRequired, +} + +export default OrganizationsTableRowDefault diff --git a/ui/src/admin/components/chronograf/OrganizationsTableRowNew.js b/ui/src/admin/components/chronograf/OrganizationsTableRowNew.js index 09b2a541f..2a3b8e763 100644 --- a/ui/src/admin/components/chronograf/OrganizationsTableRowNew.js +++ b/ui/src/admin/components/chronograf/OrganizationsTableRowNew.js @@ -58,20 +58,22 @@ class OrganizationsTableRowNew extends Component { })) return ( -
-
- (this.inputRef = r)} - /> -
+
+
+
+ (this.inputRef = r)} + /> +
+
span.icon { - position: absolute; - top: 50%; - right: 11px; - transform: translateY(-50%); - color: $g8-storm; - opacity: 0; - transition: opacity 0.25s ease; - } - - &:hover { - color: $g20-white; - background-color: $g5-pepper; - border-color: $g5-pepper; - cursor: text; - - > span.icon { - opacity: 1; - } - } -} - -.orgs-table--public { - height: 30px; - margin-right: 4px; - text-align: center; - width: 88px; + justify-content: center; background-color: $g4-onyx; border-radius: 4px; - line-height: 30px; position: relative; > .slide-toggle { @@ -103,69 +53,3 @@ input[type="text"].form-control.orgs-table--input { @include no-user-select(); } } - -.orgs-table--default-role, -.orgs-table--default-role-disabled { - width: 100px; - height: 30px; - margin-right: 4px; -} -.orgs-table--default-role.editing { - width: 96px; -} -.orgs-table--default-role-disabled { - background-color: $g4-onyx; - font-style: italic; - color: $g9-mountain; - padding: 0 11px; - line-height: 30px; - font-size: 13px; - font-weight: 600; - @include no-user-select(); -} -.orgs-table--delete { - height: 30px; - width: 30px; -} - -/* Table Headers */ -.orgs-table--org-labels { - display: flex; - align-items: center; - border-bottom: 2px solid $g5-pepper; - margin-bottom: 10px; - width: 100%; - @include no-user-select(); - - > .orgs-table--name, - > .orgs-table--name:hover, - > .orgs-table--public, - > .orgs-table--active { - transition: none; - background-color: transparent; - border-color: transparent; - } - - > .orgs-table--id, - > .orgs-table--name, - > .orgs-table--name:hover, - > .orgs-table--default-role, - > .orgs-table--public, - > .orgs-table--active { - color: $g17-whisper; - font-weight: 500; - } - > .orgs-table--default-role, - > .orgs-table--public, - > .orgs-table--active { - line-height: 30px; - font-size: 13px; - padding: 0 11px; - } -} - - -/* Config table beneath organizations table */ -.panel .panel-body table.table.superadmin-config { - margin-top: 60px; -} diff --git a/ui/src/style/pages/manage-providers.scss b/ui/src/style/pages/manage-providers.scss index a71d8dfb5..aaf73fbd9 100644 --- a/ui/src/style/pages/manage-providers.scss +++ b/ui/src/style/pages/manage-providers.scss @@ -4,25 +4,28 @@ */ $provider--id-width: 60px; -$provider--scheme-width: 170px; -$provider--provider-width: 170px; -$provider--providerorg-width: 220px; +$provider--scheme-width: 150px; +$provider--provider-width: 150px; +$provider--providerorg-width: 210px; $provider--redirect-width: 220px; $provider--delete-width: 30px; -.fancytable--td.provider--id, -.fancytable--th.provider--id { - padding: 0 8px; -} .provider--id {width: $provider--id-width;} .provider--scheme {width: $provider--scheme-width;} .provider--provider {width: $provider--provider-width;} .provider--providerorg {width: $provider--providerorg-width;} .provider--redirect {width: $provider--redirect-width;} -.provider--delete {width: $provider--delete-width;} +.provider--delete { + width: $provider--delete-width; + min-width: $provider--delete-width; +} .provider--arrow {flex: 1 0 0;} +.fancytable--td.provider--id, +.fancytable--th.provider--id { + padding: 0 8px; +} .provider--redirect.deleting { width: ($provider--redirect-width - $fancytable--table--margin - $provider--delete-width); } From 64b6f9871a14fd910edb5c7571eb2aa5160083e8 Mon Sep 17 00:00:00 2001 From: Alex P Date: Tue, 14 Nov 2017 11:57:43 -0800 Subject: [PATCH 13/98] Rename components to match existing naming pattern --- ui/src/admin/components/chronograf/ProvidersTable.js | 4 ++-- .../chronograf/{ProviderMap.js => ProvidersTableRow.js} | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) rename ui/src/admin/components/chronograf/{ProviderMap.js => ProvidersTableRow.js} (97%) diff --git a/ui/src/admin/components/chronograf/ProvidersTable.js b/ui/src/admin/components/chronograf/ProvidersTable.js index 061e5b0f4..986b5bb44 100644 --- a/ui/src/admin/components/chronograf/ProvidersTable.js +++ b/ui/src/admin/components/chronograf/ProvidersTable.js @@ -1,6 +1,6 @@ import React, {Component, PropTypes} from 'react' -import ProviderMap from 'src/admin/components/chronograf/ProviderMap' +import ProvidersTableRow from 'src/admin/components/chronograf/ProvidersTableRow' // import NewProviderMap from 'src/admin/components/chronograf/NewProviderMap' class ProvidersTable extends Component { @@ -66,7 +66,7 @@ class ProvidersTable extends Component { /> : null} */} {providerMaps.map(providerMap => - Date: Thu, 11 Jan 2018 18:15:27 -0800 Subject: [PATCH 14/98] Refactor & simplify InputClickToEdit to use declarative state Signed-off-by: Alex Paxton --- ui/src/shared/components/InputClickToEdit.js | 24 ++++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/ui/src/shared/components/InputClickToEdit.js b/ui/src/shared/components/InputClickToEdit.js index c82b59da6..a3ed860e6 100644 --- a/ui/src/shared/components/InputClickToEdit.js +++ b/ui/src/shared/components/InputClickToEdit.js @@ -5,34 +5,38 @@ class InputClickToEdit extends Component { super(props) this.state = { - reset: false, isEditing: null, value: this.props.value, } } + handleCancel = () => { + this.setState({ + isEditing: false, + value: this.props.value, + }) + } + handleInputClick = () => { this.setState({isEditing: true}) } - handleInputBlur = reset => e => { + handleInputBlur = e => { const {onUpdate, value} = this.props - if (!reset && value !== e.target.value) { + if (value !== e.target.value) { onUpdate(e.target.value) } - this.setState({reset: false, isEditing: false}) + this.setState({isEditing: false}) } handleKeyDown = e => { if (e.key === 'Enter') { - this.inputRef.blur() + this.handleInputBlur(e) } if (e.key === 'Escape') { - this.setState({reset: true, value: this.props.value}, () => - this.inputRef.blur() - ) + this.handleCancel() } } @@ -41,7 +45,7 @@ class InputClickToEdit extends Component { } render() { - const {reset, isEditing, value} = this.state + const {isEditing, value} = this.state const {wrapperClass, disabled} = this.props return disabled @@ -56,7 +60,7 @@ class InputClickToEdit extends Component { type="text" className="form-control input-sm provider--input" defaultValue={value} - onBlur={this.handleInputBlur(reset)} + onBlur={this.handleInputBlur} onKeyDown={this.handleKeyDown} autoFocus={true} onFocus={this.handleFocus} From 5a826bfa632d8016584ff3c4d3b9ada3850e5195 Mon Sep 17 00:00:00 2001 From: Jared Scheib Date: Thu, 11 Jan 2018 18:20:53 -0800 Subject: [PATCH 15/98] Fix subtle font clipping in Admin Chronograf table headers Signed-off-by: Jared Scheib --- ui/src/style/pages/admin.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/style/pages/admin.scss b/ui/src/style/pages/admin.scss index a46587fbe..900f2d3ad 100644 --- a/ui/src/style/pages/admin.scss +++ b/ui/src/style/pages/admin.scss @@ -47,7 +47,7 @@ font-size: 17px; font-weight: 400 !important; color: $g12-forge; - padding: 0; + padding: 6px 0; } .panel-body {min-height: 300px;} .panel-heading + .panel-body {padding-top: 0;} From 61eadd8b79d22d9a830dcc86892f2008400cc433 Mon Sep 17 00:00:00 2001 From: Jared Scheib Date: Thu, 11 Jan 2018 18:29:15 -0800 Subject: [PATCH 16/98] Fix style for button width on current organization Signed-off-by: Alex Paxton --- ui/src/style/components/organizations-table.scss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ui/src/style/components/organizations-table.scss b/ui/src/style/components/organizations-table.scss index 6bfd2a306..9403cb21d 100644 --- a/ui/src/style/components/organizations-table.scss +++ b/ui/src/style/components/organizations-table.scss @@ -24,6 +24,8 @@ $orgs-table--delete-width: 30px; } .orgs-table--active { width: $orgs-table--active-width; + + .btn {width: 100%;} } .orgs-table--default-role.deleting { width: ( From 1765846a9881e456f1f2bd0c004dbbf074ac73ad Mon Sep 17 00:00:00 2001 From: Alex Paxton Date: Thu, 11 Jan 2018 18:32:25 -0800 Subject: [PATCH 17/98] Fix style of centered mdash when creating new organization Signed-off-by: Jared Scheib --- ui/src/style/components/organizations-table.scss | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ui/src/style/components/organizations-table.scss b/ui/src/style/components/organizations-table.scss index 9403cb21d..f5cf958fd 100644 --- a/ui/src/style/components/organizations-table.scss +++ b/ui/src/style/components/organizations-table.scss @@ -24,7 +24,9 @@ $orgs-table--delete-width: 30px; } .orgs-table--active { width: $orgs-table--active-width; - + justify-content: center; + @include no-user-select(); + .btn {width: 100%;} } .orgs-table--default-role.deleting { From d5e5529153f75f8dded96c457d02dfc6fbfffe8f Mon Sep 17 00:00:00 2001 From: Jared Scheib Date: Thu, 11 Jan 2018 18:37:55 -0800 Subject: [PATCH 18/98] Add mdash Public table cell to OrganizationsTableRowNew Signed-off-by: Alex Paxton --- ui/src/admin/components/chronograf/OrganizationsTableRowNew.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ui/src/admin/components/chronograf/OrganizationsTableRowNew.js b/ui/src/admin/components/chronograf/OrganizationsTableRowNew.js index 2a3b8e763..0c5c38745 100644 --- a/ui/src/admin/components/chronograf/OrganizationsTableRowNew.js +++ b/ui/src/admin/components/chronograf/OrganizationsTableRowNew.js @@ -73,6 +73,9 @@ class OrganizationsTableRowNew extends Component { ref={r => (this.inputRef = r)} />
+
+
+
Date: Fri, 26 Jan 2018 17:07:29 -0800 Subject: [PATCH 19/98] WIP Create New Mapping in ProviderTable --- .../components/chronograf/ProvidersTable.js | 19 ++-- .../chronograf/ProvidersTableRowNew.js | 93 +++++++++++++++++++ ui/src/admin/containers/ProvidersPage.js | 3 +- 3 files changed, 106 insertions(+), 9 deletions(-) create mode 100644 ui/src/admin/components/chronograf/ProvidersTableRowNew.js diff --git a/ui/src/admin/components/chronograf/ProvidersTable.js b/ui/src/admin/components/chronograf/ProvidersTable.js index 986b5bb44..2c45b1fb2 100644 --- a/ui/src/admin/components/chronograf/ProvidersTable.js +++ b/ui/src/admin/components/chronograf/ProvidersTable.js @@ -1,7 +1,7 @@ import React, {Component, PropTypes} from 'react' import ProvidersTableRow from 'src/admin/components/chronograf/ProvidersTableRow' -// import NewProviderMap from 'src/admin/components/chronograf/NewProviderMap' +import ProvidersTableRowNew from 'src/admin/components/chronograf/ProvidersTableRowNew' class ProvidersTable extends Component { constructor(props) { @@ -15,7 +15,7 @@ class ProvidersTable extends Component { this.setState({isCreatingMap: true}) } - handleDismissCreateMap = () => { + handleCancelCreateMap = () => { this.setState({isCreatingMap: false}) } @@ -57,14 +57,10 @@ class ProvidersTable extends Component {
Organization
+
- {/* {isCreatingMap - ? - : null} */} +
hi
{providerMaps.map(providerMap => )} + {isCreatingMap + ? + : null}
) diff --git a/ui/src/admin/components/chronograf/ProvidersTableRowNew.js b/ui/src/admin/components/chronograf/ProvidersTableRowNew.js new file mode 100644 index 000000000..4f44d007c --- /dev/null +++ b/ui/src/admin/components/chronograf/ProvidersTableRowNew.js @@ -0,0 +1,93 @@ +import React, {Component, PropTypes} from 'react' + +import ConfirmButtons from 'shared/components/ConfirmButtons' +import Dropdown from 'shared/components/Dropdown' +import InputClickToEdit from 'shared/components/InputClickToEdit' + +class ProvidersTableRowNew extends Component { + constructor(props) { + super(props) + + this.state = { + scheme: null, + provider: null, + providerOrganization: null, + redirectOrg: {name: '--'}, + } + } + + handleChangeScheme = scheme => { + this.setState({scheme}) + } + + handleChangeProvider = provider => { + this.setState({provider}) + } + + handleChangeProviderOrg = providerOrganization => { + this.setState({providerOrganization}) + } + + handleChooseOrganization = org => { + this.setState({redirectOrg: org}) + } + + render() { + const {scheme, provider, providerOrganization, redirectOrg} = this.state + + const {organizations, onCreate, onCancel} = this.props + + const dropdownItems = organizations.map(role => ({ + ...role, + text: role.name, + })) + + return ( +
+
--
+ + + +
+ +
+
+ +
+ +
+ ) + } +} + +const {arrayOf, func, shape, string} = PropTypes + +ProvidersTableRowNew.propTypes = { + organizations: arrayOf( + shape({ + id: string.isRequired, + name: string.isRequired, + }) + ).isRequired, + onCreate: func.isRequired, + onCancel: func.isRequired, +} + +export default ProvidersTableRowNew diff --git a/ui/src/admin/containers/ProvidersPage.js b/ui/src/admin/containers/ProvidersPage.js index 287e4b2d9..f430249cb 100644 --- a/ui/src/admin/containers/ProvidersPage.js +++ b/ui/src/admin/containers/ProvidersPage.js @@ -29,7 +29,8 @@ class ProvidersPage extends Component { handleDeleteMap = () => {} render() { - const {organizations, providerMaps} = this.props + // const {organizations, providerMaps} = this.props + const {organizations} = this.props return organizations ? Date: Mon, 29 Jan 2018 17:40:02 -0800 Subject: [PATCH 20/98] WIP load mappings fixture into redux --- ui/src/admin/actions/chronograf.js | 26 ++++++++++++ .../components/chronograf/ProvidersTable.js | 13 +++--- .../chronograf/ProvidersTableRow.js | 40 +++++++++---------- ui/src/admin/containers/ProvidersPage.js | 28 ++++++++----- ui/src/admin/reducers/chronograf.js | 23 ++++++++++- ui/src/shared/components/InputClickToEdit.js | 2 +- 6 files changed, 93 insertions(+), 39 deletions(-) diff --git a/ui/src/admin/actions/chronograf.js b/ui/src/admin/actions/chronograf.js index d7a64731e..6d1639f9b 100644 --- a/ui/src/admin/actions/chronograf.js +++ b/ui/src/admin/actions/chronograf.js @@ -1,6 +1,8 @@ import _ from 'lodash' import uuid from 'node-uuid' +import {PROVIDER_MAPS} from 'src/admin/constants/dummyProviderMaps' + import { getUsers as getUsersAJAX, getOrganizations as getOrganizationsAJAX, @@ -94,6 +96,20 @@ export const removeOrganization = organization => ({ }, }) +export const loadMappings = mappings => ({ + type: 'CHRONOGRAF_LOAD_MAPPINGS', + payload: { + mappings, + }, +}) + +export const updateMapping = mapping => ({ + type: 'CHRONOGRAF_UPDATE_MAPPING', + payload: { + mapping, + }, +}) + // async actions (thunks) export const loadUsersAsync = url => async dispatch => { try { @@ -113,6 +129,16 @@ export const loadOrganizationsAsync = url => async dispatch => { } } +export const loadMappingsAsync = url => async dispatch => { + try { + // awaiting backend changes + // const {data} await getOrganizationsAJAX(url); + dispatch(loadMappings(PROVIDER_MAPS)) + } catch (error) { + dispatch(errorThrown(error)) + } +} + export const createUserAsync = (url, user) => async dispatch => { // temp uuid is added to be able to disambiguate a created user that has the // same scheme, provider, and name as an existing user diff --git a/ui/src/admin/components/chronograf/ProvidersTable.js b/ui/src/admin/components/chronograf/ProvidersTable.js index 2c45b1fb2..6923ff6a0 100644 --- a/ui/src/admin/components/chronograf/ProvidersTable.js +++ b/ui/src/admin/components/chronograf/ProvidersTable.js @@ -25,11 +25,11 @@ class ProvidersTable extends Component { } render() { - const {providerMaps, organizations, onUpdateMap, onDeleteMap} = this.props + const {mappings = [], organizations, onUpdateMap, onDeleteMap} = this.props const {isCreatingMap} = this.state const tableTitle = - providerMaps.length === 1 ? '1 Map' : `${providerMaps.length} Maps` + mappings.length === 1 ? '1 Map' : `${mappings.length} Maps` return (
@@ -60,11 +60,10 @@ class ProvidersTable extends Component {
-
hi
- {providerMaps.map(providerMap => + {mappings.map(mapping => { + handleDeleteMap = mapping => { const {onDelete} = this.props this.setState({isDeleting: false}) - onDelete(providerMap) + onDelete(mapping) } handleChangeScheme = scheme => { this.setState({scheme}) - this.handleUpdateProviderMap() + this.handleUpdateMapping() } handleChangeProvider = provider => { this.setState({provider}) - this.handleUpdateProviderMap() + this.handleUpdateMapping() } handleChangeProviderOrg = providerOrganization => { this.setState({providerOrganization}) - this.handleUpdateProviderMap() + this.handleUpdateMapping() } handleChooseOrganization = org => { this.setState({redirectOrg: org}) - this.handleUpdateProviderMap() + this.handleUpdateMapping() } - handleUpdateProviderMap = () => { - const {onUpdate, providerMap: {id}} = this.props + handleUpdateMapping = () => { + const {onUpdate, mapping: {id}} = this.props const {scheme, provider, providerOrganization, redirectOrg} = this.state const updatedMap = { @@ -76,7 +76,7 @@ class ProvidersTableRow extends Component { redirectOrg, isDeleting, } = this.state - const {organizations, providerMap} = this.props + const {organizations, mapping} = this.props const dropdownItems = organizations.map(role => ({ ...role, @@ -87,30 +87,30 @@ class ProvidersTableRow extends Component { ? 'fancytable--td provider--redirect deleting' : 'fancytable--td provider--redirect' - const isDefaultProviderMap = DEFAULT_PROVIDER_MAP_ID === providerMap.id + const isDefaultMapping = DEFAULT_PROVIDER_MAP_ID === mapping.id return (
- {providerMap.id} + {mapping.id}
@@ -125,7 +125,7 @@ class ProvidersTableRow extends Component {
{isDeleting ? {} - handleUpdateMap = _updatedMap => { - // console.log(_updatedMap) + handleUpdateMap = updatedMap => { + // update the redux store + this.props.actions.updateMapping(updatedMap) + + // update the server } handleDeleteMap = () => {} render() { - // const {organizations, providerMaps} = this.props - const {organizations} = this.props + const {organizations, mappings = []} = this.props return organizations ? ({ +const mapStateToProps = ({ + links, + adminChronograf: {organizations, mappings}, +}) => ({ links, organizations, + mappings, }) const mapDispatchToProps = dispatch => ({ diff --git a/ui/src/admin/reducers/chronograf.js b/ui/src/admin/reducers/chronograf.js index 621f7a7a4..2f513f5ba 100644 --- a/ui/src/admin/reducers/chronograf.js +++ b/ui/src/admin/reducers/chronograf.js @@ -58,7 +58,10 @@ const adminChronograf = (state = initialState, action) => { case 'CHRONOGRAF_ADD_ORGANIZATION': { const {organization} = action.payload - return {...state, organizations: [organization, ...state.organizations]} + return { + ...state, + organizations: [organization, ...state.organizations], + } } case 'CHRONOGRAF_RENAME_ORGANIZATION': { @@ -94,6 +97,24 @@ const adminChronograf = (state = initialState, action) => { ), } } + + case 'CHRONOGRAF_LOAD_MAPPINGS': { + const {mappings} = action.payload + return { + ...state, + mappings, + } + } + + case 'CHRONOGRAF_UPDATE_MAPPING': { + const {mapping} = action.payload + return { + ...state, + mappings: state.mappings.map( + m => (m.id === mapping.id ? {...mapping} : m) + ), + } + } } return state diff --git a/ui/src/shared/components/InputClickToEdit.js b/ui/src/shared/components/InputClickToEdit.js index a3ed860e6..0dfdcc594 100644 --- a/ui/src/shared/components/InputClickToEdit.js +++ b/ui/src/shared/components/InputClickToEdit.js @@ -28,7 +28,7 @@ class InputClickToEdit extends Component { onUpdate(e.target.value) } - this.setState({isEditing: false}) + this.setState({isEditing: false, value: e.target.value}) } handleKeyDown = e => { From 6f0b3ae48e8cdacbb16af82be598983ee0a2b163 Mon Sep 17 00:00:00 2001 From: Iris Scholten Date: Tue, 30 Jan 2018 11:09:26 -0800 Subject: [PATCH 21/98] WIP add new mappings in redux --- ui/src/admin/actions/chronograf.js | 7 +++++++ .../admin/components/chronograf/ProvidersTable.js | 3 +++ .../components/chronograf/ProvidersTableRowNew.js | 14 ++++++++++++-- ui/src/admin/containers/ProvidersPage.js | 6 +++++- ui/src/admin/reducers/chronograf.js | 8 ++++++++ 5 files changed, 35 insertions(+), 3 deletions(-) diff --git a/ui/src/admin/actions/chronograf.js b/ui/src/admin/actions/chronograf.js index 6d1639f9b..e43fdf374 100644 --- a/ui/src/admin/actions/chronograf.js +++ b/ui/src/admin/actions/chronograf.js @@ -110,6 +110,13 @@ export const updateMapping = mapping => ({ }, }) +export const createMapping = mapping => ({ + type: 'CHRONOGRAF_CREATE_MAPPING', + payload: { + mapping, + }, +}) + // async actions (thunks) export const loadUsersAsync = url => async dispatch => { try { diff --git a/ui/src/admin/components/chronograf/ProvidersTable.js b/ui/src/admin/components/chronograf/ProvidersTable.js index 6923ff6a0..d5feedcfa 100644 --- a/ui/src/admin/components/chronograf/ProvidersTable.js +++ b/ui/src/admin/components/chronograf/ProvidersTable.js @@ -21,6 +21,9 @@ class ProvidersTable extends Component { handleCreateMap = newMap => { const {onCreateMap} = this.props + this.setState({isCreatingMap: false}) + const newMapID = this.props.mappings.length.toString() + newMap.id = newMapID onCreateMap(newMap) } diff --git a/ui/src/admin/components/chronograf/ProvidersTableRowNew.js b/ui/src/admin/components/chronograf/ProvidersTableRowNew.js index 4f44d007c..20900681e 100644 --- a/ui/src/admin/components/chronograf/ProvidersTableRowNew.js +++ b/ui/src/admin/components/chronograf/ProvidersTableRowNew.js @@ -32,10 +32,17 @@ class ProvidersTableRowNew extends Component { this.setState({redirectOrg: org}) } + handleSaveNewMapping = () => { + const {scheme, provider, providerOrganization, redirectOrg} = this.state + const {onCreate} = this.props + // id is calculated in providers table + onCreate({id: '', scheme, provider, providerOrganization, redirectOrg}) + } + render() { const {scheme, provider, providerOrganization, redirectOrg} = this.state - const {organizations, onCreate, onCancel} = this.props + const {organizations, onCancel} = this.props const dropdownItems = organizations.map(role => ({ ...role, @@ -71,7 +78,10 @@ class ProvidersTableRowNew extends Component { className="dropdown-stretch" />
- +
) } diff --git a/ui/src/admin/containers/ProvidersPage.js b/ui/src/admin/containers/ProvidersPage.js index bb34c26b8..6d6cfe338 100644 --- a/ui/src/admin/containers/ProvidersPage.js +++ b/ui/src/admin/containers/ProvidersPage.js @@ -6,6 +6,7 @@ import * as adminChronografActionCreators from 'src/admin/actions/chronograf' import {publishAutoDismissingNotification} from 'shared/dispatchers' import ProvidersTable from 'src/admin/components/chronograf/ProvidersTable' +import {createMapping} from '../actions/chronograf' class ProvidersPage extends Component { constructor(props) { @@ -22,7 +23,10 @@ class ProvidersPage extends Component { loadMappingsAsync(links.mappings) } - handleCreateMap = () => {} + handleCreateMap = mapping => { + // update the redux store + this.props.actions.createMapping(mapping) + } handleUpdateMap = updatedMap => { // update the redux store diff --git a/ui/src/admin/reducers/chronograf.js b/ui/src/admin/reducers/chronograf.js index 2f513f5ba..e3ddb0496 100644 --- a/ui/src/admin/reducers/chronograf.js +++ b/ui/src/admin/reducers/chronograf.js @@ -115,6 +115,14 @@ const adminChronograf = (state = initialState, action) => { ), } } + + case 'CHRONOGRAF_CREATE_MAPPING': { + const {mapping} = action.payload + return { + ...state, + mappings: [...state.mappings, mapping], + } + } } return state From a460e6c81f0e1d9aa6ad4f1a51f31e878cccc28b Mon Sep 17 00:00:00 2001 From: Iris Scholten Date: Tue, 30 Jan 2018 11:27:30 -0800 Subject: [PATCH 22/98] WIP delete mapping in redux --- ui/src/admin/actions/chronograf.js | 7 +++++++ ui/src/admin/components/chronograf/ProvidersTable.js | 1 + ui/src/admin/containers/ProvidersPage.js | 6 +++--- ui/src/admin/reducers/chronograf.js | 8 ++++++++ 4 files changed, 19 insertions(+), 3 deletions(-) diff --git a/ui/src/admin/actions/chronograf.js b/ui/src/admin/actions/chronograf.js index e43fdf374..758ceab8d 100644 --- a/ui/src/admin/actions/chronograf.js +++ b/ui/src/admin/actions/chronograf.js @@ -117,6 +117,13 @@ export const createMapping = mapping => ({ }, }) +export const deleteMapping = mapping => ({ + type: 'CHRONOGRAF_DELETE_MAPPING', + payload: { + mapping, + }, +}) + // async actions (thunks) export const loadUsersAsync = url => async dispatch => { try { diff --git a/ui/src/admin/components/chronograf/ProvidersTable.js b/ui/src/admin/components/chronograf/ProvidersTable.js index d5feedcfa..b37c04acd 100644 --- a/ui/src/admin/components/chronograf/ProvidersTable.js +++ b/ui/src/admin/components/chronograf/ProvidersTable.js @@ -21,6 +21,7 @@ class ProvidersTable extends Component { handleCreateMap = newMap => { const {onCreateMap} = this.props + //todo: better way of getting mapping id this.setState({isCreatingMap: false}) const newMapID = this.props.mappings.length.toString() newMap.id = newMapID diff --git a/ui/src/admin/containers/ProvidersPage.js b/ui/src/admin/containers/ProvidersPage.js index 6d6cfe338..f6b7f95e3 100644 --- a/ui/src/admin/containers/ProvidersPage.js +++ b/ui/src/admin/containers/ProvidersPage.js @@ -6,7 +6,6 @@ import * as adminChronografActionCreators from 'src/admin/actions/chronograf' import {publishAutoDismissingNotification} from 'shared/dispatchers' import ProvidersTable from 'src/admin/components/chronograf/ProvidersTable' -import {createMapping} from '../actions/chronograf' class ProvidersPage extends Component { constructor(props) { @@ -24,7 +23,6 @@ class ProvidersPage extends Component { } handleCreateMap = mapping => { - // update the redux store this.props.actions.createMapping(mapping) } @@ -35,7 +33,9 @@ class ProvidersPage extends Component { // update the server } - handleDeleteMap = () => {} + handleDeleteMap = mapping => { + this.props.actions.deleteMapping(mapping) + } render() { const {organizations, mappings = []} = this.props diff --git a/ui/src/admin/reducers/chronograf.js b/ui/src/admin/reducers/chronograf.js index e3ddb0496..29355fd9d 100644 --- a/ui/src/admin/reducers/chronograf.js +++ b/ui/src/admin/reducers/chronograf.js @@ -123,6 +123,14 @@ const adminChronograf = (state = initialState, action) => { mappings: [...state.mappings, mapping], } } + + case 'CHRONOGRAF_DELETE_MAPPING': { + const {mapping} = action.payload + return { + ...state, + mappings: state.mappings.filter(m => m.id !== mapping.id), + } + } } return state From 83aff58e12e42eee85d18ec3c624a7bec76a20b6 Mon Sep 17 00:00:00 2001 From: Iris Scholten Date: Tue, 30 Jan 2018 15:26:08 -0800 Subject: [PATCH 23/98] WIP fully connect redux to provider react componenets --- ui/src/admin/actions/chronograf.js | 56 +++++++++++++++++-- .../components/chronograf/ProvidersTable.js | 7 +-- .../chronograf/ProvidersTableRow.js | 24 ++++---- .../chronograf/ProvidersTableRowNew.js | 14 +++-- ui/src/admin/constants/dummyProviderMaps.js | 4 +- ui/src/admin/containers/ProvidersPage.js | 27 ++++++--- ui/src/admin/reducers/chronograf.js | 11 +++- 7 files changed, 102 insertions(+), 41 deletions(-) diff --git a/ui/src/admin/actions/chronograf.js b/ui/src/admin/actions/chronograf.js index 758ceab8d..d0b3b3774 100644 --- a/ui/src/admin/actions/chronograf.js +++ b/ui/src/admin/actions/chronograf.js @@ -110,15 +110,15 @@ export const updateMapping = mapping => ({ }, }) -export const createMapping = mapping => ({ - type: 'CHRONOGRAF_CREATE_MAPPING', +export const addMapping = mapping => ({ + type: 'CHRONOGRAF_ADD_MAPPING', payload: { mapping, }, }) -export const deleteMapping = mapping => ({ - type: 'CHRONOGRAF_DELETE_MAPPING', +export const removeMapping = mapping => ({ + type: 'CHRONOGRAF_REMOVE_MAPPING', payload: { mapping, }, @@ -143,9 +143,10 @@ export const loadOrganizationsAsync = url => async dispatch => { } } -export const loadMappingsAsync = url => async dispatch => { +export const loadMappingsAsync = () => async dispatch => { try { // awaiting backend changes + // todo: change to below instead of starting from provider maps data // const {data} await getOrganizationsAJAX(url); dispatch(loadMappings(PROVIDER_MAPS)) } catch (error) { @@ -153,6 +154,51 @@ export const loadMappingsAsync = url => async dispatch => { } } +export const createMappingAsync = (url, mapping) => async dispatch => { + const mappingWithTempID = {...mapping, _tempID: uuid.v4()} + dispatch(addMapping(mappingWithTempID)) + try { + /* const {data} = await createMappingAJAX(url, mapping) + dispatch(updateMapping(data)) + */ + } catch (error) { + const message = `${_.upperFirst( + _.toLower(error.data.message) + )}: Scheme: ${mapping.scheme} Provider: ${mapping.provider}` + dispatch(errorThrown(error, message)) + setTimeout( + () => dispatch(removeMapping(mappingWithTempID)), + REVERT_STATE_DELAY + ) + } +} + +export const deleteMappingAsync = mapping => async dispatch => { + dispatch(removeMapping(mapping)) + try { + // await deleteMappingAJAX(mapping) + dispatch( + publishAutoDismissingNotification( + 'success', + `Mapping deleted: ${mapping.id} ${mapping.scheme}` + ) + ) + } catch (error) { + dispatch(errorThrown(error)) + dispatch(addMapping(mapping)) + } +} + +export const updateMappingAsync = mapping => async dispatch => { + dispatch(updateMapping(mapping)) + try { + // const {data} = await updateMappingAJAX(mapping) + } catch (error) { + dispatch(errorThrown(error)) + dispatch(updateMapping(mapping)) + } +} + export const createUserAsync = (url, user) => async dispatch => { // temp uuid is added to be able to disambiguate a created user that has the // same scheme, provider, and name as an existing user diff --git a/ui/src/admin/components/chronograf/ProvidersTable.js b/ui/src/admin/components/chronograf/ProvidersTable.js index b37c04acd..c177eaec3 100644 --- a/ui/src/admin/components/chronograf/ProvidersTable.js +++ b/ui/src/admin/components/chronograf/ProvidersTable.js @@ -21,7 +21,7 @@ class ProvidersTable extends Component { handleCreateMap = newMap => { const {onCreateMap} = this.props - //todo: better way of getting mapping id + // todo: better way of getting mapping id this.setState({isCreatingMap: false}) const newMapID = this.props.mappings.length.toString() newMap.id = newMapID @@ -95,10 +95,7 @@ ProvidersTable.propTypes = { scheme: string, provider: string, providerOrganization: string, - redirectOrg: shape({ - id: string.isRequired, - name: string.isRequired, - }), + organizationId: string, }) ).isRequired, organizations: arrayOf( diff --git a/ui/src/admin/components/chronograf/ProvidersTableRow.js b/ui/src/admin/components/chronograf/ProvidersTableRow.js index 51a2ab22c..5134aaa7c 100644 --- a/ui/src/admin/components/chronograf/ProvidersTableRow.js +++ b/ui/src/admin/components/chronograf/ProvidersTableRow.js @@ -14,7 +14,7 @@ class ProvidersTableRow extends Component { scheme: this.props.mapping.scheme, provider: this.props.mapping.provider, providerOrganization: this.props.mapping.providerOrganization, - redirectOrg: this.props.mapping.redirectOrg, + organizationId: this.props.mapping.organizationId, isDeleting: false, } } @@ -29,7 +29,6 @@ class ProvidersTableRow extends Component { handleDeleteMap = mapping => { const {onDelete} = this.props - this.setState({isDeleting: false}) onDelete(mapping) } @@ -50,20 +49,20 @@ class ProvidersTableRow extends Component { } handleChooseOrganization = org => { - this.setState({redirectOrg: org}) + this.setState({organizationId: org.id}) this.handleUpdateMapping() } handleUpdateMapping = () => { const {onUpdate, mapping: {id}} = this.props - const {scheme, provider, providerOrganization, redirectOrg} = this.state + const {scheme, provider, providerOrganization, organizationId} = this.state const updatedMap = { id, scheme, provider, providerOrganization, - redirectOrg, + organizationId, } onUpdate(updatedMap) } @@ -73,17 +72,19 @@ class ProvidersTableRow extends Component { scheme, provider, providerOrganization, - redirectOrg, + organizationId, isDeleting, } = this.state const {organizations, mapping} = this.props + const selectedOrg = organizations.find(o => o.id === organizationId) + const dropdownItems = organizations.map(role => ({ ...role, text: role.name, })) - const redirectOrgClassName = isDeleting + const organizationIdClassName = isDeleting ? 'fancytable--td provider--redirect deleting' : 'fancytable--td provider--redirect' @@ -115,11 +116,11 @@ class ProvidersTableRow extends Component {
-
+
@@ -149,10 +150,7 @@ ProvidersTableRow.propTypes = { scheme: string, provider: string, providerOrganization: string, - redirectOrg: shape({ - id: string.isRequired, - name: string.isRequired, - }), + organizationId: string, }), organizations: arrayOf( shape({ diff --git a/ui/src/admin/components/chronograf/ProvidersTableRowNew.js b/ui/src/admin/components/chronograf/ProvidersTableRowNew.js index 20900681e..ccd5ffd5e 100644 --- a/ui/src/admin/components/chronograf/ProvidersTableRowNew.js +++ b/ui/src/admin/components/chronograf/ProvidersTableRowNew.js @@ -12,7 +12,7 @@ class ProvidersTableRowNew extends Component { scheme: null, provider: null, providerOrganization: null, - redirectOrg: {name: '--'}, + organizationId: 'default', } } @@ -29,21 +29,23 @@ class ProvidersTableRowNew extends Component { } handleChooseOrganization = org => { - this.setState({redirectOrg: org}) + this.setState({organizationId: org.id}) } handleSaveNewMapping = () => { - const {scheme, provider, providerOrganization, redirectOrg} = this.state + const {scheme, provider, providerOrganization, organizationId} = this.state const {onCreate} = this.props // id is calculated in providers table - onCreate({id: '', scheme, provider, providerOrganization, redirectOrg}) + onCreate({id: '', scheme, provider, providerOrganization, organizationId}) } render() { - const {scheme, provider, providerOrganization, redirectOrg} = this.state + const {scheme, provider, providerOrganization, organizationId} = this.state const {organizations, onCancel} = this.props + const selectedOrg = organizations.find(o => o.id === organizationId) + const dropdownItems = organizations.map(role => ({ ...role, text: role.name, @@ -74,7 +76,7 @@ class ProvidersTableRowNew extends Component {
diff --git a/ui/src/admin/constants/dummyProviderMaps.js b/ui/src/admin/constants/dummyProviderMaps.js index f3a936c5b..c1a9b27a3 100644 --- a/ui/src/admin/constants/dummyProviderMaps.js +++ b/ui/src/admin/constants/dummyProviderMaps.js @@ -7,13 +7,13 @@ export const PROVIDER_MAPS = [ scheme: '*', provider: '*', providerOrganization: '*', - redirectOrg: {id: DEFAULT_ORG_ID, name: 'Default'}, + organizationId: DEFAULT_ORG_ID, }, { id: '1', scheme: 'oauth2', provider: 'github', providerOrganization: null, - redirectOrg: {id: '5', name: 'moarOrg'}, + organizationId: '2', }, ] diff --git a/ui/src/admin/containers/ProvidersPage.js b/ui/src/admin/containers/ProvidersPage.js index f6b7f95e3..39e9511dd 100644 --- a/ui/src/admin/containers/ProvidersPage.js +++ b/ui/src/admin/containers/ProvidersPage.js @@ -23,18 +23,34 @@ class ProvidersPage extends Component { } handleCreateMap = mapping => { - this.props.actions.createMapping(mapping) + this.props.actions.createMappingAsync('', mapping) + /* + const { + links, + actions: {createMappingAsync} + } = this.props + await createMappingAsync(links.mappings, mapping) + // this.refreshMe()? -- why + */ } handleUpdateMap = updatedMap => { // update the redux store - this.props.actions.updateMapping(updatedMap) + this.props.actions.updateMappingAsync(updatedMap) // update the server + /* + const {actionsAdmin: {updateMappingAsync}} = this.props + await updateMappingAsync(mapping) + */ } handleDeleteMap = mapping => { - this.props.actions.deleteMapping(mapping) + this.props.actions.deleteMappingAsync(mapping) + /* + const {actionsAdmin: {deleteOrganizationAsync}} = this.props + deleteMappingAsync(mapping) // why no await? + */ } render() { @@ -70,10 +86,7 @@ ProvidersPage.propTypes = { scheme: string, provider: string, providerOrganization: string, - redirectOrg: shape({ - id: string.isRequired, - name: string.isRequired, - }), + organizationId: string, }) ), actions: shape({ diff --git a/ui/src/admin/reducers/chronograf.js b/ui/src/admin/reducers/chronograf.js index 29355fd9d..17fb3bacd 100644 --- a/ui/src/admin/reducers/chronograf.js +++ b/ui/src/admin/reducers/chronograf.js @@ -116,7 +116,7 @@ const adminChronograf = (state = initialState, action) => { } } - case 'CHRONOGRAF_CREATE_MAPPING': { + case 'CHRONOGRAF_ADD_MAPPING': { const {mapping} = action.payload return { ...state, @@ -124,11 +124,16 @@ const adminChronograf = (state = initialState, action) => { } } - case 'CHRONOGRAF_DELETE_MAPPING': { + case 'CHRONOGRAF_REMOVE_MAPPING': { const {mapping} = action.payload return { ...state, - mappings: state.mappings.filter(m => m.id !== mapping.id), + mappings: state.mappings.filter( + m => + mapping._tempID + ? m._tempID !== mapping._tempID + : m.id !== mapping.id + ), } } } From fc55f82506434d77e316f3c45388dfb23714d29d Mon Sep 17 00:00:00 2001 From: Iris Scholten Date: Fri, 2 Feb 2018 15:26:31 -0800 Subject: [PATCH 24/98] make scheme a dropdown, update the redux store for a mapping change corectly, and add an empty state for the provider mappings page --- .../components/chronograf/ProvidersTable.js | 87 ++++++++++++------- .../chronograf/ProvidersTableRow.js | 80 +++++++++++++---- .../chronograf/ProvidersTableRowNew.js | 23 +++-- ui/src/admin/containers/ProvidersPage.js | 1 - 4 files changed, 132 insertions(+), 59 deletions(-) diff --git a/ui/src/admin/components/chronograf/ProvidersTable.js b/ui/src/admin/components/chronograf/ProvidersTable.js index c177eaec3..f1c95928a 100644 --- a/ui/src/admin/components/chronograf/ProvidersTable.js +++ b/ui/src/admin/components/chronograf/ProvidersTable.js @@ -35,6 +35,14 @@ class ProvidersTable extends Component { const tableTitle = mappings.length === 1 ? '1 Map' : `${mappings.length} Maps` + // define scheme options + const schemes = [ + {text: '*'}, + {text: 'oauth2'}, + {text: 'option2'}, + {text: 'option3'}, + ] + return (
@@ -49,38 +57,55 @@ class ProvidersTable extends Component { Create Map
-
-
-
ID
-
Scheme
-
Provider
-
- Provider Org + {(mappings && mappings.length) || isCreatingMap + ?
+
+
ID
+
Scheme
+
+ Provider +
+
+ Provider Org +
+
+
+ Organization +
+
+
+
+ {mappings.map(mapping => + + )} + {isCreatingMap + ? + : null}
-
-
- Organization -
-
-
-
- {mappings.map(mapping => - - )} - {isCreatingMap - ? - : null} -
+ :
+

+ Looks like you don’t have any mappings +

+ +
}
) } diff --git a/ui/src/admin/components/chronograf/ProvidersTableRow.js b/ui/src/admin/components/chronograf/ProvidersTableRow.js index 5134aaa7c..8f40b0857 100644 --- a/ui/src/admin/components/chronograf/ProvidersTableRow.js +++ b/ui/src/admin/components/chronograf/ProvidersTableRow.js @@ -33,30 +33,68 @@ class ProvidersTableRow extends Component { onDelete(mapping) } - handleChangeScheme = scheme => { - this.setState({scheme}) - this.handleUpdateMapping() - } - handleChangeProvider = provider => { this.setState({provider}) - this.handleUpdateMapping() + const {scheme, organizationId, providerOrganization} = this.state + const {onUpdate, mapping: {id}} = this.props + const updatedMap = { + id, + scheme, + provider, + providerOrganization, + organizationId, + } + onUpdate(updatedMap) } handleChangeProviderOrg = providerOrganization => { this.setState({providerOrganization}) - this.handleUpdateMapping() + const {onUpdate, mapping: {id}} = this.props + const {scheme, provider, organizationId} = this.state + const updatedMap = { + id, + scheme, + provider, + providerOrganization, + organizationId, + } + onUpdate(updatedMap) } handleChooseOrganization = org => { - this.setState({organizationId: org.id}) - this.handleUpdateMapping() + const organizationId = org.id + this.setState({organizationId}) + const {onUpdate, mapping: {id}} = this.props + const {scheme, provider, providerOrganization} = this.state + const updatedMap = { + id, + scheme, + provider, + providerOrganization, + organizationId, + } + onUpdate(updatedMap) + } + + handleChooseScheme = s => { + const scheme = s.text + this.setState({scheme}) + const {onUpdate, mapping: {id}} = this.props + const {provider, providerOrganization, organizationId} = this.state + const updatedMap = { + id, + scheme, + provider, + providerOrganization, + organizationId, + } + onUpdate(updatedMap) } handleUpdateMapping = () => { + // this was getting called by all the handlers for input/dropdown changes but it meant the state was not getting updated so the updated map was stale const {onUpdate, mapping: {id}} = this.props const {scheme, provider, providerOrganization, organizationId} = this.state - const updatedMap = { id, scheme, @@ -75,11 +113,10 @@ class ProvidersTableRow extends Component { organizationId, isDeleting, } = this.state - const {organizations, mapping} = this.props + const {organizations, mapping, schemes} = this.props const selectedOrg = organizations.find(o => o.id === organizationId) - - const dropdownItems = organizations.map(role => ({ + const orgDropdownItems = organizations.map(role => ({ ...role, text: role.name, })) @@ -95,11 +132,11 @@ class ProvidersTableRow extends Component {
{mapping.id}
-
{ - this.setState({scheme}) + handleChooseScheme = scheme => { + this.setState({scheme: scheme.text}) } handleChangeProvider = provider => { @@ -42,7 +42,7 @@ class ProvidersTableRowNew extends Component { render() { const {scheme, provider, providerOrganization, organizationId} = this.state - const {organizations, onCancel} = this.props + const {organizations, onCancel, schemes} = this.props const selectedOrg = organizations.find(o => o.id === organizationId) @@ -54,10 +54,12 @@ class ProvidersTableRowNew extends Component { return (
--
- { // update the redux store this.props.actions.updateMappingAsync(updatedMap) - // update the server /* const {actionsAdmin: {updateMappingAsync}} = this.props From ab085ab23d31edd2cfdb9c0de15396297e4c79a1 Mon Sep 17 00:00:00 2001 From: Iris Scholten Date: Fri, 2 Feb 2018 16:57:26 -0800 Subject: [PATCH 25/98] refactor of update methods in ProvidersTableRow --- .../chronograf/ProvidersTableRow.js | 77 +++---------------- ui/src/shared/components/InputClickToEdit.js | 5 +- 2 files changed, 15 insertions(+), 67 deletions(-) diff --git a/ui/src/admin/components/chronograf/ProvidersTableRow.js b/ui/src/admin/components/chronograf/ProvidersTableRow.js index 8f40b0857..d758f2819 100644 --- a/ui/src/admin/components/chronograf/ProvidersTableRow.js +++ b/ui/src/admin/components/chronograf/ProvidersTableRow.js @@ -33,77 +33,22 @@ class ProvidersTableRow extends Component { onDelete(mapping) } - handleChangeProvider = provider => { - this.setState({provider}) - const {scheme, organizationId, providerOrganization} = this.state + handleUpdateMapping = changes => { const {onUpdate, mapping: {id}} = this.props - const updatedMap = { - id, - scheme, - provider, - providerOrganization, - organizationId, - } - onUpdate(updatedMap) + const newState = {...this.state, ...changes, id} + this.setState(newState) + onUpdate(newState) } - handleChangeProviderOrg = providerOrganization => { - this.setState({providerOrganization}) - const {onUpdate, mapping: {id}} = this.props - const {scheme, provider, organizationId} = this.state - const updatedMap = { - id, - scheme, - provider, - providerOrganization, - organizationId, - } - onUpdate(updatedMap) - } + handleChangeProvider = provider => this.handleUpdateMapping({provider}) - handleChooseOrganization = org => { - const organizationId = org.id - this.setState({organizationId}) - const {onUpdate, mapping: {id}} = this.props - const {scheme, provider, providerOrganization} = this.state - const updatedMap = { - id, - scheme, - provider, - providerOrganization, - organizationId, - } - onUpdate(updatedMap) - } + handleChangeProviderOrg = providerOrganization => + this.handleUpdateMapping({providerOrganization}) - handleChooseScheme = s => { - const scheme = s.text - this.setState({scheme}) - const {onUpdate, mapping: {id}} = this.props - const {provider, providerOrganization, organizationId} = this.state - const updatedMap = { - id, - scheme, - provider, - providerOrganization, - organizationId, - } - onUpdate(updatedMap) - } + handleChooseOrganization = ({id: organizationId}) => + this.handleUpdateMapping({organizationId}) - handleUpdateMapping = () => { - // this was getting called by all the handlers for input/dropdown changes but it meant the state was not getting updated so the updated map was stale - const {onUpdate, mapping: {id}} = this.props - const {scheme, provider, providerOrganization, organizationId} = this.state - const updatedMap = { - id, - scheme, - provider, - providerOrganization, - organizationId, - } - onUpdate(updatedMap) - } + handleChooseScheme = ({text: scheme}) => this.handleUpdateMapping({scheme}) render() { const { @@ -143,12 +88,14 @@ class ProvidersTableRow extends Component { wrapperClass="fancytable--td provider--provider" onUpdate={this.handleChangeProvider} disabled={isDefaultMapping} + tabIndex="1" />
diff --git a/ui/src/shared/components/InputClickToEdit.js b/ui/src/shared/components/InputClickToEdit.js index 0dfdcc594..af422d509 100644 --- a/ui/src/shared/components/InputClickToEdit.js +++ b/ui/src/shared/components/InputClickToEdit.js @@ -46,7 +46,7 @@ class InputClickToEdit extends Component { render() { const {isEditing, value} = this.state - const {wrapperClass, disabled} = this.props + const {wrapperClass, disabled, tabIndex} = this.props return disabled ?
@@ -54,7 +54,7 @@ class InputClickToEdit extends Component { {value}
- :
+ :
{isEditing ? Date: Fri, 2 Feb 2018 17:25:43 -0800 Subject: [PATCH 26/98] Add expand/collapse interaction to each kapacitor log item --- .../components/LogItemKapacitorPoint.js | 116 ++++++++++++------ ui/src/kapacitor/components/LogsTable.js | 12 +- ui/src/kapacitor/components/LogsTableRow.js | 29 +++-- ui/src/kapacitor/components/Tickscript.js | 6 +- ui/src/kapacitor/containers/TickscriptPage.js | 9 ++ .../components/kapacitor-logs-table.scss | 53 +++++++- 6 files changed, 171 insertions(+), 54 deletions(-) diff --git a/ui/src/kapacitor/components/LogItemKapacitorPoint.js b/ui/src/kapacitor/components/LogItemKapacitorPoint.js index 6a7639330..1e9b54b75 100644 --- a/ui/src/kapacitor/components/LogItemKapacitorPoint.js +++ b/ui/src/kapacitor/components/LogItemKapacitorPoint.js @@ -1,43 +1,87 @@ -import React, {PropTypes} from 'react' +import React, {Component, PropTypes} from 'react' -const renderKeysAndValues = object => { - if (!object) { - return -- +class LogItemKapacitorPoint extends Component { + renderKeysAndValues = (object, name) => { + if (!object) { + return -- + } + const objKeys = Object.keys(object) + const objValues = Object.values(object) + + if (objKeys.length > 2) { + return ( +
+ {`${objKeys.length} ${name}...`} +
+ ) + } + + const objElements = objKeys.map((objKey, i) => +
+ {objKey}: {objValues[i]} +
+ ) + return objElements } - const objKeys = Object.keys(object) - const objValues = Object.values(object) - const objElements = objKeys.map((objKey, i) => -
- {objKey}: {objValues[i]} -
- ) - return objElements + handleToggleExpand = () => { + const {onToggleExpandLog, logIndex} = this.props + + if (this.isExpandable()) { + onToggleExpandLog(logIndex) + } + } + + isExpandable = () => { + const {logItem} = this.props + + if ( + Object.keys(logItem.tag).length > 2 || + Object.keys(logItem.field).length > 2 + ) { + return true + } + return false + } + + render() { + const {logItem} = this.props + + const rowClass = `logs-table--row${this.isExpandable() + ? ' logs-table--row__expandable' + : ''}${logItem.expanded ? ' expanded' : ''}` + + return ( +
+ {this.isExpandable() && +
+ Click to show all items +
} +
+
+
+ {logItem.ts} +
+
+
+
Kapacitor Point
+
+
+

TAGS

+ {this.renderKeysAndValues(logItem.tag, 'Tags')} +
+
+

FIELDS

+ {this.renderKeysAndValues(logItem.field, 'Fields')} +
+
+
+
+ ) + } } -const LogItemKapacitorPoint = ({logItem}) => -
-
-
-
- {logItem.ts} -
-
-
-
Kapacitor Point
-
-
- TAGS
- {renderKeysAndValues(logItem.tag)} -
-
- FIELDS
- {renderKeysAndValues(logItem.field)} -
-
-
-
-const {shape, string} = PropTypes +const {func, number, shape, string} = PropTypes LogItemKapacitorPoint.propTypes = { logItem: shape({ @@ -46,6 +90,8 @@ LogItemKapacitorPoint.propTypes = { tag: shape.isRequired, field: shape.isRequired, }), + onToggleExpandLog: func.isRequired, + logIndex: number.isRequired, } export default LogItemKapacitorPoint diff --git a/ui/src/kapacitor/components/LogsTable.js b/ui/src/kapacitor/components/LogsTable.js index ad7945c1b..070740cf0 100644 --- a/ui/src/kapacitor/components/LogsTable.js +++ b/ui/src/kapacitor/components/LogsTable.js @@ -3,7 +3,7 @@ import React, {PropTypes} from 'react' import InfiniteScroll from 'shared/components/InfiniteScroll' import LogsTableRow from 'src/kapacitor/components/LogsTableRow' -const LogsTable = ({logs}) => +const LogsTable = ({logs, onToggleExpandLog}) =>

Logs

@@ -14,14 +14,19 @@ const LogsTable = ({logs}) => className="logs-table" itemHeight={87} items={logs.map((log, i) => - + )} /> :
}
-const {arrayOf, shape, string} = PropTypes +const {arrayOf, func, shape, string} = PropTypes LogsTable.propTypes = { logs: arrayOf( @@ -32,6 +37,7 @@ LogsTable.propTypes = { msg: string.isRequired, }) ).isRequired, + onToggleExpandLog: func.isRequired, } export default LogsTable diff --git a/ui/src/kapacitor/components/LogsTableRow.js b/ui/src/kapacitor/components/LogsTableRow.js index 32b658c67..c635e63d7 100644 --- a/ui/src/kapacitor/components/LogsTableRow.js +++ b/ui/src/kapacitor/components/LogsTableRow.js @@ -8,31 +8,37 @@ import LogItemKapacitorError from 'src/kapacitor/components/LogItemKapacitorErro import LogItemKapacitorDebug from 'src/kapacitor/components/LogItemKapacitorDebug' import LogItemInfluxDBDebug from 'src/kapacitor/components/LogItemInfluxDBDebug' -const LogsTableRow = ({logItem, index}) => { +const LogsTableRow = ({logItem, index, onToggleExpandLog}) => { if (logItem.service === 'sessions') { - return + return } if (logItem.service === 'http' && logItem.msg === 'http request') { - return + return } if (logItem.service === 'kapacitor' && logItem.msg === 'point') { - return + return ( + + ) } if (logItem.service === 'httpd_server_errors' && logItem.lvl === 'error') { - return + return } if (logItem.service === 'kapacitor' && logItem.lvl === 'error') { - return + return } if (logItem.service === 'kapacitor' && logItem.lvl === 'debug') { - return + return } if (logItem.service === 'influxdb' && logItem.lvl === 'debug') { - return + return } return ( -
+
@@ -53,7 +59,7 @@ const LogsTableRow = ({logItem, index}) => { ) } -const {number, shape, string} = PropTypes +const {func, number, shape, string} = PropTypes LogsTableRow.propTypes = { logItem: shape({ @@ -62,7 +68,8 @@ LogsTableRow.propTypes = { lvl: string.isRequired, msg: string.isRequired, }).isRequired, - index: number, + index: number.isRequired, + onToggleExpandLog: func.isRequired, } export default LogsTableRow diff --git a/ui/src/kapacitor/components/Tickscript.js b/ui/src/kapacitor/components/Tickscript.js index 8cec26829..8e9fe899b 100644 --- a/ui/src/kapacitor/components/Tickscript.js +++ b/ui/src/kapacitor/components/Tickscript.js @@ -20,6 +20,7 @@ const Tickscript = ({ isNewTickscript, areLogsVisible, areLogsEnabled, + onToggleExpandLog, onToggleLogsVisibility, }) =>
@@ -51,7 +52,9 @@ const Tickscript = ({ unsavedChanges={unsavedChanges} />
- {areLogsVisible ? : null} + {areLogsVisible + ? + : null}
@@ -79,6 +82,7 @@ Tickscript.propTypes = { onChangeID: func.isRequired, isNewTickscript: bool.isRequired, unsavedChanges: bool, + onToggleExpandLog: func.isRequired, } export default Tickscript diff --git a/ui/src/kapacitor/containers/TickscriptPage.js b/ui/src/kapacitor/containers/TickscriptPage.js index 0f456cdfc..19639a69c 100644 --- a/ui/src/kapacitor/containers/TickscriptPage.js +++ b/ui/src/kapacitor/containers/TickscriptPage.js @@ -2,6 +2,7 @@ import React, {PropTypes, Component} from 'react' import {connect} from 'react-redux' import {bindActionCreators} from 'redux' import uuid from 'node-uuid' +import _ from 'lodash' import Tickscript from 'src/kapacitor/components/Tickscript' import * as kapactiorActionCreators from 'src/kapacitor/actions/view' @@ -156,6 +157,13 @@ class TickscriptPage extends Component { }) } + handleToggleExpandLog = logIndex => { + const {logs} = this.state + logs[logIndex].expanded = !_.get(logs[logIndex], 'expanded', false) + + this.setState({logs}) + } + handleSave = async () => { const {kapacitor, task} = this.state const { @@ -243,6 +251,7 @@ class TickscriptPage extends Component { areLogsVisible={areLogsVisible} areLogsEnabled={areLogsEnabled} onToggleLogsVisibility={this.handleToggleLogsVisibility} + onToggleExpandLog={this.handleToggleExpandLog} /> ) } diff --git a/ui/src/style/components/kapacitor-logs-table.scss b/ui/src/style/components/kapacitor-logs-table.scss index ff1eb7985..0c163c103 100644 --- a/ui/src/style/components/kapacitor-logs-table.scss +++ b/ui/src/style/components/kapacitor-logs-table.scss @@ -36,14 +36,13 @@ $logs-margin: 4px; height: 100%; } .logs-table--row { - height: 87px; // Fixed height, required for Infinite Scroll, allows for 2 tags / fields per line + position: relative; + overflow: hidden; + height: 87px; // Fixed height, required for Infinite Scroll, allows for 2 tags / fields per line padding: 8px ($logs-table-padding - 16px) 8px ($logs-table-padding / 2); border-bottom: 2px solid $g3-castle; transition: background-color 0.25s ease; - &:hover { - background-color: $g4-onyx; - } &:first-child { border-bottom: none; } @@ -100,8 +99,54 @@ $logs-margin: 4px; color: $g11-sidewalk; flex: 1 0 50%; } +.logs-table--key-values h1 { + font-size: 13px; + font-weight: 700; + margin: 0; + letter-spacing: normal; + line-height: 1.42857143em; +} .logs-table--key-value { } .logs-table--key-value span { color: $c-pool; } + +.logs-table--many-keys { + color: $c-pool; + width: 100%; + display: inline-block; + font-style: italic; +} + +.logs-table--row-expander { + position: absolute; + bottom: 0; + left: 0; + width: 100%; + background-color: rgba(255,255,255,0.5); + color: $g20-white; + padding: 4px 0; + font-size: 13px; + font-weight: 600; + text-align: center; + transition: transform 0.25s ease; + transform: translateY(100%); +} +.logs-table--row:hover .logs-table--row-expander { + transform: translateY(0); +} + +// Styles for Expandable Log Items +.logs-table--row.logs-table--row__expandable { + &:hover { + background-color: $g4-onyx; + cursor: zoom-in; + } + &.expanded { + height: 240px; + &:hover { + cursor: zoom-out; + } + } +} From 160c0f50f88e2836b1e7a3a373f00f6c844babae Mon Sep 17 00:00:00 2001 From: Iris Scholten Date: Fri, 2 Feb 2018 17:30:31 -0800 Subject: [PATCH 27/98] added tabbing to InputClickToEdit components --- ui/src/admin/components/chronograf/ProvidersTable.js | 6 +++--- ui/src/shared/components/InputClickToEdit.js | 10 ++++++++-- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/ui/src/admin/components/chronograf/ProvidersTable.js b/ui/src/admin/components/chronograf/ProvidersTable.js index f1c95928a..ee5d40a07 100644 --- a/ui/src/admin/components/chronograf/ProvidersTable.js +++ b/ui/src/admin/components/chronograf/ProvidersTable.js @@ -36,7 +36,7 @@ class ProvidersTable extends Component { mappings.length === 1 ? '1 Map' : `${mappings.length} Maps` // define scheme options - const schemes = [ + const SCHEMES = [ {text: '*'}, {text: 'oauth2'}, {text: 'option2'}, @@ -80,7 +80,7 @@ class ProvidersTable extends Component { key={mapping.id} mapping={mapping} organizations={organizations} - schemes={schemes} + schemes={SCHEMES} onDelete={onDeleteMap} onUpdate={onUpdateMap} /> @@ -88,7 +88,7 @@ class ProvidersTable extends Component { {isCreatingMap ? diff --git a/ui/src/shared/components/InputClickToEdit.js b/ui/src/shared/components/InputClickToEdit.js index af422d509..38afd6ab4 100644 --- a/ui/src/shared/components/InputClickToEdit.js +++ b/ui/src/shared/components/InputClickToEdit.js @@ -54,7 +54,7 @@ class InputClickToEdit extends Component { {value}
- :
+ :
{isEditing ? (this.inputRef = r)} + tabIndex={tabIndex} /> - :
+ :
{value}
} From d036dec28ab7c25c3729f42637771c170ee1942a Mon Sep 17 00:00:00 2001 From: Alex P Date: Fri, 2 Feb 2018 18:05:32 -0800 Subject: [PATCH 28/98] Polish expand/collapse interaction and appearance of logs --- .../components/LogItemKapacitorPoint.js | 58 ++++++++++++------- .../components/kapacitor-logs-table.scss | 33 ++++------- 2 files changed, 48 insertions(+), 43 deletions(-) diff --git a/ui/src/kapacitor/components/LogItemKapacitorPoint.js b/ui/src/kapacitor/components/LogItemKapacitorPoint.js index 1e9b54b75..137d36c0b 100644 --- a/ui/src/kapacitor/components/LogItemKapacitorPoint.js +++ b/ui/src/kapacitor/components/LogItemKapacitorPoint.js @@ -1,7 +1,7 @@ import React, {Component, PropTypes} from 'react' class LogItemKapacitorPoint extends Component { - renderKeysAndValues = (object, name) => { + renderKeysAndValues = (object, name, expanded) => { if (!object) { return -- } @@ -9,19 +9,39 @@ class LogItemKapacitorPoint extends Component { const objValues = Object.values(object) if (objKeys.length > 2) { - return ( -
- {`${objKeys.length} ${name}...`} -
- ) + return expanded + ?
+

+ {`${objKeys.length} ${name}`} +

+
+ {objKeys.map((objKey, i) => +
+ {objKey}: {objValues[i]} +
+ )} +
+
+ :
+

+ {`${objKeys.length} ${name}`} +

+
Click to expand...
+
} - const objElements = objKeys.map((objKey, i) => -
- {objKey}: {objValues[i]} + return ( +
+

+ {`${objKeys.length} ${name}`} +

+ {objKeys.map((objKey, i) => +
+ {objKey}: {objValues[i]} +
+ )}
) - return objElements } handleToggleExpand = () => { @@ -53,10 +73,6 @@ class LogItemKapacitorPoint extends Component { return (
- {this.isExpandable() && -
- Click to show all items -
}
@@ -66,14 +82,12 @@ class LogItemKapacitorPoint extends Component {
Kapacitor Point
-
-

TAGS

- {this.renderKeysAndValues(logItem.tag, 'Tags')} -
-
-

FIELDS

- {this.renderKeysAndValues(logItem.field, 'Fields')} -
+ {this.renderKeysAndValues(logItem.tag, 'Tags', logItem.expanded)} + {this.renderKeysAndValues( + logItem.field, + 'Fields', + logItem.expanded + )}
diff --git a/ui/src/style/components/kapacitor-logs-table.scss b/ui/src/style/components/kapacitor-logs-table.scss index 0c163c103..a70273c42 100644 --- a/ui/src/style/components/kapacitor-logs-table.scss +++ b/ui/src/style/components/kapacitor-logs-table.scss @@ -13,7 +13,7 @@ $logs-margin: 4px; width: 50%; position: relative; height: 100%; - @include gradient-v($g3-castle,$g1-raven); + @include gradient-v(mix($g3-castle, $g2-kevlar),mix($g1-raven, $g0-obsidian)); } .logs-table--header { display: flex; @@ -105,6 +105,7 @@ $logs-margin: 4px; margin: 0; letter-spacing: normal; line-height: 1.42857143em; + text-transform: uppercase; } .logs-table--key-value { } @@ -113,40 +114,30 @@ $logs-margin: 4px; } .logs-table--many-keys { - color: $c-pool; + color: $g9-mountain; width: 100%; display: inline-block; font-style: italic; } -.logs-table--row-expander { - position: absolute; - bottom: 0; - left: 0; - width: 100%; - background-color: rgba(255,255,255,0.5); - color: $g20-white; - padding: 4px 0; - font-size: 13px; - font-weight: 600; - text-align: center; - transition: transform 0.25s ease; - transform: translateY(100%); -} -.logs-table--row:hover .logs-table--row-expander { - transform: translateY(0); -} - // Styles for Expandable Log Items .logs-table--row.logs-table--row__expandable { &:hover { - background-color: $g4-onyx; + background-color: fade-out($g0-obsidian, 0.7); cursor: zoom-in; } &.expanded { + background-color: $g0-obsidian; height: 240px; &:hover { + background-color: $g0-obsidian; cursor: zoom-out; } } } +.logs-table--keys-scrollbox { + width: 100%; + height: 190px; + overflow-y: scroll; + @include custom-scrollbar($g0-obsidian,$g7-graphite); +} From 7496c660fde39df203d535b1a4aec72bd66cdc2a Mon Sep 17 00:00:00 2001 From: Iris Scholten Date: Mon, 5 Feb 2018 11:38:22 -0800 Subject: [PATCH 29/98] disable the dropdown fields in the initial default mapping --- ui/src/admin/components/chronograf/ProvidersTableRow.js | 3 ++- ui/src/admin/constants/dummyProviderMaps.js | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/ui/src/admin/components/chronograf/ProvidersTableRow.js b/ui/src/admin/components/chronograf/ProvidersTableRow.js index d758f2819..baf75fdab 100644 --- a/ui/src/admin/components/chronograf/ProvidersTableRow.js +++ b/ui/src/admin/components/chronograf/ProvidersTableRow.js @@ -71,7 +71,6 @@ class ProvidersTableRow extends Component { : 'fancytable--td provider--redirect' const isDefaultMapping = DEFAULT_PROVIDER_MAP_ID === mapping.id - return (
@@ -82,6 +81,7 @@ class ProvidersTableRow extends Component { onChoose={this.handleChooseScheme} selected={scheme} className="fancytable--td provider--scheme" + disabled={isDefaultMapping} />
{isDeleting diff --git a/ui/src/admin/constants/dummyProviderMaps.js b/ui/src/admin/constants/dummyProviderMaps.js index c1a9b27a3..8279494c0 100644 --- a/ui/src/admin/constants/dummyProviderMaps.js +++ b/ui/src/admin/constants/dummyProviderMaps.js @@ -14,6 +14,6 @@ export const PROVIDER_MAPS = [ scheme: 'oauth2', provider: 'github', providerOrganization: null, - organizationId: '2', + organizationId: '1', }, ] From 8b60388d8198a82c095d8f07a49d1f0dff3259f6 Mon Sep 17 00:00:00 2001 From: Michael Desa Date: Mon, 5 Feb 2018 14:54:39 -0500 Subject: [PATCH 30/98] Add correct implementation of mappings --- bolt/client.go | 9 + bolt/internal/internal.go | 65 +-- bolt/internal/internal.pb.go | 214 +++++----- bolt/internal/internal.proto | 4 +- bolt/mapping.go | 128 ++++++ bolt/mapping_test.go | 489 ++++++++++++++++++++++ bolt/organizations.go | 30 +- bolt/organizations_test.go | 56 +-- chronograf.go | 53 ++- integrations/server_test.go | 377 +++++++---------- mocks/mapping.go | 35 ++ mocks/store.go | 4 + noop/mappings.go | 33 ++ server/mapping.go | 117 +++--- server/mapping_test.go | 768 +++++++++++++++++------------------ server/me.go | 10 +- server/me_test.go | 311 ++++++-------- server/mux.go | 7 + server/organizations.go | 50 +-- server/organizations_test.go | 316 +------------- server/server.go | 1 + server/stores.go | 13 + 22 files changed, 1635 insertions(+), 1455 deletions(-) create mode 100644 bolt/mapping.go create mode 100644 bolt/mapping_test.go create mode 100644 mocks/mapping.go create mode 100644 noop/mappings.go diff --git a/bolt/client.go b/bolt/client.go index fad811246..2515f1975 100644 --- a/bolt/client.go +++ b/bolt/client.go @@ -41,6 +41,7 @@ type Client struct { UsersStore *UsersStore OrganizationsStore *OrganizationsStore ConfigStore *ConfigStore + MappingsStore *MappingsStore } // NewClient initializes all stores @@ -60,6 +61,7 @@ func NewClient() *Client { c.UsersStore = &UsersStore{client: c} c.OrganizationsStore = &OrganizationsStore{client: c} c.ConfigStore = &ConfigStore{client: c} + c.MappingsStore = &MappingsStore{client: c} return c } @@ -151,6 +153,10 @@ func (c *Client) initialize(ctx context.Context) error { if _, err := tx.CreateBucketIfNotExists(BuildBucket); err != nil { return err } + // Always create Mapping bucket. + if _, err := tx.CreateBucketIfNotExists(MappingsBucket); err != nil { + return err + } return nil }); err != nil { return err @@ -184,6 +190,9 @@ func (c *Client) migrate(ctx context.Context, build chronograf.BuildInfo) error if err := c.BuildStore.Migrate(ctx, build); err != nil { return err } + if err := c.MappingsStore.Migrate(ctx); err != nil { + return err + } } return nil } diff --git a/bolt/internal/internal.go b/bolt/internal/internal.go index 436739e71..2ce4ca36b 100644 --- a/bolt/internal/internal.go +++ b/bolt/internal/internal.go @@ -578,23 +578,12 @@ func UnmarshalRolePB(data []byte, r *Role) error { // MarshalOrganization encodes a organization to binary protobuf format. func MarshalOrganization(o *chronograf.Organization) ([]byte, error) { - mappings := make([]*Mapping, len(o.Mappings)) - - for i, m := range o.Mappings { - mappings[i] = &Mapping{ - Provider: m.Provider, - Scheme: m.Scheme, - Group: m.Group, - GrantedRole: m.GrantedRole, - } - } return MarshalOrganizationPB(&Organization{ ID: o.ID, Name: o.Name, DefaultRole: o.DefaultRole, Public: o.Public, - Mappings: mappings, }) } @@ -614,19 +603,6 @@ func UnmarshalOrganization(data []byte, o *chronograf.Organization) error { o.DefaultRole = pb.DefaultRole o.Public = pb.Public - mappings := make([]chronograf.Mapping, len(pb.Mappings)) - - for i, m := range pb.Mappings { - mappings[i] = chronograf.Mapping{ - Provider: m.Provider, - Scheme: m.Scheme, - Group: m.Group, - GrantedRole: m.GrantedRole, - } - } - - o.Mappings = mappings - return nil } @@ -673,3 +649,44 @@ func UnmarshalConfigPB(data []byte, c *Config) error { } return nil } + +// MarshalMapping encodes a mapping to binary protobuf format. +func MarshalMapping(m *chronograf.Mapping) ([]byte, error) { + + return MarshalMappingPB(&Mapping{ + Provider: m.Provider, + Scheme: m.Scheme, + Group: m.Group, + ID: m.ID, + Organization: m.Organization, + }) +} + +// MarshalMappingPB encodes a mapping to binary protobuf format. +func MarshalMappingPB(m *Mapping) ([]byte, error) { + return proto.Marshal(m) +} + +// UnmarshalMapping decodes a mapping from binary protobuf data. +func UnmarshalMapping(data []byte, m *chronograf.Mapping) error { + var pb Mapping + if err := UnmarshalMappingPB(data, &pb); err != nil { + return err + } + + m.Provider = pb.Provider + m.Scheme = pb.Scheme + m.Group = pb.Group + m.Organization = pb.Organization + m.ID = pb.ID + + return nil +} + +// UnmarshalMappingPB decodes a mapping from binary protobuf data. +func UnmarshalMappingPB(data []byte, m *Mapping) error { + if err := proto.Unmarshal(data, m); err != nil { + return err + } + return nil +} diff --git a/bolt/internal/internal.pb.go b/bolt/internal/internal.pb.go index e23676351..a394c2cd4 100644 --- a/bolt/internal/internal.pb.go +++ b/bolt/internal/internal.pb.go @@ -1026,10 +1026,12 @@ func (m *Role) GetName() string { } type Mapping struct { - Provider string `protobuf:"bytes,1,opt,name=Provider,proto3" json:"Provider,omitempty"` - Scheme string `protobuf:"bytes,2,opt,name=Scheme,proto3" json:"Scheme,omitempty"` - Group string `protobuf:"bytes,3,opt,name=Group,proto3" json:"Group,omitempty"` - GrantedRole string `protobuf:"bytes,4,opt,name=GrantedRole,proto3" json:"GrantedRole,omitempty"` + Provider string `protobuf:"bytes,1,opt,name=Provider,proto3" json:"Provider,omitempty"` + Scheme string `protobuf:"bytes,2,opt,name=Scheme,proto3" json:"Scheme,omitempty"` + Group string `protobuf:"bytes,3,opt,name=Group,proto3" json:"Group,omitempty"` + // string GrantedRole = 4; // GrantedRole is the name of the role that you will be granted if you match the mapping + ID string `protobuf:"bytes,4,opt,name=ID,proto3" json:"ID,omitempty"` + Organization string `protobuf:"bytes,5,opt,name=Organization,proto3" json:"Organization,omitempty"` } func (m *Mapping) Reset() { *m = Mapping{} } @@ -1058,19 +1060,25 @@ func (m *Mapping) GetGroup() string { return "" } -func (m *Mapping) GetGrantedRole() string { +func (m *Mapping) GetID() string { if m != nil { - return m.GrantedRole + return m.ID + } + return "" +} + +func (m *Mapping) GetOrganization() string { + if m != nil { + return m.Organization } return "" } type Organization struct { - ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"` - Name string `protobuf:"bytes,2,opt,name=Name,proto3" json:"Name,omitempty"` - DefaultRole string `protobuf:"bytes,3,opt,name=DefaultRole,proto3" json:"DefaultRole,omitempty"` - Public bool `protobuf:"varint,4,opt,name=Public,proto3" json:"Public,omitempty"` - Mappings []*Mapping `protobuf:"bytes,5,rep,name=Mappings" json:"Mappings,omitempty"` + ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"` + Name string `protobuf:"bytes,2,opt,name=Name,proto3" json:"Name,omitempty"` + DefaultRole string `protobuf:"bytes,3,opt,name=DefaultRole,proto3" json:"DefaultRole,omitempty"` + Public bool `protobuf:"varint,4,opt,name=Public,proto3" json:"Public,omitempty"` } func (m *Organization) Reset() { *m = Organization{} } @@ -1106,13 +1114,6 @@ func (m *Organization) GetPublic() bool { return false } -func (m *Organization) GetMappings() []*Mapping { - if m != nil { - return m.Mappings - } - return nil -} - type Config struct { Auth *AuthConfig `protobuf:"bytes,1,opt,name=Auth" json:"Auth,omitempty"` } @@ -1197,93 +1198,92 @@ func init() { func init() { proto.RegisterFile("internal.proto", fileDescriptorInternal) } var fileDescriptorInternal = []byte{ - // 1396 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x57, 0x5f, 0x8f, 0xdb, 0x44, - 0x10, 0x97, 0xe3, 0x38, 0xb1, 0x27, 0xd7, 0x52, 0xcc, 0x89, 0x9a, 0x22, 0xa1, 0x60, 0x81, 0x38, - 0x04, 0x3d, 0xd0, 0x55, 0x48, 0x08, 0x01, 0x52, 0xee, 0x82, 0xca, 0xd1, 0x7f, 0xd7, 0x4d, 0xef, - 0x78, 0x42, 0xd5, 0xc6, 0x99, 0x24, 0x56, 0x1d, 0xdb, 0xac, 0xed, 0xbb, 0x33, 0x5f, 0x05, 0x09, - 0x09, 0x89, 0x4f, 0x80, 0x78, 0xe7, 0x15, 0xf1, 0xca, 0x77, 0xe0, 0x2b, 0xf0, 0x8a, 0x66, 0x77, - 0xfd, 0x27, 0x97, 0xb4, 0xea, 0x03, 0xe2, 0x6d, 0x7f, 0x33, 0x9b, 0xd9, 0xd9, 0x99, 0xdf, 0xfc, - 0xd6, 0x81, 0xeb, 0x61, 0x9c, 0xa3, 0x88, 0x79, 0xb4, 0x9f, 0x8a, 0x24, 0x4f, 0x5c, 0xbb, 0xc2, - 0xfe, 0xdf, 0x1d, 0xe8, 0x4d, 0x92, 0x42, 0x04, 0xe8, 0x5e, 0x87, 0xce, 0xf1, 0xd8, 0x33, 0x86, - 0xc6, 0x9e, 0xc9, 0x3a, 0xc7, 0x63, 0xd7, 0x85, 0xee, 0x43, 0xbe, 0x42, 0xaf, 0x33, 0x34, 0xf6, - 0x1c, 0x26, 0xd7, 0x64, 0x7b, 0x52, 0xa6, 0xe8, 0x99, 0xca, 0x46, 0x6b, 0xf7, 0x16, 0xd8, 0xa7, - 0x19, 0x45, 0x5b, 0xa1, 0xd7, 0x95, 0xf6, 0x1a, 0x93, 0xef, 0x84, 0x67, 0xd9, 0x45, 0x22, 0x66, - 0x9e, 0xa5, 0x7c, 0x15, 0x76, 0x6f, 0x80, 0x79, 0xca, 0xee, 0x7b, 0x3d, 0x69, 0xa6, 0xa5, 0xeb, - 0x41, 0x7f, 0x8c, 0x73, 0x5e, 0x44, 0xb9, 0xd7, 0x1f, 0x1a, 0x7b, 0x36, 0xab, 0x20, 0xc5, 0x79, - 0x82, 0x11, 0x2e, 0x04, 0x9f, 0x7b, 0xb6, 0x8a, 0x53, 0x61, 0x77, 0x1f, 0xdc, 0xe3, 0x38, 0xc3, - 0xa0, 0x10, 0x38, 0x79, 0x16, 0xa6, 0x67, 0x28, 0xc2, 0x79, 0xe9, 0x39, 0x32, 0xc0, 0x16, 0x0f, - 0x9d, 0xf2, 0x00, 0x73, 0x4e, 0x67, 0x83, 0x0c, 0x55, 0x41, 0xd7, 0x87, 0x9d, 0xc9, 0x92, 0x0b, - 0x9c, 0x4d, 0x30, 0x10, 0x98, 0x7b, 0x03, 0xe9, 0x5e, 0xb3, 0xd1, 0x9e, 0x47, 0x62, 0xc1, 0xe3, - 0xf0, 0x07, 0x9e, 0x87, 0x49, 0xec, 0xed, 0xa8, 0x3d, 0x6d, 0x1b, 0x55, 0x89, 0x25, 0x11, 0x7a, - 0xd7, 0x54, 0x95, 0x68, 0xed, 0xff, 0x66, 0x80, 0x33, 0xe6, 0xd9, 0x72, 0x9a, 0x70, 0x31, 0x7b, - 0xa9, 0x5a, 0xdf, 0x06, 0x2b, 0xc0, 0x28, 0xca, 0x3c, 0x73, 0x68, 0xee, 0x0d, 0x0e, 0x6e, 0xee, - 0xd7, 0x4d, 0xac, 0xe3, 0x1c, 0x61, 0x14, 0x31, 0xb5, 0xcb, 0xfd, 0x18, 0x9c, 0x1c, 0x57, 0x69, - 0xc4, 0x73, 0xcc, 0xbc, 0xae, 0xfc, 0x89, 0xdb, 0xfc, 0xe4, 0x89, 0x76, 0xb1, 0x66, 0xd3, 0xc6, - 0x55, 0xac, 0xcd, 0xab, 0xf8, 0x7f, 0x75, 0xe0, 0xda, 0xda, 0x71, 0xee, 0x0e, 0x18, 0x97, 0x32, - 0x73, 0x8b, 0x19, 0x97, 0x84, 0x4a, 0x99, 0xb5, 0xc5, 0x8c, 0x92, 0xd0, 0x85, 0xe4, 0x86, 0xc5, - 0x8c, 0x0b, 0x42, 0x4b, 0xc9, 0x08, 0x8b, 0x19, 0x4b, 0xf7, 0x7d, 0xe8, 0x7f, 0x5f, 0xa0, 0x08, - 0x31, 0xf3, 0x2c, 0x99, 0xdd, 0x2b, 0x4d, 0x76, 0x8f, 0x0b, 0x14, 0x25, 0xab, 0xfc, 0x54, 0x0d, - 0xc9, 0x26, 0x45, 0x0d, 0xb9, 0x26, 0x5b, 0x4e, 0xcc, 0xeb, 0x2b, 0x1b, 0xad, 0x75, 0x15, 0x15, - 0x1f, 0xa8, 0x8a, 0x9f, 0x40, 0x97, 0x5f, 0x62, 0xe6, 0x39, 0x32, 0xfe, 0xdb, 0xcf, 0x29, 0xd8, - 0xfe, 0xe8, 0x12, 0xb3, 0xaf, 0xe2, 0x5c, 0x94, 0x4c, 0x6e, 0x77, 0xdf, 0x83, 0x5e, 0x90, 0x44, - 0x89, 0xc8, 0x3c, 0xb8, 0x9a, 0xd8, 0x11, 0xd9, 0x99, 0x76, 0xdf, 0xba, 0x0b, 0x4e, 0xfd, 0x5b, - 0xa2, 0xef, 0x33, 0x2c, 0x65, 0x25, 0x1c, 0x46, 0x4b, 0xf7, 0x1d, 0xb0, 0xce, 0x79, 0x54, 0xa8, - 0x2e, 0x0e, 0x0e, 0xae, 0x37, 0x61, 0x46, 0x97, 0x61, 0xc6, 0x94, 0xf3, 0xb3, 0xce, 0xa7, 0x86, - 0xbf, 0x00, 0x4b, 0x46, 0x6e, 0xf1, 0xc0, 0xa9, 0x78, 0x20, 0xe7, 0xab, 0xd3, 0x9a, 0xaf, 0x1b, - 0x60, 0x7e, 0x8d, 0x97, 0x7a, 0xe4, 0x68, 0x59, 0xb3, 0xa5, 0xdb, 0x62, 0xcb, 0x2e, 0x58, 0x67, - 0xf2, 0x70, 0xd5, 0x45, 0x05, 0xfc, 0x5f, 0x0d, 0xe8, 0xd2, 0xe1, 0xd4, 0xeb, 0x08, 0x17, 0x3c, - 0x28, 0x0f, 0x93, 0x22, 0x9e, 0x65, 0x9e, 0x31, 0x34, 0xf7, 0x4c, 0xb6, 0x66, 0x73, 0x5f, 0x87, - 0xde, 0x54, 0x79, 0x3b, 0x43, 0x73, 0xcf, 0x61, 0x1a, 0x51, 0xe8, 0x88, 0x4f, 0x31, 0xd2, 0x29, - 0x28, 0x40, 0xbb, 0x53, 0x81, 0xf3, 0xf0, 0x52, 0xa7, 0xa1, 0x11, 0xd9, 0xb3, 0x62, 0x4e, 0x76, - 0x95, 0x89, 0x46, 0x94, 0xf4, 0x94, 0x67, 0x75, 0x53, 0x69, 0x4d, 0x91, 0xb3, 0x80, 0x47, 0x55, - 0x57, 0x15, 0xf0, 0x7f, 0x37, 0x68, 0xda, 0x15, 0x4b, 0x37, 0x2a, 0xf4, 0x06, 0xd8, 0xc4, 0xe0, - 0xa7, 0xe7, 0x5c, 0xe8, 0x2a, 0xf5, 0x09, 0x9f, 0x71, 0xe1, 0x7e, 0x04, 0x3d, 0x59, 0xe2, 0x2d, - 0x13, 0x53, 0x85, 0x93, 0x55, 0x61, 0x7a, 0x5b, 0xcd, 0xa9, 0x6e, 0x8b, 0x53, 0xf5, 0x65, 0xad, - 0xf6, 0x65, 0x6f, 0x83, 0x45, 0xe4, 0x2c, 0x65, 0xf6, 0x5b, 0x23, 0x2b, 0x0a, 0xab, 0x5d, 0xfe, - 0x29, 0x5c, 0x5b, 0x3b, 0xb1, 0x3e, 0xc9, 0x58, 0x3f, 0xa9, 0xa1, 0x8b, 0xa3, 0xe9, 0x41, 0x4a, - 0x97, 0x61, 0x84, 0x41, 0x8e, 0x33, 0x59, 0x6f, 0x9b, 0xd5, 0xd8, 0xff, 0xd9, 0x68, 0xe2, 0xca, - 0xf3, 0x48, 0xcb, 0x82, 0x64, 0xb5, 0xe2, 0xf1, 0x4c, 0x87, 0xae, 0x20, 0xd5, 0x6d, 0x36, 0xd5, - 0xa1, 0x3b, 0xb3, 0x29, 0x61, 0x91, 0xea, 0x0e, 0x76, 0x44, 0xea, 0x0e, 0x61, 0xb0, 0x42, 0x9e, - 0x15, 0x02, 0x57, 0x18, 0xe7, 0xba, 0x04, 0x6d, 0x93, 0x7b, 0x13, 0xfa, 0x39, 0x5f, 0x3c, 0x25, - 0x92, 0xeb, 0x4e, 0xe6, 0x7c, 0x71, 0x0f, 0x4b, 0xf7, 0x4d, 0x70, 0xe6, 0x21, 0x46, 0x33, 0xe9, - 0x52, 0xed, 0xb4, 0xa5, 0xe1, 0x1e, 0x96, 0xfe, 0x1f, 0x06, 0xf4, 0x26, 0x28, 0xce, 0x51, 0xbc, - 0x94, 0xc8, 0xb5, 0x1f, 0x0f, 0xf3, 0x05, 0x8f, 0x47, 0x77, 0xfb, 0xe3, 0x61, 0x35, 0x8f, 0xc7, - 0x2e, 0x58, 0x13, 0x11, 0x1c, 0x8f, 0x65, 0x46, 0x26, 0x53, 0x80, 0xd8, 0x38, 0x0a, 0xf2, 0xf0, - 0x1c, 0xf5, 0x8b, 0xa2, 0xd1, 0x86, 0xf6, 0xd9, 0x5b, 0xb4, 0xef, 0x27, 0x03, 0x7a, 0xf7, 0x79, - 0x99, 0x14, 0xf9, 0x06, 0x0b, 0x87, 0x30, 0x18, 0xa5, 0x69, 0x14, 0x06, 0xea, 0xd7, 0xea, 0x46, - 0x6d, 0x13, 0xed, 0x78, 0xd0, 0xaa, 0xaf, 0xba, 0x5b, 0xdb, 0x44, 0x72, 0x71, 0x24, 0xf5, 0x5d, - 0x89, 0x75, 0x4b, 0x2e, 0x94, 0xac, 0x4b, 0x27, 0x15, 0x61, 0x54, 0xe4, 0xc9, 0x3c, 0x4a, 0x2e, - 0xe4, 0x6d, 0x6d, 0x56, 0x63, 0xff, 0xcf, 0x0e, 0x74, 0xff, 0x2f, 0x4d, 0xde, 0x01, 0x23, 0xd4, - 0xcd, 0x36, 0xc2, 0x5a, 0xa1, 0xfb, 0x2d, 0x85, 0xf6, 0xa0, 0x5f, 0x0a, 0x1e, 0x2f, 0x30, 0xf3, - 0x6c, 0xa9, 0x2e, 0x15, 0x94, 0x1e, 0x39, 0x47, 0x4a, 0x9a, 0x1d, 0x56, 0xc1, 0x7a, 0x2e, 0xa0, - 0x35, 0x17, 0x1f, 0x6a, 0x15, 0x1f, 0xc8, 0x8c, 0xbc, 0xf5, 0xb2, 0x5c, 0x15, 0xef, 0xff, 0x4e, - 0x93, 0xff, 0x31, 0xc0, 0xaa, 0x87, 0xea, 0x68, 0x7d, 0xa8, 0x8e, 0x9a, 0xa1, 0x1a, 0x1f, 0x56, - 0x43, 0x35, 0x3e, 0x24, 0xcc, 0x4e, 0xaa, 0xa1, 0x62, 0x27, 0xd4, 0xac, 0xbb, 0x22, 0x29, 0xd2, - 0xc3, 0x52, 0x75, 0xd5, 0x61, 0x35, 0x26, 0x26, 0x7e, 0xbb, 0x44, 0xa1, 0x4b, 0xed, 0x30, 0x8d, - 0x88, 0xb7, 0xf7, 0xa5, 0xe0, 0xa8, 0xe2, 0x2a, 0xe0, 0xbe, 0x0b, 0x16, 0xa3, 0xe2, 0xc9, 0x0a, - 0xaf, 0xf5, 0x45, 0x9a, 0x99, 0xf2, 0x52, 0x50, 0xf5, 0xf5, 0xa6, 0x09, 0x5c, 0x7d, 0xcb, 0x7d, - 0x00, 0xbd, 0xc9, 0x32, 0x9c, 0xe7, 0xd5, 0x5b, 0xf8, 0x5a, 0x4b, 0xb0, 0xc2, 0x15, 0x4a, 0x1f, - 0xd3, 0x5b, 0xfc, 0xc7, 0xe0, 0xd4, 0xc6, 0x26, 0x1d, 0xa3, 0x9d, 0x8e, 0x0b, 0xdd, 0xd3, 0x38, - 0xcc, 0xab, 0xd1, 0xa5, 0x35, 0x5d, 0xf6, 0x71, 0xc1, 0xe3, 0x3c, 0xcc, 0xcb, 0x6a, 0x74, 0x2b, - 0xec, 0xdf, 0xd1, 0xe9, 0x53, 0xb8, 0xd3, 0x34, 0x45, 0xa1, 0x65, 0x40, 0x01, 0x79, 0x48, 0x72, - 0x81, 0x4a, 0xc1, 0x4d, 0xa6, 0x80, 0xff, 0x1d, 0x38, 0xa3, 0x08, 0x45, 0xce, 0x8a, 0x08, 0xb7, - 0xbd, 0x8c, 0xdf, 0x4c, 0x1e, 0x3d, 0xac, 0x32, 0xa0, 0x75, 0x33, 0xf2, 0xe6, 0x95, 0x91, 0xbf, - 0xc7, 0x53, 0x7e, 0x3c, 0x96, 0x3c, 0x37, 0x99, 0x46, 0xfe, 0x2f, 0x06, 0x74, 0x49, 0x5b, 0x5a, - 0xa1, 0xbb, 0x2f, 0xd2, 0xa5, 0x13, 0x91, 0x9c, 0x87, 0x33, 0x14, 0xd5, 0xe5, 0x2a, 0x2c, 0x8b, - 0x1e, 0x2c, 0xb1, 0x7e, 0x80, 0x35, 0x22, 0xae, 0xd1, 0xa7, 0x5e, 0x35, 0x4b, 0x2d, 0xae, 0x91, - 0x99, 0x29, 0xa7, 0xfb, 0x16, 0xc0, 0xa4, 0x48, 0x51, 0x8c, 0x66, 0xab, 0x30, 0x96, 0x4d, 0xb7, - 0x59, 0xcb, 0xe2, 0x7f, 0xa9, 0x3e, 0x1e, 0x37, 0x14, 0xca, 0xd8, 0xfe, 0xa1, 0x79, 0x35, 0x73, - 0xbf, 0x80, 0xfe, 0x03, 0x9e, 0xa6, 0x61, 0xbc, 0x58, 0xbb, 0x84, 0xf1, 0xdc, 0x4b, 0x74, 0xd6, - 0x2e, 0xb1, 0x0b, 0x96, 0xa4, 0x6c, 0xf5, 0xd8, 0x4b, 0x40, 0x6a, 0x76, 0x57, 0xf0, 0x38, 0xc7, - 0x99, 0xfc, 0xb0, 0xd5, 0xaf, 0x45, 0xcb, 0xe4, 0xff, 0x68, 0xac, 0xe7, 0xbb, 0xad, 0x81, 0x1b, - 0x55, 0x1e, 0xc2, 0x40, 0x7f, 0xe1, 0xcb, 0xb0, 0x5a, 0x24, 0x5b, 0x26, 0x4a, 0xf3, 0xa4, 0x98, - 0x46, 0x61, 0x20, 0xcf, 0xb4, 0x99, 0x46, 0xee, 0x6d, 0xb0, 0xf5, 0x2d, 0xab, 0x72, 0xbf, 0xda, - 0x94, 0x5b, 0x7b, 0x58, 0xbd, 0xc5, 0x3f, 0x80, 0xde, 0x51, 0x12, 0xcf, 0xc3, 0x85, 0xbb, 0x07, - 0xdd, 0x51, 0x91, 0x2f, 0x65, 0x62, 0x83, 0x83, 0xdd, 0x96, 0x1e, 0x14, 0xf9, 0x52, 0xed, 0x61, - 0x72, 0x87, 0xff, 0x39, 0x40, 0x63, 0xa3, 0x7f, 0x19, 0x4d, 0x93, 0x1e, 0xe2, 0x05, 0x31, 0x29, - 0x93, 0x51, 0x6c, 0xb6, 0xc5, 0xe3, 0x7f, 0x01, 0xce, 0x61, 0x11, 0x46, 0xb3, 0xe3, 0x78, 0x9e, - 0x90, 0xa2, 0x9c, 0xa1, 0xc8, 0x9a, 0x36, 0x56, 0x90, 0xee, 0x47, 0xe2, 0x52, 0x8f, 0x96, 0x46, - 0xd3, 0x9e, 0xfc, 0xa3, 0x76, 0xe7, 0xdf, 0x00, 0x00, 0x00, 0xff, 0xff, 0x3e, 0x6a, 0xf4, 0x02, - 0xba, 0x0d, 0x00, 0x00, + // 1377 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x57, 0xcf, 0x8e, 0xe3, 0xc4, + 0x13, 0x96, 0x63, 0x3b, 0x89, 0x2b, 0xb3, 0xfb, 0x5b, 0xf9, 0x37, 0x62, 0xcd, 0x22, 0xa1, 0x60, + 0x81, 0x08, 0x82, 0x1d, 0xd0, 0xac, 0x90, 0x10, 0x02, 0xa4, 0xcc, 0x04, 0x2d, 0xc3, 0xfe, 0x9b, + 0xed, 0xec, 0x0c, 0x27, 0xb4, 0xea, 0x38, 0x95, 0xc4, 0x5a, 0xc7, 0x36, 0x6d, 0x7b, 0x26, 0xe6, + 0x05, 0x78, 0x0b, 0x24, 0x24, 0x9e, 0x00, 0x71, 0xe7, 0x8a, 0xb8, 0xf2, 0x0e, 0xbc, 0x02, 0x57, + 0x54, 0xdd, 0x6d, 0xc7, 0x99, 0x64, 0x57, 0x73, 0x40, 0xdc, 0xfa, 0xab, 0xea, 0x54, 0x57, 0x57, + 0x7d, 0xf5, 0xb5, 0x03, 0x37, 0xc3, 0x38, 0x47, 0x11, 0xf3, 0xe8, 0x20, 0x15, 0x49, 0x9e, 0xb8, + 0xdd, 0x0a, 0xfb, 0x7f, 0xb5, 0xa0, 0x3d, 0x4e, 0x0a, 0x11, 0xa0, 0x7b, 0x13, 0x5a, 0x27, 0x23, + 0xcf, 0xe8, 0x1b, 0x03, 0x93, 0xb5, 0x4e, 0x46, 0xae, 0x0b, 0xd6, 0x63, 0xbe, 0x44, 0xaf, 0xd5, + 0x37, 0x06, 0x0e, 0x93, 0x6b, 0xb2, 0x3d, 0x2b, 0x53, 0xf4, 0x4c, 0x65, 0xa3, 0xb5, 0x7b, 0x07, + 0xba, 0x67, 0x19, 0x45, 0x5b, 0xa2, 0x67, 0x49, 0x7b, 0x8d, 0xc9, 0x77, 0xca, 0xb3, 0xec, 0x32, + 0x11, 0x53, 0xcf, 0x56, 0xbe, 0x0a, 0xbb, 0xb7, 0xc0, 0x3c, 0x63, 0x0f, 0xbd, 0xb6, 0x34, 0xd3, + 0xd2, 0xf5, 0xa0, 0x33, 0xc2, 0x19, 0x2f, 0xa2, 0xdc, 0xeb, 0xf4, 0x8d, 0x41, 0x97, 0x55, 0x90, + 0xe2, 0x3c, 0xc3, 0x08, 0xe7, 0x82, 0xcf, 0xbc, 0xae, 0x8a, 0x53, 0x61, 0xf7, 0x00, 0xdc, 0x93, + 0x38, 0xc3, 0xa0, 0x10, 0x38, 0x7e, 0x11, 0xa6, 0xe7, 0x28, 0xc2, 0x59, 0xe9, 0x39, 0x32, 0xc0, + 0x0e, 0x0f, 0x9d, 0xf2, 0x08, 0x73, 0x4e, 0x67, 0x83, 0x0c, 0x55, 0x41, 0xd7, 0x87, 0xbd, 0xf1, + 0x82, 0x0b, 0x9c, 0x8e, 0x31, 0x10, 0x98, 0x7b, 0x3d, 0xe9, 0xde, 0xb0, 0xd1, 0x9e, 0x27, 0x62, + 0xce, 0xe3, 0xf0, 0x7b, 0x9e, 0x87, 0x49, 0xec, 0xed, 0xa9, 0x3d, 0x4d, 0x1b, 0x55, 0x89, 0x25, + 0x11, 0x7a, 0x37, 0x54, 0x95, 0x68, 0xed, 0xff, 0x6a, 0x80, 0x33, 0xe2, 0xd9, 0x62, 0x92, 0x70, + 0x31, 0xbd, 0x56, 0xad, 0xef, 0x82, 0x1d, 0x60, 0x14, 0x65, 0x9e, 0xd9, 0x37, 0x07, 0xbd, 0xc3, + 0xdb, 0x07, 0x75, 0x13, 0xeb, 0x38, 0xc7, 0x18, 0x45, 0x4c, 0xed, 0x72, 0x3f, 0x02, 0x27, 0xc7, + 0x65, 0x1a, 0xf1, 0x1c, 0x33, 0xcf, 0x92, 0x3f, 0x71, 0xd7, 0x3f, 0x79, 0xa6, 0x5d, 0x6c, 0xbd, + 0x69, 0xeb, 0x2a, 0xf6, 0xf6, 0x55, 0xfc, 0x3f, 0x5b, 0x70, 0x63, 0xe3, 0x38, 0x77, 0x0f, 0x8c, + 0x95, 0xcc, 0xdc, 0x66, 0xc6, 0x8a, 0x50, 0x29, 0xb3, 0xb6, 0x99, 0x51, 0x12, 0xba, 0x94, 0xdc, + 0xb0, 0x99, 0x71, 0x49, 0x68, 0x21, 0x19, 0x61, 0x33, 0x63, 0xe1, 0xbe, 0x07, 0x9d, 0xef, 0x0a, + 0x14, 0x21, 0x66, 0x9e, 0x2d, 0xb3, 0xfb, 0xdf, 0x3a, 0xbb, 0xa7, 0x05, 0x8a, 0x92, 0x55, 0x7e, + 0xaa, 0x86, 0x64, 0x93, 0xa2, 0x86, 0x5c, 0x93, 0x2d, 0x27, 0xe6, 0x75, 0x94, 0x8d, 0xd6, 0xba, + 0x8a, 0x8a, 0x0f, 0x54, 0xc5, 0x8f, 0xc1, 0xe2, 0x2b, 0xcc, 0x3c, 0x47, 0xc6, 0x7f, 0xeb, 0x25, + 0x05, 0x3b, 0x18, 0xae, 0x30, 0xfb, 0x32, 0xce, 0x45, 0xc9, 0xe4, 0x76, 0xf7, 0x5d, 0x68, 0x07, + 0x49, 0x94, 0x88, 0xcc, 0x83, 0xab, 0x89, 0x1d, 0x93, 0x9d, 0x69, 0xf7, 0x9d, 0xfb, 0xe0, 0xd4, + 0xbf, 0x25, 0xfa, 0xbe, 0xc0, 0x52, 0x56, 0xc2, 0x61, 0xb4, 0x74, 0xdf, 0x06, 0xfb, 0x82, 0x47, + 0x85, 0xea, 0x62, 0xef, 0xf0, 0xe6, 0x3a, 0xcc, 0x70, 0x15, 0x66, 0x4c, 0x39, 0x3f, 0x6d, 0x7d, + 0x62, 0xf8, 0x73, 0xb0, 0x65, 0xe4, 0x06, 0x0f, 0x9c, 0x8a, 0x07, 0x72, 0xbe, 0x5a, 0x8d, 0xf9, + 0xba, 0x05, 0xe6, 0x57, 0xb8, 0xd2, 0x23, 0x47, 0xcb, 0x9a, 0x2d, 0x56, 0x83, 0x2d, 0xfb, 0x60, + 0x9f, 0xcb, 0xc3, 0x55, 0x17, 0x15, 0xf0, 0x7f, 0x31, 0xc0, 0xa2, 0xc3, 0xa9, 0xd7, 0x11, 0xce, + 0x79, 0x50, 0x1e, 0x25, 0x45, 0x3c, 0xcd, 0x3c, 0xa3, 0x6f, 0x0e, 0x4c, 0xb6, 0x61, 0x73, 0x5f, + 0x83, 0xf6, 0x44, 0x79, 0x5b, 0x7d, 0x73, 0xe0, 0x30, 0x8d, 0x28, 0x74, 0xc4, 0x27, 0x18, 0xe9, + 0x14, 0x14, 0xa0, 0xdd, 0xa9, 0xc0, 0x59, 0xb8, 0xd2, 0x69, 0x68, 0x44, 0xf6, 0xac, 0x98, 0x91, + 0x5d, 0x65, 0xa2, 0x11, 0x25, 0x3d, 0xe1, 0x59, 0xdd, 0x54, 0x5a, 0x53, 0xe4, 0x2c, 0xe0, 0x51, + 0xd5, 0x55, 0x05, 0xfc, 0xdf, 0x0c, 0x9a, 0x76, 0xc5, 0xd2, 0xad, 0x0a, 0xbd, 0x0e, 0x5d, 0x62, + 0xf0, 0xf3, 0x0b, 0x2e, 0x74, 0x95, 0x3a, 0x84, 0xcf, 0xb9, 0x70, 0x3f, 0x84, 0xb6, 0x2c, 0xf1, + 0x8e, 0x89, 0xa9, 0xc2, 0xc9, 0xaa, 0x30, 0xbd, 0xad, 0xe6, 0x94, 0xd5, 0xe0, 0x54, 0x7d, 0x59, + 0xbb, 0x79, 0xd9, 0xbb, 0x60, 0x13, 0x39, 0x4b, 0x99, 0xfd, 0xce, 0xc8, 0x8a, 0xc2, 0x6a, 0x97, + 0x7f, 0x06, 0x37, 0x36, 0x4e, 0xac, 0x4f, 0x32, 0x36, 0x4f, 0x5a, 0xd3, 0xc5, 0xd1, 0xf4, 0x20, + 0xa5, 0xcb, 0x30, 0xc2, 0x20, 0xc7, 0xa9, 0xac, 0x77, 0x97, 0xd5, 0xd8, 0xff, 0xc9, 0x58, 0xc7, + 0x95, 0xe7, 0x91, 0x96, 0x05, 0xc9, 0x72, 0xc9, 0xe3, 0xa9, 0x0e, 0x5d, 0x41, 0xaa, 0xdb, 0x74, + 0xa2, 0x43, 0xb7, 0xa6, 0x13, 0xc2, 0x22, 0xd5, 0x1d, 0x6c, 0x89, 0xd4, 0xed, 0x43, 0x6f, 0x89, + 0x3c, 0x2b, 0x04, 0x2e, 0x31, 0xce, 0x75, 0x09, 0x9a, 0x26, 0xf7, 0x36, 0x74, 0x72, 0x3e, 0x7f, + 0x4e, 0x24, 0xd7, 0x9d, 0xcc, 0xf9, 0xfc, 0x01, 0x96, 0xee, 0x1b, 0xe0, 0xcc, 0x42, 0x8c, 0xa6, + 0xd2, 0xa5, 0xda, 0xd9, 0x95, 0x86, 0x07, 0x58, 0xfa, 0xbf, 0x1b, 0xd0, 0x1e, 0xa3, 0xb8, 0x40, + 0x71, 0x2d, 0x91, 0x6b, 0x3e, 0x1e, 0xe6, 0x2b, 0x1e, 0x0f, 0x6b, 0xf7, 0xe3, 0x61, 0xaf, 0x1f, + 0x8f, 0x7d, 0xb0, 0xc7, 0x22, 0x38, 0x19, 0xc9, 0x8c, 0x4c, 0xa6, 0x00, 0xb1, 0x71, 0x18, 0xe4, + 0xe1, 0x05, 0xea, 0x17, 0x45, 0xa3, 0x2d, 0xed, 0xeb, 0xee, 0xd0, 0xbe, 0x1f, 0x0d, 0x68, 0x3f, + 0xe4, 0x65, 0x52, 0xe4, 0x5b, 0x2c, 0xec, 0x43, 0x6f, 0x98, 0xa6, 0x51, 0x18, 0xa8, 0x5f, 0xab, + 0x1b, 0x35, 0x4d, 0xb4, 0xe3, 0x51, 0xa3, 0xbe, 0xea, 0x6e, 0x4d, 0x13, 0xc9, 0xc5, 0xb1, 0xd4, + 0x77, 0x25, 0xd6, 0x0d, 0xb9, 0x50, 0xb2, 0x2e, 0x9d, 0x54, 0x84, 0x61, 0x91, 0x27, 0xb3, 0x28, + 0xb9, 0x94, 0xb7, 0xed, 0xb2, 0x1a, 0xfb, 0x7f, 0xb4, 0xc0, 0xfa, 0xaf, 0x34, 0x79, 0x0f, 0x8c, + 0x50, 0x37, 0xdb, 0x08, 0x6b, 0x85, 0xee, 0x34, 0x14, 0xda, 0x83, 0x4e, 0x29, 0x78, 0x3c, 0xc7, + 0xcc, 0xeb, 0x4a, 0x75, 0xa9, 0xa0, 0xf4, 0xc8, 0x39, 0x52, 0xd2, 0xec, 0xb0, 0x0a, 0xd6, 0x73, + 0x01, 0x8d, 0xb9, 0xf8, 0x40, 0xab, 0x78, 0x4f, 0x66, 0xe4, 0x6d, 0x96, 0xe5, 0xaa, 0x78, 0xff, + 0x7b, 0x9a, 0xfc, 0xb7, 0x01, 0x76, 0x3d, 0x54, 0xc7, 0x9b, 0x43, 0x75, 0xbc, 0x1e, 0xaa, 0xd1, + 0x51, 0x35, 0x54, 0xa3, 0x23, 0xc2, 0xec, 0xb4, 0x1a, 0x2a, 0x76, 0x4a, 0xcd, 0xba, 0x2f, 0x92, + 0x22, 0x3d, 0x2a, 0x55, 0x57, 0x1d, 0x56, 0x63, 0x62, 0xe2, 0x37, 0x0b, 0x14, 0xba, 0xd4, 0x0e, + 0xd3, 0x88, 0x78, 0xfb, 0x50, 0x0a, 0x8e, 0x2a, 0xae, 0x02, 0xee, 0x3b, 0x60, 0x33, 0x2a, 0x9e, + 0xac, 0xf0, 0x46, 0x5f, 0xa4, 0x99, 0x29, 0x2f, 0x05, 0x55, 0x5f, 0x6f, 0x9a, 0xc0, 0xd5, 0xb7, + 0xdc, 0xfb, 0xd0, 0x1e, 0x2f, 0xc2, 0x59, 0x5e, 0xbd, 0x85, 0xff, 0x6f, 0x08, 0x56, 0xb8, 0x44, + 0xe9, 0x63, 0x7a, 0x8b, 0xff, 0x14, 0x9c, 0xda, 0xb8, 0x4e, 0xc7, 0x68, 0xa6, 0xe3, 0x82, 0x75, + 0x16, 0x87, 0x79, 0x35, 0xba, 0xb4, 0xa6, 0xcb, 0x3e, 0x2d, 0x78, 0x9c, 0x87, 0x79, 0x59, 0x8d, + 0x6e, 0x85, 0xfd, 0x7b, 0x3a, 0x7d, 0x0a, 0x77, 0x96, 0xa6, 0x28, 0xb4, 0x0c, 0x28, 0x20, 0x0f, + 0x49, 0x2e, 0x51, 0x29, 0xb8, 0xc9, 0x14, 0xf0, 0xbf, 0x05, 0x67, 0x18, 0xa1, 0xc8, 0x59, 0x11, + 0xe1, 0xae, 0x97, 0xf1, 0xeb, 0xf1, 0x93, 0xc7, 0x55, 0x06, 0xb4, 0x5e, 0x8f, 0xbc, 0x79, 0x65, + 0xe4, 0x1f, 0xf0, 0x94, 0x9f, 0x8c, 0x24, 0xcf, 0x4d, 0xa6, 0x91, 0xff, 0xb3, 0x01, 0x16, 0x69, + 0x4b, 0x23, 0xb4, 0xf5, 0x2a, 0x5d, 0x3a, 0x15, 0xc9, 0x45, 0x38, 0x45, 0x51, 0x5d, 0xae, 0xc2, + 0xb2, 0xe8, 0xc1, 0x02, 0xeb, 0x07, 0x58, 0x23, 0xe2, 0x1a, 0x7d, 0xea, 0x55, 0xb3, 0xd4, 0xe0, + 0x1a, 0x99, 0x99, 0x72, 0xba, 0x6f, 0x02, 0x8c, 0x8b, 0x14, 0xc5, 0x70, 0xba, 0x0c, 0x63, 0xd9, + 0xf4, 0x2e, 0x6b, 0x58, 0xfc, 0x2f, 0xd4, 0xc7, 0xe3, 0x96, 0x42, 0x19, 0xbb, 0x3f, 0x34, 0xaf, + 0x66, 0xee, 0xff, 0x60, 0x40, 0xe7, 0x11, 0x4f, 0xd3, 0x30, 0x9e, 0x6f, 0xdc, 0xc2, 0x78, 0xe9, + 0x2d, 0x5a, 0x1b, 0xb7, 0xd8, 0x07, 0x5b, 0x72, 0xb6, 0x7a, 0xed, 0x25, 0xd0, 0x35, 0xb3, 0xea, + 0x76, 0x5c, 0xe7, 0xdb, 0x31, 0xda, 0xdc, 0xb3, 0xab, 0xa5, 0x5b, 0x75, 0xef, 0x43, 0x4f, 0x7f, + 0xf3, 0xcb, 0x2f, 0x68, 0x2d, 0x9b, 0x0d, 0x13, 0xe5, 0x7d, 0x5a, 0x4c, 0xa2, 0x30, 0x90, 0xd9, + 0x74, 0x99, 0x46, 0xfe, 0x21, 0xb4, 0x8f, 0x93, 0x78, 0x16, 0xce, 0xdd, 0x01, 0x58, 0xc3, 0x22, + 0x5f, 0xc8, 0x93, 0x7a, 0x87, 0xfb, 0x8d, 0x91, 0x2f, 0xf2, 0x85, 0xda, 0xc3, 0xe4, 0x0e, 0xff, + 0x33, 0x80, 0xb5, 0x8d, 0xfe, 0x48, 0xac, 0xfb, 0xf0, 0x18, 0x2f, 0x89, 0x2c, 0x99, 0x8c, 0xd2, + 0x65, 0x3b, 0x3c, 0xfe, 0xe7, 0xe0, 0x1c, 0x15, 0x61, 0x34, 0x3d, 0x89, 0x67, 0x09, 0x89, 0xc6, + 0x39, 0x8a, 0x6c, 0xdd, 0xa9, 0x0a, 0x52, 0xc2, 0xa4, 0x1f, 0xf5, 0xf4, 0x68, 0x34, 0x69, 0xcb, + 0xff, 0x62, 0xf7, 0xfe, 0x09, 0x00, 0x00, 0xff, 0xff, 0x82, 0x96, 0xb8, 0xb3, 0x9d, 0x0d, 0x00, + 0x00, } diff --git a/bolt/internal/internal.proto b/bolt/internal/internal.proto index 6cfd6723f..10b37511c 100644 --- a/bolt/internal/internal.proto +++ b/bolt/internal/internal.proto @@ -161,7 +161,8 @@ message Mapping { string Provider = 1; // Provider is the provider that certifies and issues this user's authentication, e.g. GitHub string Scheme = 2; // Scheme is the scheme used to perform this user's authentication, e.g. OAuth2 or LDAP string Group = 3; // Group is the group or organizations that you are a part of in an auth provider - string GrantedRole = 4; // GrantedRole is the name of the role that you will be granted if you match the mapping + string ID = 4; // ID is the unique ID for the mapping + string Organization = 5; // Organization is the organization ID that resource belongs to } message Organization { @@ -169,7 +170,6 @@ message Organization { string Name = 2; // Name is the organization's name string DefaultRole = 3; // DefaultRole is the name of the role that is the default for any users added to the organization bool Public = 4; // Public specifies that users must be explicitly added to the organization - repeated Mapping Mappings = 5; // Mappings is set of mappings a organization has } message Config { diff --git a/bolt/mapping.go b/bolt/mapping.go new file mode 100644 index 000000000..81c899c2b --- /dev/null +++ b/bolt/mapping.go @@ -0,0 +1,128 @@ +package bolt + +import ( + "context" + "fmt" + + "github.com/boltdb/bolt" + "github.com/influxdata/chronograf" + "github.com/influxdata/chronograf/bolt/internal" +) + +// Ensure MappingsStore implements chronograf.MappingsStore. +var _ chronograf.MappingsStore = &MappingsStore{} + +var ( + // MappingsBucket is the bucket where organizations are stored. + MappingsBucket = []byte("MappingsV1") +) + +// MappingsStore uses bolt to store and retrieve Mappings +type MappingsStore struct { + client *Client +} + +// Migrate sets the default organization at runtime +func (s *MappingsStore) Migrate(ctx context.Context) error { + return nil +} + +// Add creates a new Mapping in the MappingsStore +func (s *MappingsStore) Add(ctx context.Context, o *chronograf.Mapping) (*chronograf.Mapping, error) { + err := s.client.db.Update(func(tx *bolt.Tx) error { + b := tx.Bucket(MappingsBucket) + seq, err := b.NextSequence() + if err != nil { + return err + } + o.ID = fmt.Sprintf("%d", seq) + + v, err := internal.MarshalMapping(o) + if err != nil { + return err + } + + return b.Put([]byte(o.ID), v) + }) + + if err != nil { + return nil, err + } + + return o, nil +} + +// All returns all known organizations +func (s *MappingsStore) All(ctx context.Context) ([]chronograf.Mapping, error) { + var orgs []chronograf.Mapping + err := s.each(ctx, func(o *chronograf.Mapping) { + orgs = append(orgs, *o) + }) + + if err != nil { + return nil, err + } + + return orgs, nil +} + +// Delete the organization from MappingsStore +func (s *MappingsStore) Delete(ctx context.Context, o *chronograf.Mapping) error { + _, err := s.get(ctx, o.ID) + if err != nil { + return err + } + if err := s.client.db.Update(func(tx *bolt.Tx) error { + return tx.Bucket(MappingsBucket).Delete([]byte(o.ID)) + }); err != nil { + return err + } + return nil +} + +func (s *MappingsStore) get(ctx context.Context, id string) (*chronograf.Mapping, error) { + var o chronograf.Mapping + err := s.client.db.View(func(tx *bolt.Tx) error { + v := tx.Bucket(MappingsBucket).Get([]byte(id)) + if v == nil { + return chronograf.ErrMappingNotFound + } + return internal.UnmarshalMapping(v, &o) + }) + + if err != nil { + return nil, err + } + + return &o, nil +} + +func (s *MappingsStore) each(ctx context.Context, fn func(*chronograf.Mapping)) error { + return s.client.db.View(func(tx *bolt.Tx) error { + return tx.Bucket(MappingsBucket).ForEach(func(k, v []byte) error { + var org chronograf.Mapping + if err := internal.UnmarshalMapping(v, &org); err != nil { + return err + } + fn(&org) + return nil + }) + }) +} + +// Get returns a Mapping if the id exists. +func (s *MappingsStore) Get(ctx context.Context, id string) (*chronograf.Mapping, error) { + return s.get(ctx, id) +} + +// Update the organization in MappingsStore +func (s *MappingsStore) Update(ctx context.Context, o *chronograf.Mapping) error { + return s.client.db.Update(func(tx *bolt.Tx) error { + if v, err := internal.MarshalMapping(o); err != nil { + return err + } else if err := tx.Bucket(MappingsBucket).Put([]byte(o.ID), v); err != nil { + return err + } + return nil + }) +} diff --git a/bolt/mapping_test.go b/bolt/mapping_test.go new file mode 100644 index 000000000..bbbccfb6d --- /dev/null +++ b/bolt/mapping_test.go @@ -0,0 +1,489 @@ +package bolt_test + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/influxdata/chronograf" +) + +var mappingCmpOptions = cmp.Options{ + cmpopts.IgnoreFields(chronograf.Mapping{}, "ID"), + cmpopts.EquateEmpty(), +} + +func TestMappingStore_Add(t *testing.T) { + type fields struct { + mappings []*chronograf.Mapping + } + type args struct { + mapping *chronograf.Mapping + } + type wants struct { + mapping *chronograf.Mapping + err error + } + tests := []struct { + name string + fields fields + args args + wants wants + }{ + { + name: "default with wildcards", + args: args{ + mapping: &chronograf.Mapping{ + Organization: "default", + Provider: "*", + Scheme: "*", + Group: "*", + }, + }, + wants: wants{ + mapping: &chronograf.Mapping{ + Organization: "default", + Provider: "*", + Scheme: "*", + Group: "*", + }, + }, + }, + { + name: "simple", + args: args{ + mapping: &chronograf.Mapping{ + Organization: "default", + Provider: "github", + Scheme: "oauth2", + Group: "idk", + }, + }, + wants: wants{ + mapping: &chronograf.Mapping{ + Organization: "default", + Provider: "github", + Scheme: "oauth2", + Group: "idk", + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + client, err := NewTestClient() + if err != nil { + t.Fatal(err) + } + defer client.Close() + + s := client.MappingsStore + ctx := context.Background() + + for _, mapping := range tt.fields.mappings { + // YOLO database prepopulation + _, _ = s.Add(ctx, mapping) + } + + tt.args.mapping, err = s.Add(ctx, tt.args.mapping) + + if (err != nil) != (tt.wants.err != nil) { + t.Errorf("MappingsStore.Add() error = %v, want error %v", err, tt.wants.err) + return + } + + got, err := s.Get(ctx, tt.args.mapping.ID) + if err != nil { + t.Fatalf("failed to get mapping: %v", err) + return + } + if diff := cmp.Diff(got, tt.wants.mapping, mappingCmpOptions...); diff != "" { + t.Errorf("MappingStore.Add():\n-got/+want\ndiff %s", diff) + return + } + }) + } +} + +func TestMappingStore_All(t *testing.T) { + type fields struct { + mappings []*chronograf.Mapping + } + type args struct { + } + type wants struct { + mappings []chronograf.Mapping + err error + } + tests := []struct { + name string + fields fields + args args + wants wants + }{ + { + name: "simple", + fields: fields{ + mappings: []*chronograf.Mapping{ + &chronograf.Mapping{ + Organization: "default", + Provider: "*", + Scheme: "*", + Group: "*", + }, + &chronograf.Mapping{ + Organization: "0", + Provider: "google", + Scheme: "ldap", + Group: "*", + }, + }, + }, + wants: wants{ + mappings: []chronograf.Mapping{ + chronograf.Mapping{ + Organization: "default", + Provider: "*", + Scheme: "*", + Group: "*", + }, + chronograf.Mapping{ + Organization: "0", + Provider: "google", + Scheme: "ldap", + Group: "*", + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + client, err := NewTestClient() + if err != nil { + t.Fatal(err) + } + defer client.Close() + + s := client.MappingsStore + ctx := context.Background() + + for _, mapping := range tt.fields.mappings { + // YOLO database prepopulation + _, _ = s.Add(ctx, mapping) + } + + got, err := s.All(ctx) + + if (err != nil) != (tt.wants.err != nil) { + t.Errorf("MappingsStore.All() error = %v, want error %v", err, tt.wants.err) + return + } + + if diff := cmp.Diff(got, tt.wants.mappings, mappingCmpOptions...); diff != "" { + t.Errorf("MappingStore.All():\n-got/+want\ndiff %s", diff) + return + } + }) + } +} + +func TestMappingStore_Delete(t *testing.T) { + type fields struct { + mappings []*chronograf.Mapping + } + type args struct { + mapping *chronograf.Mapping + } + type wants struct { + err error + } + tests := []struct { + name string + fields fields + args args + wants wants + }{ + { + name: "simple", + fields: fields{ + mappings: []*chronograf.Mapping{ + &chronograf.Mapping{ + Organization: "default", + Provider: "*", + Scheme: "*", + Group: "*", + }, + &chronograf.Mapping{ + Organization: "0", + Provider: "google", + Scheme: "ldap", + Group: "*", + }, + }, + }, + args: args{ + mapping: &chronograf.Mapping{ + ID: "1", + Organization: "default", + Provider: "*", + Scheme: "*", + Group: "*", + }, + }, + wants: wants{ + err: nil, + }, + }, + { + name: "mapping not found", + fields: fields{ + mappings: []*chronograf.Mapping{ + &chronograf.Mapping{ + Organization: "default", + Provider: "*", + Scheme: "*", + Group: "*", + }, + &chronograf.Mapping{ + Organization: "0", + Provider: "google", + Scheme: "ldap", + Group: "*", + }, + }, + }, + args: args{ + mapping: &chronograf.Mapping{ + ID: "0", + Organization: "default", + Provider: "*", + Scheme: "*", + Group: "*", + }, + }, + wants: wants{ + err: chronograf.ErrMappingNotFound, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + client, err := NewTestClient() + if err != nil { + t.Fatal(err) + } + defer client.Close() + + s := client.MappingsStore + ctx := context.Background() + + for _, mapping := range tt.fields.mappings { + // YOLO database prepopulation + _, _ = s.Add(ctx, mapping) + } + + err = s.Delete(ctx, tt.args.mapping) + + if (err != nil) != (tt.wants.err != nil) { + t.Errorf("MappingsStore.Delete() error = %v, want error %v", err, tt.wants.err) + return + } + }) + } +} + +func TestMappingStore_Get(t *testing.T) { + type fields struct { + mappings []*chronograf.Mapping + } + type args struct { + mappingID string + } + type wants struct { + mapping *chronograf.Mapping + err error + } + tests := []struct { + name string + fields fields + args args + wants wants + }{ + { + name: "simple", + fields: fields{ + mappings: []*chronograf.Mapping{ + &chronograf.Mapping{ + Organization: "default", + Provider: "*", + Scheme: "*", + Group: "*", + }, + &chronograf.Mapping{ + Organization: "0", + Provider: "google", + Scheme: "ldap", + Group: "*", + }, + }, + }, + args: args{ + mappingID: "1", + }, + wants: wants{ + mapping: &chronograf.Mapping{ + ID: "1", + Organization: "default", + Provider: "*", + Scheme: "*", + Group: "*", + }, + err: nil, + }, + }, + { + name: "mapping not found", + fields: fields{ + mappings: []*chronograf.Mapping{ + &chronograf.Mapping{ + Organization: "default", + Provider: "*", + Scheme: "*", + Group: "*", + }, + &chronograf.Mapping{ + Organization: "0", + Provider: "google", + Scheme: "ldap", + Group: "*", + }, + }, + }, + args: args{ + mappingID: "0", + }, + wants: wants{ + err: chronograf.ErrMappingNotFound, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + client, err := NewTestClient() + if err != nil { + t.Fatal(err) + } + defer client.Close() + + s := client.MappingsStore + ctx := context.Background() + + for _, mapping := range tt.fields.mappings { + // YOLO database prepopulation + _, _ = s.Add(ctx, mapping) + } + + got, err := s.Get(ctx, tt.args.mappingID) + if (err != nil) != (tt.wants.err != nil) { + t.Errorf("MappingsStore.Get() error = %v, want error %v", err, tt.wants.err) + return + } + if diff := cmp.Diff(got, tt.wants.mapping, mappingCmpOptions...); diff != "" { + t.Errorf("MappingStore.Get():\n-got/+want\ndiff %s", diff) + return + } + }) + } +} + +func TestMappingStore_Update(t *testing.T) { + type fields struct { + mappings []*chronograf.Mapping + } + type args struct { + mapping *chronograf.Mapping + } + type wants struct { + mapping *chronograf.Mapping + err error + } + tests := []struct { + name string + fields fields + args args + wants wants + }{ + { + name: "simple", + fields: fields{ + mappings: []*chronograf.Mapping{ + &chronograf.Mapping{ + Organization: "default", + Provider: "*", + Scheme: "*", + Group: "*", + }, + &chronograf.Mapping{ + Organization: "0", + Provider: "google", + Scheme: "ldap", + Group: "*", + }, + }, + }, + args: args{ + mapping: &chronograf.Mapping{ + ID: "1", + Organization: "default", + Provider: "cool", + Scheme: "it", + Group: "works", + }, + }, + wants: wants{ + mapping: &chronograf.Mapping{ + ID: "1", + Organization: "default", + Provider: "cool", + Scheme: "it", + Group: "works", + }, + err: nil, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + client, err := NewTestClient() + if err != nil { + t.Fatal(err) + } + defer client.Close() + + s := client.MappingsStore + ctx := context.Background() + + for _, mapping := range tt.fields.mappings { + // YOLO database prepopulation + _, _ = s.Add(ctx, mapping) + } + + err = s.Update(ctx, tt.args.mapping) + if (err != nil) != (tt.wants.err != nil) { + t.Errorf("MappingsStore.Update() error = %v, want error %v", err, tt.wants.err) + return + } + if diff := cmp.Diff(tt.args.mapping, tt.wants.mapping, mappingCmpOptions...); diff != "" { + t.Errorf("MappingStore.Update():\n-got/+want\ndiff %s", diff) + return + } + }) + } +} diff --git a/bolt/organizations.go b/bolt/organizations.go index 8f0103759..4b0c5dc59 100644 --- a/bolt/organizations.go +++ b/bolt/organizations.go @@ -29,14 +29,6 @@ const ( DefaultOrganizationPublic bool = true ) -// DefaultOrganizationMapping is the mapping for the default organization -var DefaultOrganizationMapping = chronograf.Mapping{ - Provider: chronograf.MappingWildcard, - Scheme: chronograf.MappingWildcard, - Group: chronograf.MappingWildcard, - GrantedRole: DefaultOrganizationRole, -} - // OrganizationsStore uses bolt to store and retrieve Organizations type OrganizationsStore struct { client *Client @@ -54,9 +46,14 @@ func (s *OrganizationsStore) CreateDefault(ctx context.Context) error { Name: DefaultOrganizationName, DefaultRole: DefaultOrganizationRole, Public: DefaultOrganizationPublic, - Mappings: []chronograf.Mapping{ - DefaultOrganizationMapping, - }, + } + + m := chronograf.Mapping{ + ID: string(DefaultOrganizationID), + Organization: string(DefaultOrganizationID), + Provider: chronograf.MappingWildcard, + Scheme: chronograf.MappingWildcard, + Group: chronograf.MappingWildcard, } return s.client.db.Update(func(tx *bolt.Tx) error { b := tx.Bucket(OrganizationsBucket) @@ -70,6 +67,17 @@ func (s *OrganizationsStore) CreateDefault(ctx context.Context) error { return err } + b = tx.Bucket(MappingsBucket) + v = b.Get(DefaultOrganizationID) + if v != nil { + return nil + } + if v, err := internal.MarshalMapping(&m); err != nil { + return err + } else if err := b.Put(DefaultOrganizationID, v); err != nil { + return err + } + return nil }) } diff --git a/bolt/organizations_test.go b/bolt/organizations_test.go index abf1fcddf..a44fa0681 100644 --- a/bolt/organizations_test.go +++ b/bolt/organizations_test.go @@ -42,26 +42,10 @@ func TestOrganizationsStore_GetWithName(t *testing.T) { ctx: context.Background(), org: &chronograf.Organization{ Name: "EE - Evil Empire", - Mappings: []chronograf.Mapping{ - chronograf.Mapping{ - Provider: chronograf.MappingWildcard, - Scheme: chronograf.MappingWildcard, - Group: chronograf.MappingWildcard, - GrantedRole: roles.ViewerRoleName, - }, - }, }, }, want: &chronograf.Organization{ Name: "EE - Evil Empire", - Mappings: []chronograf.Mapping{ - chronograf.Mapping{ - Provider: chronograf.MappingWildcard, - Scheme: chronograf.MappingWildcard, - Group: chronograf.MappingWildcard, - GrantedRole: roles.ViewerRoleName, - }, - }, }, addFirst: true, }, @@ -187,9 +171,6 @@ func TestOrganizationsStore_All(t *testing.T) { Name: "EE - Evil Empire", DefaultRole: roles.MemberRoleName, Public: true, - Mappings: []chronograf.Mapping{ - bolt.DefaultOrganizationMapping, - }, }, { Name: "The Good Place", @@ -203,9 +184,6 @@ func TestOrganizationsStore_All(t *testing.T) { Name: "EE - Evil Empire", DefaultRole: roles.MemberRoleName, Public: true, - Mappings: []chronograf.Mapping{ - bolt.DefaultOrganizationMapping, - }, }, { Name: "The Good Place", @@ -216,9 +194,6 @@ func TestOrganizationsStore_All(t *testing.T) { Name: bolt.DefaultOrganizationName, DefaultRole: bolt.DefaultOrganizationRole, Public: bolt.DefaultOrganizationPublic, - Mappings: []chronograf.Mapping{ - bolt.DefaultOrganizationMapping, - }, }, }, addFirst: true, @@ -260,10 +235,9 @@ func TestOrganizationsStore_Update(t *testing.T) { orgs []chronograf.Organization } type args struct { - ctx context.Context - initial *chronograf.Organization - updates *chronograf.Organization - mappings []chronograf.Mapping + ctx context.Context + initial *chronograf.Organization + updates *chronograf.Organization } tests := []struct { name string @@ -387,7 +361,7 @@ func TestOrganizationsStore_Update(t *testing.T) { addFirst: true, }, { - name: "Update organization name and mappings", + name: "Update organization name", fields: fields{}, args: args{ ctx: context.Background(), @@ -397,26 +371,10 @@ func TestOrganizationsStore_Update(t *testing.T) { }, updates: &chronograf.Organization{ Name: "The Bad Place", - Mappings: []chronograf.Mapping{ - chronograf.Mapping{ - Provider: chronograf.MappingWildcard, - Scheme: chronograf.MappingWildcard, - Group: chronograf.MappingWildcard, - GrantedRole: roles.ViewerRoleName, - }, - }, }, }, want: &chronograf.Organization{ Name: "The Bad Place", - Mappings: []chronograf.Mapping{ - chronograf.Mapping{ - Provider: chronograf.MappingWildcard, - Scheme: chronograf.MappingWildcard, - Group: chronograf.MappingWildcard, - GrantedRole: roles.ViewerRoleName, - }, - }, }, addFirst: true, }, @@ -468,9 +426,6 @@ func TestOrganizationsStore_Update(t *testing.T) { if tt.args.updates.DefaultRole != "" { tt.args.initial.DefaultRole = tt.args.updates.DefaultRole } - if tt.args.updates.Mappings != nil { - tt.args.initial.Mappings = tt.args.updates.Mappings - } if tt.args.updates.Public != tt.args.initial.Public { tt.args.initial.Public = tt.args.updates.Public @@ -682,9 +637,6 @@ func TestOrganizationsStore_DefaultOrganization(t *testing.T) { Name: bolt.DefaultOrganizationName, DefaultRole: bolt.DefaultOrganizationRole, Public: bolt.DefaultOrganizationPublic, - Mappings: []chronograf.Mapping{ - bolt.DefaultOrganizationMapping, - }, }, wantErr: false, }, diff --git a/chronograf.go b/chronograf.go index 724a62696..d794dddc5 100644 --- a/chronograf.go +++ b/chronograf.go @@ -27,6 +27,7 @@ const ( ErrInvalidColor = Error("Invalid color. Accepted color format is #RRGGBB") ErrUserAlreadyExists = Error("user already exists") ErrOrganizationNotFound = Error("organization not found") + ErrMappingNotFound = Error("mapping not found") ErrOrganizationAlreadyExists = Error("organization already exists") ErrCannotDeleteDefaultOrganization = Error("cannot delete default organization") ErrConfigNotFound = Error("cannot find configuration") @@ -563,18 +564,6 @@ type LayoutsStore interface { Update(context.Context, Layout) error } -// Organization is a group of resources under a common name -type Organization struct { - ID string `json:"id"` - Name string `json:"name"` - // DefaultRole is the name of the role that is the default for any users added to the organization - DefaultRole string `json:"defaultRole,omitempty"` - // Public specifies whether users must be explicitly added to the organization. - // It is currently only used by the default organization, but that may change in the future. - Public bool `json:"public"` - Mappings []Mapping `json:"mappings"` -} - // MappingWildcard is the wildcard value for mappings const MappingWildcard string = "*" @@ -584,19 +573,47 @@ const MappingWildcard string = "*" // explicit role within the organization. // // One can think of a mapping like so: -// Provider:Scheme:Group -> GrantedRole -// github:oauth2:influxdata -> Viewer -// beyondcorp:ldap:influxdata -> Admin +// Provider:Scheme:Group -> Organization +// github:oauth2:influxdata -> Happy +// beyondcorp:ldap:influxdata -> TheBillHilliettas // // Any of Provider, Scheme, or Group may be provided as a wildcard * -// github:oauth2:* -> Editor -// *:*:* -> Member +// github:oauth2:* -> MyOrg +// *:*:* -> AllOrg type Mapping struct { + ID string `json:"id"` + Organization string `json:"organization"` + Provider string `json:"provider"` Scheme string `json:"scheme"` Group string `json:"group"` +} - GrantedRole string `json:"grantedRole"` +// MappingsStore is the storage and retrieval of Mappings +type MappingsStore interface { + // Add creates a new Mapping. + // The Created mapping is returned back to the user with the + // ID field populated. + Add(context.Context, *Mapping) (*Mapping, error) + // All lists all Mapping in the MappingsStore + All(context.Context) ([]Mapping, error) + // Delete removes an Mapping from the MappingsStore + Delete(context.Context, *Mapping) error + // Get retrieves an Mapping from the MappingsStore + Get(context.Context, string) (*Mapping, error) + // Update updates an Mapping in the MappingsStore + Update(context.Context, *Mapping) error +} + +// Organization is a group of resources under a common name +type Organization struct { + ID string `json:"id"` + Name string `json:"name"` + // DefaultRole is the name of the role that is the default for any users added to the organization + DefaultRole string `json:"defaultRole,omitempty"` + // Public specifies whether users must be explicitly added to the organization. + // It is currently only used by the default organization, but that may change in the future. + Public bool `json:"public"` } // OrganizationQuery represents the attributes that a organization may be retrieved by. diff --git a/integrations/server_test.go b/integrations/server_test.go index 0eb028bd9..c20984655 100644 --- a/integrations/server_test.go +++ b/integrations/server_test.go @@ -26,6 +26,7 @@ import ( func TestServer(t *testing.T) { type fields struct { Organizations []chronograf.Organization + Mappings []chronograf.Mapping Users []chronograf.User Sources []chronograf.Source Servers []chronograf.Server @@ -351,15 +352,7 @@ func TestServer(t *testing.T) { "id": "default", "name": "Default", "defaultRole": "member", - "public": true, - "mappings": [ - { - "provider": "*", - "scheme": "*", - "group": "*", - "grantedRole": "member" - } - ] + "public": true }, { "links": { @@ -368,9 +361,7 @@ func TestServer(t *testing.T) { "id": "howdy", "name": "An Organization", "defaultRole": "viewer", - "public": false, - "mappings": [ - ] + "public": false } ] }`, @@ -419,9 +410,7 @@ func TestServer(t *testing.T) { "id": "howdy", "name": "An Organization", "defaultRole": "viewer", - "public": false, - "mappings": [ - ] + "public": false }`, }, }, @@ -1303,201 +1292,6 @@ func TestServer(t *testing.T) { "code": 401, "message": "user cannot modify their own SuperAdmin status" } -`, - }, - }, - { - name: "GET /me", - subName: "New user hits me for the first time", - fields: fields{ - Config: &chronograf.Config{ - Auth: chronograf.AuthConfig{ - SuperAdminNewUsers: false, - }, - }, - Organizations: []chronograf.Organization{ - { - ID: "1", - Name: "Sweet", - DefaultRole: roles.ViewerRoleName, - Mappings: []chronograf.Mapping{ - chronograf.Mapping{ - Provider: chronograf.MappingWildcard, - Scheme: chronograf.MappingWildcard, - Group: chronograf.MappingWildcard, - GrantedRole: roles.EditorRoleName, - }, - chronograf.Mapping{ - Provider: "github", - Scheme: chronograf.MappingWildcard, - Group: "influxdata", - GrantedRole: roles.AdminRoleName, - }, - chronograf.Mapping{ - Provider: "github", - Scheme: chronograf.MappingWildcard, - Group: "mimi", - GrantedRole: roles.ViewerRoleName, - }, - }, - }, - { - ID: "2", - Name: "What", - DefaultRole: roles.ViewerRoleName, - Mappings: []chronograf.Mapping{ - chronograf.Mapping{ - Provider: chronograf.MappingWildcard, - Scheme: chronograf.MappingWildcard, - Group: chronograf.MappingWildcard, - GrantedRole: roles.MemberRoleName, - }, - chronograf.Mapping{ - Provider: "github", - Scheme: chronograf.MappingWildcard, - Group: "mimi", - GrantedRole: roles.ViewerRoleName, - }, - }, - }, - { - ID: "3", - Name: "Okay", - DefaultRole: roles.ViewerRoleName, - Mappings: []chronograf.Mapping{}, - }, - }, - Users: []chronograf.User{ - { - ID: 1, // This is artificial, but should be reflective of the users actual ID - Name: "billibob", - Provider: "github", - Scheme: "oauth2", - SuperAdmin: true, - Roles: []chronograf.Role{ - { - Name: "admin", - Organization: "default", - }, - }, - }, - }, - }, - args: args{ - server: &server.Server{ - GithubClientID: "not empty", - GithubClientSecret: "not empty", - }, - method: "GET", - path: "/chronograf/v1/me", - principal: oauth2.Principal{ - Subject: "billietta", - Issuer: "github", - Group: "influxdata,idk,mimi", - }, - }, - wants: wants{ - statusCode: 200, - body: ` -{ - "id": "2", - "name": "billietta", - "roles": [ - { - "name": "admin", - "organization": "1" - }, - { - "name": "viewer", - "organization": "2" - }, - { - "name": "member", - "organization": "default" - } - ], - "provider": "github", - "scheme": "oauth2", - "links": { - "self": "/chronograf/v1/users/2" - }, - "organizations": [ - { - "id": "1", - "name": "Sweet", - "defaultRole": "viewer", - "public": false, - "mappings": [ - { - "provider": "*", - "scheme": "*", - "group": "*", - "grantedRole": "editor" - }, - { - "provider": "github", - "scheme": "*", - "group": "influxdata", - "grantedRole": "admin" - }, - { - "provider": "github", - "scheme": "*", - "group": "mimi", - "grantedRole": "viewer" - } - ] - }, - { - "id": "2", - "name": "What", - "defaultRole": "viewer", - "public": false, - "mappings": [ - { - "provider": "*", - "scheme": "*", - "group": "*", - "grantedRole": "member" - }, - { - "provider": "github", - "scheme": "*", - "group": "mimi", - "grantedRole": "viewer" - } - ] - }, - { - "id": "default", - "name": "Default", - "defaultRole": "member", - "public": true, - "mappings": [ - { - "provider": "*", - "scheme": "*", - "group": "*", - "grantedRole": "member" - } - ] - } - ], - "currentOrganization": { - "id": "default", - "name": "Default", - "defaultRole": "member", - "public": true, - "mappings": [ - { - "provider": "*", - "scheme": "*", - "group": "*", - "grantedRole": "member" - } - ] - } -} `, }, }, @@ -1576,32 +1370,20 @@ func TestServer(t *testing.T) { "id": "1", "name": "Sweet", "defaultRole": "viewer", - "public": false, - "mappings": [ - ] + "public": false }, { "id": "default", "name": "Default", "defaultRole": "member", - "public": true, - "mappings": [ - { - "provider": "*", - "scheme": "*", - "group": "*", - "grantedRole": "member" - } - ] + "public": true } ], "currentOrganization": { "id": "1", "name": "Sweet", "defaultRole": "viewer", - "public": false, - "mappings": [ - ] + "public": false } }`, }, @@ -1663,6 +1445,141 @@ func TestServer(t *testing.T) { }`, }, }, + { + name: "GET /me", + subName: "New user hits me for the first time", + fields: fields{ + Config: &chronograf.Config{ + Auth: chronograf.AuthConfig{ + SuperAdminNewUsers: false, + }, + }, + Mappings: []chronograf.Mapping{ + { + ID: "1", + Organization: "1", + Provider: "*", + Scheme: "*", + Group: "influxdata", + }, + { + ID: "1", + Organization: "1", + Provider: "*", + Scheme: "*", + Group: "*", + }, + { + ID: "2", + Organization: "2", + Provider: "github", + Scheme: "*", + Group: "*", + }, + { + ID: "3", + Organization: "3", + Provider: "auth0", + Scheme: "ldap", + Group: "*", + }, + }, + Organizations: []chronograf.Organization{ + { + ID: "1", + Name: "Sweet", + DefaultRole: roles.ViewerRoleName, + }, + { + ID: "2", + Name: "What", + DefaultRole: roles.EditorRoleName, + }, + { + ID: "3", + Name: "Okay", + DefaultRole: roles.AdminRoleName, + }, + }, + Users: []chronograf.User{ + { + ID: 1, // This is artificial, but should be reflective of the users actual ID + Name: "billibob", + Provider: "github", + Scheme: "oauth2", + SuperAdmin: true, + Roles: []chronograf.Role{}, + }, + }, + }, + args: args{ + server: &server.Server{ + GithubClientID: "not empty", + GithubClientSecret: "not empty", + }, + method: "GET", + path: "/chronograf/v1/me", + principal: oauth2.Principal{ + Subject: "billietta", + Issuer: "github", + Group: "influxdata,idk,mimi", + }, + }, + wants: wants{ + statusCode: 200, + body: ` +{ + "id": "2", + "name": "billietta", + "roles": [ + { + "name": "viewer", + "organization": "1" + }, + { + "name": "editor", + "organization": "2" + }, + { + "name": "member", + "organization": "default" + } + ], + "provider": "github", + "scheme": "oauth2", + "links": { + "self": "/chronograf/v1/users/2" + }, + "organizations": [ + { + "id": "1", + "name": "Sweet", + "defaultRole": "viewer", + "public": false + }, + { + "id": "2", + "name": "What", + "defaultRole": "editor", + "public": false + }, + { + "id": "default", + "name": "Default", + "defaultRole": "member", + "public": true + } + ], + "currentOrganization": { + "id": "default", + "name": "Default", + "defaultRole": "member", + "public": true + } +} +`, + }, + }, } for _, tt := range tests { @@ -1702,6 +1619,16 @@ func TestServer(t *testing.T) { } } + // Populate Organizations + for i, mapping := range tt.fields.Mappings { + o, err := boltdb.MappingsStore.Add(ctx, &mapping) + if err != nil { + t.Fatalf("failed to add mapping: %v", err) + return + } + tt.fields.Mappings[i] = *o + } + // Populate Organizations for i, organization := range tt.fields.Organizations { o, err := boltdb.OrganizationsStore.Add(ctx, &organization) diff --git a/mocks/mapping.go b/mocks/mapping.go new file mode 100644 index 000000000..60463e3d8 --- /dev/null +++ b/mocks/mapping.go @@ -0,0 +1,35 @@ +package mocks + +import ( + "context" + + "github.com/influxdata/chronograf" +) + +type MappingsStore struct { + AddF func(context.Context, *chronograf.Mapping) (*chronograf.Mapping, error) + AllF func(context.Context) ([]chronograf.Mapping, error) + DeleteF func(context.Context, *chronograf.Mapping) error + UpdateF func(context.Context, *chronograf.Mapping) error + GetF func(context.Context, string) (*chronograf.Mapping, error) +} + +func (s *MappingsStore) Add(ctx context.Context, m *chronograf.Mapping) (*chronograf.Mapping, error) { + return s.AddF(ctx, m) +} + +func (s *MappingsStore) All(ctx context.Context) ([]chronograf.Mapping, error) { + return s.AllF(ctx) +} + +func (s *MappingsStore) Delete(ctx context.Context, m *chronograf.Mapping) error { + return s.DeleteF(ctx, m) +} + +func (s *MappingsStore) Get(ctx context.Context, id string) (*chronograf.Mapping, error) { + return s.GetF(ctx, id) +} + +func (s *MappingsStore) Update(ctx context.Context, m *chronograf.Mapping) error { + return s.UpdateF(ctx, m) +} diff --git a/mocks/store.go b/mocks/store.go index ebc05ea49..8cb27b11b 100644 --- a/mocks/store.go +++ b/mocks/store.go @@ -9,6 +9,7 @@ import ( // Store is a server.DataStore type Store struct { SourcesStore chronograf.SourcesStore + MappingsStore chronograf.MappingsStore ServersStore chronograf.ServersStore LayoutsStore chronograf.LayoutsStore UsersStore chronograf.UsersStore @@ -36,6 +37,9 @@ func (s *Store) Users(ctx context.Context) chronograf.UsersStore { func (s *Store) Organizations(ctx context.Context) chronograf.OrganizationsStore { return s.OrganizationsStore } +func (s *Store) Mappings(ctx context.Context) chronograf.MappingsStore { + return s.MappingsStore +} func (s *Store) Dashboards(ctx context.Context) chronograf.DashboardsStore { return s.DashboardsStore diff --git a/noop/mappings.go b/noop/mappings.go new file mode 100644 index 000000000..e6f5a73bb --- /dev/null +++ b/noop/mappings.go @@ -0,0 +1,33 @@ +package noop + +import ( + "context" + "fmt" + + "github.com/influxdata/chronograf" +) + +// ensure MappingsStore implements chronograf.MappingsStore +var _ chronograf.MappingsStore = &MappingsStore{} + +type MappingsStore struct{} + +func (s *MappingsStore) All(context.Context) ([]chronograf.Mapping, error) { + return nil, fmt.Errorf("no mappings found") +} + +func (s *MappingsStore) Add(context.Context, *chronograf.Mapping) (*chronograf.Mapping, error) { + return nil, fmt.Errorf("failed to add mapping") +} + +func (s *MappingsStore) Delete(context.Context, *chronograf.Mapping) error { + return fmt.Errorf("failed to delete mapping") +} + +func (s *MappingsStore) Get(ctx context.Context, ID string) (*chronograf.Mapping, error) { + return nil, chronograf.ErrMappingNotFound +} + +func (s *MappingsStore) Update(context.Context, *chronograf.Mapping) error { + return fmt.Errorf("failed to update mapping") +} diff --git a/server/mapping.go b/server/mapping.go index 07b675188..f5cda33d8 100644 --- a/server/mapping.go +++ b/server/mapping.go @@ -1,56 +1,61 @@ package server import ( + "context" + "fmt" "strings" "github.com/influxdata/chronograf" "github.com/influxdata/chronograf/oauth2" - "github.com/influxdata/chronograf/roles" ) -func MappedRole(o chronograf.Organization, p oauth2.Principal) *chronograf.Role { - roles := []*chronograf.Role{} - for _, mapping := range o.Mappings { - role := applyMapping(mapping, p) - if role != nil { - role.Organization = o.ID - roles = append(roles, role) +func (s *Service) mapPrincipalToRoles(ctx context.Context, p oauth2.Principal) ([]chronograf.Role, error) { + mappings, err := s.Store.Mappings(ctx).All(ctx) + if err != nil { + return nil, err + } + roles := []chronograf.Role{} +MappingsLoop: + for _, mapping := range mappings { + if applyMapping(mapping, p) { + org, err := s.Store.Organizations(ctx).Get(ctx, chronograf.OrganizationQuery{ID: &mapping.Organization}) + if err != nil { + return nil, err + } + + for _, role := range roles { + if role.Organization == org.ID { + continue MappingsLoop + } + } + roles = append(roles, chronograf.Role{Organization: org.ID, Name: org.DefaultRole}) } } + fmt.Println("Here 2") - return maxRole(roles) + return roles, nil } -func applyMapping(m chronograf.Mapping, p oauth2.Principal) *chronograf.Role { +func applyMapping(m chronograf.Mapping, p oauth2.Principal) bool { switch m.Provider { case chronograf.MappingWildcard, p.Issuer: default: - return nil + return false } switch m.Scheme { case chronograf.MappingWildcard, "oauth2": default: - return nil + return false } if m.Group == chronograf.MappingWildcard { - return &chronograf.Role{ - Name: m.GrantedRole, - } + return true } groups := strings.Split(p.Group, ",") - match := matchGroup(m.Group, groups) - - if match { - return &chronograf.Role{ - Name: m.GrantedRole, - } - } - - return nil + return matchGroup(m.Group, groups) } func matchGroup(match string, groups []string) bool { @@ -63,49 +68,21 @@ func matchGroup(match string, groups []string) bool { return false } -func maxRole(roles []*chronograf.Role) *chronograf.Role { - var max *chronograf.Role - for _, role := range roles { - max = maximumRole(max, role) - } - - return max -} - -func maximumRole(r1, r2 *chronograf.Role) *chronograf.Role { - if r1 == nil { - return r2 - } - if r2 == nil { - return r2 - } - if r1.Name == roles.AdminRoleName { - return r1 - } - if r2.Name == roles.AdminRoleName { - return r2 - } - - if r1.Name == roles.EditorRoleName { - return r1 - } - if r2.Name == roles.EditorRoleName { - return r2 - } - - if r1.Name == roles.ViewerRoleName { - return r1 - } - if r2.Name == roles.ViewerRoleName { - return r2 - } - - if r1.Name == roles.MemberRoleName { - return r1 - } - if r2.Name == roles.MemberRoleName { - return r2 - } - - return nil -} +//func (r *organizationRequest) ValidMappings() error { +// for _, m := range r.Mappings { +// if m.Provider == "" { +// return fmt.Errorf("mapping must specify provider") +// } +// if m.Scheme == "" { +// return fmt.Errorf("mapping must specify scheme") +// } +// if m.Group == "" { +// return fmt.Errorf("mapping must specify group") +// } +// if m.GrantedRole == "" { +// return fmt.Errorf("mapping must specify grantedRole") +// } +// } +// +// return nil +//} diff --git a/server/mapping_test.go b/server/mapping_test.go index 084b0e77a..9e99d837b 100644 --- a/server/mapping_test.go +++ b/server/mapping_test.go @@ -1,391 +1,381 @@ package server_test -import ( - "testing" - - "github.com/google/go-cmp/cmp" - "github.com/influxdata/chronograf" - "github.com/influxdata/chronograf/oauth2" - "github.com/influxdata/chronograf/roles" - "github.com/influxdata/chronograf/server" -) - -func TestMappedRole(t *testing.T) { - type args struct { - org chronograf.Organization - principal oauth2.Principal - } - type wants struct { - role *chronograf.Role - } - - tests := []struct { - name string - args args - wants wants - }{ - { - name: "single mapping all wildcards", - args: args{ - org: chronograf.Organization{ - ID: "cool", - Name: "Cool Org", - Mappings: []chronograf.Mapping{ - chronograf.Mapping{ - Provider: chronograf.MappingWildcard, - Scheme: chronograf.MappingWildcard, - Group: chronograf.MappingWildcard, - GrantedRole: roles.ViewerRoleName, - }, - }, - }, - }, - wants: wants{ - role: &chronograf.Role{ - Name: roles.ViewerRoleName, - Organization: "cool", - }, - }, - }, - { - name: "two mapping all wildcards", - args: args{ - org: chronograf.Organization{ - ID: "cool", - Name: "Cool Org", - Mappings: []chronograf.Mapping{ - chronograf.Mapping{ - Provider: chronograf.MappingWildcard, - Scheme: chronograf.MappingWildcard, - Group: chronograf.MappingWildcard, - GrantedRole: roles.ViewerRoleName, - }, - chronograf.Mapping{ - Provider: chronograf.MappingWildcard, - Scheme: chronograf.MappingWildcard, - Group: chronograf.MappingWildcard, - GrantedRole: roles.EditorRoleName, - }, - }, - }, - }, - wants: wants{ - role: &chronograf.Role{ - Name: roles.EditorRoleName, - Organization: "cool", - }, - }, - }, - { - name: "two mapping all wildcards, different order", - args: args{ - org: chronograf.Organization{ - ID: "cool", - Name: "Cool Org", - Mappings: []chronograf.Mapping{ - chronograf.Mapping{ - Provider: chronograf.MappingWildcard, - Scheme: chronograf.MappingWildcard, - Group: chronograf.MappingWildcard, - GrantedRole: roles.EditorRoleName, - }, - chronograf.Mapping{ - Provider: chronograf.MappingWildcard, - Scheme: chronograf.MappingWildcard, - Group: chronograf.MappingWildcard, - GrantedRole: roles.ViewerRoleName, - }, - }, - }, - }, - wants: wants{ - role: &chronograf.Role{ - Name: roles.EditorRoleName, - Organization: "cool", - }, - }, - }, - { - name: "two mappings with explicit principal", - args: args{ - principal: oauth2.Principal{ - Subject: "billieta@influxdata.com", - Issuer: "google", - Group: "influxdata.com", - }, - org: chronograf.Organization{ - ID: "cool", - Name: "Cool Org", - Mappings: []chronograf.Mapping{ - chronograf.Mapping{ - Provider: chronograf.MappingWildcard, - Scheme: "oauth2", - Group: chronograf.MappingWildcard, - GrantedRole: roles.MemberRoleName, - }, - chronograf.Mapping{ - Provider: chronograf.MappingWildcard, - Scheme: chronograf.MappingWildcard, - Group: chronograf.MappingWildcard, - GrantedRole: roles.MemberRoleName, - }, - }, - }, - }, - wants: wants{ - role: &chronograf.Role{ - Name: roles.MemberRoleName, - Organization: "cool", - }, - }, - }, - { - name: "different two mapping all wildcards", - args: args{ - org: chronograf.Organization{ - ID: "cool", - Name: "Cool Org", - Mappings: []chronograf.Mapping{ - chronograf.Mapping{ - Provider: chronograf.MappingWildcard, - Scheme: chronograf.MappingWildcard, - Group: chronograf.MappingWildcard, - GrantedRole: roles.ViewerRoleName, - }, - chronograf.Mapping{ - Provider: chronograf.MappingWildcard, - Scheme: chronograf.MappingWildcard, - Group: chronograf.MappingWildcard, - GrantedRole: roles.MemberRoleName, - }, - }, - }, - }, - wants: wants{ - role: &chronograf.Role{ - Name: roles.ViewerRoleName, - Organization: "cool", - }, - }, - }, - { - name: "different two mapping all wildcards, different ordering", - args: args{ - org: chronograf.Organization{ - ID: "cool", - Name: "Cool Org", - Mappings: []chronograf.Mapping{ - chronograf.Mapping{ - Provider: chronograf.MappingWildcard, - Scheme: chronograf.MappingWildcard, - Group: chronograf.MappingWildcard, - GrantedRole: roles.MemberRoleName, - }, - chronograf.Mapping{ - Provider: chronograf.MappingWildcard, - Scheme: chronograf.MappingWildcard, - Group: chronograf.MappingWildcard, - GrantedRole: roles.ViewerRoleName, - }, - }, - }, - }, - wants: wants{ - role: &chronograf.Role{ - Name: roles.ViewerRoleName, - Organization: "cool", - }, - }, - }, - { - name: "three mapping all wildcards", - args: args{ - org: chronograf.Organization{ - ID: "cool", - Name: "Cool Org", - Mappings: []chronograf.Mapping{ - chronograf.Mapping{ - Provider: chronograf.MappingWildcard, - Scheme: chronograf.MappingWildcard, - Group: chronograf.MappingWildcard, - GrantedRole: roles.EditorRoleName, - }, - chronograf.Mapping{ - Provider: chronograf.MappingWildcard, - Scheme: chronograf.MappingWildcard, - Group: chronograf.MappingWildcard, - GrantedRole: roles.ViewerRoleName, - }, - chronograf.Mapping{ - Provider: chronograf.MappingWildcard, - Scheme: chronograf.MappingWildcard, - Group: chronograf.MappingWildcard, - GrantedRole: roles.AdminRoleName, - }, - }, - }, - }, - wants: wants{ - role: &chronograf.Role{ - Name: roles.AdminRoleName, - Organization: "cool", - }, - }, - }, - { - name: "three mapping only one match", - args: args{ - principal: oauth2.Principal{ - Subject: "billieta@influxdata.com", - Issuer: "google", - Group: "influxdata.com", - }, - org: chronograf.Organization{ - ID: "cool", - Name: "Cool Org", - Mappings: []chronograf.Mapping{ - chronograf.Mapping{ - Provider: "google", - Scheme: chronograf.MappingWildcard, - Group: "influxdata.com", - GrantedRole: roles.EditorRoleName, - }, - chronograf.Mapping{ - Provider: "google", - Scheme: chronograf.MappingWildcard, - Group: "not_influxdata", - GrantedRole: roles.AdminRoleName, - }, - chronograf.Mapping{ - Provider: chronograf.MappingWildcard, - Scheme: "ldap", - Group: chronograf.MappingWildcard, - GrantedRole: roles.AdminRoleName, - }, - }, - }, - }, - wants: wants{ - role: &chronograf.Role{ - Name: roles.EditorRoleName, - Organization: "cool", - }, - }, - }, - { - name: "three mapping only two matches", - args: args{ - principal: oauth2.Principal{ - Subject: "billieta@influxdata.com", - Issuer: "google", - Group: "influxdata.com", - }, - org: chronograf.Organization{ - ID: "cool", - Name: "Cool Org", - Mappings: []chronograf.Mapping{ - chronograf.Mapping{ - Provider: "google", - Scheme: chronograf.MappingWildcard, - Group: "influxdata.com", - GrantedRole: roles.AdminRoleName, - }, - chronograf.Mapping{ - Provider: "google", - Scheme: chronograf.MappingWildcard, - Group: chronograf.MappingWildcard, - GrantedRole: roles.EditorRoleName, - }, - chronograf.Mapping{ - Provider: chronograf.MappingWildcard, - Scheme: "ldap", - Group: chronograf.MappingWildcard, - GrantedRole: roles.AdminRoleName, - }, - }, - }, - }, - wants: wants{ - role: &chronograf.Role{ - Name: roles.AdminRoleName, - Organization: "cool", - }, - }, - }, - { - name: "missing provider", - args: args{ - principal: oauth2.Principal{ - Subject: "billieta@influxdata.com", - Issuer: "google", - Group: "influxdata.com", - }, - org: chronograf.Organization{ - ID: "cool", - Name: "Cool Org", - Mappings: []chronograf.Mapping{ - chronograf.Mapping{ - Provider: "", - Scheme: "ldap", - Group: chronograf.MappingWildcard, - GrantedRole: roles.AdminRoleName, - }, - }, - }, - }, - wants: wants{ - role: nil, - }, - }, - { - name: "user is in multiple github groups", - args: args{ - principal: oauth2.Principal{ - Subject: "billieta@influxdata.com", - Issuer: "github", - Group: "influxdata,another,mimi", - }, - org: chronograf.Organization{ - ID: "cool", - Name: "Cool Org", - Mappings: []chronograf.Mapping{ - chronograf.Mapping{ - Provider: "github", - Scheme: chronograf.MappingWildcard, - Group: "influxdata", - GrantedRole: roles.MemberRoleName, - }, - chronograf.Mapping{ - Provider: "github", - Scheme: chronograf.MappingWildcard, - Group: "mimi", - GrantedRole: roles.EditorRoleName, - }, - chronograf.Mapping{ - Provider: "github", - Scheme: chronograf.MappingWildcard, - Group: "another", - GrantedRole: roles.AdminRoleName, - }, - }, - }, - }, - wants: wants{ - role: &chronograf.Role{ - Name: roles.AdminRoleName, - Organization: "cool", - }, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - role := server.MappedRole(tt.args.org, tt.args.principal) - - if diff := cmp.Diff(role, tt.wants.role); diff != "" { - t.Errorf("%q. MappedRole():\n-got/+want\ndiff %s", tt.name, diff) - } - }) - } -} +//func TestMappedRole(t *testing.T) { +// type args struct { +// org chronograf.Organization +// principal oauth2.Principal +// } +// type wants struct { +// role *chronograf.Role +// } +// +// tests := []struct { +// name string +// args args +// wants wants +// }{ +// { +// name: "single mapping all wildcards", +// args: args{ +// org: chronograf.Organization{ +// ID: "cool", +// Name: "Cool Org", +// Mappings: []chronograf.Mapping{ +// chronograf.Mapping{ +// Provider: chronograf.MappingWildcard, +// Scheme: chronograf.MappingWildcard, +// Group: chronograf.MappingWildcard, +// GrantedRole: roles.ViewerRoleName, +// }, +// }, +// }, +// }, +// wants: wants{ +// role: &chronograf.Role{ +// Name: roles.ViewerRoleName, +// Organization: "cool", +// }, +// }, +// }, +// { +// name: "two mapping all wildcards", +// args: args{ +// org: chronograf.Organization{ +// ID: "cool", +// Name: "Cool Org", +// Mappings: []chronograf.Mapping{ +// chronograf.Mapping{ +// Provider: chronograf.MappingWildcard, +// Scheme: chronograf.MappingWildcard, +// Group: chronograf.MappingWildcard, +// GrantedRole: roles.ViewerRoleName, +// }, +// chronograf.Mapping{ +// Provider: chronograf.MappingWildcard, +// Scheme: chronograf.MappingWildcard, +// Group: chronograf.MappingWildcard, +// GrantedRole: roles.EditorRoleName, +// }, +// }, +// }, +// }, +// wants: wants{ +// role: &chronograf.Role{ +// Name: roles.EditorRoleName, +// Organization: "cool", +// }, +// }, +// }, +// { +// name: "two mapping all wildcards, different order", +// args: args{ +// org: chronograf.Organization{ +// ID: "cool", +// Name: "Cool Org", +// Mappings: []chronograf.Mapping{ +// chronograf.Mapping{ +// Provider: chronograf.MappingWildcard, +// Scheme: chronograf.MappingWildcard, +// Group: chronograf.MappingWildcard, +// GrantedRole: roles.EditorRoleName, +// }, +// chronograf.Mapping{ +// Provider: chronograf.MappingWildcard, +// Scheme: chronograf.MappingWildcard, +// Group: chronograf.MappingWildcard, +// GrantedRole: roles.ViewerRoleName, +// }, +// }, +// }, +// }, +// wants: wants{ +// role: &chronograf.Role{ +// Name: roles.EditorRoleName, +// Organization: "cool", +// }, +// }, +// }, +// { +// name: "two mappings with explicit principal", +// args: args{ +// principal: oauth2.Principal{ +// Subject: "billieta@influxdata.com", +// Issuer: "google", +// Group: "influxdata.com", +// }, +// org: chronograf.Organization{ +// ID: "cool", +// Name: "Cool Org", +// Mappings: []chronograf.Mapping{ +// chronograf.Mapping{ +// Provider: chronograf.MappingWildcard, +// Scheme: "oauth2", +// Group: chronograf.MappingWildcard, +// GrantedRole: roles.MemberRoleName, +// }, +// chronograf.Mapping{ +// Provider: chronograf.MappingWildcard, +// Scheme: chronograf.MappingWildcard, +// Group: chronograf.MappingWildcard, +// GrantedRole: roles.MemberRoleName, +// }, +// }, +// }, +// }, +// wants: wants{ +// role: &chronograf.Role{ +// Name: roles.MemberRoleName, +// Organization: "cool", +// }, +// }, +// }, +// { +// name: "different two mapping all wildcards", +// args: args{ +// org: chronograf.Organization{ +// ID: "cool", +// Name: "Cool Org", +// Mappings: []chronograf.Mapping{ +// chronograf.Mapping{ +// Provider: chronograf.MappingWildcard, +// Scheme: chronograf.MappingWildcard, +// Group: chronograf.MappingWildcard, +// GrantedRole: roles.ViewerRoleName, +// }, +// chronograf.Mapping{ +// Provider: chronograf.MappingWildcard, +// Scheme: chronograf.MappingWildcard, +// Group: chronograf.MappingWildcard, +// GrantedRole: roles.MemberRoleName, +// }, +// }, +// }, +// }, +// wants: wants{ +// role: &chronograf.Role{ +// Name: roles.ViewerRoleName, +// Organization: "cool", +// }, +// }, +// }, +// { +// name: "different two mapping all wildcards, different ordering", +// args: args{ +// org: chronograf.Organization{ +// ID: "cool", +// Name: "Cool Org", +// Mappings: []chronograf.Mapping{ +// chronograf.Mapping{ +// Provider: chronograf.MappingWildcard, +// Scheme: chronograf.MappingWildcard, +// Group: chronograf.MappingWildcard, +// GrantedRole: roles.MemberRoleName, +// }, +// chronograf.Mapping{ +// Provider: chronograf.MappingWildcard, +// Scheme: chronograf.MappingWildcard, +// Group: chronograf.MappingWildcard, +// GrantedRole: roles.ViewerRoleName, +// }, +// }, +// }, +// }, +// wants: wants{ +// role: &chronograf.Role{ +// Name: roles.ViewerRoleName, +// Organization: "cool", +// }, +// }, +// }, +// { +// name: "three mapping all wildcards", +// args: args{ +// org: chronograf.Organization{ +// ID: "cool", +// Name: "Cool Org", +// Mappings: []chronograf.Mapping{ +// chronograf.Mapping{ +// Provider: chronograf.MappingWildcard, +// Scheme: chronograf.MappingWildcard, +// Group: chronograf.MappingWildcard, +// GrantedRole: roles.EditorRoleName, +// }, +// chronograf.Mapping{ +// Provider: chronograf.MappingWildcard, +// Scheme: chronograf.MappingWildcard, +// Group: chronograf.MappingWildcard, +// GrantedRole: roles.ViewerRoleName, +// }, +// chronograf.Mapping{ +// Provider: chronograf.MappingWildcard, +// Scheme: chronograf.MappingWildcard, +// Group: chronograf.MappingWildcard, +// GrantedRole: roles.AdminRoleName, +// }, +// }, +// }, +// }, +// wants: wants{ +// role: &chronograf.Role{ +// Name: roles.AdminRoleName, +// Organization: "cool", +// }, +// }, +// }, +// { +// name: "three mapping only one match", +// args: args{ +// principal: oauth2.Principal{ +// Subject: "billieta@influxdata.com", +// Issuer: "google", +// Group: "influxdata.com", +// }, +// org: chronograf.Organization{ +// ID: "cool", +// Name: "Cool Org", +// Mappings: []chronograf.Mapping{ +// chronograf.Mapping{ +// Provider: "google", +// Scheme: chronograf.MappingWildcard, +// Group: "influxdata.com", +// GrantedRole: roles.EditorRoleName, +// }, +// chronograf.Mapping{ +// Provider: "google", +// Scheme: chronograf.MappingWildcard, +// Group: "not_influxdata", +// GrantedRole: roles.AdminRoleName, +// }, +// chronograf.Mapping{ +// Provider: chronograf.MappingWildcard, +// Scheme: "ldap", +// Group: chronograf.MappingWildcard, +// GrantedRole: roles.AdminRoleName, +// }, +// }, +// }, +// }, +// wants: wants{ +// role: &chronograf.Role{ +// Name: roles.EditorRoleName, +// Organization: "cool", +// }, +// }, +// }, +// { +// name: "three mapping only two matches", +// args: args{ +// principal: oauth2.Principal{ +// Subject: "billieta@influxdata.com", +// Issuer: "google", +// Group: "influxdata.com", +// }, +// org: chronograf.Organization{ +// ID: "cool", +// Name: "Cool Org", +// Mappings: []chronograf.Mapping{ +// chronograf.Mapping{ +// Provider: "google", +// Scheme: chronograf.MappingWildcard, +// Group: "influxdata.com", +// GrantedRole: roles.AdminRoleName, +// }, +// chronograf.Mapping{ +// Provider: "google", +// Scheme: chronograf.MappingWildcard, +// Group: chronograf.MappingWildcard, +// GrantedRole: roles.EditorRoleName, +// }, +// chronograf.Mapping{ +// Provider: chronograf.MappingWildcard, +// Scheme: "ldap", +// Group: chronograf.MappingWildcard, +// GrantedRole: roles.AdminRoleName, +// }, +// }, +// }, +// }, +// wants: wants{ +// role: &chronograf.Role{ +// Name: roles.AdminRoleName, +// Organization: "cool", +// }, +// }, +// }, +// { +// name: "missing provider", +// args: args{ +// principal: oauth2.Principal{ +// Subject: "billieta@influxdata.com", +// Issuer: "google", +// Group: "influxdata.com", +// }, +// org: chronograf.Organization{ +// ID: "cool", +// Name: "Cool Org", +// Mappings: []chronograf.Mapping{ +// chronograf.Mapping{ +// Provider: "", +// Scheme: "ldap", +// Group: chronograf.MappingWildcard, +// GrantedRole: roles.AdminRoleName, +// }, +// }, +// }, +// }, +// wants: wants{ +// role: nil, +// }, +// }, +// { +// name: "user is in multiple github groups", +// args: args{ +// principal: oauth2.Principal{ +// Subject: "billieta@influxdata.com", +// Issuer: "github", +// Group: "influxdata,another,mimi", +// }, +// org: chronograf.Organization{ +// ID: "cool", +// Name: "Cool Org", +// Mappings: []chronograf.Mapping{ +// chronograf.Mapping{ +// Provider: "github", +// Scheme: chronograf.MappingWildcard, +// Group: "influxdata", +// GrantedRole: roles.MemberRoleName, +// }, +// chronograf.Mapping{ +// Provider: "github", +// Scheme: chronograf.MappingWildcard, +// Group: "mimi", +// GrantedRole: roles.EditorRoleName, +// }, +// chronograf.Mapping{ +// Provider: "github", +// Scheme: chronograf.MappingWildcard, +// Group: "another", +// GrantedRole: roles.AdminRoleName, +// }, +// }, +// }, +// }, +// wants: wants{ +// role: &chronograf.Role{ +// Name: roles.AdminRoleName, +// Organization: "cool", +// }, +// }, +// }, +// } +// +// for _, tt := range tests { +// t.Run(tt.name, func(t *testing.T) { +// role := server.MappedRole(tt.args.org, tt.args.principal) +// +// if diff := cmp.Diff(role, tt.wants.role); diff != "" { +// t.Errorf("%q. MappedRole():\n-got/+want\ndiff %s", tt.name, diff) +// } +// }) +// } +//} diff --git a/server/me.go b/server/me.go index 3b53b9dc8..dd0bdf4be 100644 --- a/server/me.go +++ b/server/me.go @@ -270,6 +270,7 @@ func (s *Service) Me(w http.ResponseWriter, r *http.Request) { return } + // TODO(desa) get rid of this // If users must be explicitly added to the default organization, respond with 403 // forbidden if !defaultOrg.Public { @@ -288,18 +289,11 @@ func (s *Service) Me(w http.ResponseWriter, r *http.Request) { SuperAdmin: s.newUsersAreSuperAdmin(), } - allOrgs, err := s.Store.Organizations(serverCtx).All(serverCtx) + roles, err := s.mapPrincipalToRoles(serverCtx, p) if err != nil { Error(w, http.StatusInternalServerError, err.Error(), s.Logger) return } - roles := []chronograf.Role{} - for _, org := range allOrgs { - role := MappedRole(org, p) - if role != nil { - roles = append(roles, *role) - } - } user.Roles = roles diff --git a/server/me_test.go b/server/me_test.go index f52a9725c..7fefe8ccf 100644 --- a/server/me_test.go +++ b/server/me_test.go @@ -23,6 +23,7 @@ func TestService_Me(t *testing.T) { type fields struct { UsersStore chronograf.UsersStore OrganizationsStore chronograf.OrganizationsStore + MappingsStore chronograf.MappingsStore ConfigStore chronograf.ConfigStore Logger chronograf.Logger UseAuth bool @@ -56,6 +57,18 @@ func TestService_Me(t *testing.T) { }, }, }, + MappingsStore: &mocks.MappingsStore{ + AllF: func(ctx context.Context) ([]chronograf.Mapping, error) { + return []chronograf.Mapping{ + { + Organization: "0", + Provider: chronograf.MappingWildcard, + Scheme: chronograf.MappingWildcard, + Group: chronograf.MappingWildcard, + }, + }, nil + }, + }, OrganizationsStore: &mocks.OrganizationsStore{ DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) { return &chronograf.Organization{ @@ -63,7 +76,6 @@ func TestService_Me(t *testing.T) { Name: "Default", DefaultRole: roles.ViewerRoleName, Public: false, - Mappings: []chronograf.Mapping{}, }, nil }, GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { @@ -74,14 +86,12 @@ func TestService_Me(t *testing.T) { Name: "Default", DefaultRole: roles.ViewerRoleName, Public: false, - Mappings: []chronograf.Mapping{}, }, nil case "1": return &chronograf.Organization{ - ID: "1", - Name: "The Bad Place", - Public: false, - Mappings: []chronograf.Mapping{}, + ID: "1", + Name: "The Bad Place", + Public: false, }, nil } return nil, nil @@ -124,6 +134,11 @@ func TestService_Me(t *testing.T) { fields: fields{ UseAuth: true, Logger: log.New(log.DebugLevel), + MappingsStore: &mocks.MappingsStore{ + AllF: func(ctx context.Context) ([]chronograf.Mapping, error) { + return []chronograf.Mapping{}, nil + }, + }, OrganizationsStore: &mocks.OrganizationsStore{ DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) { return &chronograf.Organization{ @@ -131,7 +146,6 @@ func TestService_Me(t *testing.T) { Name: "Default", DefaultRole: roles.ViewerRoleName, Public: false, - Mappings: []chronograf.Mapping{}, }, nil }, GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { @@ -142,14 +156,12 @@ func TestService_Me(t *testing.T) { Name: "Default", DefaultRole: roles.ViewerRoleName, Public: true, - Mappings: []chronograf.Mapping{}, }, nil case "1": return &chronograf.Organization{ - ID: "1", - Name: "The Bad Place", - Public: true, - Mappings: []chronograf.Mapping{}, + ID: "1", + Name: "The Bad Place", + Public: true, }, nil } return nil, nil @@ -182,7 +194,7 @@ func TestService_Me(t *testing.T) { }, wantStatus: http.StatusOK, wantContentType: "application/json", - wantBody: `{"name":"me","roles":[{"name":"viewer","organization":"0"}],"provider":"github","scheme":"oauth2","superAdmin":true,"links":{"self":"/chronograf/v1/users/0"},"organizations":[{"id":"0","name":"Default","defaultRole":"viewer","public":true,"mappings":[]}],"currentOrganization":{"id":"0","name":"Default","defaultRole":"viewer","public":true,"mappings":[]}}`, + wantBody: `{"name":"me","roles":[{"name":"viewer","organization":"0"}],"provider":"github","scheme":"oauth2","superAdmin":true,"links":{"self":"/chronograf/v1/users/0"},"organizations":[{"id":"0","name":"Default","defaultRole":"viewer","public":true}],"currentOrganization":{"id":"0","name":"Default","defaultRole":"viewer","public":true}}`, }, { name: "Existing user - private default org", @@ -193,6 +205,11 @@ func TestService_Me(t *testing.T) { fields: fields{ UseAuth: true, Logger: log.New(log.DebugLevel), + MappingsStore: &mocks.MappingsStore{ + AllF: func(ctx context.Context) ([]chronograf.Mapping, error) { + return []chronograf.Mapping{}, nil + }, + }, OrganizationsStore: &mocks.OrganizationsStore{ DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) { return &chronograf.Organization{ @@ -200,7 +217,6 @@ func TestService_Me(t *testing.T) { Name: "Default", DefaultRole: roles.ViewerRoleName, Public: false, - Mappings: []chronograf.Mapping{}, }, nil }, GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { @@ -211,14 +227,12 @@ func TestService_Me(t *testing.T) { Name: "Default", DefaultRole: roles.ViewerRoleName, Public: true, - Mappings: []chronograf.Mapping{}, }, nil case "1": return &chronograf.Organization{ - ID: "1", - Name: "The Bad Place", - Public: true, - Mappings: []chronograf.Mapping{}, + ID: "1", + Name: "The Bad Place", + Public: true, }, nil } return nil, nil @@ -261,6 +275,11 @@ func TestService_Me(t *testing.T) { fields: fields{ UseAuth: true, Logger: log.New(log.DebugLevel), + MappingsStore: &mocks.MappingsStore{ + AllF: func(ctx context.Context) ([]chronograf.Mapping, error) { + return []chronograf.Mapping{}, nil + }, + }, OrganizationsStore: &mocks.OrganizationsStore{ DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) { return &chronograf.Organization{ @@ -268,7 +287,6 @@ func TestService_Me(t *testing.T) { Name: "Default", DefaultRole: roles.ViewerRoleName, Public: true, - Mappings: []chronograf.Mapping{}, }, nil }, GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { @@ -279,14 +297,12 @@ func TestService_Me(t *testing.T) { Name: "Default", DefaultRole: roles.ViewerRoleName, Public: true, - Mappings: []chronograf.Mapping{}, }, nil case "1": return &chronograf.Organization{ - ID: "1", - Name: "The Bad Place", - Public: true, - Mappings: []chronograf.Mapping{}, + ID: "1", + Name: "The Bad Place", + Public: true, }, nil } return nil, nil @@ -318,7 +334,7 @@ func TestService_Me(t *testing.T) { }, wantStatus: http.StatusOK, wantContentType: "application/json", - wantBody: `{"name":"me","roles":[{"name":"viewer","organization":"0"}],"provider":"github","scheme":"oauth2","links":{"self":"/chronograf/v1/users/0"},"organizations":[{"id":"0","name":"Default","defaultRole":"viewer","public":true,"mappings":[]}],"currentOrganization":{"id":"0","name":"Default","defaultRole":"viewer","public":true,"mappings":[]}}`, + wantBody: `{"name":"me","roles":[{"name":"viewer","organization":"0"}],"provider":"github","scheme":"oauth2","links":{"self":"/chronograf/v1/users/0"},"organizations":[{"id":"0","name":"Default","defaultRole":"viewer","public":true}],"currentOrganization":{"id":"0","name":"Default","defaultRole":"viewer","public":true}}`, }, { name: "Existing user - organization doesn't exist", @@ -329,6 +345,11 @@ func TestService_Me(t *testing.T) { fields: fields{ UseAuth: true, Logger: log.New(log.DebugLevel), + MappingsStore: &mocks.MappingsStore{ + AllF: func(ctx context.Context) ([]chronograf.Mapping, error) { + return []chronograf.Mapping{}, nil + }, + }, OrganizationsStore: &mocks.OrganizationsStore{ DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) { return &chronograf.Organization{ @@ -336,7 +357,6 @@ func TestService_Me(t *testing.T) { Name: "Default", DefaultRole: roles.ViewerRoleName, Public: true, - Mappings: []chronograf.Mapping{}, }, nil }, GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { @@ -347,7 +367,6 @@ func TestService_Me(t *testing.T) { Name: "Default", DefaultRole: roles.ViewerRoleName, Public: true, - Mappings: []chronograf.Mapping{}, }, nil } return nil, chronograf.ErrOrganizationNotFound @@ -394,6 +413,18 @@ func TestService_Me(t *testing.T) { }, }, }, + MappingsStore: &mocks.MappingsStore{ + AllF: func(ctx context.Context) ([]chronograf.Mapping, error) { + return []chronograf.Mapping{ + { + Organization: "0", + Provider: chronograf.MappingWildcard, + Scheme: chronograf.MappingWildcard, + Group: chronograf.MappingWildcard, + }, + }, nil + }, + }, OrganizationsStore: &mocks.OrganizationsStore{ DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) { return &chronograf.Organization{ @@ -401,7 +432,6 @@ func TestService_Me(t *testing.T) { Name: "The Gnarly Default", DefaultRole: roles.ViewerRoleName, Public: true, - Mappings: []chronograf.Mapping{}, }, nil }, GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { @@ -410,7 +440,6 @@ func TestService_Me(t *testing.T) { Name: "The Gnarly Default", DefaultRole: roles.ViewerRoleName, Public: true, - Mappings: []chronograf.Mapping{}, }, nil }, AllF: func(ctx context.Context) ([]chronograf.Organization, error) { @@ -420,14 +449,6 @@ func TestService_Me(t *testing.T) { Name: "The Gnarly Default", DefaultRole: roles.ViewerRoleName, Public: true, - Mappings: []chronograf.Mapping{ - chronograf.Mapping{ - Provider: chronograf.MappingWildcard, - Scheme: chronograf.MappingWildcard, - Group: chronograf.MappingWildcard, - GrantedRole: roles.ViewerRoleName, - }, - }, }, }, nil }, @@ -457,7 +478,7 @@ func TestService_Me(t *testing.T) { }, wantStatus: http.StatusOK, wantContentType: "application/json", - wantBody: `{"name":"secret","roles":[{"name":"viewer","organization":"0"}],"provider":"auth0","scheme":"oauth2","superAdmin":true,"links":{"self":"/chronograf/v1/users/0"},"organizations":[{"id":"0","name":"The Gnarly Default","defaultRole":"viewer","public":true,"mappings":[]}],"currentOrganization":{"id":"0","name":"The Gnarly Default","defaultRole":"viewer","public":true,"mappings":[]}}`, + wantBody: `{"name":"secret","roles":[{"name":"viewer","organization":"0"}],"provider":"auth0","scheme":"oauth2","superAdmin":true,"links":{"self":"/chronograf/v1/users/0"},"organizations":[{"id":"0","name":"The Gnarly Default","defaultRole":"viewer","public":true}],"currentOrganization":{"id":"0","name":"The Gnarly Default","defaultRole":"viewer","public":true}}`, }, { name: "New user - New users not super admin, not first user", @@ -475,6 +496,18 @@ func TestService_Me(t *testing.T) { }, }, }, + MappingsStore: &mocks.MappingsStore{ + AllF: func(ctx context.Context) ([]chronograf.Mapping, error) { + return []chronograf.Mapping{ + { + Organization: "0", + Provider: chronograf.MappingWildcard, + Scheme: chronograf.MappingWildcard, + Group: chronograf.MappingWildcard, + }, + }, nil + }, + }, OrganizationsStore: &mocks.OrganizationsStore{ DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) { return &chronograf.Organization{ @@ -482,7 +515,6 @@ func TestService_Me(t *testing.T) { Name: "The Gnarly Default", DefaultRole: roles.ViewerRoleName, Public: true, - Mappings: []chronograf.Mapping{}, }, nil }, GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { @@ -491,7 +523,6 @@ func TestService_Me(t *testing.T) { Name: "The Gnarly Default", DefaultRole: roles.ViewerRoleName, Public: true, - Mappings: []chronograf.Mapping{}, }, nil }, AllF: func(ctx context.Context) ([]chronograf.Organization, error) { @@ -501,14 +532,6 @@ func TestService_Me(t *testing.T) { Name: "The Gnarly Default", DefaultRole: roles.ViewerRoleName, Public: true, - Mappings: []chronograf.Mapping{ - chronograf.Mapping{ - Provider: chronograf.MappingWildcard, - Scheme: chronograf.MappingWildcard, - Group: chronograf.MappingWildcard, - GrantedRole: roles.ViewerRoleName, - }, - }, }, }, nil }, @@ -538,7 +561,7 @@ func TestService_Me(t *testing.T) { }, wantStatus: http.StatusOK, wantContentType: "application/json", - wantBody: `{"name":"secret","roles":[{"name":"viewer","organization":"0"}],"provider":"auth0","scheme":"oauth2","links":{"self":"/chronograf/v1/users/0"},"organizations":[{"id":"0","name":"The Gnarly Default","defaultRole":"viewer","public":true,"mappings":[]}],"currentOrganization":{"id":"0","name":"The Gnarly Default","defaultRole":"viewer","public":true,"mappings":[]}}`, + wantBody: `{"name":"secret","roles":[{"name":"viewer","organization":"0"}],"provider":"auth0","scheme":"oauth2","links":{"self":"/chronograf/v1/users/0"},"organizations":[{"id":"0","name":"The Gnarly Default","defaultRole":"viewer","public":true}],"currentOrganization":{"id":"0","name":"The Gnarly Default","defaultRole":"viewer","public":true}}`, }, { name: "New user - New users not super admin, first user", @@ -556,6 +579,18 @@ func TestService_Me(t *testing.T) { }, }, }, + MappingsStore: &mocks.MappingsStore{ + AllF: func(ctx context.Context) ([]chronograf.Mapping, error) { + return []chronograf.Mapping{ + { + Organization: "0", + Provider: chronograf.MappingWildcard, + Scheme: chronograf.MappingWildcard, + Group: chronograf.MappingWildcard, + }, + }, nil + }, + }, OrganizationsStore: &mocks.OrganizationsStore{ DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) { return &chronograf.Organization{ @@ -563,7 +598,6 @@ func TestService_Me(t *testing.T) { Name: "The Gnarly Default", DefaultRole: roles.ViewerRoleName, Public: true, - Mappings: []chronograf.Mapping{}, }, nil }, GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { @@ -572,7 +606,6 @@ func TestService_Me(t *testing.T) { Name: "The Gnarly Default", DefaultRole: roles.ViewerRoleName, Public: true, - Mappings: []chronograf.Mapping{}, }, nil }, AllF: func(ctx context.Context) ([]chronograf.Organization, error) { @@ -582,14 +615,6 @@ func TestService_Me(t *testing.T) { Name: "The Gnarly Default", DefaultRole: roles.ViewerRoleName, Public: true, - Mappings: []chronograf.Mapping{ - chronograf.Mapping{ - Provider: chronograf.MappingWildcard, - Scheme: chronograf.MappingWildcard, - Group: chronograf.MappingWildcard, - GrantedRole: roles.ViewerRoleName, - }, - }, }, }, nil }, @@ -619,7 +644,7 @@ func TestService_Me(t *testing.T) { }, wantStatus: http.StatusOK, wantContentType: "application/json", - wantBody: `{"name":"secret","roles":[{"name":"viewer","organization":"0"}],"provider":"auth0","scheme":"oauth2","superAdmin":true,"links":{"self":"/chronograf/v1/users/0"},"organizations":[{"id":"0","name":"The Gnarly Default","defaultRole":"viewer","public":true,"mappings":[]}],"currentOrganization":{"id":"0","name":"The Gnarly Default","defaultRole":"viewer","public":true,"mappings":[]}}`, + wantBody: `{"name":"secret","roles":[{"name":"viewer","organization":"0"}],"provider":"auth0","scheme":"oauth2","superAdmin":true,"links":{"self":"/chronograf/v1/users/0"},"organizations":[{"id":"0","name":"The Gnarly Default","defaultRole":"viewer","public":true}],"currentOrganization":{"id":"0","name":"The Gnarly Default","defaultRole":"viewer","public":true}}`, }, { name: "Error adding user", @@ -636,21 +661,24 @@ func TestService_Me(t *testing.T) { }, }, }, + MappingsStore: &mocks.MappingsStore{ + AllF: func(ctx context.Context) ([]chronograf.Mapping, error) { + return []chronograf.Mapping{}, nil + }, + }, OrganizationsStore: &mocks.OrganizationsStore{ DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) { return &chronograf.Organization{ - ID: "0", - Name: "The Bad Place", - Public: true, - Mappings: []chronograf.Mapping{}, + ID: "0", + Name: "The Bad Place", + Public: true, }, nil }, GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { return &chronograf.Organization{ - ID: "0", - Name: "The Bad Place", - Public: true, - Mappings: []chronograf.Mapping{}, + ID: "0", + Name: "The Bad Place", + Public: true, }, nil }, AllF: func(ctx context.Context) ([]chronograf.Organization, error) { @@ -660,14 +688,6 @@ func TestService_Me(t *testing.T) { Name: "The Bad Place", DefaultRole: roles.ViewerRoleName, Public: true, - Mappings: []chronograf.Mapping{ - chronograf.Mapping{ - Provider: chronograf.MappingWildcard, - Scheme: chronograf.MappingWildcard, - Group: chronograf.MappingWildcard, - GrantedRole: roles.ViewerRoleName, - }, - }, }, }, nil }, @@ -751,6 +771,11 @@ func TestService_Me(t *testing.T) { fields: fields{ UseAuth: true, Logger: log.New(log.DebugLevel), + MappingsStore: &mocks.MappingsStore{ + AllF: func(ctx context.Context) ([]chronograf.Mapping, error) { + return []chronograf.Mapping{}, nil + }, + }, OrganizationsStore: &mocks.OrganizationsStore{ DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) { return &chronograf.Organization{ @@ -758,7 +783,6 @@ func TestService_Me(t *testing.T) { Name: "The Bad Place", DefaultRole: roles.MemberRoleName, Public: false, - Mappings: []chronograf.Mapping{}, }, nil }, }, @@ -789,101 +813,6 @@ func TestService_Me(t *testing.T) { wantContentType: "application/json", wantBody: `{"code":403,"message":"This organization is private. To gain access, you must be explicitly added by an administrator."}`, }, - { - name: "New user multiple mappings", - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest("GET", "http://example.com/foo", nil), - }, - fields: fields{ - UseAuth: true, - Logger: log.New(log.DebugLevel), - ConfigStore: &mocks.ConfigStore{ - Config: &chronograf.Config{ - Auth: chronograf.AuthConfig{ - SuperAdminNewUsers: false, - }, - }, - }, - OrganizationsStore: &mocks.OrganizationsStore{ - DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "0", - Name: "The Gnarly Default", - DefaultRole: roles.ViewerRoleName, - Public: true, - Mappings: []chronograf.Mapping{}, - }, nil - }, - GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "0", - Name: "The Gnarly Default", - DefaultRole: roles.ViewerRoleName, - Public: true, - Mappings: []chronograf.Mapping{}, - }, nil - }, - AllF: func(ctx context.Context) ([]chronograf.Organization, error) { - return []chronograf.Organization{ - chronograf.Organization{ - ID: "0", - Name: "The Gnarly Default", - DefaultRole: roles.ViewerRoleName, - Public: true, - Mappings: []chronograf.Mapping{ - chronograf.Mapping{ - Provider: chronograf.MappingWildcard, - Scheme: chronograf.MappingWildcard, - Group: chronograf.MappingWildcard, - GrantedRole: roles.ViewerRoleName, - }, - }, - }, - chronograf.Organization{ - ID: "1", - Name: "another org", - DefaultRole: roles.ViewerRoleName, - Public: true, - Mappings: []chronograf.Mapping{ - chronograf.Mapping{ - Provider: chronograf.MappingWildcard, - Scheme: chronograf.MappingWildcard, - Group: chronograf.MappingWildcard, - GrantedRole: roles.EditorRoleName, - }, - }, - }, - }, nil - }, - }, - UsersStore: &mocks.UsersStore{ - NumF: func(ctx context.Context) (int, error) { - // This function gets to verify that there is at least one first user - return 1, nil - }, - GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - if q.Name == nil || q.Provider == nil || q.Scheme == nil { - return nil, fmt.Errorf("Invalid user query: missing Name, Provider, and/or Scheme") - } - return nil, chronograf.ErrUserNotFound - }, - AddF: func(ctx context.Context, u *chronograf.User) (*chronograf.User, error) { - return u, nil - }, - UpdateF: func(ctx context.Context, u *chronograf.User) error { - return nil - }, - }, - }, - principal: oauth2.Principal{ - Subject: "secret", - Issuer: "auth0", - }, - wantStatus: http.StatusOK, - wantContentType: "application/json", - wantBody: `{"name":"secret","roles":[{"name":"viewer","organization":"0"},{"name":"editor","organization":"1"}],"provider":"auth0","scheme":"oauth2","links":{"self":"/chronograf/v1/users/0"},"organizations":[{"id":"0","name":"The Gnarly Default","defaultRole":"viewer","public":true,"mappings":[]},{"id":"0","name":"The Gnarly Default","defaultRole":"viewer","public":true,"mappings":[]}],"currentOrganization":{"id":"0","name":"The Gnarly Default","defaultRole":"viewer","public":true,"mappings":[]}}`, - }, } for _, tt := range tests { tt.args.r = tt.args.r.WithContext(context.WithValue(context.Background(), oauth2.PrincipalKey, tt.principal)) @@ -891,6 +820,7 @@ func TestService_Me(t *testing.T) { Store: &mocks.Store{ UsersStore: tt.fields.UsersStore, OrganizationsStore: tt.fields.OrganizationsStore, + MappingsStore: tt.fields.MappingsStore, ConfigStore: tt.fields.ConfigStore, }, Logger: tt.fields.Logger, @@ -981,7 +911,6 @@ func TestService_UpdateMe(t *testing.T) { Name: "Default", DefaultRole: roles.AdminRoleName, Public: true, - Mappings: []chronograf.Mapping{}, }, nil }, GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { @@ -995,14 +924,12 @@ func TestService_UpdateMe(t *testing.T) { Name: "Default", DefaultRole: roles.AdminRoleName, Public: true, - Mappings: []chronograf.Mapping{}, }, nil case "1337": return &chronograf.Organization{ - ID: "1337", - Name: "The ShillBillThrilliettas", - Public: true, - Mappings: []chronograf.Mapping{}, + ID: "1337", + Name: "The ShillBillThrilliettas", + Public: true, }, nil } return nil, nil @@ -1015,7 +942,7 @@ func TestService_UpdateMe(t *testing.T) { }, wantStatus: http.StatusOK, wantContentType: "application/json", - wantBody: `{"name":"me","roles":[{"name":"admin","organization":"1337"},{"name":"admin","organization":"0"}],"provider":"github","scheme":"oauth2","links":{"self":"/chronograf/v1/users/0"},"organizations":[{"id":"0","name":"Default","defaultRole":"admin","public":true,"mappings":[]},{"id":"1337","name":"The ShillBillThrilliettas","public":true,"mappings":[]}],"currentOrganization":{"id":"1337","name":"The ShillBillThrilliettas","public":true,"mappings":[]}}`, + wantBody: `{"name":"me","roles":[{"name":"admin","organization":"1337"},{"name":"admin","organization":"0"}],"provider":"github","scheme":"oauth2","links":{"self":"/chronograf/v1/users/0"},"organizations":[{"id":"0","name":"Default","defaultRole":"admin","public":true},{"id":"1337","name":"The ShillBillThrilliettas","public":true}],"currentOrganization":{"id":"1337","name":"The ShillBillThrilliettas","public":true}}`, }, { name: "Change the current User's organization", @@ -1058,7 +985,6 @@ func TestService_UpdateMe(t *testing.T) { Name: "Default", DefaultRole: roles.EditorRoleName, Public: true, - Mappings: []chronograf.Mapping{}, }, nil }, GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { @@ -1068,10 +994,9 @@ func TestService_UpdateMe(t *testing.T) { switch *q.ID { case "1337": return &chronograf.Organization{ - ID: "1337", - Name: "The ThrillShilliettos", - Public: false, - Mappings: []chronograf.Mapping{}, + ID: "1337", + Name: "The ThrillShilliettos", + Public: false, }, nil case "0": return &chronograf.Organization{ @@ -1079,7 +1004,6 @@ func TestService_UpdateMe(t *testing.T) { Name: "Default", DefaultRole: roles.EditorRoleName, Public: true, - Mappings: []chronograf.Mapping{}, }, nil } return nil, nil @@ -1093,7 +1017,7 @@ func TestService_UpdateMe(t *testing.T) { }, wantStatus: http.StatusOK, wantContentType: "application/json", - wantBody: `{"name":"me","roles":[{"name":"admin","organization":"1337"},{"name":"editor","organization":"0"}],"provider":"github","scheme":"oauth2","links":{"self":"/chronograf/v1/users/0"},"organizations":[{"id":"0","name":"Default","defaultRole":"editor","public":true,"mappings":[]},{"id":"1337","name":"The ThrillShilliettos","public":false,"mappings":[]}],"currentOrganization":{"id":"1337","name":"The ThrillShilliettos","public":false,"mappings":[]}}`, + wantBody: `{"name":"me","roles":[{"name":"admin","organization":"1337"},{"name":"editor","organization":"0"}],"provider":"github","scheme":"oauth2","links":{"self":"/chronograf/v1/users/0"},"organizations":[{"id":"0","name":"Default","defaultRole":"editor","public":true},{"id":"1337","name":"The ThrillShilliettos","public":false}],"currentOrganization":{"id":"1337","name":"The ThrillShilliettos","public":false}}`, }, { name: "Unable to find requested user in valid organization", @@ -1132,8 +1056,7 @@ func TestService_UpdateMe(t *testing.T) { OrganizationsStore: &mocks.OrganizationsStore{ DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) { return &chronograf.Organization{ - ID: "0", - Mappings: []chronograf.Mapping{}, + ID: "0", }, nil }, GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { @@ -1141,10 +1064,9 @@ func TestService_UpdateMe(t *testing.T) { return nil, fmt.Errorf("Invalid organization query: missing ID") } return &chronograf.Organization{ - ID: "1337", - Name: "The ShillBillThrilliettas", - Public: true, - Mappings: []chronograf.Mapping{}, + ID: "1337", + Name: "The ShillBillThrilliettas", + Public: true, }, nil }, }, @@ -1195,8 +1117,7 @@ func TestService_UpdateMe(t *testing.T) { OrganizationsStore: &mocks.OrganizationsStore{ DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) { return &chronograf.Organization{ - ID: "0", - Mappings: []chronograf.Mapping{}, + ID: "0", }, nil }, GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { diff --git a/server/mux.go b/server/mux.go index aaa1648ea..7a0e20eb9 100644 --- a/server/mux.go +++ b/server/mux.go @@ -118,6 +118,13 @@ func NewMux(opts MuxOpts, service Service) http.Handler { router.PATCH("/chronograf/v1/organizations/:id", EnsureSuperAdmin(service.UpdateOrganization)) router.DELETE("/chronograf/v1/organizations/:id", EnsureSuperAdmin(service.RemoveOrganization)) + // Mappings + //router.GET("/chronograf/v1/mappings", EnsureSuperAdmin(service.Mappings)) + //router.POST("/chronograf/v1/mappings", EnsureSuperAdmin(service.NewMapping)) + + //router.PUT("/chronograf/v1/mappings/:id", EnsureSuperAdmin(service.UpdateMappings)) + //router.DELETE("/chronograf/v1/mappings/:id", EnsureSuperAdmin(service.RemoveMappings)) + // Sources router.GET("/chronograf/v1/sources", EnsureViewer(service.Sources)) router.POST("/chronograf/v1/sources", EnsureEditor(service.NewSource)) diff --git a/server/organizations.go b/server/organizations.go index 6f27bbba4..5b2227953 100644 --- a/server/organizations.go +++ b/server/organizations.go @@ -13,10 +13,9 @@ import ( ) type organizationRequest struct { - Name string `json:"name"` - DefaultRole string `json:"defaultRole"` - Public *bool `json:"public"` - Mappings []chronograf.Mapping `json:"mappings"` + Name string `json:"name"` + DefaultRole string `json:"defaultRole"` + Public *bool `json:"public"` } func (r *organizationRequest) ValidCreate() error { @@ -24,36 +23,11 @@ func (r *organizationRequest) ValidCreate() error { return fmt.Errorf("Name required on Chronograf Organization request body") } - if len(r.Mappings) > 0 { - if err := r.ValidMappings(); err != nil { - return err - } - } - return r.ValidDefaultRole() } -func (r *organizationRequest) ValidMappings() error { - for _, m := range r.Mappings { - if m.Provider == "" { - return fmt.Errorf("mapping must specify provider") - } - if m.Scheme == "" { - return fmt.Errorf("mapping must specify scheme") - } - if m.Group == "" { - return fmt.Errorf("mapping must specify group") - } - if m.GrantedRole == "" { - return fmt.Errorf("mapping must specify grantedRole") - } - } - - return nil -} - func (r *organizationRequest) ValidUpdate() error { - if r.Name == "" && r.DefaultRole == "" && r.Public == nil && len(r.Mappings) == 0 { + if r.Name == "" && r.DefaultRole == "" && r.Public == nil { return fmt.Errorf("No fields to update") } @@ -61,12 +35,6 @@ func (r *organizationRequest) ValidUpdate() error { return r.ValidDefaultRole() } - if len(r.Mappings) > 0 { - if err := r.ValidMappings(); err != nil { - return err - } - } - return nil } @@ -92,12 +60,6 @@ func newOrganizationResponse(o *chronograf.Organization) *organizationResponse { if o == nil { o = &chronograf.Organization{} } - // This ensures that any user response with no roles returns an empty array instead of - // null when marshaled into JSON. That way, JavaScript doesn't need any guard on the - // key existing and it can simply be iterated over. - if o.Mappings == nil { - o.Mappings = []chronograf.Mapping{} - } return &organizationResponse{ Organization: *o, Links: selfLinks{ @@ -249,10 +211,6 @@ func (s *Service) UpdateOrganization(w http.ResponseWriter, r *http.Request) { org.Public = *req.Public } - if req.Mappings != nil { - org.Mappings = req.Mappings - } - err = s.Store.Organizations(ctx).Update(ctx, org) if err != nil { Error(w, http.StatusBadRequest, err.Error(), s.Logger) diff --git a/server/organizations_test.go b/server/organizations_test.go index 515ce7256..3d6efd05b 100644 --- a/server/organizations_test.go +++ b/server/organizations_test.go @@ -65,10 +65,10 @@ func TestService_OrganizationID(t *testing.T) { id: "1337", wantStatus: http.StatusOK, wantContentType: "application/json", - wantBody: `{"links":{"self":"/chronograf/v1/organizations/1337"},"id":"1337","name":"The Good Place","public":false,"mappings":[]}`, + wantBody: `{"links":{"self":"/chronograf/v1/organizations/1337"},"id":"1337","name":"The Good Place","public":false}`, }, { - name: "Get Single Organization - with mappings", + name: "Get Single Organization", args: args{ w: httptest.NewRecorder(), r: httptest.NewRequest( @@ -86,14 +86,6 @@ func TestService_OrganizationID(t *testing.T) { return &chronograf.Organization{ ID: "1337", Name: "The Good Place", - Mappings: []chronograf.Mapping{ - { - Provider: chronograf.MappingWildcard, - Scheme: chronograf.MappingWildcard, - Group: chronograf.MappingWildcard, - GrantedRole: roles.ViewerRoleName, - }, - }, }, nil default: return nil, fmt.Errorf("Organization with ID %s not found", *q.ID) @@ -104,7 +96,7 @@ func TestService_OrganizationID(t *testing.T) { id: "1337", wantStatus: http.StatusOK, wantContentType: "application/json", - wantBody: `{"id":"1337","name":"The Good Place","public":false,"mappings":[{"provider":"*","scheme":"*","group":"*","grantedRole":"viewer"}],"links":{"self":"/chronograf/v1/organizations/1337"}}`, + wantBody: `{"id":"1337","name":"The Good Place","public":false,"links":{"self":"/chronograf/v1/organizations/1337"}}`, }, } @@ -186,14 +178,6 @@ func TestService_Organizations(t *testing.T) { ID: "100", Name: "The Bad Place", Public: false, - Mappings: []chronograf.Mapping{ - { - Provider: chronograf.MappingWildcard, - Scheme: chronograf.MappingWildcard, - Group: chronograf.MappingWildcard, - GrantedRole: roles.ViewerRoleName, - }, - }, }, }, nil }, @@ -201,7 +185,7 @@ func TestService_Organizations(t *testing.T) { }, wantStatus: http.StatusOK, wantContentType: "application/json", - wantBody: `{"links":{"self":"/chronograf/v1/organizations"},"organizations":[{"links":{"self":"/chronograf/v1/organizations/1337"},"mappings":[],"id":"1337","name":"The Good Place","public":false},{"links":{"self":"/chronograf/v1/organizations/100"},"id":"100","name":"The Bad Place","public":false,"mappings":[{"provider":"*","scheme":"*","group":"*","grantedRole":"viewer"}]}]}`, + wantBody: `{"links":{"self":"/chronograf/v1/organizations"},"organizations":[{"links":{"self":"/chronograf/v1/organizations/1337"},"id":"1337","name":"The Good Place","public":false},{"links":{"self":"/chronograf/v1/organizations/100"},"id":"100","name":"The Bad Place","public":false}]}`, }, } @@ -286,243 +270,7 @@ func TestService_UpdateOrganization(t *testing.T) { id: "1337", wantStatus: http.StatusOK, wantContentType: "application/json", - wantBody: `{"id":"1337","mappings":[],"name":"The Bad Place","defaultRole":"viewer","links":{"self":"/chronograf/v1/organizations/1337"},"public":false}`, - }, - { - name: "Update Organization mappings", - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest( - "GET", - "http://any.url", // can be any valid URL as we are bypassing mux - nil, - ), - org: &organizationRequest{ - Mappings: []chronograf.Mapping{ - { - Provider: chronograf.MappingWildcard, - Scheme: chronograf.MappingWildcard, - Group: chronograf.MappingWildcard, - GrantedRole: roles.AdminRoleName, - }, - }, - }, - }, - fields: fields{ - Logger: log.New(log.DebugLevel), - OrganizationsStore: &mocks.OrganizationsStore{ - UpdateF: func(ctx context.Context, o *chronograf.Organization) error { - return nil - }, - GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "1337", - Name: "The Good Place", - DefaultRole: roles.ViewerRoleName, - Mappings: []chronograf.Mapping{ - { - Provider: chronograf.MappingWildcard, - Scheme: chronograf.MappingWildcard, - Group: chronograf.MappingWildcard, - GrantedRole: roles.ViewerRoleName, - }, - }, - }, nil - }, - }, - }, - id: "1337", - wantStatus: http.StatusOK, - wantContentType: "application/json", - wantBody: `{"id":"1337","public":false,"mappings":[{"provider":"*","scheme":"*","group":"*","grantedRole":"admin"}],"name":"The Good Place","defaultRole":"viewer","links":{"self":"/chronograf/v1/organizations/1337"}}`, - }, - { - name: "Fail to update Organization mappings - no provider", - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest( - "GET", - "http://any.url", // can be any valid URL as we are bypassing mux - nil, - ), - org: &organizationRequest{ - Mappings: []chronograf.Mapping{ - { - Scheme: chronograf.MappingWildcard, - Group: chronograf.MappingWildcard, - GrantedRole: roles.AdminRoleName, - }, - }, - }, - }, - fields: fields{ - Logger: log.New(log.DebugLevel), - OrganizationsStore: &mocks.OrganizationsStore{ - UpdateF: func(ctx context.Context, o *chronograf.Organization) error { - return nil - }, - GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "1337", - Name: "The Good Place", - DefaultRole: roles.ViewerRoleName, - Mappings: []chronograf.Mapping{ - { - Provider: chronograf.MappingWildcard, - Scheme: chronograf.MappingWildcard, - Group: chronograf.MappingWildcard, - GrantedRole: roles.ViewerRoleName, - }, - }, - }, nil - }, - }, - }, - id: "1337", - wantStatus: http.StatusUnprocessableEntity, - wantContentType: "application/json", - wantBody: `{"code":422,"message":"mapping must specify provider"}`, - }, - { - name: "Fail to update Organization mappings - no scheme", - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest( - "GET", - "http://any.url", // can be any valid URL as we are bypassing mux - nil, - ), - org: &organizationRequest{ - Mappings: []chronograf.Mapping{ - { - Provider: chronograf.MappingWildcard, - Group: chronograf.MappingWildcard, - GrantedRole: roles.AdminRoleName, - }, - }, - }, - }, - fields: fields{ - Logger: log.New(log.DebugLevel), - OrganizationsStore: &mocks.OrganizationsStore{ - UpdateF: func(ctx context.Context, o *chronograf.Organization) error { - return nil - }, - GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "1337", - Name: "The Good Place", - DefaultRole: roles.ViewerRoleName, - Mappings: []chronograf.Mapping{ - { - Provider: chronograf.MappingWildcard, - Scheme: chronograf.MappingWildcard, - Group: chronograf.MappingWildcard, - GrantedRole: roles.ViewerRoleName, - }, - }, - }, nil - }, - }, - }, - id: "1337", - wantStatus: http.StatusUnprocessableEntity, - wantContentType: "application/json", - wantBody: `{"code":422,"message":"mapping must specify scheme"}`, - }, - { - name: "Fail to update Organization mappings - no group", - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest( - "GET", - "http://any.url", // can be any valid URL as we are bypassing mux - nil, - ), - org: &organizationRequest{ - Mappings: []chronograf.Mapping{ - { - Provider: chronograf.MappingWildcard, - Scheme: chronograf.MappingWildcard, - GrantedRole: roles.AdminRoleName, - }, - }, - }, - }, - fields: fields{ - Logger: log.New(log.DebugLevel), - OrganizationsStore: &mocks.OrganizationsStore{ - UpdateF: func(ctx context.Context, o *chronograf.Organization) error { - return nil - }, - GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "1337", - Name: "The Good Place", - DefaultRole: roles.ViewerRoleName, - Mappings: []chronograf.Mapping{ - { - Provider: chronograf.MappingWildcard, - Scheme: chronograf.MappingWildcard, - Group: chronograf.MappingWildcard, - GrantedRole: roles.ViewerRoleName, - }, - }, - }, nil - }, - }, - }, - id: "1337", - wantStatus: http.StatusUnprocessableEntity, - wantContentType: "application/json", - wantBody: `{"code":422,"message":"mapping must specify group"}`, - }, - { - name: "Fail to update Organization mappings - no granted role", - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest( - "GET", - "http://any.url", // can be any valid URL as we are bypassing mux - nil, - ), - org: &organizationRequest{ - Mappings: []chronograf.Mapping{ - { - Provider: chronograf.MappingWildcard, - Scheme: chronograf.MappingWildcard, - Group: chronograf.MappingWildcard, - }, - }, - }, - }, - fields: fields{ - Logger: log.New(log.DebugLevel), - OrganizationsStore: &mocks.OrganizationsStore{ - UpdateF: func(ctx context.Context, o *chronograf.Organization) error { - return nil - }, - GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "1337", - Name: "The Good Place", - DefaultRole: roles.ViewerRoleName, - Mappings: []chronograf.Mapping{ - { - Provider: chronograf.MappingWildcard, - Scheme: chronograf.MappingWildcard, - Group: chronograf.MappingWildcard, - GrantedRole: roles.ViewerRoleName, - }, - }, - }, nil - }, - }, - }, - id: "1337", - wantStatus: http.StatusUnprocessableEntity, - wantContentType: "application/json", - wantBody: `{"code":422,"message":"mapping must specify grantedRole"}`, + wantBody: `{"id":"1337","name":"The Bad Place","defaultRole":"viewer","links":{"self":"/chronograf/v1/organizations/1337"},"public":false}`, }, { name: "Update Organization public", @@ -556,7 +304,7 @@ func TestService_UpdateOrganization(t *testing.T) { id: "0", wantStatus: http.StatusOK, wantContentType: "application/json", - wantBody: `{"id":"0","mappings":[],"name":"The Good Place","defaultRole":"viewer","public":false,"links":{"self":"/chronograf/v1/organizations/0"}}`, + wantBody: `{"id":"0","name":"The Good Place","defaultRole":"viewer","public":false,"links":{"self":"/chronograf/v1/organizations/0"}}`, }, { name: "Update Organization - nothing to update", @@ -622,7 +370,7 @@ func TestService_UpdateOrganization(t *testing.T) { id: "1337", wantStatus: http.StatusOK, wantContentType: "application/json", - wantBody: `{"links":{"self":"/chronograf/v1/organizations/1337"},"mappings":[],"id":"1337","name":"The Good Place","defaultRole":"viewer","public":false}`, + wantBody: `{"links":{"self":"/chronograf/v1/organizations/1337"},"id":"1337","name":"The Good Place","defaultRole":"viewer","public":false}`, }, { name: "Update Organization - invalid update", @@ -865,7 +613,7 @@ func TestService_NewOrganization(t *testing.T) { }, wantStatus: http.StatusCreated, wantContentType: "application/json", - wantBody: `{"id":"1337","mappings":[],"public":false,"name":"The Good Place","links":{"self":"/chronograf/v1/organizations/1337"}}`, + wantBody: `{"id":"1337","public":false,"name":"The Good Place","links":{"self":"/chronograf/v1/organizations/1337"}}`, }, { name: "Fail to create Organization - no org name", @@ -906,54 +654,6 @@ func TestService_NewOrganization(t *testing.T) { wantContentType: "application/json", wantBody: `{"code":422,"message":"Name required on Chronograf Organization request body"}`, }, - { - name: "Fail to create Organization - mappings missing provider", - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest( - "GET", - "http://any.url", // can be any valid URL as we are bypassing mux - nil, - ), - user: &chronograf.User{ - ID: 1, - Name: "bobetta", - Provider: "github", - Scheme: "oauth2", - }, - org: &organizationRequest{ - Name: "The Good Place", - Mappings: []chronograf.Mapping{ - { - Scheme: chronograf.MappingWildcard, - Group: chronograf.MappingWildcard, - GrantedRole: roles.ViewerRoleName, - }, - }, - }, - }, - fields: fields{ - Logger: log.New(log.DebugLevel), - UsersStore: &mocks.UsersStore{ - AddF: func(ctx context.Context, u *chronograf.User) (*chronograf.User, error) { - return &chronograf.User{ - ID: 1, - Name: "bobetta", - Provider: "github", - Scheme: "oauth2", - }, nil - }, - }, - OrganizationsStore: &mocks.OrganizationsStore{ - AddF: func(ctx context.Context, o *chronograf.Organization) (*chronograf.Organization, error) { - return nil, nil - }, - }, - }, - wantStatus: http.StatusUnprocessableEntity, - wantContentType: "application/json", - wantBody: `{"code":422,"message":"mapping must specify provider"}`, - }, { name: "Create Organization - no user on context", args: args{ diff --git a/server/server.go b/server/server.go index c469dd7d6..d93974ce5 100644 --- a/server/server.go +++ b/server/server.go @@ -484,6 +484,7 @@ func openService(ctx context.Context, buildInfo chronograf.BuildInfo, boltPath s OrganizationsStore: organizations, UsersStore: db.UsersStore, ConfigStore: db.ConfigStore, + MappingsStore: db.MappingsStore, }, Logger: logger, UseAuth: useAuth, diff --git a/server/stores.go b/server/stores.go index 7f9d8ac52..9646a4b7d 100644 --- a/server/stores.go +++ b/server/stores.go @@ -88,6 +88,7 @@ type DataStore interface { Layouts(ctx context.Context) chronograf.LayoutsStore Users(ctx context.Context) chronograf.UsersStore Organizations(ctx context.Context) chronograf.OrganizationsStore + Mappings(ctx context.Context) chronograf.MappingsStore Dashboards(ctx context.Context) chronograf.DashboardsStore Config(ctx context.Context) chronograf.ConfigStore } @@ -102,6 +103,7 @@ type Store struct { LayoutsStore chronograf.LayoutsStore UsersStore chronograf.UsersStore DashboardsStore chronograf.DashboardsStore + MappingsStore chronograf.MappingsStore OrganizationsStore chronograf.OrganizationsStore ConfigStore chronograf.ConfigStore } @@ -191,3 +193,14 @@ func (s *Store) Config(ctx context.Context) chronograf.ConfigStore { } return &noop.ConfigStore{} } + +// Mappings returns the underlying MappingsStore. +func (s *Store) Mappings(ctx context.Context) chronograf.MappingsStore { + if isServer := hasServerContext(ctx); isServer { + return s.MappingsStore + } + if isSuperAdmin := hasSuperAdminContext(ctx); isSuperAdmin { + return s.MappingsStore + } + return &noop.MappingsStore{} +} From 2926a127d048e1dc5879b1c64351c35600b905bd Mon Sep 17 00:00:00 2001 From: Alex P Date: Mon, 5 Feb 2018 13:45:50 -0800 Subject: [PATCH 31/98] Remove infinite scroll from logs table and render only 200 most recent logs --- ui/src/kapacitor/components/LogsTable.js | 35 ++++++++++++------------ 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/ui/src/kapacitor/components/LogsTable.js b/ui/src/kapacitor/components/LogsTable.js index 070740cf0..c3f86f00c 100644 --- a/ui/src/kapacitor/components/LogsTable.js +++ b/ui/src/kapacitor/components/LogsTable.js @@ -1,29 +1,30 @@ import React, {PropTypes} from 'react' -import InfiniteScroll from 'shared/components/InfiniteScroll' import LogsTableRow from 'src/kapacitor/components/LogsTableRow' +import FancyScrollbar from 'src/shared/components/FancyScrollbar' + +const numLogsToRender = 200 const LogsTable = ({logs, onToggleExpandLog}) =>
-

Logs

+

{`${numLogsToRender} Most Recent Logs`}

-
- {logs.length - ? - - )} + + {logs + .slice(0, numLogsToRender) + .map((log, i) => + - :
} -
+ )} +
const {arrayOf, func, shape, string} = PropTypes From efd691d1b5c2c439a9ed9fe3e7749c61085fa09f Mon Sep 17 00:00:00 2001 From: Michael Desa Date: Mon, 5 Feb 2018 16:47:44 -0500 Subject: [PATCH 32/98] Add Mappings CUDA routes --- bolt/mapping_test.go | 18 +- bolt/organizations.go | 12 + integrations/server_test.go | 365 ++++++++++++++++++ server/mapping.go | 199 +++++++++- server/mapping_test.go | 741 ++++++++++++++++++------------------ server/mux.go | 8 +- 6 files changed, 928 insertions(+), 415 deletions(-) diff --git a/bolt/mapping_test.go b/bolt/mapping_test.go index bbbccfb6d..66a24d187 100644 --- a/bolt/mapping_test.go +++ b/bolt/mapping_test.go @@ -127,12 +127,6 @@ func TestMappingStore_All(t *testing.T) { name: "simple", fields: fields{ mappings: []*chronograf.Mapping{ - &chronograf.Mapping{ - Organization: "default", - Provider: "*", - Scheme: "*", - Group: "*", - }, &chronograf.Mapping{ Organization: "0", Provider: "google", @@ -143,18 +137,18 @@ func TestMappingStore_All(t *testing.T) { }, wants: wants{ mappings: []chronograf.Mapping{ - chronograf.Mapping{ - Organization: "default", - Provider: "*", - Scheme: "*", - Group: "*", - }, chronograf.Mapping{ Organization: "0", Provider: "google", Scheme: "ldap", Group: "*", }, + chronograf.Mapping{ + Organization: "default", + Provider: "*", + Scheme: "*", + Group: "*", + }, }, }, }, diff --git a/bolt/organizations.go b/bolt/organizations.go index 4b0c5dc59..1bc7520f9 100644 --- a/bolt/organizations.go +++ b/bolt/organizations.go @@ -208,6 +208,18 @@ func (s *OrganizationsStore) Delete(ctx context.Context, o *chronograf.Organizat } } + mappings, err := s.client.MappingsStore.All(ctx) + if err != nil { + return err + } + for _, mapping := range mappings { + if mapping.Organization == o.ID { + if err := s.client.MappingsStore.Delete(ctx, &mapping); err != nil { + return err + } + } + } + return nil } diff --git a/integrations/server_test.go b/integrations/server_test.go index c20984655..70c67b0b7 100644 --- a/integrations/server_test.go +++ b/integrations/server_test.go @@ -1577,6 +1577,371 @@ func TestServer(t *testing.T) { "public": true } } +`, + }, + }, + { + name: "GET /mappings", + subName: "get all mappings", + fields: fields{ + Config: &chronograf.Config{ + Auth: chronograf.AuthConfig{ + SuperAdminNewUsers: false, + }, + }, + Mappings: []chronograf.Mapping{ + { + ID: "1", + Organization: "1", + Provider: "*", + Scheme: "*", + Group: "influxdata", + }, + { + ID: "1", + Organization: "1", + Provider: "*", + Scheme: "*", + Group: "*", + }, + { + ID: "2", + Organization: "2", + Provider: "github", + Scheme: "*", + Group: "*", + }, + { + ID: "3", + Organization: "3", + Provider: "auth0", + Scheme: "ldap", + Group: "*", + }, + }, + Organizations: []chronograf.Organization{ + { + ID: "1", + Name: "Sweet", + DefaultRole: roles.ViewerRoleName, + }, + { + ID: "2", + Name: "What", + DefaultRole: roles.EditorRoleName, + }, + { + ID: "3", + Name: "Okay", + DefaultRole: roles.AdminRoleName, + }, + }, + Users: []chronograf.User{ + { + ID: 1, // This is artificial, but should be reflective of the users actual ID + Name: "billibob", + Provider: "github", + Scheme: "oauth2", + SuperAdmin: true, + }, + }, + }, + args: args{ + server: &server.Server{ + GithubClientID: "not empty", + GithubClientSecret: "not empty", + }, + method: "GET", + path: "/chronograf/v1/mappings", + principal: oauth2.Principal{ + Subject: "billibob", + Issuer: "github", + Group: "influxdata,idk,mimi", + }, + }, + wants: wants{ + statusCode: 200, + body: ` +{ + "links": { + "self": "/chronograf/v1/mappings" + }, + "mappings": [ + { + "links": { + "self": "/chronograf/v1/mappings/1" + }, + "id": "default", + "organization": "default", + "provider": "*", + "scheme": "*", + "group": "*" + }, + { + "links": { + "self": "/chronograf/v1/mappings/2" + }, + "id": "default", + "organization": "default", + "provider": "*", + "scheme": "*", + "group": "*" + }, + { + "links": { + "self": "/chronograf/v1/mappings/3" + }, + "id": "default", + "organization": "default", + "provider": "*", + "scheme": "*", + "group": "*" + }, + { + "links": { + "self": "/chronograf/v1/mappings/4" + }, + "id": "default", + "organization": "default", + "provider": "*", + "scheme": "*", + "group": "*" + }, + { + "links": { + "self": "/chronograf/v1/mappings/default" + }, + "id": "default", + "organization": "default", + "provider": "*", + "scheme": "*", + "group": "*" + } + ] +} +`, + }, + }, + { + name: "GET /mappings", + subName: "get all mappings - user is not super admin", + fields: fields{ + Config: &chronograf.Config{ + Auth: chronograf.AuthConfig{ + SuperAdminNewUsers: false, + }, + }, + Mappings: []chronograf.Mapping{ + { + ID: "1", + Organization: "1", + Provider: "*", + Scheme: "*", + Group: "influxdata", + }, + { + ID: "1", + Organization: "1", + Provider: "*", + Scheme: "*", + Group: "*", + }, + { + ID: "2", + Organization: "2", + Provider: "github", + Scheme: "*", + Group: "*", + }, + { + ID: "3", + Organization: "3", + Provider: "auth0", + Scheme: "ldap", + Group: "*", + }, + }, + Organizations: []chronograf.Organization{ + { + ID: "1", + Name: "Sweet", + DefaultRole: roles.ViewerRoleName, + }, + { + ID: "2", + Name: "What", + DefaultRole: roles.EditorRoleName, + }, + { + ID: "3", + Name: "Okay", + DefaultRole: roles.AdminRoleName, + }, + }, + Users: []chronograf.User{ + { + ID: 1, // This is artificial, but should be reflective of the users actual ID + Name: "billibob", + Provider: "github", + Scheme: "oauth2", + SuperAdmin: false, + }, + }, + }, + args: args{ + server: &server.Server{ + GithubClientID: "not empty", + GithubClientSecret: "not empty", + }, + method: "GET", + path: "/chronograf/v1/mappings", + principal: oauth2.Principal{ + Subject: "billibob", + Issuer: "github", + Group: "influxdata,idk,mimi", + }, + }, + wants: wants{ + statusCode: 403, + body: ` +{ + "code": 403, + "message": "User is not authorized" +} +`, + }, + }, + { + name: "POST /mappings", + subName: "create new mapping", + fields: fields{ + Config: &chronograf.Config{ + Auth: chronograf.AuthConfig{ + SuperAdminNewUsers: false, + }, + }, + Mappings: []chronograf.Mapping{}, + Organizations: []chronograf.Organization{ + { + ID: "1", + Name: "Sweet", + DefaultRole: roles.ViewerRoleName, + }, + }, + Users: []chronograf.User{ + { + ID: 1, // This is artificial, but should be reflective of the users actual ID + Name: "billibob", + Provider: "github", + Scheme: "oauth2", + SuperAdmin: true, + }, + }, + }, + args: args{ + server: &server.Server{ + GithubClientID: "not empty", + GithubClientSecret: "not empty", + }, + method: "POST", + path: "/chronograf/v1/mappings", + payload: &chronograf.Mapping{ + ID: "1", + Organization: "1", + Provider: "*", + Scheme: "*", + Group: "influxdata", + }, + principal: oauth2.Principal{ + Subject: "billibob", + Issuer: "github", + Group: "influxdata,idk,mimi", + }, + }, + wants: wants{ + statusCode: 201, + body: ` +{ + "links": { + "self": "/chronograf/v1/mappings/1" + }, + "id": "1", + "organization": "1", + "provider": "*", + "scheme": "*", + "group": "influxdata" +} +`, + }, + }, + { + name: "PUT /mappings", + subName: "update new mapping", + fields: fields{ + Config: &chronograf.Config{ + Auth: chronograf.AuthConfig{ + SuperAdminNewUsers: false, + }, + }, + Mappings: []chronograf.Mapping{ + chronograf.Mapping{ + ID: "1", + Organization: "1", + Provider: "*", + Scheme: "*", + Group: "influxdata", + }, + }, + Organizations: []chronograf.Organization{ + { + ID: "1", + Name: "Sweet", + DefaultRole: roles.ViewerRoleName, + }, + }, + Users: []chronograf.User{ + { + ID: 1, // This is artificial, but should be reflective of the users actual ID + Name: "billibob", + Provider: "github", + Scheme: "oauth2", + SuperAdmin: true, + }, + }, + }, + args: args{ + server: &server.Server{ + GithubClientID: "not empty", + GithubClientSecret: "not empty", + }, + method: "PUT", + path: "/chronograf/v1/mappings/1", + payload: &chronograf.Mapping{ + ID: "1", + Organization: "1", + Provider: "*", + Scheme: "*", + Group: "*", + }, + principal: oauth2.Principal{ + Subject: "billibob", + Issuer: "github", + Group: "influxdata,idk,mimi", + }, + }, + wants: wants{ + statusCode: 200, + body: ` +{ + "links": { + "self": "/chronograf/v1/mappings/1" + }, + "id": "1", + "organization": "1", + "provider": "*", + "scheme": "*", + "group": "*" +} `, }, }, diff --git a/server/mapping.go b/server/mapping.go index f5cda33d8..4105e6b9e 100644 --- a/server/mapping.go +++ b/server/mapping.go @@ -2,9 +2,12 @@ package server import ( "context" + "encoding/json" "fmt" + "net/http" "strings" + "github.com/bouk/httprouter" "github.com/influxdata/chronograf" "github.com/influxdata/chronograf/oauth2" ) @@ -31,7 +34,6 @@ MappingsLoop: roles = append(roles, chronograf.Role{Organization: org.ID, Name: org.DefaultRole}) } } - fmt.Println("Here 2") return roles, nil } @@ -68,21 +70,180 @@ func matchGroup(match string, groups []string) bool { return false } -//func (r *organizationRequest) ValidMappings() error { -// for _, m := range r.Mappings { -// if m.Provider == "" { -// return fmt.Errorf("mapping must specify provider") -// } -// if m.Scheme == "" { -// return fmt.Errorf("mapping must specify scheme") -// } -// if m.Group == "" { -// return fmt.Errorf("mapping must specify group") -// } -// if m.GrantedRole == "" { -// return fmt.Errorf("mapping must specify grantedRole") -// } -// } -// -// return nil -//} +type mappingsRequest chronograf.Mapping + +// Valid determines if a mapping request is valid +func (m *mappingsRequest) Valid() error { + if m.Provider == "" { + return fmt.Errorf("mapping must specify provider") + } + if m.Scheme == "" { + return fmt.Errorf("mapping must specify scheme") + } + if m.Group == "" { + return fmt.Errorf("mapping must specify group") + } + + return nil +} + +type mappingResponse struct { + Links selfLinks `json:"links"` + *chronograf.Mapping +} + +func newMappingResponse(m *chronograf.Mapping) *mappingResponse { + id := "unknown" + if m != nil { + id = m.ID + } + return &mappingResponse{ + Links: selfLinks{ + Self: fmt.Sprintf("/chronograf/v1/mappings/%s", id), + }, + Mapping: m, + } +} + +type mappingsResponse struct { + Links selfLinks `json:"links"` + Mappings []*mappingResponse `json:"mappings"` +} + +func newMappingsResponse(ms []chronograf.Mapping) *mappingsResponse { + mappings := []*mappingResponse{} + for _, m := range ms { + mappings = append(mappings, newMappingResponse(&m)) + } + return &mappingsResponse{ + Links: selfLinks{ + Self: "/chronograf/v1/mappings", + }, + Mappings: mappings, + } +} + +// Mappings retrives all mappings +func (s *Service) Mappings(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + mappings, err := s.Store.Mappings(ctx).All(ctx) + if err != nil { + Error(w, http.StatusInternalServerError, "failed to retrieve mappings from database", s.Logger) + return + } + + res := newMappingsResponse(mappings) + encodeJSON(w, http.StatusOK, res, s.Logger) +} + +// NewMapping adds a new mapping +func (s *Service) NewMapping(w http.ResponseWriter, r *http.Request) { + var req mappingsRequest + 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 + } + + ctx := r.Context() + + // validate that the organization exists + if !s.organizationExists(ctx, req.Organization) { + invalidData(w, fmt.Errorf("organization does not exist"), s.Logger) + return + } + + mapping := &chronograf.Mapping{ + Organization: req.Organization, + Scheme: req.Scheme, + Provider: req.Provider, + Group: req.Group, + } + + m, err := s.Store.Mappings(ctx).Add(ctx, mapping) + if err != nil { + Error(w, http.StatusInternalServerError, "failed to add mapping to database", s.Logger) + return + } + + cu := newMappingResponse(m) + location(w, cu.Links.Self) + encodeJSON(w, http.StatusCreated, cu, s.Logger) +} + +// UpdateMapping updates a mapping +func (s *Service) UpdateMapping(w http.ResponseWriter, r *http.Request) { + var req mappingsRequest + 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 + } + + ctx := r.Context() + + // validate that the organization exists + if !s.organizationExists(ctx, req.Organization) { + invalidData(w, fmt.Errorf("organization does not exist"), s.Logger) + return + } + + mapping := &chronograf.Mapping{ + ID: req.ID, + Organization: req.Organization, + Scheme: req.Scheme, + Provider: req.Provider, + Group: req.Group, + } + + err := s.Store.Mappings(ctx).Update(ctx, mapping) + if err != nil { + Error(w, http.StatusInternalServerError, "failed to update mapping in database", s.Logger) + return + } + + cu := newMappingResponse(mapping) + location(w, cu.Links.Self) + encodeJSON(w, http.StatusOK, cu, s.Logger) +} + +// RemoveMapping removes a mapping +func (s *Service) RemoveMapping(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + id := httprouter.GetParamFromContext(ctx, "id") + + m, err := s.Store.Mappings(ctx).Get(ctx, id) + if err == chronograf.ErrMappingNotFound { + Error(w, http.StatusNotFound, err.Error(), s.Logger) + return + } + + if err != nil { + Error(w, http.StatusInternalServerError, "failed to retrieve mapping from database", s.Logger) + return + } + + if err := s.Store.Mappings(ctx).Delete(ctx, m); err != nil { + Error(w, http.StatusInternalServerError, "failed to remove mapping from database", s.Logger) + return + } + + w.WriteHeader(http.StatusNoContent) +} + +func (s *Service) organizationExists(ctx context.Context, orgID string) bool { + if _, err := s.Store.Organizations(ctx).Get(ctx, chronograf.OrganizationQuery{ID: &orgID}); err != nil { + return false + } + + return true +} diff --git a/server/mapping_test.go b/server/mapping_test.go index 9e99d837b..94f2ac06f 100644 --- a/server/mapping_test.go +++ b/server/mapping_test.go @@ -1,381 +1,362 @@ -package server_test +package server -//func TestMappedRole(t *testing.T) { -// type args struct { -// org chronograf.Organization -// principal oauth2.Principal -// } -// type wants struct { -// role *chronograf.Role -// } -// -// tests := []struct { -// name string -// args args -// wants wants -// }{ -// { -// name: "single mapping all wildcards", -// args: args{ -// org: chronograf.Organization{ -// ID: "cool", -// Name: "Cool Org", -// Mappings: []chronograf.Mapping{ -// chronograf.Mapping{ -// Provider: chronograf.MappingWildcard, -// Scheme: chronograf.MappingWildcard, -// Group: chronograf.MappingWildcard, -// GrantedRole: roles.ViewerRoleName, -// }, -// }, -// }, -// }, -// wants: wants{ -// role: &chronograf.Role{ -// Name: roles.ViewerRoleName, -// Organization: "cool", -// }, -// }, -// }, -// { -// name: "two mapping all wildcards", -// args: args{ -// org: chronograf.Organization{ -// ID: "cool", -// Name: "Cool Org", -// Mappings: []chronograf.Mapping{ -// chronograf.Mapping{ -// Provider: chronograf.MappingWildcard, -// Scheme: chronograf.MappingWildcard, -// Group: chronograf.MappingWildcard, -// GrantedRole: roles.ViewerRoleName, -// }, -// chronograf.Mapping{ -// Provider: chronograf.MappingWildcard, -// Scheme: chronograf.MappingWildcard, -// Group: chronograf.MappingWildcard, -// GrantedRole: roles.EditorRoleName, -// }, -// }, -// }, -// }, -// wants: wants{ -// role: &chronograf.Role{ -// Name: roles.EditorRoleName, -// Organization: "cool", -// }, -// }, -// }, -// { -// name: "two mapping all wildcards, different order", -// args: args{ -// org: chronograf.Organization{ -// ID: "cool", -// Name: "Cool Org", -// Mappings: []chronograf.Mapping{ -// chronograf.Mapping{ -// Provider: chronograf.MappingWildcard, -// Scheme: chronograf.MappingWildcard, -// Group: chronograf.MappingWildcard, -// GrantedRole: roles.EditorRoleName, -// }, -// chronograf.Mapping{ -// Provider: chronograf.MappingWildcard, -// Scheme: chronograf.MappingWildcard, -// Group: chronograf.MappingWildcard, -// GrantedRole: roles.ViewerRoleName, -// }, -// }, -// }, -// }, -// wants: wants{ -// role: &chronograf.Role{ -// Name: roles.EditorRoleName, -// Organization: "cool", -// }, -// }, -// }, -// { -// name: "two mappings with explicit principal", -// args: args{ -// principal: oauth2.Principal{ -// Subject: "billieta@influxdata.com", -// Issuer: "google", -// Group: "influxdata.com", -// }, -// org: chronograf.Organization{ -// ID: "cool", -// Name: "Cool Org", -// Mappings: []chronograf.Mapping{ -// chronograf.Mapping{ -// Provider: chronograf.MappingWildcard, -// Scheme: "oauth2", -// Group: chronograf.MappingWildcard, -// GrantedRole: roles.MemberRoleName, -// }, -// chronograf.Mapping{ -// Provider: chronograf.MappingWildcard, -// Scheme: chronograf.MappingWildcard, -// Group: chronograf.MappingWildcard, -// GrantedRole: roles.MemberRoleName, -// }, -// }, -// }, -// }, -// wants: wants{ -// role: &chronograf.Role{ -// Name: roles.MemberRoleName, -// Organization: "cool", -// }, -// }, -// }, -// { -// name: "different two mapping all wildcards", -// args: args{ -// org: chronograf.Organization{ -// ID: "cool", -// Name: "Cool Org", -// Mappings: []chronograf.Mapping{ -// chronograf.Mapping{ -// Provider: chronograf.MappingWildcard, -// Scheme: chronograf.MappingWildcard, -// Group: chronograf.MappingWildcard, -// GrantedRole: roles.ViewerRoleName, -// }, -// chronograf.Mapping{ -// Provider: chronograf.MappingWildcard, -// Scheme: chronograf.MappingWildcard, -// Group: chronograf.MappingWildcard, -// GrantedRole: roles.MemberRoleName, -// }, -// }, -// }, -// }, -// wants: wants{ -// role: &chronograf.Role{ -// Name: roles.ViewerRoleName, -// Organization: "cool", -// }, -// }, -// }, -// { -// name: "different two mapping all wildcards, different ordering", -// args: args{ -// org: chronograf.Organization{ -// ID: "cool", -// Name: "Cool Org", -// Mappings: []chronograf.Mapping{ -// chronograf.Mapping{ -// Provider: chronograf.MappingWildcard, -// Scheme: chronograf.MappingWildcard, -// Group: chronograf.MappingWildcard, -// GrantedRole: roles.MemberRoleName, -// }, -// chronograf.Mapping{ -// Provider: chronograf.MappingWildcard, -// Scheme: chronograf.MappingWildcard, -// Group: chronograf.MappingWildcard, -// GrantedRole: roles.ViewerRoleName, -// }, -// }, -// }, -// }, -// wants: wants{ -// role: &chronograf.Role{ -// Name: roles.ViewerRoleName, -// Organization: "cool", -// }, -// }, -// }, -// { -// name: "three mapping all wildcards", -// args: args{ -// org: chronograf.Organization{ -// ID: "cool", -// Name: "Cool Org", -// Mappings: []chronograf.Mapping{ -// chronograf.Mapping{ -// Provider: chronograf.MappingWildcard, -// Scheme: chronograf.MappingWildcard, -// Group: chronograf.MappingWildcard, -// GrantedRole: roles.EditorRoleName, -// }, -// chronograf.Mapping{ -// Provider: chronograf.MappingWildcard, -// Scheme: chronograf.MappingWildcard, -// Group: chronograf.MappingWildcard, -// GrantedRole: roles.ViewerRoleName, -// }, -// chronograf.Mapping{ -// Provider: chronograf.MappingWildcard, -// Scheme: chronograf.MappingWildcard, -// Group: chronograf.MappingWildcard, -// GrantedRole: roles.AdminRoleName, -// }, -// }, -// }, -// }, -// wants: wants{ -// role: &chronograf.Role{ -// Name: roles.AdminRoleName, -// Organization: "cool", -// }, -// }, -// }, -// { -// name: "three mapping only one match", -// args: args{ -// principal: oauth2.Principal{ -// Subject: "billieta@influxdata.com", -// Issuer: "google", -// Group: "influxdata.com", -// }, -// org: chronograf.Organization{ -// ID: "cool", -// Name: "Cool Org", -// Mappings: []chronograf.Mapping{ -// chronograf.Mapping{ -// Provider: "google", -// Scheme: chronograf.MappingWildcard, -// Group: "influxdata.com", -// GrantedRole: roles.EditorRoleName, -// }, -// chronograf.Mapping{ -// Provider: "google", -// Scheme: chronograf.MappingWildcard, -// Group: "not_influxdata", -// GrantedRole: roles.AdminRoleName, -// }, -// chronograf.Mapping{ -// Provider: chronograf.MappingWildcard, -// Scheme: "ldap", -// Group: chronograf.MappingWildcard, -// GrantedRole: roles.AdminRoleName, -// }, -// }, -// }, -// }, -// wants: wants{ -// role: &chronograf.Role{ -// Name: roles.EditorRoleName, -// Organization: "cool", -// }, -// }, -// }, -// { -// name: "three mapping only two matches", -// args: args{ -// principal: oauth2.Principal{ -// Subject: "billieta@influxdata.com", -// Issuer: "google", -// Group: "influxdata.com", -// }, -// org: chronograf.Organization{ -// ID: "cool", -// Name: "Cool Org", -// Mappings: []chronograf.Mapping{ -// chronograf.Mapping{ -// Provider: "google", -// Scheme: chronograf.MappingWildcard, -// Group: "influxdata.com", -// GrantedRole: roles.AdminRoleName, -// }, -// chronograf.Mapping{ -// Provider: "google", -// Scheme: chronograf.MappingWildcard, -// Group: chronograf.MappingWildcard, -// GrantedRole: roles.EditorRoleName, -// }, -// chronograf.Mapping{ -// Provider: chronograf.MappingWildcard, -// Scheme: "ldap", -// Group: chronograf.MappingWildcard, -// GrantedRole: roles.AdminRoleName, -// }, -// }, -// }, -// }, -// wants: wants{ -// role: &chronograf.Role{ -// Name: roles.AdminRoleName, -// Organization: "cool", -// }, -// }, -// }, -// { -// name: "missing provider", -// args: args{ -// principal: oauth2.Principal{ -// Subject: "billieta@influxdata.com", -// Issuer: "google", -// Group: "influxdata.com", -// }, -// org: chronograf.Organization{ -// ID: "cool", -// Name: "Cool Org", -// Mappings: []chronograf.Mapping{ -// chronograf.Mapping{ -// Provider: "", -// Scheme: "ldap", -// Group: chronograf.MappingWildcard, -// GrantedRole: roles.AdminRoleName, -// }, -// }, -// }, -// }, -// wants: wants{ -// role: nil, -// }, -// }, -// { -// name: "user is in multiple github groups", -// args: args{ -// principal: oauth2.Principal{ -// Subject: "billieta@influxdata.com", -// Issuer: "github", -// Group: "influxdata,another,mimi", -// }, -// org: chronograf.Organization{ -// ID: "cool", -// Name: "Cool Org", -// Mappings: []chronograf.Mapping{ -// chronograf.Mapping{ -// Provider: "github", -// Scheme: chronograf.MappingWildcard, -// Group: "influxdata", -// GrantedRole: roles.MemberRoleName, -// }, -// chronograf.Mapping{ -// Provider: "github", -// Scheme: chronograf.MappingWildcard, -// Group: "mimi", -// GrantedRole: roles.EditorRoleName, -// }, -// chronograf.Mapping{ -// Provider: "github", -// Scheme: chronograf.MappingWildcard, -// Group: "another", -// GrantedRole: roles.AdminRoleName, -// }, -// }, -// }, -// }, -// wants: wants{ -// role: &chronograf.Role{ -// Name: roles.AdminRoleName, -// Organization: "cool", -// }, -// }, -// }, -// } -// -// for _, tt := range tests { -// t.Run(tt.name, func(t *testing.T) { -// role := server.MappedRole(tt.args.org, tt.args.principal) -// -// if diff := cmp.Diff(role, tt.wants.role); diff != "" { -// t.Errorf("%q. MappedRole():\n-got/+want\ndiff %s", tt.name, diff) -// } -// }) -// } -//} +import ( + "bytes" + "context" + "encoding/json" + "io/ioutil" + "net/http/httptest" + "testing" + + "github.com/bouk/httprouter" + "github.com/influxdata/chronograf" + "github.com/influxdata/chronograf/log" + "github.com/influxdata/chronograf/mocks" + "github.com/influxdata/chronograf/roles" +) + +func TestMappings_All(t *testing.T) { + type fields struct { + MappingsStore chronograf.MappingsStore + } + type args struct { + } + type wants struct { + statusCode int + contentType string + body string + } + + tests := []struct { + name string + fields fields + args args + wants wants + }{ + { + name: "get all mappings", + fields: fields{ + MappingsStore: &mocks.MappingsStore{ + AllF: func(ctx context.Context) ([]chronograf.Mapping, error) { + return []chronograf.Mapping{ + { + Organization: "0", + Provider: chronograf.MappingWildcard, + Scheme: chronograf.MappingWildcard, + Group: chronograf.MappingWildcard, + }, + }, nil + }, + }, + }, + wants: wants{ + statusCode: 200, + contentType: "application/json", + body: `{"links":{"self":"/chronograf/v1/mappings"},"mappings":[{"links":{"self":"/chronograf/v1/mappings/"},"id":"","organization":"0","provider":"*","scheme":"*","group":"*"}]}`, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := &Service{ + Store: &mocks.Store{ + MappingsStore: tt.fields.MappingsStore, + }, + Logger: log.New(log.DebugLevel), + } + + w := httptest.NewRecorder() + r := httptest.NewRequest("GET", "http://any.url", nil) + s.Mappings(w, r) + + resp := w.Result() + content := resp.Header.Get("Content-Type") + body, _ := ioutil.ReadAll(resp.Body) + + if resp.StatusCode != tt.wants.statusCode { + t.Errorf("%q. Mappings() = %v, want %v", tt.name, resp.StatusCode, tt.wants.statusCode) + } + if tt.wants.contentType != "" && content != tt.wants.contentType { + t.Errorf("%q. Mappings() = %v, want %v", tt.name, content, tt.wants.contentType) + } + if eq, _ := jsonEqual(string(body), tt.wants.body); tt.wants.body != "" && !eq { + t.Errorf("%q. Mappings() = \n***%v***\n,\nwant\n***%v***", tt.name, string(body), tt.wants.body) + } + }) + } +} + +func TestMappings_Add(t *testing.T) { + type fields struct { + MappingsStore chronograf.MappingsStore + OrganizationsStore chronograf.OrganizationsStore + } + type args struct { + mapping *chronograf.Mapping + } + type wants struct { + statusCode int + contentType string + body string + } + + tests := []struct { + name string + fields fields + args args + wants wants + }{ + { + name: "create new mapping", + fields: fields{ + OrganizationsStore: &mocks.OrganizationsStore{ + GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { + return &chronograf.Organization{ + ID: "0", + Name: "The Gnarly Default", + DefaultRole: roles.ViewerRoleName, + Public: true, + }, nil + }, + }, + MappingsStore: &mocks.MappingsStore{ + AddF: func(ctx context.Context, m *chronograf.Mapping) (*chronograf.Mapping, error) { + m.ID = "0" + return m, nil + }, + }, + }, + args: args{ + mapping: &chronograf.Mapping{ + Organization: "0", + Provider: "*", + Scheme: "*", + Group: "*", + }, + }, + wants: wants{ + statusCode: 201, + contentType: "application/json", + body: `{"links":{"self":"/chronograf/v1/mappings/0"},"id":"0","organization":"0","provider":"*","scheme":"*","group":"*"}`, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := &Service{ + Store: &mocks.Store{ + MappingsStore: tt.fields.MappingsStore, + OrganizationsStore: tt.fields.OrganizationsStore, + }, + Logger: log.New(log.DebugLevel), + } + + w := httptest.NewRecorder() + r := httptest.NewRequest("GET", "http://any.url", nil) + + buf, _ := json.Marshal(tt.args.mapping) + r.Body = ioutil.NopCloser(bytes.NewReader(buf)) + + s.NewMapping(w, r) + + resp := w.Result() + content := resp.Header.Get("Content-Type") + body, _ := ioutil.ReadAll(resp.Body) + + if resp.StatusCode != tt.wants.statusCode { + t.Errorf("%q. Add() = %v, want %v", tt.name, resp.StatusCode, tt.wants.statusCode) + } + if tt.wants.contentType != "" && content != tt.wants.contentType { + t.Errorf("%q. Add() = %v, want %v", tt.name, content, tt.wants.contentType) + } + if eq, _ := jsonEqual(string(body), tt.wants.body); tt.wants.body != "" && !eq { + t.Errorf("%q. Add() = \n***%v***\n,\nwant\n***%v***", tt.name, string(body), tt.wants.body) + } + }) + } +} + +func TestMappings_Update(t *testing.T) { + type fields struct { + MappingsStore chronograf.MappingsStore + OrganizationsStore chronograf.OrganizationsStore + } + type args struct { + mapping *chronograf.Mapping + } + type wants struct { + statusCode int + contentType string + body string + } + + tests := []struct { + name string + fields fields + args args + wants wants + }{ + { + name: "update new mapping", + fields: fields{ + OrganizationsStore: &mocks.OrganizationsStore{ + GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { + return &chronograf.Organization{ + ID: "0", + Name: "The Gnarly Default", + DefaultRole: roles.ViewerRoleName, + Public: true, + }, nil + }, + }, + MappingsStore: &mocks.MappingsStore{ + UpdateF: func(ctx context.Context, m *chronograf.Mapping) error { + return nil + }, + }, + }, + args: args{ + mapping: &chronograf.Mapping{ + ID: "1", + Organization: "0", + Provider: "*", + Scheme: "*", + Group: "*", + }, + }, + wants: wants{ + statusCode: 200, + contentType: "application/json", + body: `{"links":{"self":"/chronograf/v1/mappings/1"},"id":"1","organization":"0","provider":"*","scheme":"*","group":"*"}`, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := &Service{ + Store: &mocks.Store{ + MappingsStore: tt.fields.MappingsStore, + OrganizationsStore: tt.fields.OrganizationsStore, + }, + Logger: log.New(log.DebugLevel), + } + + w := httptest.NewRecorder() + r := httptest.NewRequest("GET", "http://any.url", nil) + + buf, _ := json.Marshal(tt.args.mapping) + r.Body = ioutil.NopCloser(bytes.NewReader(buf)) + r = r.WithContext(httprouter.WithParams( + context.Background(), + httprouter.Params{ + { + Key: "id", + Value: tt.args.mapping.ID, + }, + })) + + s.UpdateMapping(w, r) + + resp := w.Result() + content := resp.Header.Get("Content-Type") + body, _ := ioutil.ReadAll(resp.Body) + + if resp.StatusCode != tt.wants.statusCode { + t.Errorf("%q. Add() = %v, want %v", tt.name, resp.StatusCode, tt.wants.statusCode) + } + if tt.wants.contentType != "" && content != tt.wants.contentType { + t.Errorf("%q. Add() = %v, want %v", tt.name, content, tt.wants.contentType) + } + if eq, _ := jsonEqual(string(body), tt.wants.body); tt.wants.body != "" && !eq { + t.Errorf("%q. Add() = \n***%v***\n,\nwant\n***%v***", tt.name, string(body), tt.wants.body) + } + }) + } +} + +func TestMappings_Remove(t *testing.T) { + type fields struct { + MappingsStore chronograf.MappingsStore + } + type args struct { + id string + } + type wants struct { + statusCode int + contentType string + body string + } + + tests := []struct { + name string + fields fields + args args + wants wants + }{ + { + name: "remove mapping", + fields: fields{ + MappingsStore: &mocks.MappingsStore{ + GetF: func(ctx context.Context, id string) (*chronograf.Mapping, error) { + return &chronograf.Mapping{ + ID: "1", + Organization: "0", + Provider: "*", + Scheme: "*", + Group: "*", + }, nil + }, + DeleteF: func(ctx context.Context, m *chronograf.Mapping) error { + return nil + }, + }, + }, + args: args{}, + wants: wants{ + statusCode: 204, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := &Service{ + Store: &mocks.Store{ + MappingsStore: tt.fields.MappingsStore, + }, + Logger: log.New(log.DebugLevel), + } + + w := httptest.NewRecorder() + r := httptest.NewRequest("GET", "http://any.url", nil) + + r = r.WithContext(httprouter.WithParams( + context.Background(), + httprouter.Params{ + { + Key: "id", + Value: tt.args.id, + }, + })) + + s.RemoveMapping(w, r) + + resp := w.Result() + content := resp.Header.Get("Content-Type") + body, _ := ioutil.ReadAll(resp.Body) + + if resp.StatusCode != tt.wants.statusCode { + t.Errorf("%q. Remove() = %v, want %v", tt.name, resp.StatusCode, tt.wants.statusCode) + } + if tt.wants.contentType != "" && content != tt.wants.contentType { + t.Errorf("%q. Remove() = %v, want %v", tt.name, content, tt.wants.contentType) + } + if eq, _ := jsonEqual(string(body), tt.wants.body); tt.wants.body != "" && !eq { + t.Errorf("%q. Remove() = \n***%v***\n,\nwant\n***%v***", tt.name, string(body), tt.wants.body) + } + }) + } +} diff --git a/server/mux.go b/server/mux.go index 7a0e20eb9..d2cb4ce5a 100644 --- a/server/mux.go +++ b/server/mux.go @@ -119,11 +119,11 @@ func NewMux(opts MuxOpts, service Service) http.Handler { router.DELETE("/chronograf/v1/organizations/:id", EnsureSuperAdmin(service.RemoveOrganization)) // Mappings - //router.GET("/chronograf/v1/mappings", EnsureSuperAdmin(service.Mappings)) - //router.POST("/chronograf/v1/mappings", EnsureSuperAdmin(service.NewMapping)) + router.GET("/chronograf/v1/mappings", EnsureSuperAdmin(service.Mappings)) + router.POST("/chronograf/v1/mappings", EnsureSuperAdmin(service.NewMapping)) - //router.PUT("/chronograf/v1/mappings/:id", EnsureSuperAdmin(service.UpdateMappings)) - //router.DELETE("/chronograf/v1/mappings/:id", EnsureSuperAdmin(service.RemoveMappings)) + router.PUT("/chronograf/v1/mappings/:id", EnsureSuperAdmin(service.UpdateMapping)) + router.DELETE("/chronograf/v1/mappings/:id", EnsureSuperAdmin(service.RemoveMapping)) // Sources router.GET("/chronograf/v1/sources", EnsureViewer(service.Sources)) From 1128040be230554dffd66d872d497be6f1d4489e Mon Sep 17 00:00:00 2001 From: Alex P Date: Mon, 5 Feb 2018 14:15:56 -0800 Subject: [PATCH 33/98] Remove concept of expand/collapse and redesign log table rows Each row sized to fit content, if the keys/values list is too long it scrolls. Should comfortably fit about 17 items before scrolling --- .../components/LogItemKapacitorPoint.js | 122 +++++------------- ui/src/kapacitor/components/LogsTable.js | 18 +-- ui/src/kapacitor/components/LogsTableRow.js | 13 +- ui/src/kapacitor/components/Tickscript.js | 6 +- ui/src/kapacitor/containers/TickscriptPage.js | 9 -- .../components/kapacitor-logs-table.scss | 58 +++------ ui/src/style/modules/mixins.scss | 8 +- 7 files changed, 66 insertions(+), 168 deletions(-) diff --git a/ui/src/kapacitor/components/LogItemKapacitorPoint.js b/ui/src/kapacitor/components/LogItemKapacitorPoint.js index 137d36c0b..a56df87a4 100644 --- a/ui/src/kapacitor/components/LogItemKapacitorPoint.js +++ b/ui/src/kapacitor/components/LogItemKapacitorPoint.js @@ -1,101 +1,45 @@ -import React, {Component, PropTypes} from 'react' +import React, {PropTypes} from 'react' -class LogItemKapacitorPoint extends Component { - renderKeysAndValues = (object, name, expanded) => { - if (!object) { - return -- - } - const objKeys = Object.keys(object) - const objValues = Object.values(object) +const renderKeysAndValues = (object, name) => { + if (!object) { + return -- + } + const objKeys = Object.keys(object) + const objValues = Object.values(object) - if (objKeys.length > 2) { - return expanded - ?
-

- {`${objKeys.length} ${name}`} -

-
- {objKeys.map((objKey, i) => -
- {objKey}: {objValues[i]} -
- )} -
-
- :
-

- {`${objKeys.length} ${name}`} -

-
Click to expand...
-
- } - - return ( -
-

- {`${objKeys.length} ${name}`} -

+ return ( +
+

+ {`${objKeys.length} ${name}`} +

+
{objKeys.map((objKey, i) =>
{objKey}: {objValues[i]}
)}
- ) - } - - handleToggleExpand = () => { - const {onToggleExpandLog, logIndex} = this.props - - if (this.isExpandable()) { - onToggleExpandLog(logIndex) - } - } - - isExpandable = () => { - const {logItem} = this.props - - if ( - Object.keys(logItem.tag).length > 2 || - Object.keys(logItem.field).length > 2 - ) { - return true - } - return false - } - - render() { - const {logItem} = this.props - - const rowClass = `logs-table--row${this.isExpandable() - ? ' logs-table--row__expandable' - : ''}${logItem.expanded ? ' expanded' : ''}` - - return ( -
-
-
-
- {logItem.ts} -
-
-
-
Kapacitor Point
-
- {this.renderKeysAndValues(logItem.tag, 'Tags', logItem.expanded)} - {this.renderKeysAndValues( - logItem.field, - 'Fields', - logItem.expanded - )} -
-
-
- ) - } +
+ ) } +const LogItemKapacitorPoint = ({logItem}) => +
+
+
+
+ {logItem.ts} +
+
+
+
Kapacitor Point
+
+ {renderKeysAndValues(logItem.tag, 'Tags')} + {renderKeysAndValues(logItem.field, 'Fields')} +
+
+
-const {func, number, shape, string} = PropTypes +const {shape, string} = PropTypes LogItemKapacitorPoint.propTypes = { logItem: shape({ @@ -104,8 +48,6 @@ LogItemKapacitorPoint.propTypes = { tag: shape.isRequired, field: shape.isRequired, }), - onToggleExpandLog: func.isRequired, - logIndex: number.isRequired, } export default LogItemKapacitorPoint diff --git a/ui/src/kapacitor/components/LogsTable.js b/ui/src/kapacitor/components/LogsTable.js index c3f86f00c..b49c0014a 100644 --- a/ui/src/kapacitor/components/LogsTable.js +++ b/ui/src/kapacitor/components/LogsTable.js @@ -5,29 +5,24 @@ import FancyScrollbar from 'src/shared/components/FancyScrollbar' const numLogsToRender = 200 -const LogsTable = ({logs, onToggleExpandLog}) => -
+const LogsTable = ({logs}) => +
-

{`${numLogsToRender} Most Recent Logs`}

+ {`${numLogsToRender} Most Recent Logs`}
{logs .slice(0, numLogsToRender) .map((log, i) => - + )}
-const {arrayOf, func, shape, string} = PropTypes +const {arrayOf, shape, string} = PropTypes LogsTable.propTypes = { logs: arrayOf( @@ -38,7 +33,6 @@ LogsTable.propTypes = { msg: string.isRequired, }) ).isRequired, - onToggleExpandLog: func.isRequired, } export default LogsTable diff --git a/ui/src/kapacitor/components/LogsTableRow.js b/ui/src/kapacitor/components/LogsTableRow.js index c635e63d7..53e46230b 100644 --- a/ui/src/kapacitor/components/LogsTableRow.js +++ b/ui/src/kapacitor/components/LogsTableRow.js @@ -8,7 +8,7 @@ import LogItemKapacitorError from 'src/kapacitor/components/LogItemKapacitorErro import LogItemKapacitorDebug from 'src/kapacitor/components/LogItemKapacitorDebug' import LogItemInfluxDBDebug from 'src/kapacitor/components/LogItemInfluxDBDebug' -const LogsTableRow = ({logItem, index, onToggleExpandLog}) => { +const LogsTableRow = ({logItem, index}) => { if (logItem.service === 'sessions') { return } @@ -16,13 +16,7 @@ const LogsTableRow = ({logItem, index, onToggleExpandLog}) => { return } if (logItem.service === 'kapacitor' && logItem.msg === 'point') { - return ( - - ) + return } if (logItem.service === 'httpd_server_errors' && logItem.lvl === 'error') { return @@ -59,7 +53,7 @@ const LogsTableRow = ({logItem, index, onToggleExpandLog}) => { ) } -const {func, number, shape, string} = PropTypes +const {number, shape, string} = PropTypes LogsTableRow.propTypes = { logItem: shape({ @@ -69,7 +63,6 @@ LogsTableRow.propTypes = { msg: string.isRequired, }).isRequired, index: number.isRequired, - onToggleExpandLog: func.isRequired, } export default LogsTableRow diff --git a/ui/src/kapacitor/components/Tickscript.js b/ui/src/kapacitor/components/Tickscript.js index 8e9fe899b..8cec26829 100644 --- a/ui/src/kapacitor/components/Tickscript.js +++ b/ui/src/kapacitor/components/Tickscript.js @@ -20,7 +20,6 @@ const Tickscript = ({ isNewTickscript, areLogsVisible, areLogsEnabled, - onToggleExpandLog, onToggleLogsVisibility, }) =>
@@ -52,9 +51,7 @@ const Tickscript = ({ unsavedChanges={unsavedChanges} />
- {areLogsVisible - ? - : null} + {areLogsVisible ? : null}
@@ -82,7 +79,6 @@ Tickscript.propTypes = { onChangeID: func.isRequired, isNewTickscript: bool.isRequired, unsavedChanges: bool, - onToggleExpandLog: func.isRequired, } export default Tickscript diff --git a/ui/src/kapacitor/containers/TickscriptPage.js b/ui/src/kapacitor/containers/TickscriptPage.js index 19639a69c..0f456cdfc 100644 --- a/ui/src/kapacitor/containers/TickscriptPage.js +++ b/ui/src/kapacitor/containers/TickscriptPage.js @@ -2,7 +2,6 @@ import React, {PropTypes, Component} from 'react' import {connect} from 'react-redux' import {bindActionCreators} from 'redux' import uuid from 'node-uuid' -import _ from 'lodash' import Tickscript from 'src/kapacitor/components/Tickscript' import * as kapactiorActionCreators from 'src/kapacitor/actions/view' @@ -157,13 +156,6 @@ class TickscriptPage extends Component { }) } - handleToggleExpandLog = logIndex => { - const {logs} = this.state - logs[logIndex].expanded = !_.get(logs[logIndex], 'expanded', false) - - this.setState({logs}) - } - handleSave = async () => { const {kapacitor, task} = this.state const { @@ -251,7 +243,6 @@ class TickscriptPage extends Component { areLogsVisible={areLogsVisible} areLogsEnabled={areLogsEnabled} onToggleLogsVisibility={this.handleToggleLogsVisibility} - onToggleExpandLog={this.handleToggleExpandLog} /> ) } diff --git a/ui/src/style/components/kapacitor-logs-table.scss b/ui/src/style/components/kapacitor-logs-table.scss index a70273c42..0c85bfd25 100644 --- a/ui/src/style/components/kapacitor-logs-table.scss +++ b/ui/src/style/components/kapacitor-logs-table.scss @@ -9,11 +9,10 @@ $logs-row-indent: 6px; $logs-level-dot: 8px; $logs-margin: 4px; -.logs-table--container { +.logs-table { width: 50%; position: relative; height: 100%; - @include gradient-v(mix($g3-castle, $g2-kevlar),mix($g1-raven, $g0-obsidian)); } .logs-table--header { display: flex; @@ -23,27 +22,28 @@ $logs-margin: 4px; height: $logs-table-header-height; padding: 0 $logs-table-padding 0 ($logs-table-padding / 2); background-color: $g4-onyx; + white-space: nowrap; + font-size: 17px; + @include no-user-select(); + letter-spacing: 0.015em; + font-weight: 500; } -.logs-table--panel { +.logs-table--container { position: absolute !important; top: $logs-table-header-height; left: 0; width: 100%; height: calc(100% - #{$logs-table-header-height}) !important; + @include gradient-v(mix($g3-castle, $g2-kevlar),mix($g1-raven, $g0-obsidian)); } -.logs-table { - height: 100%; -} .logs-table--row { position: relative; - overflow: hidden; - height: 87px; // Fixed height, required for Infinite Scroll, allows for 2 tags / fields per line padding: 8px ($logs-table-padding - 16px) 8px ($logs-table-padding / 2); border-bottom: 2px solid $g3-castle; transition: background-color 0.25s ease; - &:first-child { + &:last-of-type { border-bottom: none; } } @@ -61,13 +61,13 @@ $logs-margin: 4px; &.debug {background-color: $c-comet;} &.info {background-color: $g6-smoke;} &.warn {background-color: $c-pineapple;} - &.ok {background-color: $c-rainforest;} + &.ok {background-color: $c-pool;} &.error {background-color: $c-dreamsicle;} } .logs-table--timestamp { font-family: $code-font; font-weight: 500; - font-size: 11px; + font-size: 13px; color: $g9-mountain; flex: 1 0 0; } @@ -75,9 +75,10 @@ $logs-margin: 4px; display: flex; align-items: flex-start; font-size: 13px; - color: $g13-mist; + color: $g11-sidewalk; font-weight: 600; padding-left: ($logs-level-dot + $logs-row-indent); + margin-top: 1px; .error {color: $c-dreamsicle;} .debug {color: $c-comet;} @@ -106,38 +107,17 @@ $logs-margin: 4px; letter-spacing: normal; line-height: 1.42857143em; text-transform: uppercase; + color: $g16-pearl; } .logs-table--key-value { + white-space: nowrap; } .logs-table--key-value span { - color: $c-pool; -} - -.logs-table--many-keys { - color: $g9-mountain; - width: 100%; - display: inline-block; - font-style: italic; -} - -// Styles for Expandable Log Items -.logs-table--row.logs-table--row__expandable { - &:hover { - background-color: fade-out($g0-obsidian, 0.7); - cursor: zoom-in; - } - &.expanded { - background-color: $g0-obsidian; - height: 240px; - &:hover { - background-color: $g0-obsidian; - cursor: zoom-out; - } - } + color: $c-rainforest; } .logs-table--keys-scrollbox { width: 100%; - height: 190px; - overflow-y: scroll; - @include custom-scrollbar($g0-obsidian,$g7-graphite); + max-height: 300px; + overflow-y: auto; + @include custom-scrollbar-round($g0-obsidian,$c-rainforest); } diff --git a/ui/src/style/modules/mixins.scss b/ui/src/style/modules/mixins.scss index e2b623dea..ad8bb3775 100644 --- a/ui/src/style/modules/mixins.scss +++ b/ui/src/style/modules/mixins.scss @@ -71,16 +71,18 @@ $scrollbar-offset: 3px; width: $scrollbar-width; border-top-right-radius: $radius; border-top-left-radius: $radius; - border-bottom-right-radius: $radius; border-bottom-left-radius: $radius; + border-bottom-right-radius: $radius; &-button { background-color: $trackColor; } &-track { background-color: $trackColor; - border-top-right-radius: $radius; - border-bottom-right-radius: $radius; + border-top-right-radius: ($scrollbar-width / 2); + border-top-left-radius: ($scrollbar-width / 2); + border-bottom-left-radius: ($scrollbar-width / 2); + border-bottom-right-radius: ($scrollbar-width / 2); } &-track-piece { background-color: $trackColor; From 6afa86c61a3bbaa5de4b649cd51ac62011ab9be8 Mon Sep 17 00:00:00 2001 From: Alex P Date: Mon, 5 Feb 2018 14:36:15 -0800 Subject: [PATCH 34/98] Improve log message text wrapping Also renamed some css classes to be more generic and understandable --- .../kapacitor/components/LogItemHTTPError.js | 2 +- .../components/LogItemInfluxDBDebug.js | 2 +- .../components/LogItemKapacitorDebug.js | 2 +- .../components/LogItemKapacitorError.js | 2 +- .../components/LogItemKapacitorPoint.js | 6 +++--- ui/src/kapacitor/components/LogsTableRow.js | 2 +- .../components/kapacitor-logs-table.scss | 21 ++++++++++++------- 7 files changed, 21 insertions(+), 16 deletions(-) diff --git a/ui/src/kapacitor/components/LogItemHTTPError.js b/ui/src/kapacitor/components/LogItemHTTPError.js index fa0e79694..c2f1d7407 100644 --- a/ui/src/kapacitor/components/LogItemHTTPError.js +++ b/ui/src/kapacitor/components/LogItemHTTPError.js @@ -10,7 +10,7 @@ const LogItemHTTPError = ({logItem}) =>
HTTP Server
-
+
ERROR: {logItem.msg}
diff --git a/ui/src/kapacitor/components/LogItemInfluxDBDebug.js b/ui/src/kapacitor/components/LogItemInfluxDBDebug.js index b6be11705..bbbd292df 100644 --- a/ui/src/kapacitor/components/LogItemInfluxDBDebug.js +++ b/ui/src/kapacitor/components/LogItemInfluxDBDebug.js @@ -10,7 +10,7 @@ const LogItemInfluxDBDebug = ({logItem}) =>
InfluxDB
-
+
DEBUG: {logItem.msg}
diff --git a/ui/src/kapacitor/components/LogItemKapacitorDebug.js b/ui/src/kapacitor/components/LogItemKapacitorDebug.js index 9b99d5129..3f694dd4e 100644 --- a/ui/src/kapacitor/components/LogItemKapacitorDebug.js +++ b/ui/src/kapacitor/components/LogItemKapacitorDebug.js @@ -10,7 +10,7 @@ const LogItemKapacitorDebug = ({logItem}) =>
Kapacitor
-
+
DEBUG: {logItem.msg}
diff --git a/ui/src/kapacitor/components/LogItemKapacitorError.js b/ui/src/kapacitor/components/LogItemKapacitorError.js index 1d4ca573d..83fa42b81 100644 --- a/ui/src/kapacitor/components/LogItemKapacitorError.js +++ b/ui/src/kapacitor/components/LogItemKapacitorError.js @@ -10,7 +10,7 @@ const LogItemKapacitorError = ({logItem}) =>
Kapacitor
-
+
ERROR: {logItem.msg}
diff --git a/ui/src/kapacitor/components/LogItemKapacitorPoint.js b/ui/src/kapacitor/components/LogItemKapacitorPoint.js index a56df87a4..8e5d12b7d 100644 --- a/ui/src/kapacitor/components/LogItemKapacitorPoint.js +++ b/ui/src/kapacitor/components/LogItemKapacitorPoint.js @@ -8,11 +8,11 @@ const renderKeysAndValues = (object, name) => { const objValues = Object.values(object) return ( -
+

{`${objKeys.length} ${name}`}

-
+
{objKeys.map((objKey, i) =>
{objKey}: {objValues[i]} @@ -32,7 +32,7 @@ const LogItemKapacitorPoint = ({logItem}) =>
Kapacitor Point
-
+
{renderKeysAndValues(logItem.tag, 'Tags')} {renderKeysAndValues(logItem.field, 'Fields')}
diff --git a/ui/src/kapacitor/components/LogsTableRow.js b/ui/src/kapacitor/components/LogsTableRow.js index 53e46230b..44584b457 100644 --- a/ui/src/kapacitor/components/LogsTableRow.js +++ b/ui/src/kapacitor/components/LogsTableRow.js @@ -43,7 +43,7 @@ const LogsTableRow = ({logItem, index}) => {
{logItem.service || '--'}
-
+
{logItem.msg || '--'}
diff --git a/ui/src/style/components/kapacitor-logs-table.scss b/ui/src/style/components/kapacitor-logs-table.scss index 0c85bfd25..60ef3f295 100644 --- a/ui/src/style/components/kapacitor-logs-table.scss +++ b/ui/src/style/components/kapacitor-logs-table.scss @@ -74,17 +74,21 @@ $logs-margin: 4px; .logs-table--details { display: flex; align-items: flex-start; + flex-wrap: wrap; font-size: 13px; color: $g11-sidewalk; font-weight: 600; padding-left: ($logs-level-dot + $logs-row-indent); - margin-top: 1px; .error {color: $c-dreamsicle;} .debug {color: $c-comet;} } /* Logs Table Item Types */ +.logs-table--service, +.logs-table--column h1 { + margin-top: 2px; +} .logs-table--session { text-transform: capitalize; font-style: italic; @@ -92,15 +96,16 @@ $logs-margin: 4px; .logs-table--service { width: 140px; } -.logs-table--blah { +.logs-table--columns { display: flex; flex: 1 0 0; + flex-wrap: wrap; } -.logs-table--key-values { +.logs-table--column { color: $g11-sidewalk; flex: 1 0 50%; } -.logs-table--key-values h1 { +.logs-table--column h1 { font-size: 13px; font-weight: 700; margin: 0; @@ -111,11 +116,11 @@ $logs-margin: 4px; } .logs-table--key-value { white-space: nowrap; + span { + color: $c-rainforest; + } } -.logs-table--key-value span { - color: $c-rainforest; -} -.logs-table--keys-scrollbox { +.logs-table--scrollbox { width: 100%; max-height: 300px; overflow-y: auto; From e0f54aaccacf271fc7e0d0ce284de303c6d377f0 Mon Sep 17 00:00:00 2001 From: Alex P Date: Mon, 5 Feb 2018 14:49:21 -0800 Subject: [PATCH 35/98] Render key/value pairs in log messages alphabetically by key --- ui/src/kapacitor/components/LogItemKapacitorPoint.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ui/src/kapacitor/components/LogItemKapacitorPoint.js b/ui/src/kapacitor/components/LogItemKapacitorPoint.js index 8e5d12b7d..00ee10c21 100644 --- a/ui/src/kapacitor/components/LogItemKapacitorPoint.js +++ b/ui/src/kapacitor/components/LogItemKapacitorPoint.js @@ -4,7 +4,8 @@ const renderKeysAndValues = (object, name) => { if (!object) { return -- } - const objKeys = Object.keys(object) + + const objKeys = Object.keys(object).sort() const objValues = Object.values(object) return ( From 53e57eb2796557884456ceba54fcc83498ae3cef Mon Sep 17 00:00:00 2001 From: Alex P Date: Mon, 5 Feb 2018 15:12:50 -0800 Subject: [PATCH 36/98] Enforce horizontal scrolling on code mirror and improve styles --- ui/src/kapacitor/components/Tickscript.js | 5 ++++- ui/src/style/components/code-mirror-theme.scss | 9 ++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/ui/src/kapacitor/components/Tickscript.js b/ui/src/kapacitor/components/Tickscript.js index 8cec26829..65320d056 100644 --- a/ui/src/kapacitor/components/Tickscript.js +++ b/ui/src/kapacitor/components/Tickscript.js @@ -34,7 +34,10 @@ const Tickscript = ({ isNewTickscript={isNewTickscript} />
-
+
Date: Mon, 5 Feb 2018 18:20:03 -0500 Subject: [PATCH 37/98] Prevent error if organization not found in mapping chris go vet fix --- bolt/internal/internal.go | 5 +---- server/mapping.go | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/bolt/internal/internal.go b/bolt/internal/internal.go index 2ce4ca36b..72ffc888c 100644 --- a/bolt/internal/internal.go +++ b/bolt/internal/internal.go @@ -685,8 +685,5 @@ func UnmarshalMapping(data []byte, m *chronograf.Mapping) error { // UnmarshalMappingPB decodes a mapping from binary protobuf data. func UnmarshalMappingPB(data []byte, m *Mapping) error { - if err := proto.Unmarshal(data, m); err != nil { - return err - } - return nil + return proto.Unmarshal(data, m) } diff --git a/server/mapping.go b/server/mapping.go index 4105e6b9e..872e2a36d 100644 --- a/server/mapping.go +++ b/server/mapping.go @@ -23,7 +23,7 @@ MappingsLoop: if applyMapping(mapping, p) { org, err := s.Store.Organizations(ctx).Get(ctx, chronograf.OrganizationQuery{ID: &mapping.Organization}) if err != nil { - return nil, err + continue MappingsLoop } for _, role := range roles { From cb3ba24c725117ff5ce23470bccf4f1b34f2f8b8 Mon Sep 17 00:00:00 2001 From: Alex P Date: Mon, 5 Feb 2018 15:23:13 -0800 Subject: [PATCH 38/98] Updoot log of change --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e8147906..ffc2deace 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ ### Bug Fixes 1. [#2684](https://github.com/influxdata/chronograf/pull/2684): Fix TICKscript Sensu alerts when no group by tags selected 1. [#2735](https://github.com/influxdata/chronograf/pull/2735): Remove cli options from systemd service file +1. [#2756](https://github.com/influxdata/chronograf/pull/2756): Display only 200 most recent TICKscript log messages and prevent overlapping ## v1.4.0.1 [2017-1-9] ### Features From d01bafdeaee8b7bcb9a627a86e2e56c33b86ff1e Mon Sep 17 00:00:00 2001 From: Iris Scholten Date: Mon, 5 Feb 2018 17:45:46 -0800 Subject: [PATCH 39/98] WIP: wire up the server with the front end --- ui/src/admin/actions/chronograf.js | 45 ++++++++-------- ui/src/admin/apis/chronograf.js | 51 +++++++++++++++++++ .../chronograf/ProvidersTableRow.js | 6 +-- ui/src/admin/constants/chronografAdmin.js | 1 + ui/src/admin/constants/dummyProviderMaps.js | 19 ------- ui/src/admin/containers/ProvidersPage.js | 24 ++------- ui/src/admin/reducers/chronograf.js | 15 ++++-- 7 files changed, 94 insertions(+), 67 deletions(-) delete mode 100644 ui/src/admin/constants/dummyProviderMaps.js diff --git a/ui/src/admin/actions/chronograf.js b/ui/src/admin/actions/chronograf.js index d0b3b3774..cb7d9f102 100644 --- a/ui/src/admin/actions/chronograf.js +++ b/ui/src/admin/actions/chronograf.js @@ -1,8 +1,6 @@ import _ from 'lodash' import uuid from 'node-uuid' -import {PROVIDER_MAPS} from 'src/admin/constants/dummyProviderMaps' - import { getUsers as getUsersAJAX, getOrganizations as getOrganizationsAJAX, @@ -12,6 +10,10 @@ import { createOrganization as createOrganizationAJAX, updateOrganization as updateOrganizationAJAX, deleteOrganization as deleteOrganizationAJAX, + getMappings as getMappingsAJAX, + createMapping as createMappingAJAX, + updateMapping as updateMappingAJAX, + deleteMapping as deleteMappingAJAX, } from 'src/admin/apis/chronograf' import {publishAutoDismissingNotification} from 'shared/dispatchers' @@ -96,17 +98,18 @@ export const removeOrganization = organization => ({ }, }) -export const loadMappings = mappings => ({ +export const loadMappings = ({mappings}) => ({ type: 'CHRONOGRAF_LOAD_MAPPINGS', payload: { mappings, }, }) -export const updateMapping = mapping => ({ +export const updateMapping = (staleMapping, updatedMapping) => ({ type: 'CHRONOGRAF_UPDATE_MAPPING', payload: { - mapping, + staleMapping, + updatedMapping, }, }) @@ -143,31 +146,28 @@ export const loadOrganizationsAsync = url => async dispatch => { } } -export const loadMappingsAsync = () => async dispatch => { +export const loadMappingsAsync = url => async dispatch => { try { - // awaiting backend changes - // todo: change to below instead of starting from provider maps data - // const {data} await getOrganizationsAJAX(url); - dispatch(loadMappings(PROVIDER_MAPS)) + const {data} = await getMappingsAJAX(url) + dispatch(loadMappings(data)) } catch (error) { dispatch(errorThrown(error)) } } export const createMappingAsync = (url, mapping) => async dispatch => { - const mappingWithTempID = {...mapping, _tempID: uuid.v4()} - dispatch(addMapping(mappingWithTempID)) + const mappingWithTempId = {...mapping, _tempID: uuid.v4()} + dispatch(addMapping(mappingWithTempId)) try { - /* const {data} = await createMappingAJAX(url, mapping) - dispatch(updateMapping(data)) - */ + const {data} = await createMappingAJAX(url, mapping) + dispatch(updateMapping(mappingWithTempId, data)) } catch (error) { const message = `${_.upperFirst( _.toLower(error.data.message) )}: Scheme: ${mapping.scheme} Provider: ${mapping.provider}` dispatch(errorThrown(error, message)) setTimeout( - () => dispatch(removeMapping(mappingWithTempID)), + () => dispatch(removeMapping(mappingWithTempId)), REVERT_STATE_DELAY ) } @@ -176,7 +176,7 @@ export const createMappingAsync = (url, mapping) => async dispatch => { export const deleteMappingAsync = mapping => async dispatch => { dispatch(removeMapping(mapping)) try { - // await deleteMappingAJAX(mapping) + await deleteMappingAJAX(mapping) dispatch( publishAutoDismissingNotification( 'success', @@ -189,13 +189,16 @@ export const deleteMappingAsync = mapping => async dispatch => { } } -export const updateMappingAsync = mapping => async dispatch => { - dispatch(updateMapping(mapping)) +export const updateMappingAsync = ( + staleMapping, + updatedMapping +) => async dispatch => { + dispatch(updateMapping(staleMapping, updatedMapping)) try { - // const {data} = await updateMappingAJAX(mapping) + await updateMappingAJAX(updatedMapping) } catch (error) { dispatch(errorThrown(error)) - dispatch(updateMapping(mapping)) + dispatch(updateMapping(updatedMapping, staleMapping)) } } diff --git a/ui/src/admin/apis/chronograf.js b/ui/src/admin/apis/chronograf.js index 300884928..6b4f7a848 100644 --- a/ui/src/admin/apis/chronograf.js +++ b/ui/src/admin/apis/chronograf.js @@ -102,3 +102,54 @@ export const deleteOrganization = async organization => { throw error } } + +// Mappings +export const createMapping = async (url, mapping) => { + try { + return await AJAX({ + method: 'POST', + resource: 'mappings', + data: mapping, + }) + } catch (error) { + console.error(error) + throw error + } +} + +export const getMappings = async url => { + try { + return await AJAX({ + method: 'GET', + resource: 'mappings', + }) + } catch (error) { + console.error(error) + throw error + } +} + +export const updateMapping = async mapping => { + try { + return await AJAX({ + method: 'PUT', + url: mapping.links.self, + data: mapping, + }) + } catch (error) { + console.error(error) + throw error + } +} + +export const deleteMapping = async mapping => { + try { + return await AJAX({ + method: 'DELETE', + url: mapping.links.self, + }) + } catch (error) { + console.error(error) + throw error + } +} diff --git a/ui/src/admin/components/chronograf/ProvidersTableRow.js b/ui/src/admin/components/chronograf/ProvidersTableRow.js index baf75fdab..a9d08848d 100644 --- a/ui/src/admin/components/chronograf/ProvidersTableRow.js +++ b/ui/src/admin/components/chronograf/ProvidersTableRow.js @@ -4,7 +4,7 @@ import ConfirmButtons from 'shared/components/ConfirmButtons' import Dropdown from 'shared/components/Dropdown' import InputClickToEdit from 'shared/components/InputClickToEdit' -import {DEFAULT_PROVIDER_MAP_ID} from 'src/admin/constants/dummyProviderMaps' +import {DEFAULT_MAPPING_ID} from 'src/admin/constants/chronografAdmin' class ProvidersTableRow extends Component { constructor(props) { @@ -37,7 +37,7 @@ class ProvidersTableRow extends Component { const {onUpdate, mapping: {id}} = this.props const newState = {...this.state, ...changes, id} this.setState(newState) - onUpdate(newState) + onUpdate(this.props.mapping, newState) } handleChangeProvider = provider => this.handleUpdateMapping({provider}) @@ -70,7 +70,7 @@ class ProvidersTableRow extends Component { ? 'fancytable--td provider--redirect deleting' : 'fancytable--td provider--redirect' - const isDefaultMapping = DEFAULT_PROVIDER_MAP_ID === mapping.id + const isDefaultMapping = DEFAULT_MAPPING_ID === mapping.id return (
diff --git a/ui/src/admin/constants/chronografAdmin.js b/ui/src/admin/constants/chronografAdmin.js index c3773e327..e26d0e121 100644 --- a/ui/src/admin/constants/chronografAdmin.js +++ b/ui/src/admin/constants/chronografAdmin.js @@ -13,3 +13,4 @@ export const USER_ROLES = [ ] export const DEFAULT_ORG_ID = 'default' +export const DEFAULT_MAPPING_ID = 'default' diff --git a/ui/src/admin/constants/dummyProviderMaps.js b/ui/src/admin/constants/dummyProviderMaps.js deleted file mode 100644 index 8279494c0..000000000 --- a/ui/src/admin/constants/dummyProviderMaps.js +++ /dev/null @@ -1,19 +0,0 @@ -import {DEFAULT_ORG_ID} from 'src/admin/constants/chronografAdmin' - -export const DEFAULT_PROVIDER_MAP_ID = '0' -export const PROVIDER_MAPS = [ - { - id: DEFAULT_PROVIDER_MAP_ID, - scheme: '*', - provider: '*', - providerOrganization: '*', - organizationId: DEFAULT_ORG_ID, - }, - { - id: '1', - scheme: 'oauth2', - provider: 'github', - providerOrganization: null, - organizationId: '1', - }, -] diff --git a/ui/src/admin/containers/ProvidersPage.js b/ui/src/admin/containers/ProvidersPage.js index c0965ce9c..5d74334ec 100644 --- a/ui/src/admin/containers/ProvidersPage.js +++ b/ui/src/admin/containers/ProvidersPage.js @@ -23,33 +23,15 @@ class ProvidersPage extends Component { } handleCreateMap = mapping => { - this.props.actions.createMappingAsync('', mapping) - /* - const { - links, - actions: {createMappingAsync} - } = this.props - await createMappingAsync(links.mappings, mapping) - // this.refreshMe()? -- why - */ + this.props.actions.createMappingAsync(this.props.links.mappings, mapping) } - handleUpdateMap = updatedMap => { - // update the redux store - this.props.actions.updateMappingAsync(updatedMap) - // update the server - /* - const {actionsAdmin: {updateMappingAsync}} = this.props - await updateMappingAsync(mapping) - */ + handleUpdateMap = (staleMap, updatedMap) => { + this.props.actions.updateMappingAsync(staleMap, updatedMap) } handleDeleteMap = mapping => { this.props.actions.deleteMappingAsync(mapping) - /* - const {actionsAdmin: {deleteOrganizationAsync}} = this.props - deleteMappingAsync(mapping) // why no await? - */ } render() { diff --git a/ui/src/admin/reducers/chronograf.js b/ui/src/admin/reducers/chronograf.js index 17fb3bacd..f943074d1 100644 --- a/ui/src/admin/reducers/chronograf.js +++ b/ui/src/admin/reducers/chronograf.js @@ -107,11 +107,11 @@ const adminChronograf = (state = initialState, action) => { } case 'CHRONOGRAF_UPDATE_MAPPING': { - const {mapping} = action.payload + const {staleMapping, updatedMapping} = action.payload return { ...state, - mappings: state.mappings.map( - m => (m.id === mapping.id ? {...mapping} : m) + mappings: state.mappings.map(m => + replaceMapping(m, staleMapping, updatedMapping) ), } } @@ -141,4 +141,13 @@ const adminChronograf = (state = initialState, action) => { return state } +function replaceMapping(m, staleMapping, updatedMapping) { + if (staleMapping._tempID && m._tempID === staleMapping._tempID) { + return {...updatedMapping} + } else if (m.id === staleMapping.id) { + return {...updatedMapping} + } + return m +} + export default adminChronograf From 3c3e524b46e9eaa1bb316cb595b0251cde581bc5 Mon Sep 17 00:00:00 2001 From: Iris Scholten Date: Tue, 6 Feb 2018 18:33:27 -0800 Subject: [PATCH 40/98] Group=>ProviderOrganization; organization=>organiztionId; fix pointer bug --- bolt/internal/internal.go | 12 +- bolt/internal/internal.pb.go | 186 +++++++++--------- bolt/internal/internal.proto | 10 +- bolt/mapping.go | 14 +- bolt/mapping_test.go | 186 +++++++++--------- bolt/organizations.go | 10 +- chronograf.go | 11 +- integrations/server_test.go | 178 ++++++++--------- server/mapping.go | 31 +-- server/mapping_test.go | 42 ++-- server/me_test.go | 32 +-- ui/src/admin/actions/chronograf.js | 4 +- ui/src/admin/apis/chronograf.js | 2 +- .../components/chronograf/ProvidersTable.js | 55 ++++-- .../chronograf/ProvidersTableRow.js | 14 +- .../chronograf/ProvidersTableRowNew.js | 6 +- ui/src/admin/containers/ProvidersPage.js | 32 +-- ui/src/admin/reducers/chronograf.js | 1 + 18 files changed, 422 insertions(+), 404 deletions(-) diff --git a/bolt/internal/internal.go b/bolt/internal/internal.go index 72ffc888c..29f2f09cc 100644 --- a/bolt/internal/internal.go +++ b/bolt/internal/internal.go @@ -654,11 +654,11 @@ func UnmarshalConfigPB(data []byte, c *Config) error { func MarshalMapping(m *chronograf.Mapping) ([]byte, error) { return MarshalMappingPB(&Mapping{ - Provider: m.Provider, - Scheme: m.Scheme, - Group: m.Group, - ID: m.ID, - Organization: m.Organization, + Provider: m.Provider, + Scheme: m.Scheme, + ProviderOrganization: m.ProviderOrganization, + ID: m.ID, + Organization: m.Organization, }) } @@ -676,7 +676,7 @@ func UnmarshalMapping(data []byte, m *chronograf.Mapping) error { m.Provider = pb.Provider m.Scheme = pb.Scheme - m.Group = pb.Group + m.ProviderOrganization = pb.ProviderOrganization m.Organization = pb.Organization m.ID = pb.ID diff --git a/bolt/internal/internal.pb.go b/bolt/internal/internal.pb.go index a394c2cd4..31d30bb4f 100644 --- a/bolt/internal/internal.pb.go +++ b/bolt/internal/internal.pb.go @@ -1,6 +1,5 @@ -// Code generated by protoc-gen-gogo. +// Code generated by protoc-gen-gogo. DO NOT EDIT. // source: internal.proto -// DO NOT EDIT! /* Package internal is a generated protocol buffer package. @@ -1026,12 +1025,11 @@ func (m *Role) GetName() string { } type Mapping struct { - Provider string `protobuf:"bytes,1,opt,name=Provider,proto3" json:"Provider,omitempty"` - Scheme string `protobuf:"bytes,2,opt,name=Scheme,proto3" json:"Scheme,omitempty"` - Group string `protobuf:"bytes,3,opt,name=Group,proto3" json:"Group,omitempty"` - // string GrantedRole = 4; // GrantedRole is the name of the role that you will be granted if you match the mapping - ID string `protobuf:"bytes,4,opt,name=ID,proto3" json:"ID,omitempty"` - Organization string `protobuf:"bytes,5,opt,name=Organization,proto3" json:"Organization,omitempty"` + Provider string `protobuf:"bytes,1,opt,name=Provider,proto3" json:"Provider,omitempty"` + Scheme string `protobuf:"bytes,2,opt,name=Scheme,proto3" json:"Scheme,omitempty"` + ProviderOrganization string `protobuf:"bytes,3,opt,name=ProviderOrganization,proto3" json:"ProviderOrganization,omitempty"` + ID string `protobuf:"bytes,4,opt,name=ID,proto3" json:"ID,omitempty"` + Organization string `protobuf:"bytes,5,opt,name=Organization,proto3" json:"Organization,omitempty"` } func (m *Mapping) Reset() { *m = Mapping{} } @@ -1053,9 +1051,9 @@ func (m *Mapping) GetScheme() string { return "" } -func (m *Mapping) GetGroup() string { +func (m *Mapping) GetProviderOrganization() string { if m != nil { - return m.Group + return m.ProviderOrganization } return "" } @@ -1198,92 +1196,92 @@ func init() { func init() { proto.RegisterFile("internal.proto", fileDescriptorInternal) } var fileDescriptorInternal = []byte{ - // 1377 bytes of a gzipped FileDescriptorProto + // 1379 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x57, 0xcf, 0x8e, 0xe3, 0xc4, 0x13, 0x96, 0x63, 0x3b, 0x89, 0x2b, 0xb3, 0xfb, 0x5b, 0xf9, 0x37, 0x62, 0xcd, 0x22, 0xa1, 0x60, 0x81, 0x08, 0x82, 0x1d, 0xd0, 0xac, 0x90, 0x10, 0x02, 0xa4, 0xcc, 0x04, 0x2d, 0xc3, 0xfe, 0x9b, 0xed, 0xec, 0x0c, 0x27, 0xb4, 0xea, 0x38, 0x95, 0xc4, 0x5a, 0xc7, 0x36, 0x6d, 0x7b, 0x26, 0xe6, - 0x05, 0x78, 0x0b, 0x24, 0x24, 0x9e, 0x00, 0x71, 0xe7, 0x8a, 0xb8, 0xf2, 0x0e, 0xbc, 0x02, 0x57, - 0x54, 0xdd, 0x6d, 0xc7, 0x99, 0x64, 0x57, 0x73, 0x40, 0xdc, 0xfa, 0xab, 0xea, 0x54, 0x57, 0x57, - 0x7d, 0xf5, 0xb5, 0x03, 0x37, 0xc3, 0x38, 0x47, 0x11, 0xf3, 0xe8, 0x20, 0x15, 0x49, 0x9e, 0xb8, - 0xdd, 0x0a, 0xfb, 0x7f, 0xb5, 0xa0, 0x3d, 0x4e, 0x0a, 0x11, 0xa0, 0x7b, 0x13, 0x5a, 0x27, 0x23, - 0xcf, 0xe8, 0x1b, 0x03, 0x93, 0xb5, 0x4e, 0x46, 0xae, 0x0b, 0xd6, 0x63, 0xbe, 0x44, 0xaf, 0xd5, - 0x37, 0x06, 0x0e, 0x93, 0x6b, 0xb2, 0x3d, 0x2b, 0x53, 0xf4, 0x4c, 0x65, 0xa3, 0xb5, 0x7b, 0x07, - 0xba, 0x67, 0x19, 0x45, 0x5b, 0xa2, 0x67, 0x49, 0x7b, 0x8d, 0xc9, 0x77, 0xca, 0xb3, 0xec, 0x32, - 0x11, 0x53, 0xcf, 0x56, 0xbe, 0x0a, 0xbb, 0xb7, 0xc0, 0x3c, 0x63, 0x0f, 0xbd, 0xb6, 0x34, 0xd3, - 0xd2, 0xf5, 0xa0, 0x33, 0xc2, 0x19, 0x2f, 0xa2, 0xdc, 0xeb, 0xf4, 0x8d, 0x41, 0x97, 0x55, 0x90, - 0xe2, 0x3c, 0xc3, 0x08, 0xe7, 0x82, 0xcf, 0xbc, 0xae, 0x8a, 0x53, 0x61, 0xf7, 0x00, 0xdc, 0x93, - 0x38, 0xc3, 0xa0, 0x10, 0x38, 0x7e, 0x11, 0xa6, 0xe7, 0x28, 0xc2, 0x59, 0xe9, 0x39, 0x32, 0xc0, - 0x0e, 0x0f, 0x9d, 0xf2, 0x08, 0x73, 0x4e, 0x67, 0x83, 0x0c, 0x55, 0x41, 0xd7, 0x87, 0xbd, 0xf1, - 0x82, 0x0b, 0x9c, 0x8e, 0x31, 0x10, 0x98, 0x7b, 0x3d, 0xe9, 0xde, 0xb0, 0xd1, 0x9e, 0x27, 0x62, - 0xce, 0xe3, 0xf0, 0x7b, 0x9e, 0x87, 0x49, 0xec, 0xed, 0xa9, 0x3d, 0x4d, 0x1b, 0x55, 0x89, 0x25, - 0x11, 0x7a, 0x37, 0x54, 0x95, 0x68, 0xed, 0xff, 0x6a, 0x80, 0x33, 0xe2, 0xd9, 0x62, 0x92, 0x70, - 0x31, 0xbd, 0x56, 0xad, 0xef, 0x82, 0x1d, 0x60, 0x14, 0x65, 0x9e, 0xd9, 0x37, 0x07, 0xbd, 0xc3, - 0xdb, 0x07, 0x75, 0x13, 0xeb, 0x38, 0xc7, 0x18, 0x45, 0x4c, 0xed, 0x72, 0x3f, 0x02, 0x27, 0xc7, - 0x65, 0x1a, 0xf1, 0x1c, 0x33, 0xcf, 0x92, 0x3f, 0x71, 0xd7, 0x3f, 0x79, 0xa6, 0x5d, 0x6c, 0xbd, - 0x69, 0xeb, 0x2a, 0xf6, 0xf6, 0x55, 0xfc, 0x3f, 0x5b, 0x70, 0x63, 0xe3, 0x38, 0x77, 0x0f, 0x8c, - 0x95, 0xcc, 0xdc, 0x66, 0xc6, 0x8a, 0x50, 0x29, 0xb3, 0xb6, 0x99, 0x51, 0x12, 0xba, 0x94, 0xdc, - 0xb0, 0x99, 0x71, 0x49, 0x68, 0x21, 0x19, 0x61, 0x33, 0x63, 0xe1, 0xbe, 0x07, 0x9d, 0xef, 0x0a, - 0x14, 0x21, 0x66, 0x9e, 0x2d, 0xb3, 0xfb, 0xdf, 0x3a, 0xbb, 0xa7, 0x05, 0x8a, 0x92, 0x55, 0x7e, - 0xaa, 0x86, 0x64, 0x93, 0xa2, 0x86, 0x5c, 0x93, 0x2d, 0x27, 0xe6, 0x75, 0x94, 0x8d, 0xd6, 0xba, - 0x8a, 0x8a, 0x0f, 0x54, 0xc5, 0x8f, 0xc1, 0xe2, 0x2b, 0xcc, 0x3c, 0x47, 0xc6, 0x7f, 0xeb, 0x25, - 0x05, 0x3b, 0x18, 0xae, 0x30, 0xfb, 0x32, 0xce, 0x45, 0xc9, 0xe4, 0x76, 0xf7, 0x5d, 0x68, 0x07, - 0x49, 0x94, 0x88, 0xcc, 0x83, 0xab, 0x89, 0x1d, 0x93, 0x9d, 0x69, 0xf7, 0x9d, 0xfb, 0xe0, 0xd4, - 0xbf, 0x25, 0xfa, 0xbe, 0xc0, 0x52, 0x56, 0xc2, 0x61, 0xb4, 0x74, 0xdf, 0x06, 0xfb, 0x82, 0x47, - 0x85, 0xea, 0x62, 0xef, 0xf0, 0xe6, 0x3a, 0xcc, 0x70, 0x15, 0x66, 0x4c, 0x39, 0x3f, 0x6d, 0x7d, - 0x62, 0xf8, 0x73, 0xb0, 0x65, 0xe4, 0x06, 0x0f, 0x9c, 0x8a, 0x07, 0x72, 0xbe, 0x5a, 0x8d, 0xf9, - 0xba, 0x05, 0xe6, 0x57, 0xb8, 0xd2, 0x23, 0x47, 0xcb, 0x9a, 0x2d, 0x56, 0x83, 0x2d, 0xfb, 0x60, - 0x9f, 0xcb, 0xc3, 0x55, 0x17, 0x15, 0xf0, 0x7f, 0x31, 0xc0, 0xa2, 0xc3, 0xa9, 0xd7, 0x11, 0xce, - 0x79, 0x50, 0x1e, 0x25, 0x45, 0x3c, 0xcd, 0x3c, 0xa3, 0x6f, 0x0e, 0x4c, 0xb6, 0x61, 0x73, 0x5f, - 0x83, 0xf6, 0x44, 0x79, 0x5b, 0x7d, 0x73, 0xe0, 0x30, 0x8d, 0x28, 0x74, 0xc4, 0x27, 0x18, 0xe9, - 0x14, 0x14, 0xa0, 0xdd, 0xa9, 0xc0, 0x59, 0xb8, 0xd2, 0x69, 0x68, 0x44, 0xf6, 0xac, 0x98, 0x91, - 0x5d, 0x65, 0xa2, 0x11, 0x25, 0x3d, 0xe1, 0x59, 0xdd, 0x54, 0x5a, 0x53, 0xe4, 0x2c, 0xe0, 0x51, - 0xd5, 0x55, 0x05, 0xfc, 0xdf, 0x0c, 0x9a, 0x76, 0xc5, 0xd2, 0xad, 0x0a, 0xbd, 0x0e, 0x5d, 0x62, - 0xf0, 0xf3, 0x0b, 0x2e, 0x74, 0x95, 0x3a, 0x84, 0xcf, 0xb9, 0x70, 0x3f, 0x84, 0xb6, 0x2c, 0xf1, - 0x8e, 0x89, 0xa9, 0xc2, 0xc9, 0xaa, 0x30, 0xbd, 0xad, 0xe6, 0x94, 0xd5, 0xe0, 0x54, 0x7d, 0x59, - 0xbb, 0x79, 0xd9, 0xbb, 0x60, 0x13, 0x39, 0x4b, 0x99, 0xfd, 0xce, 0xc8, 0x8a, 0xc2, 0x6a, 0x97, - 0x7f, 0x06, 0x37, 0x36, 0x4e, 0xac, 0x4f, 0x32, 0x36, 0x4f, 0x5a, 0xd3, 0xc5, 0xd1, 0xf4, 0x20, - 0xa5, 0xcb, 0x30, 0xc2, 0x20, 0xc7, 0xa9, 0xac, 0x77, 0x97, 0xd5, 0xd8, 0xff, 0xc9, 0x58, 0xc7, - 0x95, 0xe7, 0x91, 0x96, 0x05, 0xc9, 0x72, 0xc9, 0xe3, 0xa9, 0x0e, 0x5d, 0x41, 0xaa, 0xdb, 0x74, - 0xa2, 0x43, 0xb7, 0xa6, 0x13, 0xc2, 0x22, 0xd5, 0x1d, 0x6c, 0x89, 0xd4, 0xed, 0x43, 0x6f, 0x89, - 0x3c, 0x2b, 0x04, 0x2e, 0x31, 0xce, 0x75, 0x09, 0x9a, 0x26, 0xf7, 0x36, 0x74, 0x72, 0x3e, 0x7f, - 0x4e, 0x24, 0xd7, 0x9d, 0xcc, 0xf9, 0xfc, 0x01, 0x96, 0xee, 0x1b, 0xe0, 0xcc, 0x42, 0x8c, 0xa6, - 0xd2, 0xa5, 0xda, 0xd9, 0x95, 0x86, 0x07, 0x58, 0xfa, 0xbf, 0x1b, 0xd0, 0x1e, 0xa3, 0xb8, 0x40, - 0x71, 0x2d, 0x91, 0x6b, 0x3e, 0x1e, 0xe6, 0x2b, 0x1e, 0x0f, 0x6b, 0xf7, 0xe3, 0x61, 0xaf, 0x1f, - 0x8f, 0x7d, 0xb0, 0xc7, 0x22, 0x38, 0x19, 0xc9, 0x8c, 0x4c, 0xa6, 0x00, 0xb1, 0x71, 0x18, 0xe4, - 0xe1, 0x05, 0xea, 0x17, 0x45, 0xa3, 0x2d, 0xed, 0xeb, 0xee, 0xd0, 0xbe, 0x1f, 0x0d, 0x68, 0x3f, - 0xe4, 0x65, 0x52, 0xe4, 0x5b, 0x2c, 0xec, 0x43, 0x6f, 0x98, 0xa6, 0x51, 0x18, 0xa8, 0x5f, 0xab, - 0x1b, 0x35, 0x4d, 0xb4, 0xe3, 0x51, 0xa3, 0xbe, 0xea, 0x6e, 0x4d, 0x13, 0xc9, 0xc5, 0xb1, 0xd4, - 0x77, 0x25, 0xd6, 0x0d, 0xb9, 0x50, 0xb2, 0x2e, 0x9d, 0x54, 0x84, 0x61, 0x91, 0x27, 0xb3, 0x28, - 0xb9, 0x94, 0xb7, 0xed, 0xb2, 0x1a, 0xfb, 0x7f, 0xb4, 0xc0, 0xfa, 0xaf, 0x34, 0x79, 0x0f, 0x8c, - 0x50, 0x37, 0xdb, 0x08, 0x6b, 0x85, 0xee, 0x34, 0x14, 0xda, 0x83, 0x4e, 0x29, 0x78, 0x3c, 0xc7, - 0xcc, 0xeb, 0x4a, 0x75, 0xa9, 0xa0, 0xf4, 0xc8, 0x39, 0x52, 0xd2, 0xec, 0xb0, 0x0a, 0xd6, 0x73, - 0x01, 0x8d, 0xb9, 0xf8, 0x40, 0xab, 0x78, 0x4f, 0x66, 0xe4, 0x6d, 0x96, 0xe5, 0xaa, 0x78, 0xff, - 0x7b, 0x9a, 0xfc, 0xb7, 0x01, 0x76, 0x3d, 0x54, 0xc7, 0x9b, 0x43, 0x75, 0xbc, 0x1e, 0xaa, 0xd1, - 0x51, 0x35, 0x54, 0xa3, 0x23, 0xc2, 0xec, 0xb4, 0x1a, 0x2a, 0x76, 0x4a, 0xcd, 0xba, 0x2f, 0x92, - 0x22, 0x3d, 0x2a, 0x55, 0x57, 0x1d, 0x56, 0x63, 0x62, 0xe2, 0x37, 0x0b, 0x14, 0xba, 0xd4, 0x0e, - 0xd3, 0x88, 0x78, 0xfb, 0x50, 0x0a, 0x8e, 0x2a, 0xae, 0x02, 0xee, 0x3b, 0x60, 0x33, 0x2a, 0x9e, - 0xac, 0xf0, 0x46, 0x5f, 0xa4, 0x99, 0x29, 0x2f, 0x05, 0x55, 0x5f, 0x6f, 0x9a, 0xc0, 0xd5, 0xb7, - 0xdc, 0xfb, 0xd0, 0x1e, 0x2f, 0xc2, 0x59, 0x5e, 0xbd, 0x85, 0xff, 0x6f, 0x08, 0x56, 0xb8, 0x44, - 0xe9, 0x63, 0x7a, 0x8b, 0xff, 0x14, 0x9c, 0xda, 0xb8, 0x4e, 0xc7, 0x68, 0xa6, 0xe3, 0x82, 0x75, - 0x16, 0x87, 0x79, 0x35, 0xba, 0xb4, 0xa6, 0xcb, 0x3e, 0x2d, 0x78, 0x9c, 0x87, 0x79, 0x59, 0x8d, - 0x6e, 0x85, 0xfd, 0x7b, 0x3a, 0x7d, 0x0a, 0x77, 0x96, 0xa6, 0x28, 0xb4, 0x0c, 0x28, 0x20, 0x0f, - 0x49, 0x2e, 0x51, 0x29, 0xb8, 0xc9, 0x14, 0xf0, 0xbf, 0x05, 0x67, 0x18, 0xa1, 0xc8, 0x59, 0x11, - 0xe1, 0xae, 0x97, 0xf1, 0xeb, 0xf1, 0x93, 0xc7, 0x55, 0x06, 0xb4, 0x5e, 0x8f, 0xbc, 0x79, 0x65, - 0xe4, 0x1f, 0xf0, 0x94, 0x9f, 0x8c, 0x24, 0xcf, 0x4d, 0xa6, 0x91, 0xff, 0xb3, 0x01, 0x16, 0x69, - 0x4b, 0x23, 0xb4, 0xf5, 0x2a, 0x5d, 0x3a, 0x15, 0xc9, 0x45, 0x38, 0x45, 0x51, 0x5d, 0xae, 0xc2, - 0xb2, 0xe8, 0xc1, 0x02, 0xeb, 0x07, 0x58, 0x23, 0xe2, 0x1a, 0x7d, 0xea, 0x55, 0xb3, 0xd4, 0xe0, - 0x1a, 0x99, 0x99, 0x72, 0xba, 0x6f, 0x02, 0x8c, 0x8b, 0x14, 0xc5, 0x70, 0xba, 0x0c, 0x63, 0xd9, - 0xf4, 0x2e, 0x6b, 0x58, 0xfc, 0x2f, 0xd4, 0xc7, 0xe3, 0x96, 0x42, 0x19, 0xbb, 0x3f, 0x34, 0xaf, - 0x66, 0xee, 0xff, 0x60, 0x40, 0xe7, 0x11, 0x4f, 0xd3, 0x30, 0x9e, 0x6f, 0xdc, 0xc2, 0x78, 0xe9, - 0x2d, 0x5a, 0x1b, 0xb7, 0xd8, 0x07, 0x5b, 0x72, 0xb6, 0x7a, 0xed, 0x25, 0xd0, 0x35, 0xb3, 0xea, - 0x76, 0x5c, 0xe7, 0xdb, 0x31, 0xda, 0xdc, 0xb3, 0xab, 0xa5, 0x5b, 0x75, 0xef, 0x43, 0x4f, 0x7f, - 0xf3, 0xcb, 0x2f, 0x68, 0x2d, 0x9b, 0x0d, 0x13, 0xe5, 0x7d, 0x5a, 0x4c, 0xa2, 0x30, 0x90, 0xd9, - 0x74, 0x99, 0x46, 0xfe, 0x21, 0xb4, 0x8f, 0x93, 0x78, 0x16, 0xce, 0xdd, 0x01, 0x58, 0xc3, 0x22, - 0x5f, 0xc8, 0x93, 0x7a, 0x87, 0xfb, 0x8d, 0x91, 0x2f, 0xf2, 0x85, 0xda, 0xc3, 0xe4, 0x0e, 0xff, - 0x33, 0x80, 0xb5, 0x8d, 0xfe, 0x48, 0xac, 0xfb, 0xf0, 0x18, 0x2f, 0x89, 0x2c, 0x99, 0x8c, 0xd2, - 0x65, 0x3b, 0x3c, 0xfe, 0xe7, 0xe0, 0x1c, 0x15, 0x61, 0x34, 0x3d, 0x89, 0x67, 0x09, 0x89, 0xc6, - 0x39, 0x8a, 0x6c, 0xdd, 0xa9, 0x0a, 0x52, 0xc2, 0xa4, 0x1f, 0xf5, 0xf4, 0x68, 0x34, 0x69, 0xcb, - 0xff, 0x62, 0xf7, 0xfe, 0x09, 0x00, 0x00, 0xff, 0xff, 0x82, 0x96, 0xb8, 0xb3, 0x9d, 0x0d, 0x00, - 0x00, + 0x61, 0x90, 0x90, 0xe0, 0x05, 0x10, 0x77, 0xae, 0x88, 0x2b, 0xef, 0xc0, 0x2b, 0x70, 0x45, 0xd5, + 0xdd, 0x76, 0x9c, 0x49, 0x76, 0x35, 0x07, 0xc4, 0xad, 0xbf, 0xaa, 0x4e, 0x75, 0x75, 0xd5, 0x57, + 0x5f, 0x3b, 0x70, 0x33, 0x8c, 0x73, 0x14, 0x31, 0x8f, 0x0e, 0x52, 0x91, 0xe4, 0x89, 0xdb, 0xad, + 0xb0, 0xff, 0x57, 0x0b, 0xda, 0xe3, 0xa4, 0x10, 0x01, 0xba, 0x37, 0xa1, 0x75, 0x32, 0xf2, 0x8c, + 0xbe, 0x31, 0x30, 0x59, 0xeb, 0x64, 0xe4, 0xba, 0x60, 0x3d, 0xe6, 0x4b, 0xf4, 0x5a, 0x7d, 0x63, + 0xe0, 0x30, 0xb9, 0x26, 0xdb, 0xb3, 0x32, 0x45, 0xcf, 0x54, 0x36, 0x5a, 0xbb, 0x77, 0xa0, 0x7b, + 0x96, 0x51, 0xb4, 0x25, 0x7a, 0x96, 0xb4, 0xd7, 0x98, 0x7c, 0xa7, 0x3c, 0xcb, 0x2e, 0x13, 0x31, + 0xf5, 0x6c, 0xe5, 0xab, 0xb0, 0x7b, 0x0b, 0xcc, 0x33, 0xf6, 0xd0, 0x6b, 0x4b, 0x33, 0x2d, 0x5d, + 0x0f, 0x3a, 0x23, 0x9c, 0xf1, 0x22, 0xca, 0xbd, 0x4e, 0xdf, 0x18, 0x74, 0x59, 0x05, 0x29, 0xce, + 0x33, 0x8c, 0x70, 0x2e, 0xf8, 0xcc, 0xeb, 0xaa, 0x38, 0x15, 0x76, 0x0f, 0xc0, 0x3d, 0x89, 0x33, + 0x0c, 0x0a, 0x81, 0xe3, 0x17, 0x61, 0x7a, 0x8e, 0x22, 0x9c, 0x95, 0x9e, 0x23, 0x03, 0xec, 0xf0, + 0xd0, 0x29, 0x8f, 0x30, 0xe7, 0x74, 0x36, 0xc8, 0x50, 0x15, 0x74, 0x7d, 0xd8, 0x1b, 0x2f, 0xb8, + 0xc0, 0xe9, 0x18, 0x03, 0x81, 0xb9, 0xd7, 0x93, 0xee, 0x0d, 0x1b, 0xed, 0x79, 0x22, 0xe6, 0x3c, + 0x0e, 0xbf, 0xe7, 0x79, 0x98, 0xc4, 0xde, 0x9e, 0xda, 0xd3, 0xb4, 0x51, 0x95, 0x58, 0x12, 0xa1, + 0x77, 0x43, 0x55, 0x89, 0xd6, 0xfe, 0xaf, 0x06, 0x38, 0x23, 0x9e, 0x2d, 0x26, 0x09, 0x17, 0xd3, + 0x6b, 0xd5, 0xfa, 0x2e, 0xd8, 0x01, 0x46, 0x51, 0xe6, 0x99, 0x7d, 0x73, 0xd0, 0x3b, 0xbc, 0x7d, + 0x50, 0x37, 0xb1, 0x8e, 0x73, 0x8c, 0x51, 0xc4, 0xd4, 0x2e, 0xf7, 0x23, 0x70, 0x72, 0x5c, 0xa6, + 0x11, 0xcf, 0x31, 0xf3, 0x2c, 0xf9, 0x13, 0x77, 0xfd, 0x93, 0x67, 0xda, 0xc5, 0xd6, 0x9b, 0xb6, + 0xae, 0x62, 0x6f, 0x5f, 0xc5, 0xff, 0xb3, 0x05, 0x37, 0x36, 0x8e, 0x73, 0xf7, 0xc0, 0x58, 0xc9, + 0xcc, 0x6d, 0x66, 0xac, 0x08, 0x95, 0x32, 0x6b, 0x9b, 0x19, 0x25, 0xa1, 0x4b, 0xc9, 0x0d, 0x9b, + 0x19, 0x97, 0x84, 0x16, 0x92, 0x11, 0x36, 0x33, 0x16, 0xee, 0x7b, 0xd0, 0xf9, 0xae, 0x40, 0x11, + 0x62, 0xe6, 0xd9, 0x32, 0xbb, 0xff, 0xad, 0xb3, 0x7b, 0x5a, 0xa0, 0x28, 0x59, 0xe5, 0xa7, 0x6a, + 0x48, 0x36, 0x29, 0x6a, 0xc8, 0x35, 0xd9, 0x72, 0x62, 0x5e, 0x47, 0xd9, 0x68, 0xad, 0xab, 0xa8, + 0xf8, 0x40, 0x55, 0xfc, 0x18, 0x2c, 0xbe, 0xc2, 0xcc, 0x73, 0x64, 0xfc, 0xb7, 0x5e, 0x52, 0xb0, + 0x83, 0xe1, 0x0a, 0xb3, 0x2f, 0xe3, 0x5c, 0x94, 0x4c, 0x6e, 0x77, 0xdf, 0x85, 0x76, 0x90, 0x44, + 0x89, 0xc8, 0x3c, 0xb8, 0x9a, 0xd8, 0x31, 0xd9, 0x99, 0x76, 0xdf, 0xb9, 0x0f, 0x4e, 0xfd, 0x5b, + 0xa2, 0xef, 0x0b, 0x2c, 0x65, 0x25, 0x1c, 0x46, 0x4b, 0xf7, 0x6d, 0xb0, 0x2f, 0x78, 0x54, 0xa8, + 0x2e, 0xf6, 0x0e, 0x6f, 0xae, 0xc3, 0x0c, 0x57, 0x61, 0xc6, 0x94, 0xf3, 0xd3, 0xd6, 0x27, 0x86, + 0x3f, 0x07, 0x5b, 0x46, 0x6e, 0xf0, 0xc0, 0xa9, 0x78, 0x20, 0xe7, 0xab, 0xd5, 0x98, 0xaf, 0x5b, + 0x60, 0x7e, 0x85, 0x2b, 0x3d, 0x72, 0xb4, 0xac, 0xd9, 0x62, 0x35, 0xd8, 0xb2, 0x0f, 0xf6, 0xb9, + 0x3c, 0x5c, 0x75, 0x51, 0x01, 0xff, 0x17, 0x03, 0x2c, 0x3a, 0x9c, 0x7a, 0x1d, 0xe1, 0x9c, 0x07, + 0xe5, 0x51, 0x52, 0xc4, 0xd3, 0xcc, 0x33, 0xfa, 0xe6, 0xc0, 0x64, 0x1b, 0x36, 0xf7, 0x35, 0x68, + 0x4f, 0x94, 0xb7, 0xd5, 0x37, 0x07, 0x0e, 0xd3, 0x88, 0x42, 0x47, 0x7c, 0x82, 0x91, 0x4e, 0x41, + 0x01, 0xda, 0x9d, 0x0a, 0x9c, 0x85, 0x2b, 0x9d, 0x86, 0x46, 0x64, 0xcf, 0x8a, 0x19, 0xd9, 0x55, + 0x26, 0x1a, 0x51, 0xd2, 0x13, 0x9e, 0xd5, 0x4d, 0xa5, 0x35, 0x45, 0xce, 0x02, 0x1e, 0x55, 0x5d, + 0x55, 0xc0, 0xff, 0xcd, 0xa0, 0x69, 0x57, 0x2c, 0xdd, 0xaa, 0xd0, 0xeb, 0xd0, 0x25, 0x06, 0x3f, + 0xbf, 0xe0, 0x42, 0x57, 0xa9, 0x43, 0xf8, 0x9c, 0x0b, 0xf7, 0x43, 0x68, 0xcb, 0x12, 0xef, 0x98, + 0x98, 0x2a, 0x9c, 0xac, 0x0a, 0xd3, 0xdb, 0x6a, 0x4e, 0x59, 0x0d, 0x4e, 0xd5, 0x97, 0xb5, 0x9b, + 0x97, 0xbd, 0x0b, 0x36, 0x91, 0xb3, 0x94, 0xd9, 0xef, 0x8c, 0xac, 0x28, 0xac, 0x76, 0xf9, 0x67, + 0x70, 0x63, 0xe3, 0xc4, 0xfa, 0x24, 0x63, 0xf3, 0xa4, 0x35, 0x5d, 0x1c, 0x4d, 0x0f, 0x52, 0xba, + 0x0c, 0x23, 0x0c, 0x72, 0x9c, 0xca, 0x7a, 0x77, 0x59, 0x8d, 0xfd, 0x1f, 0x8d, 0x75, 0x5c, 0x79, + 0x1e, 0x69, 0x59, 0x90, 0x2c, 0x97, 0x3c, 0x9e, 0xea, 0xd0, 0x15, 0xa4, 0xba, 0x4d, 0x27, 0x3a, + 0x74, 0x6b, 0x3a, 0x21, 0x2c, 0x52, 0xdd, 0xc1, 0x96, 0x48, 0xdd, 0x3e, 0xf4, 0x96, 0xc8, 0xb3, + 0x42, 0xe0, 0x12, 0xe3, 0x5c, 0x97, 0xa0, 0x69, 0x72, 0x6f, 0x43, 0x27, 0xe7, 0xf3, 0xe7, 0x44, + 0x72, 0xdd, 0xc9, 0x9c, 0xcf, 0x1f, 0x60, 0xe9, 0xbe, 0x01, 0xce, 0x2c, 0xc4, 0x68, 0x2a, 0x5d, + 0xaa, 0x9d, 0x5d, 0x69, 0x78, 0x80, 0xa5, 0xff, 0xbb, 0x01, 0xed, 0x31, 0x8a, 0x0b, 0x14, 0xd7, + 0x12, 0xb9, 0xe6, 0xe3, 0x61, 0xbe, 0xe2, 0xf1, 0xb0, 0x76, 0x3f, 0x1e, 0xf6, 0xfa, 0xf1, 0xd8, + 0x07, 0x7b, 0x2c, 0x82, 0x93, 0x91, 0xcc, 0xc8, 0x64, 0x0a, 0x10, 0x1b, 0x87, 0x41, 0x1e, 0x5e, + 0xa0, 0x7e, 0x51, 0x34, 0xda, 0xd2, 0xbe, 0xee, 0x0e, 0xed, 0xfb, 0xc1, 0x80, 0xf6, 0x43, 0x5e, + 0x26, 0x45, 0xbe, 0xc5, 0xc2, 0x3e, 0xf4, 0x86, 0x69, 0x1a, 0x85, 0x81, 0xfa, 0xb5, 0xba, 0x51, + 0xd3, 0x44, 0x3b, 0x1e, 0x35, 0xea, 0xab, 0xee, 0xd6, 0x34, 0x91, 0x5c, 0x1c, 0x4b, 0x7d, 0x57, + 0x62, 0xdd, 0x90, 0x0b, 0x25, 0xeb, 0xd2, 0x49, 0x45, 0x18, 0x16, 0x79, 0x32, 0x8b, 0x92, 0x4b, + 0x79, 0xdb, 0x2e, 0xab, 0xb1, 0xff, 0x47, 0x0b, 0xac, 0xff, 0x4a, 0x93, 0xf7, 0xc0, 0x08, 0x75, + 0xb3, 0x8d, 0xb0, 0x56, 0xe8, 0x4e, 0x43, 0xa1, 0x3d, 0xe8, 0x94, 0x82, 0xc7, 0x73, 0xcc, 0xbc, + 0xae, 0x54, 0x97, 0x0a, 0x4a, 0x8f, 0x9c, 0x23, 0x25, 0xcd, 0x0e, 0xab, 0x60, 0x3d, 0x17, 0xd0, + 0x98, 0x8b, 0x0f, 0xb4, 0x8a, 0xf7, 0x64, 0x46, 0xde, 0x66, 0x59, 0xae, 0x8a, 0xf7, 0xbf, 0xa7, + 0xc9, 0x7f, 0x1b, 0x60, 0xd7, 0x43, 0x75, 0xbc, 0x39, 0x54, 0xc7, 0xeb, 0xa1, 0x1a, 0x1d, 0x55, + 0x43, 0x35, 0x3a, 0x22, 0xcc, 0x4e, 0xab, 0xa1, 0x62, 0xa7, 0xd4, 0xac, 0xfb, 0x22, 0x29, 0xd2, + 0xa3, 0x52, 0x75, 0xd5, 0x61, 0x35, 0x26, 0x26, 0x7e, 0xb3, 0x40, 0xa1, 0x4b, 0xed, 0x30, 0x8d, + 0x88, 0xb7, 0x0f, 0xa5, 0xe0, 0xa8, 0xe2, 0x2a, 0xe0, 0xbe, 0x03, 0x36, 0xa3, 0xe2, 0xc9, 0x0a, + 0x6f, 0xf4, 0x45, 0x9a, 0x99, 0xf2, 0x52, 0x50, 0xf5, 0xf5, 0xa6, 0x09, 0x5c, 0x7d, 0xcb, 0xbd, + 0x0f, 0xed, 0xf1, 0x22, 0x9c, 0xe5, 0xd5, 0x5b, 0xf8, 0xff, 0x86, 0x60, 0x85, 0x4b, 0x94, 0x3e, + 0xa6, 0xb7, 0xf8, 0x4f, 0xc1, 0xa9, 0x8d, 0xeb, 0x74, 0x8c, 0x66, 0x3a, 0x2e, 0x58, 0x67, 0x71, + 0x98, 0x57, 0xa3, 0x4b, 0x6b, 0xba, 0xec, 0xd3, 0x82, 0xc7, 0x79, 0x98, 0x97, 0xd5, 0xe8, 0x56, + 0xd8, 0xbf, 0xa7, 0xd3, 0xa7, 0x70, 0x67, 0x69, 0x8a, 0x42, 0xcb, 0x80, 0x02, 0xf2, 0x90, 0xe4, + 0x12, 0x95, 0x82, 0x9b, 0x4c, 0x01, 0xff, 0x5b, 0x70, 0x86, 0x11, 0x8a, 0x9c, 0x15, 0x11, 0xee, + 0x7a, 0x19, 0xbf, 0x1e, 0x3f, 0x79, 0x5c, 0x65, 0x40, 0xeb, 0xf5, 0xc8, 0x9b, 0x57, 0x46, 0xfe, + 0x01, 0x4f, 0xf9, 0xc9, 0x48, 0xf2, 0xdc, 0x64, 0x1a, 0xf9, 0x3f, 0x19, 0x60, 0x91, 0xb6, 0x34, + 0x42, 0x5b, 0xaf, 0xd2, 0xa5, 0x53, 0x91, 0x5c, 0x84, 0x53, 0x14, 0xd5, 0xe5, 0x2a, 0x2c, 0x8b, + 0x1e, 0x2c, 0xb0, 0x7e, 0x80, 0x35, 0x22, 0xae, 0xd1, 0xa7, 0x5e, 0x35, 0x4b, 0x0d, 0xae, 0x91, + 0x99, 0x29, 0xa7, 0xfb, 0x26, 0xc0, 0xb8, 0x48, 0x51, 0x0c, 0xa7, 0xcb, 0x30, 0x96, 0x4d, 0xef, + 0xb2, 0x86, 0xc5, 0xff, 0x42, 0x7d, 0x3c, 0x6e, 0x29, 0x94, 0xb1, 0xfb, 0x43, 0xf3, 0x6a, 0xe6, + 0xfe, 0xcf, 0x06, 0x74, 0x1e, 0xf1, 0x34, 0x0d, 0xe3, 0xf9, 0xc6, 0x2d, 0x8c, 0x97, 0xde, 0xa2, + 0xb5, 0x71, 0x8b, 0x43, 0xd8, 0xaf, 0xf6, 0x6c, 0x9c, 0xaf, 0xaa, 0xb0, 0xd3, 0xa7, 0x2b, 0x6a, + 0xd5, 0xcd, 0xba, 0xce, 0x97, 0x65, 0xb4, 0xb9, 0x67, 0x57, 0xc3, 0xb7, 0xba, 0xd2, 0x87, 0x9e, + 0xfe, 0x47, 0x20, 0xbf, 0xaf, 0xb5, 0xa8, 0x36, 0x4c, 0x74, 0xab, 0xd3, 0x62, 0x12, 0x85, 0x81, + 0xcc, 0xa6, 0xcb, 0x34, 0xf2, 0x0f, 0xa1, 0x7d, 0x9c, 0xc4, 0xb3, 0x70, 0xee, 0x0e, 0xc0, 0x1a, + 0x16, 0xf9, 0x42, 0x9e, 0xd4, 0x3b, 0xdc, 0x6f, 0x08, 0x42, 0x91, 0x2f, 0xd4, 0x1e, 0x26, 0x77, + 0xf8, 0x9f, 0x01, 0xac, 0x6d, 0xf4, 0x37, 0x63, 0xdd, 0xa5, 0xc7, 0x78, 0x49, 0x54, 0xca, 0x64, + 0x94, 0x2e, 0xdb, 0xe1, 0xf1, 0x3f, 0x07, 0xe7, 0xa8, 0x08, 0xa3, 0xe9, 0x49, 0x3c, 0x4b, 0x48, + 0x52, 0xce, 0x51, 0x64, 0xeb, 0x3e, 0x56, 0x90, 0x12, 0x26, 0x75, 0xa9, 0x67, 0x4b, 0xa3, 0x49, + 0x5b, 0xfe, 0x53, 0xbb, 0xf7, 0x4f, 0x00, 0x00, 0x00, 0xff, 0xff, 0xa0, 0x76, 0x27, 0x50, 0xbb, + 0x0d, 0x00, 0x00, } diff --git a/bolt/internal/internal.proto b/bolt/internal/internal.proto index 10b37511c..b707e0fe4 100644 --- a/bolt/internal/internal.proto +++ b/bolt/internal/internal.proto @@ -158,11 +158,11 @@ message Role { } message Mapping { - string Provider = 1; // Provider is the provider that certifies and issues this user's authentication, e.g. GitHub - string Scheme = 2; // Scheme is the scheme used to perform this user's authentication, e.g. OAuth2 or LDAP - string Group = 3; // Group is the group or organizations that you are a part of in an auth provider - string ID = 4; // ID is the unique ID for the mapping - string Organization = 5; // Organization is the organization ID that resource belongs to + string Provider = 1; // Provider is the provider that certifies and issues this user's authentication, e.g. GitHub + string Scheme = 2; // Scheme is the scheme used to perform this user's authentication, e.g. OAuth2 or LDAP + string ProviderOrganization = 3; // ProviderOrganization is the group or organizations that you are a part of in an auth provider + string ID = 4; // ID is the unique ID for the mapping + string Organization = 5; // Organization is the organization ID that resource belongs to } message Organization { diff --git a/bolt/mapping.go b/bolt/mapping.go index 81c899c2b..52e643dfc 100644 --- a/bolt/mapping.go +++ b/bolt/mapping.go @@ -54,16 +54,16 @@ func (s *MappingsStore) Add(ctx context.Context, o *chronograf.Mapping) (*chrono // All returns all known organizations func (s *MappingsStore) All(ctx context.Context) ([]chronograf.Mapping, error) { - var orgs []chronograf.Mapping - err := s.each(ctx, func(o *chronograf.Mapping) { - orgs = append(orgs, *o) + var mappings []chronograf.Mapping + err := s.each(ctx, func(m *chronograf.Mapping) { + mappings = append(mappings, *m) }) if err != nil { return nil, err } - return orgs, nil + return mappings, nil } // Delete the organization from MappingsStore @@ -100,11 +100,11 @@ func (s *MappingsStore) get(ctx context.Context, id string) (*chronograf.Mapping func (s *MappingsStore) each(ctx context.Context, fn func(*chronograf.Mapping)) error { return s.client.db.View(func(tx *bolt.Tx) error { return tx.Bucket(MappingsBucket).ForEach(func(k, v []byte) error { - var org chronograf.Mapping - if err := internal.UnmarshalMapping(v, &org); err != nil { + var m chronograf.Mapping + if err := internal.UnmarshalMapping(v, &m); err != nil { return err } - fn(&org) + fn(&m) return nil }) }) diff --git a/bolt/mapping_test.go b/bolt/mapping_test.go index 66a24d187..745f5df90 100644 --- a/bolt/mapping_test.go +++ b/bolt/mapping_test.go @@ -35,18 +35,18 @@ func TestMappingStore_Add(t *testing.T) { name: "default with wildcards", args: args{ mapping: &chronograf.Mapping{ - Organization: "default", - Provider: "*", - Scheme: "*", - Group: "*", + Organization: "default", + Provider: "*", + Scheme: "*", + ProviderOrganization: "*", }, }, wants: wants{ mapping: &chronograf.Mapping{ - Organization: "default", - Provider: "*", - Scheme: "*", - Group: "*", + Organization: "default", + Provider: "*", + Scheme: "*", + ProviderOrganization: "*", }, }, }, @@ -54,18 +54,18 @@ func TestMappingStore_Add(t *testing.T) { name: "simple", args: args{ mapping: &chronograf.Mapping{ - Organization: "default", - Provider: "github", - Scheme: "oauth2", - Group: "idk", + Organization: "default", + Provider: "github", + Scheme: "oauth2", + ProviderOrganization: "idk", }, }, wants: wants{ mapping: &chronograf.Mapping{ - Organization: "default", - Provider: "github", - Scheme: "oauth2", - Group: "idk", + Organization: "default", + Provider: "github", + Scheme: "oauth2", + ProviderOrganization: "idk", }, }, }, @@ -128,26 +128,26 @@ func TestMappingStore_All(t *testing.T) { fields: fields{ mappings: []*chronograf.Mapping{ &chronograf.Mapping{ - Organization: "0", - Provider: "google", - Scheme: "ldap", - Group: "*", + Organization: "0", + Provider: "google", + Scheme: "ldap", + ProviderOrganization: "*", }, }, }, wants: wants{ mappings: []chronograf.Mapping{ chronograf.Mapping{ - Organization: "0", - Provider: "google", - Scheme: "ldap", - Group: "*", + Organization: "0", + Provider: "google", + Scheme: "ldap", + ProviderOrganization: "*", }, chronograf.Mapping{ - Organization: "default", - Provider: "*", - Scheme: "*", - Group: "*", + Organization: "default", + Provider: "*", + Scheme: "*", + ProviderOrganization: "*", }, }, }, @@ -206,26 +206,26 @@ func TestMappingStore_Delete(t *testing.T) { fields: fields{ mappings: []*chronograf.Mapping{ &chronograf.Mapping{ - Organization: "default", - Provider: "*", - Scheme: "*", - Group: "*", + Organization: "default", + Provider: "*", + Scheme: "*", + ProviderOrganization: "*", }, &chronograf.Mapping{ - Organization: "0", - Provider: "google", - Scheme: "ldap", - Group: "*", + Organization: "0", + Provider: "google", + Scheme: "ldap", + ProviderOrganization: "*", }, }, }, args: args{ mapping: &chronograf.Mapping{ - ID: "1", - Organization: "default", - Provider: "*", - Scheme: "*", - Group: "*", + ID: "1", + Organization: "default", + Provider: "*", + Scheme: "*", + ProviderOrganization: "*", }, }, wants: wants{ @@ -237,26 +237,26 @@ func TestMappingStore_Delete(t *testing.T) { fields: fields{ mappings: []*chronograf.Mapping{ &chronograf.Mapping{ - Organization: "default", - Provider: "*", - Scheme: "*", - Group: "*", + Organization: "default", + Provider: "*", + Scheme: "*", + ProviderOrganization: "*", }, &chronograf.Mapping{ - Organization: "0", - Provider: "google", - Scheme: "ldap", - Group: "*", + Organization: "0", + Provider: "google", + Scheme: "ldap", + ProviderOrganization: "*", }, }, }, args: args{ mapping: &chronograf.Mapping{ - ID: "0", - Organization: "default", - Provider: "*", - Scheme: "*", - Group: "*", + ID: "0", + Organization: "default", + Provider: "*", + Scheme: "*", + ProviderOrganization: "*", }, }, wants: wants{ @@ -313,16 +313,16 @@ func TestMappingStore_Get(t *testing.T) { fields: fields{ mappings: []*chronograf.Mapping{ &chronograf.Mapping{ - Organization: "default", - Provider: "*", - Scheme: "*", - Group: "*", + Organization: "default", + Provider: "*", + Scheme: "*", + ProviderOrganization: "*", }, &chronograf.Mapping{ - Organization: "0", - Provider: "google", - Scheme: "ldap", - Group: "*", + Organization: "0", + Provider: "google", + Scheme: "ldap", + ProviderOrganization: "*", }, }, }, @@ -331,11 +331,11 @@ func TestMappingStore_Get(t *testing.T) { }, wants: wants{ mapping: &chronograf.Mapping{ - ID: "1", - Organization: "default", - Provider: "*", - Scheme: "*", - Group: "*", + ID: "1", + Organization: "default", + Provider: "*", + Scheme: "*", + ProviderOrganization: "*", }, err: nil, }, @@ -345,16 +345,16 @@ func TestMappingStore_Get(t *testing.T) { fields: fields{ mappings: []*chronograf.Mapping{ &chronograf.Mapping{ - Organization: "default", - Provider: "*", - Scheme: "*", - Group: "*", + Organization: "default", + Provider: "*", + Scheme: "*", + ProviderOrganization: "*", }, &chronograf.Mapping{ - Organization: "0", - Provider: "google", - Scheme: "ldap", - Group: "*", + Organization: "0", + Provider: "google", + Scheme: "ldap", + ProviderOrganization: "*", }, }, }, @@ -418,35 +418,35 @@ func TestMappingStore_Update(t *testing.T) { fields: fields{ mappings: []*chronograf.Mapping{ &chronograf.Mapping{ - Organization: "default", - Provider: "*", - Scheme: "*", - Group: "*", + Organization: "default", + Provider: "*", + Scheme: "*", + ProviderOrganization: "*", }, &chronograf.Mapping{ - Organization: "0", - Provider: "google", - Scheme: "ldap", - Group: "*", + Organization: "0", + Provider: "google", + Scheme: "ldap", + ProviderOrganization: "*", }, }, }, args: args{ mapping: &chronograf.Mapping{ - ID: "1", - Organization: "default", - Provider: "cool", - Scheme: "it", - Group: "works", + ID: "1", + Organization: "default", + Provider: "cool", + Scheme: "it", + ProviderOrganization: "works", }, }, wants: wants{ mapping: &chronograf.Mapping{ - ID: "1", - Organization: "default", - Provider: "cool", - Scheme: "it", - Group: "works", + ID: "1", + Organization: "default", + Provider: "cool", + Scheme: "it", + ProviderOrganization: "works", }, err: nil, }, diff --git a/bolt/organizations.go b/bolt/organizations.go index 1bc7520f9..be05b15a1 100644 --- a/bolt/organizations.go +++ b/bolt/organizations.go @@ -49,11 +49,11 @@ func (s *OrganizationsStore) CreateDefault(ctx context.Context) error { } m := chronograf.Mapping{ - ID: string(DefaultOrganizationID), - Organization: string(DefaultOrganizationID), - Provider: chronograf.MappingWildcard, - Scheme: chronograf.MappingWildcard, - Group: chronograf.MappingWildcard, + ID: string(DefaultOrganizationID), + Organization: string(DefaultOrganizationID), + Provider: chronograf.MappingWildcard, + Scheme: chronograf.MappingWildcard, + ProviderOrganization: chronograf.MappingWildcard, } return s.client.db.Update(func(tx *bolt.Tx) error { b := tx.Bucket(OrganizationsBucket) diff --git a/chronograf.go b/chronograf.go index d794dddc5..9050cb4be 100644 --- a/chronograf.go +++ b/chronograf.go @@ -581,12 +581,11 @@ const MappingWildcard string = "*" // github:oauth2:* -> MyOrg // *:*:* -> AllOrg type Mapping struct { - ID string `json:"id"` - Organization string `json:"organization"` - - Provider string `json:"provider"` - Scheme string `json:"scheme"` - Group string `json:"group"` + ID string `json:"id"` + Organization string `json:"organizationId"` + Provider string `json:"provider"` + Scheme string `json:"scheme"` + ProviderOrganization string `json:"providerOrganization"` } // MappingsStore is the storage and retrieval of Mappings diff --git a/integrations/server_test.go b/integrations/server_test.go index 70c67b0b7..f8ac03793 100644 --- a/integrations/server_test.go +++ b/integrations/server_test.go @@ -1456,32 +1456,32 @@ func TestServer(t *testing.T) { }, Mappings: []chronograf.Mapping{ { - ID: "1", - Organization: "1", - Provider: "*", - Scheme: "*", - Group: "influxdata", + ID: "1", + Organization: "1", + Provider: "*", + Scheme: "*", + ProviderOrganization: "influxdata", }, { - ID: "1", - Organization: "1", - Provider: "*", - Scheme: "*", - Group: "*", + ID: "1", + Organization: "1", + Provider: "*", + Scheme: "*", + ProviderOrganization: "*", }, { - ID: "2", - Organization: "2", - Provider: "github", - Scheme: "*", - Group: "*", + ID: "2", + Organization: "2", + Provider: "github", + Scheme: "*", + ProviderOrganization: "*", }, { - ID: "3", - Organization: "3", - Provider: "auth0", - Scheme: "ldap", - Group: "*", + ID: "3", + Organization: "3", + Provider: "auth0", + Scheme: "ldap", + ProviderOrganization: "*", }, }, Organizations: []chronograf.Organization{ @@ -1591,32 +1591,32 @@ func TestServer(t *testing.T) { }, Mappings: []chronograf.Mapping{ { - ID: "1", - Organization: "1", - Provider: "*", - Scheme: "*", - Group: "influxdata", + ID: "1", + Organization: "1", + Provider: "*", + Scheme: "*", + ProviderOrganization: "influxdata", }, { - ID: "1", - Organization: "1", - Provider: "*", - Scheme: "*", - Group: "*", + ID: "1", + Organization: "1", + Provider: "*", + Scheme: "*", + ProviderOrganization: "*", }, { - ID: "2", - Organization: "2", - Provider: "github", - Scheme: "*", - Group: "*", + ID: "2", + Organization: "2", + Provider: "github", + Scheme: "*", + ProviderOrganization: "*", }, { - ID: "3", - Organization: "3", - Provider: "auth0", - Scheme: "ldap", - Group: "*", + ID: "3", + Organization: "3", + Provider: "auth0", + Scheme: "ldap", + ProviderOrganization: "*", }, }, Organizations: []chronograf.Organization{ @@ -1672,50 +1672,50 @@ func TestServer(t *testing.T) { "self": "/chronograf/v1/mappings/1" }, "id": "default", - "organization": "default", + "organizationId": "default", "provider": "*", "scheme": "*", - "group": "*" + "providerOrganization": "*" }, { "links": { "self": "/chronograf/v1/mappings/2" }, "id": "default", - "organization": "default", + "organizationId": "default", "provider": "*", "scheme": "*", - "group": "*" + "providerOrganization": "*" }, { "links": { "self": "/chronograf/v1/mappings/3" }, "id": "default", - "organization": "default", + "organizationId": "default", "provider": "*", "scheme": "*", - "group": "*" + "providerOrganization": "*" }, { "links": { "self": "/chronograf/v1/mappings/4" }, "id": "default", - "organization": "default", + "organizationId": "default", "provider": "*", "scheme": "*", - "group": "*" + "providerOrganization": "*" }, { "links": { "self": "/chronograf/v1/mappings/default" }, "id": "default", - "organization": "default", + "organizationId": "default", "provider": "*", "scheme": "*", - "group": "*" + "providerOrganization": "*" } ] } @@ -1733,32 +1733,32 @@ func TestServer(t *testing.T) { }, Mappings: []chronograf.Mapping{ { - ID: "1", - Organization: "1", - Provider: "*", - Scheme: "*", - Group: "influxdata", + ID: "1", + Organization: "1", + Provider: "*", + Scheme: "*", + ProviderOrganization: "influxdata", }, { - ID: "1", - Organization: "1", - Provider: "*", - Scheme: "*", - Group: "*", + ID: "1", + Organization: "1", + Provider: "*", + Scheme: "*", + ProviderOrganization: "*", }, { - ID: "2", - Organization: "2", - Provider: "github", - Scheme: "*", - Group: "*", + ID: "2", + Organization: "2", + Provider: "github", + Scheme: "*", + ProviderOrganization: "*", }, { - ID: "3", - Organization: "3", - Provider: "auth0", - Scheme: "ldap", - Group: "*", + ID: "3", + Organization: "3", + Provider: "auth0", + Scheme: "ldap", + ProviderOrganization: "*", }, }, Organizations: []chronograf.Organization{ @@ -1846,11 +1846,11 @@ func TestServer(t *testing.T) { method: "POST", path: "/chronograf/v1/mappings", payload: &chronograf.Mapping{ - ID: "1", - Organization: "1", - Provider: "*", - Scheme: "*", - Group: "influxdata", + ID: "1", + Organization: "1", + Provider: "*", + Scheme: "*", + ProviderOrganization: "influxdata", }, principal: oauth2.Principal{ Subject: "billibob", @@ -1866,10 +1866,10 @@ func TestServer(t *testing.T) { "self": "/chronograf/v1/mappings/1" }, "id": "1", - "organization": "1", + "organizationId": "1", "provider": "*", "scheme": "*", - "group": "influxdata" + "providerOrganization": "influxdata" } `, }, @@ -1885,11 +1885,11 @@ func TestServer(t *testing.T) { }, Mappings: []chronograf.Mapping{ chronograf.Mapping{ - ID: "1", - Organization: "1", - Provider: "*", - Scheme: "*", - Group: "influxdata", + ID: "1", + Organization: "1", + Provider: "*", + Scheme: "*", + ProviderOrganization: "influxdata", }, }, Organizations: []chronograf.Organization{ @@ -1917,11 +1917,11 @@ func TestServer(t *testing.T) { method: "PUT", path: "/chronograf/v1/mappings/1", payload: &chronograf.Mapping{ - ID: "1", - Organization: "1", - Provider: "*", - Scheme: "*", - Group: "*", + ID: "1", + Organization: "1", + Provider: "*", + Scheme: "*", + ProviderOrganization: "*", }, principal: oauth2.Principal{ Subject: "billibob", @@ -1937,10 +1937,10 @@ func TestServer(t *testing.T) { "self": "/chronograf/v1/mappings/1" }, "id": "1", - "organization": "1", + "organizationId": "1", "provider": "*", "scheme": "*", - "group": "*" + "providerOrganization": "*" } `, }, diff --git a/server/mapping.go b/server/mapping.go index 872e2a36d..442277059 100644 --- a/server/mapping.go +++ b/server/mapping.go @@ -2,6 +2,7 @@ package server import ( "context" + "encoding/json" "fmt" "net/http" @@ -51,13 +52,13 @@ func applyMapping(m chronograf.Mapping, p oauth2.Principal) bool { return false } - if m.Group == chronograf.MappingWildcard { + if m.ProviderOrganization == chronograf.MappingWildcard { return true } groups := strings.Split(p.Group, ",") - return matchGroup(m.Group, groups) + return matchGroup(m.ProviderOrganization, groups) } func matchGroup(match string, groups []string) bool { @@ -80,7 +81,7 @@ func (m *mappingsRequest) Valid() error { if m.Scheme == "" { return fmt.Errorf("mapping must specify scheme") } - if m.Group == "" { + if m.ProviderOrganization == "" { return fmt.Errorf("mapping must specify group") } @@ -89,7 +90,7 @@ func (m *mappingsRequest) Valid() error { type mappingResponse struct { Links selfLinks `json:"links"` - *chronograf.Mapping + chronograf.Mapping } func newMappingResponse(m *chronograf.Mapping) *mappingResponse { @@ -97,11 +98,12 @@ func newMappingResponse(m *chronograf.Mapping) *mappingResponse { if m != nil { id = m.ID } + return &mappingResponse{ Links: selfLinks{ Self: fmt.Sprintf("/chronograf/v1/mappings/%s", id), }, - Mapping: m, + Mapping: *m, } } @@ -134,6 +136,7 @@ func (s *Service) Mappings(w http.ResponseWriter, r *http.Request) { } res := newMappingsResponse(mappings) + encodeJSON(w, http.StatusOK, res, s.Logger) } @@ -159,10 +162,10 @@ func (s *Service) NewMapping(w http.ResponseWriter, r *http.Request) { } mapping := &chronograf.Mapping{ - Organization: req.Organization, - Scheme: req.Scheme, - Provider: req.Provider, - Group: req.Group, + Organization: req.Organization, + Scheme: req.Scheme, + Provider: req.Provider, + ProviderOrganization: req.ProviderOrganization, } m, err := s.Store.Mappings(ctx).Add(ctx, mapping) @@ -198,11 +201,11 @@ func (s *Service) UpdateMapping(w http.ResponseWriter, r *http.Request) { } mapping := &chronograf.Mapping{ - ID: req.ID, - Organization: req.Organization, - Scheme: req.Scheme, - Provider: req.Provider, - Group: req.Group, + ID: req.ID, + Organization: req.Organization, + Scheme: req.Scheme, + Provider: req.Provider, + ProviderOrganization: req.ProviderOrganization, } err := s.Store.Mappings(ctx).Update(ctx, mapping) diff --git a/server/mapping_test.go b/server/mapping_test.go index 94f2ac06f..a8352b65b 100644 --- a/server/mapping_test.go +++ b/server/mapping_test.go @@ -40,10 +40,10 @@ func TestMappings_All(t *testing.T) { AllF: func(ctx context.Context) ([]chronograf.Mapping, error) { return []chronograf.Mapping{ { - Organization: "0", - Provider: chronograf.MappingWildcard, - Scheme: chronograf.MappingWildcard, - Group: chronograf.MappingWildcard, + Organization: "0", + Provider: chronograf.MappingWildcard, + Scheme: chronograf.MappingWildcard, + ProviderOrganization: chronograf.MappingWildcard, }, }, nil }, @@ -52,7 +52,7 @@ func TestMappings_All(t *testing.T) { wants: wants{ statusCode: 200, contentType: "application/json", - body: `{"links":{"self":"/chronograf/v1/mappings"},"mappings":[{"links":{"self":"/chronograf/v1/mappings/"},"id":"","organization":"0","provider":"*","scheme":"*","group":"*"}]}`, + body: `{"links":{"self":"/chronograf/v1/mappings"},"mappings":[{"links":{"self":"/chronograf/v1/mappings/"},"id":"","organizationId":"0","provider":"*","scheme":"*","providerOrganization":"*"}]}`, }, }, } @@ -129,16 +129,16 @@ func TestMappings_Add(t *testing.T) { }, args: args{ mapping: &chronograf.Mapping{ - Organization: "0", - Provider: "*", - Scheme: "*", - Group: "*", + Organization: "0", + Provider: "*", + Scheme: "*", + ProviderOrganization: "*", }, }, wants: wants{ statusCode: 201, contentType: "application/json", - body: `{"links":{"self":"/chronograf/v1/mappings/0"},"id":"0","organization":"0","provider":"*","scheme":"*","group":"*"}`, + body: `{"links":{"self":"/chronograf/v1/mappings/0"},"id":"0","organizationId":"0","provider":"*","scheme":"*","providerOrganization":"*"}`, }, }, } @@ -219,17 +219,17 @@ func TestMappings_Update(t *testing.T) { }, args: args{ mapping: &chronograf.Mapping{ - ID: "1", - Organization: "0", - Provider: "*", - Scheme: "*", - Group: "*", + ID: "1", + Organization: "0", + Provider: "*", + Scheme: "*", + ProviderOrganization: "*", }, }, wants: wants{ statusCode: 200, contentType: "application/json", - body: `{"links":{"self":"/chronograf/v1/mappings/1"},"id":"1","organization":"0","provider":"*","scheme":"*","group":"*"}`, + body: `{"links":{"self":"/chronograf/v1/mappings/1"},"id":"1","organizationId":"0","provider":"*","scheme":"*","providerOrganization":"*"}`, }, }, } @@ -302,11 +302,11 @@ func TestMappings_Remove(t *testing.T) { MappingsStore: &mocks.MappingsStore{ GetF: func(ctx context.Context, id string) (*chronograf.Mapping, error) { return &chronograf.Mapping{ - ID: "1", - Organization: "0", - Provider: "*", - Scheme: "*", - Group: "*", + ID: "1", + Organization: "0", + Provider: "*", + Scheme: "*", + ProviderOrganization: "*", }, nil }, DeleteF: func(ctx context.Context, m *chronograf.Mapping) error { diff --git a/server/me_test.go b/server/me_test.go index 7fefe8ccf..fd68e183b 100644 --- a/server/me_test.go +++ b/server/me_test.go @@ -61,10 +61,10 @@ func TestService_Me(t *testing.T) { AllF: func(ctx context.Context) ([]chronograf.Mapping, error) { return []chronograf.Mapping{ { - Organization: "0", - Provider: chronograf.MappingWildcard, - Scheme: chronograf.MappingWildcard, - Group: chronograf.MappingWildcard, + Organization: "0", + Provider: chronograf.MappingWildcard, + Scheme: chronograf.MappingWildcard, + ProviderOrganization: chronograf.MappingWildcard, }, }, nil }, @@ -417,10 +417,10 @@ func TestService_Me(t *testing.T) { AllF: func(ctx context.Context) ([]chronograf.Mapping, error) { return []chronograf.Mapping{ { - Organization: "0", - Provider: chronograf.MappingWildcard, - Scheme: chronograf.MappingWildcard, - Group: chronograf.MappingWildcard, + Organization: "0", + Provider: chronograf.MappingWildcard, + Scheme: chronograf.MappingWildcard, + ProviderOrganization: chronograf.MappingWildcard, }, }, nil }, @@ -500,10 +500,10 @@ func TestService_Me(t *testing.T) { AllF: func(ctx context.Context) ([]chronograf.Mapping, error) { return []chronograf.Mapping{ { - Organization: "0", - Provider: chronograf.MappingWildcard, - Scheme: chronograf.MappingWildcard, - Group: chronograf.MappingWildcard, + Organization: "0", + Provider: chronograf.MappingWildcard, + Scheme: chronograf.MappingWildcard, + ProviderOrganization: chronograf.MappingWildcard, }, }, nil }, @@ -583,10 +583,10 @@ func TestService_Me(t *testing.T) { AllF: func(ctx context.Context) ([]chronograf.Mapping, error) { return []chronograf.Mapping{ { - Organization: "0", - Provider: chronograf.MappingWildcard, - Scheme: chronograf.MappingWildcard, - Group: chronograf.MappingWildcard, + Organization: "0", + Provider: chronograf.MappingWildcard, + Scheme: chronograf.MappingWildcard, + ProviderOrganization: chronograf.MappingWildcard, }, }, nil }, diff --git a/ui/src/admin/actions/chronograf.js b/ui/src/admin/actions/chronograf.js index cb7d9f102..3da42b0e8 100644 --- a/ui/src/admin/actions/chronograf.js +++ b/ui/src/admin/actions/chronograf.js @@ -146,9 +146,9 @@ export const loadOrganizationsAsync = url => async dispatch => { } } -export const loadMappingsAsync = url => async dispatch => { +export const loadMappingsAsync = () => async dispatch => { try { - const {data} = await getMappingsAJAX(url) + const {data} = await getMappingsAJAX() dispatch(loadMappings(data)) } catch (error) { dispatch(errorThrown(error)) diff --git a/ui/src/admin/apis/chronograf.js b/ui/src/admin/apis/chronograf.js index 6b4f7a848..ed529b0e2 100644 --- a/ui/src/admin/apis/chronograf.js +++ b/ui/src/admin/apis/chronograf.js @@ -117,7 +117,7 @@ export const createMapping = async (url, mapping) => { } } -export const getMappings = async url => { +export const getMappings = async () => { try { return await AJAX({ method: 'GET', diff --git a/ui/src/admin/components/chronograf/ProvidersTable.js b/ui/src/admin/components/chronograf/ProvidersTable.js index ee5d40a07..eff7c2465 100644 --- a/ui/src/admin/components/chronograf/ProvidersTable.js +++ b/ui/src/admin/components/chronograf/ProvidersTable.js @@ -20,16 +20,18 @@ class ProvidersTable extends Component { } handleCreateMap = newMap => { - const {onCreateMap} = this.props - // todo: better way of getting mapping id + this.props.onCreateMap(newMap) this.setState({isCreatingMap: false}) - const newMapID = this.props.mappings.length.toString() - newMap.id = newMapID - onCreateMap(newMap) } render() { - const {mappings = [], organizations, onUpdateMap, onDeleteMap} = this.props + const { + mappings = [], + organizations, + onUpdateMap, + onDeleteMap, + isLoading, + } = this.props const {isCreatingMap} = this.state const tableTitle = @@ -43,6 +45,16 @@ class ProvidersTable extends Component { {text: 'option3'}, ] + if (isLoading) { + return ( +
+
+
+
+
+ ) + } + return (
@@ -54,10 +66,10 @@ class ProvidersTable extends Component { onClick={this.handleClickCreateMap} disabled={isCreatingMap} > - Create Map + Create Mapping
- {(mappings && mappings.length) || isCreatingMap + {mappings.length || isCreatingMap ?
ID
@@ -94,24 +106,26 @@ class ProvidersTable extends Component { /> : null}
- :
-

- Looks like you don’t have any mappings -

- + :
+
+

+ Looks like you have no mappings +

+ +
}
) } } -const {arrayOf, func, shape, string} = PropTypes +const {arrayOf, bool, func, shape, string} = PropTypes ProvidersTable.propTypes = { mappings: arrayOf( @@ -132,5 +146,6 @@ ProvidersTable.propTypes = { onCreateMap: func.isRequired, onUpdateMap: func.isRequired, onDeleteMap: func.isRequired, + isLoading: bool.isRequired, } export default ProvidersTable diff --git a/ui/src/admin/components/chronograf/ProvidersTableRow.js b/ui/src/admin/components/chronograf/ProvidersTableRow.js index a9d08848d..92dee29af 100644 --- a/ui/src/admin/components/chronograf/ProvidersTableRow.js +++ b/ui/src/admin/components/chronograf/ProvidersTableRow.js @@ -11,10 +11,7 @@ class ProvidersTableRow extends Component { super(props) this.state = { - scheme: this.props.mapping.scheme, - provider: this.props.mapping.provider, - providerOrganization: this.props.mapping.providerOrganization, - organizationId: this.props.mapping.organizationId, + ...this.props.mapping, isDeleting: false, } } @@ -34,10 +31,10 @@ class ProvidersTableRow extends Component { } handleUpdateMapping = changes => { - const {onUpdate, mapping: {id}} = this.props - const newState = {...this.state, ...changes, id} + const {onUpdate, mapping} = this.props + const newState = {...mapping, ...changes} this.setState(newState) - onUpdate(this.props.mapping, newState) + onUpdate(mapping, newState) } handleChangeProvider = provider => this.handleUpdateMapping({provider}) @@ -73,9 +70,6 @@ class ProvidersTableRow extends Component { const isDefaultMapping = DEFAULT_MAPPING_ID === mapping.id return (
-
- {mapping.id} -
{ - const {scheme, provider, providerOrganization, organizationId} = this.state const {onCreate} = this.props - // id is calculated in providers table - onCreate({id: '', scheme, provider, providerOrganization, organizationId}) + onCreate(this.state) } render() { @@ -65,11 +63,13 @@ class ProvidersTableRowNew extends Component { value={provider} wrapperClass="fancytable--td provider--provider" onUpdate={this.handleChangeProvider} + tabIndex="1" />
diff --git a/ui/src/admin/containers/ProvidersPage.js b/ui/src/admin/containers/ProvidersPage.js index 5d74334ec..037eaf020 100644 --- a/ui/src/admin/containers/ProvidersPage.js +++ b/ui/src/admin/containers/ProvidersPage.js @@ -10,16 +10,22 @@ import ProvidersTable from 'src/admin/components/chronograf/ProvidersTable' class ProvidersPage extends Component { constructor(props) { super(props) + + this.state = {isLoading: true} } - componentDidMount() { + async componentDidMount() { const { links, actions: {loadOrganizationsAsync, loadMappingsAsync}, } = this.props - loadOrganizationsAsync(links.organizations) - loadMappingsAsync(links.mappings) + await Promise.all([ + loadOrganizationsAsync(links.organizations), + loadMappingsAsync(links.mappings), + ]) + + this.setState({isLoading: false}) } handleCreateMap = mapping => { @@ -36,16 +42,18 @@ class ProvidersPage extends Component { render() { const {organizations, mappings = []} = this.props + const {isLoading} = this.state - return organizations - ? - :
+ return ( + + ) } } diff --git a/ui/src/admin/reducers/chronograf.js b/ui/src/admin/reducers/chronograf.js index f943074d1..f1ced340b 100644 --- a/ui/src/admin/reducers/chronograf.js +++ b/ui/src/admin/reducers/chronograf.js @@ -3,6 +3,7 @@ import {isSameUser} from 'shared/reducers/helpers/auth' const initialState = { users: [], organizations: [], + mappings: [], authConfig: { superAdminNewUsers: true, }, From 231801cb34449b573f6d6cee32bc68234b69d315 Mon Sep 17 00:00:00 2001 From: Iris Scholten Date: Wed, 7 Feb 2018 09:54:25 -0800 Subject: [PATCH 41/98] remove the ID column from the ui --- ui/src/admin/components/chronograf/ProvidersTable.js | 1 - ui/src/admin/components/chronograf/ProvidersTableRowNew.js | 2 -- 2 files changed, 3 deletions(-) diff --git a/ui/src/admin/components/chronograf/ProvidersTable.js b/ui/src/admin/components/chronograf/ProvidersTable.js index eff7c2465..02ef3429a 100644 --- a/ui/src/admin/components/chronograf/ProvidersTable.js +++ b/ui/src/admin/components/chronograf/ProvidersTable.js @@ -72,7 +72,6 @@ class ProvidersTable extends Component { {mappings.length || isCreatingMap ?
-
ID
Scheme
Provider diff --git a/ui/src/admin/components/chronograf/ProvidersTableRowNew.js b/ui/src/admin/components/chronograf/ProvidersTableRowNew.js index e89cef9eb..dd217efb7 100644 --- a/ui/src/admin/components/chronograf/ProvidersTableRowNew.js +++ b/ui/src/admin/components/chronograf/ProvidersTableRowNew.js @@ -51,8 +51,6 @@ class ProvidersTableRowNew extends Component { return (
-
--
- Date: Wed, 7 Feb 2018 17:03:19 -0800 Subject: [PATCH 42/98] Use separate state for gauge and single-stat colors and introduce concept of base color in single-stat Somewhat easier to reason through the code, although bulkier. The feature should now mimic the previous appearance of single-stat graphs by default (aka blue text) --- .../components/CellEditorOverlay.js | 202 +++++++++++++----- .../dashboards/components/DisplayOptions.js | 42 ++-- .../components/SingleStatOptions.js | 66 ++++-- ui/src/dashboards/constants/gaugeColors.js | 80 +++++-- ui/src/shared/components/ColorDropdown.js | 10 +- ui/src/shared/components/GaugeChart.js | 4 +- .../style/components/ceo-display-options.scss | 10 +- ui/src/style/components/color-dropdown.scss | 4 + 8 files changed, 296 insertions(+), 122 deletions(-) diff --git a/ui/src/dashboards/components/CellEditorOverlay.js b/ui/src/dashboards/components/CellEditorOverlay.js index 8432c8f31..1d392d6c2 100644 --- a/ui/src/dashboards/components/CellEditorOverlay.js +++ b/ui/src/dashboards/components/CellEditorOverlay.js @@ -28,9 +28,9 @@ import { DEFAULT_VALUE_MIN, DEFAULT_VALUE_MAX, GAUGE_COLORS, - SINGLE_STAT_TEXT, - SINGLE_STAT_BG, - validateColors, + validateGaugeColors, + validateSingleStatColors, + getSingleStatColoration, } from 'src/dashboards/constants/gaugeColors' class CellEditorOverlay extends Component { @@ -49,7 +49,8 @@ class CellEditorOverlay extends Component { source, })) ) - const colorsTypeContainsText = _.some(colors, {type: SINGLE_STAT_TEXT}) + + const singleStatColoration = getSingleStatColoration(colors) this.state = { cellWorkingName: name, @@ -58,8 +59,13 @@ class CellEditorOverlay extends Component { activeQueryIndex: 0, isDisplayOptionsTabActive: false, axes, - colorSingleStatText: colorsTypeContainsText, - colors: validateColors(colors, type, colorsTypeContainsText), + singleStatColoration, + gaugeColors: validateGaugeColors(colors, type), + singleStatColors: validateSingleStatColors( + colors, + type, + singleStatColoration + ), } } @@ -80,23 +86,17 @@ class CellEditorOverlay extends Component { this.overlayRef.focus() } - handleAddThreshold = () => { - const {colors, cellWorkingType} = this.state - const sortedColors = _.sortBy(colors, color => Number(color.value)) + handleAddGaugeThreshold = () => { + const {gaugeColors} = this.state + const sortedColors = _.sortBy(gaugeColors, color => Number(color.value)) if (sortedColors.length <= MAX_THRESHOLDS) { const randomColor = _.random(0, GAUGE_COLORS.length - 1) - const maxValue = - cellWorkingType === 'gauge' - ? Number(sortedColors[sortedColors.length - 1].value) - : DEFAULT_VALUE_MAX - const minValue = - cellWorkingType === 'gauge' - ? Number(sortedColors[0].value) - : DEFAULT_VALUE_MIN + const maxValue = Number(sortedColors[sortedColors.length - 1].value) + const minValue = Number(sortedColors[0].value) - const colorsValues = _.mapValues(colors, 'value') + const colorsValues = _.mapValues(gaugeColors, 'value') let randomValue do { @@ -111,51 +111,122 @@ class CellEditorOverlay extends Component { name: GAUGE_COLORS[randomColor].name, } - this.setState({colors: [...colors, newThreshold]}) + this.setState({gaugeColors: [...gaugeColors, newThreshold]}) } } + handleAddSingleStatThreshold = () => { + const {singleStatColors, singleStatColoration} = this.state + + const randomColor = _.random(0, GAUGE_COLORS.length - 1) + + const maxValue = Number(DEFAULT_VALUE_MIN) + const minValue = Number(DEFAULT_VALUE_MAX) + + let randomValue = _.round(_.random(minValue, maxValue, true), 2) + + if (singleStatColors.length > 0) { + const colorsValues = _.mapValues(singleStatColors, 'value') + do { + randomValue = `${_.round(_.random(minValue, maxValue, true), 2)}` + } while (_.includes(colorsValues, randomValue)) + } + + const newThreshold = { + type: singleStatColoration, + id: uuid.v4(), + value: randomValue, + hex: GAUGE_COLORS[randomColor].hex, + name: GAUGE_COLORS[randomColor].name, + } + + this.setState({singleStatColors: [...singleStatColors, newThreshold]}) + } + handleDeleteThreshold = threshold => () => { - const {colors} = this.state + const {cellWorkingType} = this.state - const newColors = colors.filter(color => color.id !== threshold.id) + if (cellWorkingType === 'gauge') { + const gaugeColors = this.state.gaugeColors.filter( + color => color.id !== threshold.id + ) - this.setState({colors: newColors}) + this.setState({gaugeColors}) + } + + if (cellWorkingType === 'single-stat') { + const singleStatColors = this.state.singleStatColors.filter( + color => color.id !== threshold.id + ) + + this.setState({singleStatColors}) + } } handleChooseColor = threshold => chosenColor => { - const {colors} = this.state + const {cellWorkingType} = this.state - const newColors = colors.map( - color => - color.id === threshold.id - ? {...color, hex: chosenColor.hex, name: chosenColor.name} - : color - ) + if (cellWorkingType === 'gauge') { + const gaugeColors = this.state.gaugeColors.map( + color => + color.id === threshold.id + ? {...color, hex: chosenColor.hex, name: chosenColor.name} + : color + ) - this.setState({colors: newColors}) + this.setState({gaugeColors}) + } + + if (cellWorkingType === 'single-stat') { + const singleStatColors = this.state.singleStatColors.map( + color => + color.id === threshold.id + ? {...color, hex: chosenColor.hex, name: chosenColor.name} + : color + ) + + this.setState({singleStatColors}) + } } handleUpdateColorValue = (threshold, newValue) => { - const {colors} = this.state - const newColors = colors.map( - color => (color.id === threshold.id ? {...color, value: newValue} : color) - ) - this.setState({colors: newColors}) + const {cellWorkingType} = this.state + + if (cellWorkingType === 'gauge') { + const gaugeColors = this.state.gaugeColors.map( + color => + color.id === threshold.id ? {...color, value: newValue} : color + ) + + this.setState({gaugeColors}) + } + + if (cellWorkingType === 'single-stat') { + const singleStatColors = this.state.singleStatColors.map( + color => + color.id === threshold.id ? {...color, value: newValue} : color + ) + + this.setState({singleStatColors}) + } } handleValidateColorValue = (threshold, e) => { - const {colors, cellWorkingType} = this.state - const sortedColors = _.sortBy(colors, color => Number(color.value)) + const {gaugeColors, singleStatColors, cellWorkingType} = this.state const thresholdValue = Number(threshold.value) const targetValueNumber = Number(e.target.value) let allowedToUpdate = false if (cellWorkingType === 'single-stat') { // If type is single-stat then value only has to be unique + const sortedColors = _.sortBy(singleStatColors, color => + Number(color.value) + ) return !sortedColors.some(color => color.value === e.target.value) } + const sortedColors = _.sortBy(gaugeColors, color => Number(color.value)) + const minValue = Number(sortedColors[0].value) const maxValue = Number(sortedColors[sortedColors.length - 1].value) @@ -189,16 +260,15 @@ class CellEditorOverlay extends Component { return allowedToUpdate } - handleToggleSingleStatText = () => { - const {colors, colorSingleStatText} = this.state - const formattedColors = colors.map(color => ({ + handleToggleSingleStatColoration = newColoration => () => { + const singleStatColors = this.state.singleStatColors.map(color => ({ ...color, - type: colorSingleStatText ? SINGLE_STAT_BG : SINGLE_STAT_TEXT, + type: newColoration, })) this.setState({ - colorSingleStatText: !colorSingleStatText, - colors: formattedColors, + singleStatColoration: newColoration, + singleStatColors, }) } @@ -298,7 +368,8 @@ class CellEditorOverlay extends Component { cellWorkingType: type, cellWorkingName: name, axes, - colors, + gaugeColors, + singleStatColors, } = this.state const {cell} = this.props @@ -314,6 +385,13 @@ class CellEditorOverlay extends Component { } }) + let colors = [] + if (type === 'gauge') { + colors = gaugeColors + } else if (type === 'single-stat') { + colors = singleStatColors + } + this.props.onSave({ ...cell, name, @@ -324,14 +402,8 @@ class CellEditorOverlay extends Component { }) } - handleSelectGraphType = graphType => () => { - const {colors, colorSingleStatText} = this.state - const validatedColors = validateColors( - colors, - graphType, - colorSingleStatText - ) - this.setState({cellWorkingType: graphType, colors: validatedColors}) + handleSelectGraphType = cellWorkingType => () => { + this.setState({cellWorkingType}) } handleClickDisplayOptionsTab = isDisplayOptionsTabActive => () => { @@ -474,13 +546,14 @@ class CellEditorOverlay extends Component { const { axes, - colors, + gaugeColors, + singleStatColors, activeQueryIndex, cellWorkingName, cellWorkingType, isDisplayOptionsTabActive, queriesWorkingDraft, - colorSingleStatText, + singleStatColoration, } = this.state const queryActions = { @@ -492,6 +565,13 @@ class CellEditorOverlay extends Component { (!!query.measurement && !!query.database && !!query.fields.length) || !!query.rawText + let visualizationColors + if (cellWorkingType === 'gauge') { + visualizationColors = gaugeColors + } else if (cellWorkingType === 'single-stat') { + visualizationColors = singleStatColors + } + return (
{ const { - colors, + gaugeColors, + singleStatColors, onSetBase, onSetScale, onSetLabel, @@ -43,13 +44,14 @@ class DisplayOptions extends Component { onSetPrefixSuffix, onSetYAxisBoundMin, onSetYAxisBoundMax, - onAddThreshold, + onAddGaugeThreshold, + onAddSingleStatThreshold, onDeleteThreshold, onChooseColor, onValidateColorValue, onUpdateColorValue, - colorSingleStatText, - onToggleSingleStatText, + singleStatColoration, + onToggleSingleStatColoration, onSetSuffix, } = this.props const {axes, axes: {y: {suffix}}} = this.state @@ -58,27 +60,27 @@ class DisplayOptions extends Component { case 'gauge': return ( ) case 'single-stat': return ( ) default: @@ -111,10 +113,11 @@ class DisplayOptions extends Component { ) } } -const {arrayOf, bool, func, shape, string} = PropTypes +const {arrayOf, func, shape, string} = PropTypes DisplayOptions.propTypes = { - onAddThreshold: func.isRequired, + onAddGaugeThreshold: func.isRequired, + onAddSingleStatThreshold: func.isRequired, onDeleteThreshold: func.isRequired, onChooseColor: func.isRequired, onValidateColorValue: func.isRequired, @@ -129,7 +132,16 @@ DisplayOptions.propTypes = { onSetLabel: func.isRequired, onSetBase: func.isRequired, axes: shape({}).isRequired, - colors: arrayOf( + gaugeColors: arrayOf( + shape({ + type: string.isRequired, + hex: string.isRequired, + id: string.isRequired, + name: string.isRequired, + value: string.isRequired, + }).isRequired + ), + singleStatColors: arrayOf( shape({ type: string.isRequired, hex: string.isRequired, @@ -139,8 +151,8 @@ DisplayOptions.propTypes = { }).isRequired ), queryConfigs: arrayOf(shape()).isRequired, - colorSingleStatText: bool.isRequired, - onToggleSingleStatText: func.isRequired, + singleStatColoration: string.isRequired, + onToggleSingleStatColoration: func.isRequired, } export default DisplayOptions diff --git a/ui/src/dashboards/components/SingleStatOptions.js b/ui/src/dashboards/components/SingleStatOptions.js index 2ee686c4e..2821cdfc1 100644 --- a/ui/src/dashboards/components/SingleStatOptions.js +++ b/ui/src/dashboards/components/SingleStatOptions.js @@ -3,9 +3,20 @@ import _ from 'lodash' import FancyScrollbar from 'shared/components/FancyScrollbar' import Threshold from 'src/dashboards/components/Threshold' +import ColorDropdown from 'shared/components/ColorDropdown' -import {MAX_THRESHOLDS} from 'src/dashboards/constants/gaugeColors' +import { + GAUGE_COLORS, + MAX_THRESHOLDS, + SINGLE_STAT_BASE, + SINGLE_STAT_TEXT, + SINGLE_STAT_BG, +} from 'src/dashboards/constants/gaugeColors' +const formatColor = color => { + const {hex, name} = color + return {hex, name} +} const SingleStatOptions = ({ suffix, onSetSuffix, @@ -15,8 +26,8 @@ const SingleStatOptions = ({ onChooseColor, onValidateColorValue, onUpdateColorValue, - colorSingleStatText, - onToggleSingleStatText, + singleStatColoration, + onToggleSingleStatColoration, }) => { const disableAddThreshold = colors.length > MAX_THRESHOLDS @@ -37,16 +48,27 @@ const SingleStatOptions = ({ > Add Threshold - {sortedColors.map(color => - + {sortedColors.map( + color => + color.id === SINGLE_STAT_BASE + ?
+
Base Color
+ +
+ : )}
@@ -54,14 +76,18 @@ const SingleStatOptions = ({
  • Background
  • Text
  • @@ -83,7 +109,7 @@ const SingleStatOptions = ({ ) } -const {arrayOf, bool, func, shape, string} = PropTypes +const {arrayOf, func, shape, string} = PropTypes SingleStatOptions.defaultProps = { colors: [], @@ -104,8 +130,8 @@ SingleStatOptions.propTypes = { onChooseColor: func.isRequired, onValidateColorValue: func.isRequired, onUpdateColorValue: func.isRequired, - colorSingleStatText: bool.isRequired, - onToggleSingleStatText: func.isRequired, + singleStatColoration: string.isRequired, + onToggleSingleStatColoration: func.isRequired, onSetSuffix: func.isRequired, suffix: string.isRequired, } diff --git a/ui/src/dashboards/constants/gaugeColors.js b/ui/src/dashboards/constants/gaugeColors.js index 7004dccbf..739f6c0bf 100644 --- a/ui/src/dashboards/constants/gaugeColors.js +++ b/ui/src/dashboards/constants/gaugeColors.js @@ -11,6 +11,7 @@ export const COLOR_TYPE_THRESHOLD = 'threshold' export const SINGLE_STAT_TEXT = 'text' export const SINGLE_STAT_BG = 'background' +export const SINGLE_STAT_BASE = 'base' export const GAUGE_COLORS = [ { @@ -81,9 +82,13 @@ export const GAUGE_COLORS = [ hex: '#545667', name: 'graphite', }, + { + hex: '#ffffff', + name: 'white', + }, ] -export const DEFAULT_COLORS = [ +export const DEFAULT_GAUGE_COLORS = [ { type: COLOR_TYPE_MIN, hex: GAUGE_COLORS[11].hex, @@ -100,27 +105,60 @@ export const DEFAULT_COLORS = [ }, ] -export const validateColors = (colors, type, colorSingleStatText) => { - if (type === 'single-stat') { - // Single stat colors should all have type of 'text' or 'background' - const colorType = colorSingleStatText ? SINGLE_STAT_TEXT : SINGLE_STAT_BG - return colors ? colors.map(color => ({...color, type: colorType})) : null - } +export const DEFAULT_SINGLESTAT_COLORS = [ + { + type: SINGLE_STAT_TEXT, + hex: GAUGE_COLORS[11].hex, + id: SINGLE_STAT_BASE, + name: GAUGE_COLORS[11].name, + value: '0', + }, +] + +export const validateSingleStatColors = (colors, coloration) => { if (!colors || colors.length === 0) { - return DEFAULT_COLORS - } - if (type === 'gauge') { - // Gauge colors should have a type of min, any number of thresholds, and a max - const formatttedColors = _.sortBy(colors, color => - Number(color.value) - ).map(c => ({ - ...c, - type: COLOR_TYPE_THRESHOLD, - })) - formatttedColors[0].type = COLOR_TYPE_MIN - formatttedColors[formatttedColors.length - 1].type = COLOR_TYPE_MAX - return formatttedColors + return DEFAULT_SINGLESTAT_COLORS } - return colors.length >= MIN_THRESHOLDS ? colors : DEFAULT_COLORS + const containsBaseColor = !!colors.filter( + color => color.id === SINGLE_STAT_BASE + ) + + const filteredColors = colors.map(c => { + if (c.id !== SINGLE_STAT_BASE) { + // Single stat colors should all have type of 'text' or 'background' + c.type = coloration + } + }) + + return containsBaseColor + ? filteredColors + : [...filteredColors, DEFAULT_SINGLESTAT_COLORS[0]] +} + +export const getSingleStatColoration = colors => { + if (!colors || colors.length === 0) { + return SINGLE_STAT_TEXT + } + + return colors[0].type +} + +export const validateGaugeColors = colors => { + if (!colors || colors.length < MIN_THRESHOLDS) { + return DEFAULT_GAUGE_COLORS + } + + // Gauge colors should have a type of min, any number of thresholds, and a max + const formatttedColors = _.sortBy(colors, color => + Number(color.value) + ).map(color => ({ + ...color, + type: COLOR_TYPE_THRESHOLD, + })) + + formatttedColors[0].type = COLOR_TYPE_MIN + formatttedColors[formatttedColors.length - 1].type = COLOR_TYPE_MAX + + return formatttedColors } diff --git a/ui/src/shared/components/ColorDropdown.js b/ui/src/shared/components/ColorDropdown.js index b91058df6..8d8275e60 100644 --- a/ui/src/shared/components/ColorDropdown.js +++ b/ui/src/shared/components/ColorDropdown.js @@ -33,11 +33,12 @@ class ColorDropdown extends Component { render() { const {visible} = this.state - const {colors, selected, disabled} = this.props + const {colors, selected, disabled, stretchToFit} = this.props - const dropdownClassNames = visible - ? 'color-dropdown open' - : 'color-dropdown' + const dropdownClassNames = classnames('color-dropdown', { + open: visible, + 'color-dropdown--stretch': stretchToFit, + }) const toggleClassNames = classnames( 'btn btn-sm btn-default color-dropdown--toggle', {active: visible, 'color-dropdown__disabled': disabled} @@ -103,6 +104,7 @@ ColorDropdown.propTypes = { name: string.isRequired, }) ).isRequired, + stretchToFit: bool, disabled: bool, } diff --git a/ui/src/shared/components/GaugeChart.js b/ui/src/shared/components/GaugeChart.js index bc49f9bf8..542fbbb20 100644 --- a/ui/src/shared/components/GaugeChart.js +++ b/ui/src/shared/components/GaugeChart.js @@ -2,7 +2,7 @@ import React, {PropTypes, PureComponent} from 'react' import lastValues from 'shared/parsing/lastValues' import Gauge from 'shared/components/Gauge' -import {DEFAULT_COLORS} from 'src/dashboards/constants/gaugeColors' +import {DEFAULT_GAUGE_COLORS} from 'src/dashboards/constants/gaugeColors' import {DASHBOARD_LAYOUT_ROW_HEIGHT} from 'shared/constants' class GaugeChart extends PureComponent { @@ -60,7 +60,7 @@ class GaugeChart extends PureComponent { const {arrayOf, bool, number, shape, string} = PropTypes GaugeChart.defaultProps = { - colors: DEFAULT_COLORS, + colors: DEFAULT_GAUGE_COLORS, } GaugeChart.propTypes = { diff --git a/ui/src/style/components/ceo-display-options.scss b/ui/src/style/components/ceo-display-options.scss index 4d5b47322..a381d4bbd 100644 --- a/ui/src/style/components/ceo-display-options.scss +++ b/ui/src/style/components/ceo-display-options.scss @@ -242,8 +242,16 @@ button.btn.btn-primary.btn-sm.gauge-controls--add-threshold { .gauge-controls--input { flex: 1 0 0; - margin: 0 4px; + margin: 0 0 0 4px; } +.gauge-controls--section .color-dropdown { + margin-left: 4px; +} +.gauge-controls--section .color-dropdown.color-dropdown--stretch { + width: auto; + flex: 1 0 0; +} + /* Cell Editor Overlay - Single-Stat Controls diff --git a/ui/src/style/components/color-dropdown.scss b/ui/src/style/components/color-dropdown.scss index ab7dd42bc..b892cd0b2 100644 --- a/ui/src/style/components/color-dropdown.scss +++ b/ui/src/style/components/color-dropdown.scss @@ -11,6 +11,10 @@ $color-dropdown--circle: 14px; position: relative; } +.color-dropdown.color-dropdown--stretch { + width: 100%; +} + .color-dropdown--toggle { width: 100%; position: relative; From b1e2e9ace4693564bd528dc087dcda5378dd0049 Mon Sep 17 00:00:00 2001 From: Alex P Date: Wed, 7 Feb 2018 18:50:37 -0800 Subject: [PATCH 43/98] Fix all kinds of problems Line + SingleStat works now --- .../components/CellEditorOverlay.js | 8 ++----- ui/src/dashboards/components/Visualization.js | 2 +- ui/src/dashboards/constants/gaugeColors.js | 24 +++++++++++-------- ui/src/shared/components/LineGraph.js | 12 +++++++++- ui/src/shared/components/RefreshingGraph.js | 1 + ui/src/shared/components/SingleStat.js | 14 +++++++---- ui/src/style/components/dygraphs.scss | 4 +--- 7 files changed, 40 insertions(+), 25 deletions(-) diff --git a/ui/src/dashboards/components/CellEditorOverlay.js b/ui/src/dashboards/components/CellEditorOverlay.js index 1d392d6c2..91fdd38ee 100644 --- a/ui/src/dashboards/components/CellEditorOverlay.js +++ b/ui/src/dashboards/components/CellEditorOverlay.js @@ -565,12 +565,8 @@ class CellEditorOverlay extends Component { (!!query.measurement && !!query.database && !!query.fields.length) || !!query.rawText - let visualizationColors - if (cellWorkingType === 'gauge') { - visualizationColors = gaugeColors - } else if (cellWorkingType === 'single-stat') { - visualizationColors = singleStatColors - } + const visualizationColors = + cellWorkingType === 'gauge' ? gaugeColors : singleStatColors return (
    { return DEFAULT_SINGLESTAT_COLORS } - const containsBaseColor = !!colors.filter( - color => color.id === SINGLE_STAT_BASE - ) + let containsBaseColor = false - const filteredColors = colors.map(c => { - if (c.id !== SINGLE_STAT_BASE) { - // Single stat colors should all have type of 'text' or 'background' - c.type = coloration + const filteredColors = colors.map(color => { + if (color.id === SINGLE_STAT_BASE) { + // Check for existance of base color + containsBaseColor = true + return color } + // Single stat colors should all have type of 'text' or 'background' + return {...color, type: coloration} }) - return containsBaseColor - ? filteredColors - : [...filteredColors, DEFAULT_SINGLESTAT_COLORS[0]] + const filteredColorsWithBase = [ + ...filteredColors, + DEFAULT_SINGLESTAT_COLORS[0], + ] + + return containsBaseColor ? filteredColors : filteredColorsWithBase } export const getSingleStatColoration = colors => { diff --git a/ui/src/shared/components/LineGraph.js b/ui/src/shared/components/LineGraph.js index 101ca72b9..699648bb0 100644 --- a/ui/src/shared/components/LineGraph.js +++ b/ui/src/shared/components/LineGraph.js @@ -40,6 +40,7 @@ class LineGraph extends Component { axes, cell, title, + colors, onZoom, queries, timeRange, @@ -106,7 +107,7 @@ class LineGraph extends Component { options={options} /> {showSingleStat - ? + ? : null}
    ) @@ -170,6 +171,15 @@ LineGraph.propTypes = { resizeCoords: shape(), queries: arrayOf(shape({}).isRequired).isRequired, data: arrayOf(shape({}).isRequired).isRequired, + colors: arrayOf( + shape({ + type: string.isRequired, + hex: string.isRequired, + id: string.isRequired, + name: string.isRequired, + value: string.isRequired, + }).isRequired + ), } export default LineGraph diff --git a/ui/src/shared/components/RefreshingGraph.js b/ui/src/shared/components/RefreshingGraph.js index 05b7917de..00bca981f 100644 --- a/ui/src/shared/components/RefreshingGraph.js +++ b/ui/src/shared/components/RefreshingGraph.js @@ -76,6 +76,7 @@ const RefreshingGraph = ({ return ( 0) { - className = 'single-stat single-stat--colored' + console.log(colors) + if (colors.length === 1) { + if (colors[0].type === SINGLE_STAT_TEXT) { + textColor = colors[0].hex + } else { + bgColor = colors[0].hex + textColor = isBackgroundLight(bgColor) ? darkText : lightText + } + } else if (colors.length > 1) { const sortedColors = _.sortBy(colors, color => Number(color.value)) const nearestCrossedThreshold = sortedColors .filter(color => lastValue > color.value) @@ -54,7 +60,7 @@ class SingleStat extends PureComponent { return (
    Date: Wed, 7 Feb 2018 19:15:46 -0800 Subject: [PATCH 44/98] Reintroduce shadow behind text in single stat + line graph --- ui/src/shared/components/LineGraph.js | 7 ++++++- ui/src/shared/components/SingleStat.js | 11 ++++++++++- ui/src/style/components/dygraphs.scss | 16 +++++++++++----- 3 files changed, 27 insertions(+), 7 deletions(-) diff --git a/ui/src/shared/components/LineGraph.js b/ui/src/shared/components/LineGraph.js index 699648bb0..58f063445 100644 --- a/ui/src/shared/components/LineGraph.js +++ b/ui/src/shared/components/LineGraph.js @@ -107,7 +107,12 @@ class LineGraph extends Component { options={options} /> {showSingleStat - ? + ? : null}
    ) diff --git a/ui/src/shared/components/SingleStat.js b/ui/src/shared/components/SingleStat.js index fbe6320b7..a645b754e 100644 --- a/ui/src/shared/components/SingleStat.js +++ b/ui/src/shared/components/SingleStat.js @@ -12,7 +12,14 @@ const lightText = '#ffffff' class SingleStat extends PureComponent { render() { - const {data, cellHeight, isFetchingInitially, colors, suffix} = this.props + const { + data, + cellHeight, + isFetchingInitially, + colors, + suffix, + lineGraph, + } = this.props // If data for this graph is being fetched for the first time, show a graph-wide spinner. if (isFetchingInitially) { @@ -70,6 +77,7 @@ class SingleStat extends PureComponent { > {roundedValue} {suffix} + {lineGraph &&
    }
    ) @@ -92,6 +100,7 @@ SingleStat.propTypes = { }).isRequired ), suffix: string, + lineGraph: bool, } export default SingleStat diff --git a/ui/src/style/components/dygraphs.scss b/ui/src/style/components/dygraphs.scss index 5a859841c..e12e4477a 100644 --- a/ui/src/style/components/dygraphs.scss +++ b/ui/src/style/components/dygraphs.scss @@ -98,7 +98,8 @@ top: 50%; left: 50%; transform: translate(-50%,-50%); - width: calc(100% - 32px); + width: auto; + max-width: calc(100% - 32px); text-align: center; font-size: 54px; line-height: 54px; @@ -113,18 +114,23 @@ } } .single-stat--shadow { - position: relative; - display: inline-block; + position: absolute; + width: 100%; + height: 100%; + top: 0; + left: 0; } .single-stat--shadow:after { content: ''; position: absolute; top: 50%; left: 50%; - width: 110%; + width: 90%; height: 0; transform: translate(-50%,-50%); - box-shadow: fade-out($g2-kevlar, 0.3) 0 0 50px 30px; + box-shadow: + $g2-kevlar 0 0 30px 10px, + fade-out($g2-kevlar, 0.3) 0 0 50px 30px; z-index: -1; } .single-stat--small .single-stat--shadow:after { From 4e90c41c0da4617159ac895906002887d79b9375 Mon Sep 17 00:00:00 2001 From: Alex P Date: Wed, 7 Feb 2018 19:19:04 -0800 Subject: [PATCH 45/98] Remove console log --- ui/src/shared/components/SingleStat.js | 1 - 1 file changed, 1 deletion(-) diff --git a/ui/src/shared/components/SingleStat.js b/ui/src/shared/components/SingleStat.js index a645b754e..26a6e6340 100644 --- a/ui/src/shared/components/SingleStat.js +++ b/ui/src/shared/components/SingleStat.js @@ -37,7 +37,6 @@ class SingleStat extends PureComponent { let bgColor = null let textColor = null - console.log(colors) if (colors.length === 1) { if (colors[0].type === SINGLE_STAT_TEXT) { textColor = colors[0].hex From 1d524e55a5d4a8a56c23136d84bf3af898b36760 Mon Sep 17 00:00:00 2001 From: Iris Scholten Date: Thu, 8 Feb 2018 10:42:32 -0800 Subject: [PATCH 46/98] change row key from id to uuid --- ui/src/admin/components/chronograf/ProvidersTable.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ui/src/admin/components/chronograf/ProvidersTable.js b/ui/src/admin/components/chronograf/ProvidersTable.js index 02ef3429a..09ad954f1 100644 --- a/ui/src/admin/components/chronograf/ProvidersTable.js +++ b/ui/src/admin/components/chronograf/ProvidersTable.js @@ -1,5 +1,6 @@ import React, {Component, PropTypes} from 'react' +import uuid from 'node-uuid' import ProvidersTableRow from 'src/admin/components/chronograf/ProvidersTableRow' import ProvidersTableRowNew from 'src/admin/components/chronograf/ProvidersTableRowNew' @@ -88,7 +89,7 @@ class ProvidersTable extends Component {
{mappings.map(mapping => Date: Thu, 8 Feb 2018 10:59:50 -0800 Subject: [PATCH 47/98] Pseudo code for removal of Public --- server/me.go | 40 ++++++++++++++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/server/me.go b/server/me.go index dd0bdf4be..a0b60b5a2 100644 --- a/server/me.go +++ b/server/me.go @@ -200,12 +200,41 @@ func (s *Service) Me(w http.ResponseWriter, r *http.Request) { ctx = context.WithValue(ctx, organizations.ContextKey, p.Organization) serverCtx := serverContext(ctx) - if p.Organization == "" { - defaultOrg, err := s.Store.Organizations(serverCtx).DefaultOrganization(serverCtx) + /* ALTERNATE 1 + If existing + If roles, let them in + If no roles, purgatory + + If new + Build new user + Generate roles for user based on defined mappings + If roles, save and let them in + If no roles, tell them box is private + */ + + /* ALTERNATE 2 + Find user => (existing) + If no user => (new) + Build new user, with superadmin based on setting + Generate roles for user based on defined mappings + + If roles, + (existing) => let them in + (new) => save, let them in + + If no roles, + (existing) => purgatory + (new) => tell them box is private + */ + + + defaultOrg, err := s.Store.Organizations(serverCtx).DefaultOrganization(serverCtx) if err != nil { unknownErrorWithMessage(w, err, s.Logger) return - } + + + if p.Organization == "" { p.Organization = defaultOrg.ID } @@ -219,11 +248,6 @@ func (s *Service) Me(w http.ResponseWriter, r *http.Request) { return } - defaultOrg, err := s.Store.Organizations(serverCtx).DefaultOrganization(serverCtx) - if err != nil { - unknownErrorWithMessage(w, err, s.Logger) - return - } if usr != nil { if defaultOrg.Public || usr.SuperAdmin == true { From 4c54f81bc9837a7da7aba5da7d6cfb60270369b9 Mon Sep 17 00:00:00 2001 From: Alex P Date: Thu, 8 Feb 2018 11:02:34 -0800 Subject: [PATCH 48/98] Fix logic in single stat coloration getter Sometimes results in a situation where coloration state gets set to a weird value, which seems to be a factor in the auto refresh bug --- ui/src/dashboards/constants/gaugeColors.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ui/src/dashboards/constants/gaugeColors.js b/ui/src/dashboards/constants/gaugeColors.js index 61121fc80..0fa0302db 100644 --- a/ui/src/dashboards/constants/gaugeColors.js +++ b/ui/src/dashboards/constants/gaugeColors.js @@ -143,9 +143,11 @@ export const validateSingleStatColors = (colors, coloration) => { export const getSingleStatColoration = colors => { if (!colors || colors.length === 0) { return SINGLE_STAT_TEXT + } else if (colors[0].type === (SINGLE_STAT_TEXT || SINGLE_STAT_BG)) { + return colors[0].type } - return colors[0].type + return SINGLE_STAT_TEXT } export const validateGaugeColors = colors => { From 597d097560990d2e57c04d7fcf374e07412aeeb0 Mon Sep 17 00:00:00 2001 From: Alex P Date: Thu, 8 Feb 2018 11:12:19 -0800 Subject: [PATCH 49/98] Connect line graph prefix and suffix to child single stat --- ui/src/shared/components/LineGraph.js | 10 ++++++++++ ui/src/shared/components/SingleStat.js | 3 +++ 2 files changed, 13 insertions(+) diff --git a/ui/src/shared/components/LineGraph.js b/ui/src/shared/components/LineGraph.js index 58f063445..9650a773c 100644 --- a/ui/src/shared/components/LineGraph.js +++ b/ui/src/shared/components/LineGraph.js @@ -84,6 +84,14 @@ class LineGraph extends Component { ? SINGLE_STAT_LINE_COLORS : overrideLineColors + let prefix + let suffix + + if (axes) { + prefix = axes.y.prefix + suffix = axes.y.suffix + } + return (
{isRefreshing ? : null} @@ -108,6 +116,8 @@ class LineGraph extends Component { /> {showSingleStat ? + {prefix} {roundedValue} {suffix} {lineGraph &&
} @@ -98,6 +100,7 @@ SingleStat.propTypes = { value: string.isRequired, }).isRequired ), + prefix: string, suffix: string, lineGraph: bool, } From 581ee11599fe8ec748744a0aac93bdd67ca521e6 Mon Sep 17 00:00:00 2001 From: Alex P Date: Thu, 8 Feb 2018 11:14:51 -0800 Subject: [PATCH 50/98] Save single stat colorization when single stat + line graph is chosen --- ui/src/dashboards/components/CellEditorOverlay.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/dashboards/components/CellEditorOverlay.js b/ui/src/dashboards/components/CellEditorOverlay.js index 91fdd38ee..a195c7730 100644 --- a/ui/src/dashboards/components/CellEditorOverlay.js +++ b/ui/src/dashboards/components/CellEditorOverlay.js @@ -388,7 +388,7 @@ class CellEditorOverlay extends Component { let colors = [] if (type === 'gauge') { colors = gaugeColors - } else if (type === 'single-stat') { + } else if (type === 'single-stat' || type === 'line-plus-single-stat') { colors = singleStatColors } From 28e47af71c7af48d525c050cd0635fc289641948 Mon Sep 17 00:00:00 2001 From: Alex P Date: Thu, 8 Feb 2018 11:26:41 -0800 Subject: [PATCH 51/98] Updoot log of changes --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a7563b2fe..dc98c3464 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,11 +12,13 @@ 1. [#2698](https://github.com/influxdata/chronograf/pull/2698): Improve clarity of terminology surrounding InfluxDB & Kapacitor connections 1. [#2746](https://github.com/influxdata/chronograf/pull/2746): Separate saving TICKscript from exiting editor page 1. [#2774](https://github.com/influxdata/chronograf/pull/2774): Enable Save (⌘ + Enter) and Cancel (Escape) hotkeys in Cell Editor Overlay +1. [#2788](https://github.com/influxdata/chronograf/pull/2788): Enable customization of Single Stat graph's "Base Color" ### Bug Fixes 1. [#2684](https://github.com/influxdata/chronograf/pull/2684): Fix TICKscript Sensu alerts when no group by tags selected 1. [#2735](https://github.com/influxdata/chronograf/pull/2735): Remove cli options from systemd service file 1. [#2761](https://github.com/influxdata/chronograf/pull/2761): Remove cli options from sysvinit service file +1. [#2788](https://github.com/influxdata/chronograf/pull/2788): Fix disappearance of text in Single Stat graphs during editing ## v1.4.0.1 [2017-1-9] ### Features From 5c688e6f569d8ca43a9d41b91cd4795e112c4199 Mon Sep 17 00:00:00 2001 From: Alex P Date: Thu, 8 Feb 2018 14:00:18 -0800 Subject: [PATCH 52/98] Make singleStat shadow less drastic --- ui/src/style/components/dygraphs.scss | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ui/src/style/components/dygraphs.scss b/ui/src/style/components/dygraphs.scss index e12e4477a..d5fe745f8 100644 --- a/ui/src/style/components/dygraphs.scss +++ b/ui/src/style/components/dygraphs.scss @@ -128,9 +128,7 @@ width: 90%; height: 0; transform: translate(-50%,-50%); - box-shadow: - $g2-kevlar 0 0 30px 10px, - fade-out($g2-kevlar, 0.3) 0 0 50px 30px; + box-shadow: fade-out($g2-kevlar, 0.3) 0 0 50px 30px; z-index: -1; } .single-stat--small .single-stat--shadow:after { From 6c858ea24decbfafeae0dc7ab29c33c5f51b11e1 Mon Sep 17 00:00:00 2001 From: Alex P Date: Thu, 8 Feb 2018 14:04:33 -0800 Subject: [PATCH 53/98] Improve handling of single stat coloring Previously would return some undesirable defaults when certain conditions are met (eg. a user with a single stat + line graph upgrades to this new version) --- ui/src/shared/components/SingleStat.js | 41 ++------- ui/src/shared/constants/colorOperations.js | 96 +++++++++++++++++++++- 2 files changed, 100 insertions(+), 37 deletions(-) diff --git a/ui/src/shared/components/SingleStat.js b/ui/src/shared/components/SingleStat.js index cd2d0f7d9..623a48dbd 100644 --- a/ui/src/shared/components/SingleStat.js +++ b/ui/src/shared/components/SingleStat.js @@ -5,10 +5,7 @@ import lastValues from 'shared/parsing/lastValues' import {SMALL_CELL_HEIGHT} from 'shared/graphs/helpers' import {SINGLE_STAT_TEXT} from 'src/dashboards/constants/gaugeColors' -import {isBackgroundLight} from 'shared/constants/colorOperations' - -const darkText = '#292933' -const lightText = '#ffffff' +import {generateSingleStatHexs} from 'shared/constants/colorOperations' class SingleStat extends PureComponent { render() { @@ -32,38 +29,16 @@ class SingleStat extends PureComponent { } const lastValue = lastValues(data)[1] - const precision = 100.0 const roundedValue = Math.round(+lastValue * precision) / precision - let bgColor = null - let textColor = null + const colorizeText = _.some(colors, {type: SINGLE_STAT_TEXT}) - if (colors.length === 1) { - if (colors[0].type === SINGLE_STAT_TEXT) { - textColor = colors[0].hex - } else { - bgColor = colors[0].hex - textColor = isBackgroundLight(bgColor) ? darkText : lightText - } - } else if (colors.length > 1) { - const sortedColors = _.sortBy(colors, color => Number(color.value)) - const nearestCrossedThreshold = sortedColors - .filter(color => lastValue > color.value) - .pop() - - const colorizeText = _.some(colors, {type: SINGLE_STAT_TEXT}) - - if (colorizeText) { - textColor = nearestCrossedThreshold - ? nearestCrossedThreshold.hex - : '#292933' - } else { - bgColor = nearestCrossedThreshold - ? nearestCrossedThreshold.hex - : '#292933' - textColor = isBackgroundLight(bgColor) ? darkText : lightText - } - } + const {bgColor, textColor} = generateSingleStatHexs( + colors, + lineGraph, + colorizeText, + lastValue + ) return (
{ const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex) return result @@ -16,9 +22,91 @@ const averageRgbValues = valuesObject => { const trueNeutralGrey = 128 -export const isBackgroundLight = backgroundColor => { - const averageBackground = averageRgbValues(hexToRgb(backgroundColor)) - const isLight = averageBackground > trueNeutralGrey +const getLegibleTextColor = bgColorHex => { + const averageBackground = averageRgbValues(hexToRgb(bgColorHex)) + const isBackgroundLight = averageBackground > trueNeutralGrey - return isLight + const darkText = '#292933' + const lightText = '#ffffff' + + return isBackgroundLight ? darkText : lightText +} + +const findNearestCrossedThreshold = (colors, lastValue) => { + const sortedColors = _.sortBy(colors, color => Number(color.value)) + const nearestCrossedThreshold = sortedColors + .filter(color => lastValue > color.value) + .pop() + + return nearestCrossedThreshold +} + +export const generateSingleStatHexs = ( + colors, + containsLineGraph, + colorizeText, + lastValue +) => { + const defaultColoring = {bgColor: null, textColor: GAUGE_COLORS[11].hex} + + if (!colors & containsLineGraph) { + return defaultColoring + } + + // baseColor is expected in all cases + const baseColor = colors.find(color => (color.id = SINGLE_STAT_BASE)) + + // If the single stat is above a line graph never have a background color + if (containsLineGraph) { + return baseColor + ? {bgColor: null, textColor: baseColor.hex} + : defaultColoring + } + + // When there is only a base color and it's applied to the text + if (colorizeText && colors.length === 1) { + return baseColor + ? {bgColor: null, textColor: baseColor.hex} + : defaultColoring + } + + // When there's multiple colors and they're applied to the text + if (colorizeText && colors.length > 1) { + const nearestCrossedThreshold = findNearestCrossedThreshold( + colors, + lastValue + ) + const bgColor = null + const textColor = nearestCrossedThreshold.hex + + return {bgColor, textColor} + } + + // When there is only a base color and it's applued to the background + if (colors.length === 1) { + const bgColor = baseColor.hex + const textColor = getLegibleTextColor(bgColor) + + return {bgColor, textColor} + } + + // When there are multiple colors and they're applied to the background + if (colors.length > 1) { + const nearestCrossedThreshold = findNearestCrossedThreshold( + colors, + lastValue + ) + + const bgColor = nearestCrossedThreshold + ? nearestCrossedThreshold.hex + : baseColor.hex + const textColor = getLegibleTextColor(bgColor) + + return {bgColor, textColor} + } + + // If all else fails, use safe default + const bgColor = null + const textColor = baseColor.hex + return {bgColor, textColor} } From 3fb626654b3890158133235733a2fdfd683237da Mon Sep 17 00:00:00 2001 From: Alex P Date: Thu, 8 Feb 2018 14:28:08 -0800 Subject: [PATCH 54/98] Make unreachable code reachable --- ui/src/shared/constants/colorOperations.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ui/src/shared/constants/colorOperations.js b/ui/src/shared/constants/colorOperations.js index cc3aa25e4..b313084f9 100644 --- a/ui/src/shared/constants/colorOperations.js +++ b/ui/src/shared/constants/colorOperations.js @@ -48,8 +48,7 @@ export const generateSingleStatHexs = ( lastValue ) => { const defaultColoring = {bgColor: null, textColor: GAUGE_COLORS[11].hex} - - if (!colors & containsLineGraph) { + if (!colors.length) { return defaultColoring } From 1f46ce7bd30cb53ee3670428de5bf508153b71a6 Mon Sep 17 00:00:00 2001 From: Alex P Date: Thu, 8 Feb 2018 15:40:20 -0800 Subject: [PATCH 55/98] Fix all kinds of weird bugs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I don’t even know how to describe what is going on, brain is melted --- .../components/CellEditorOverlay.js | 6 +----- ui/src/dashboards/constants/gaugeColors.js | 20 +++++++++++-------- ui/src/shared/components/SingleStat.js | 3 +-- ui/src/shared/constants/colorOperations.js | 7 +++++-- 4 files changed, 19 insertions(+), 17 deletions(-) diff --git a/ui/src/dashboards/components/CellEditorOverlay.js b/ui/src/dashboards/components/CellEditorOverlay.js index a195c7730..9b69db947 100644 --- a/ui/src/dashboards/components/CellEditorOverlay.js +++ b/ui/src/dashboards/components/CellEditorOverlay.js @@ -61,11 +61,7 @@ class CellEditorOverlay extends Component { axes, singleStatColoration, gaugeColors: validateGaugeColors(colors, type), - singleStatColors: validateSingleStatColors( - colors, - type, - singleStatColoration - ), + singleStatColors: validateSingleStatColors(colors, singleStatColoration), } } diff --git a/ui/src/dashboards/constants/gaugeColors.js b/ui/src/dashboards/constants/gaugeColors.js index 0fa0302db..08ab51e6f 100644 --- a/ui/src/dashboards/constants/gaugeColors.js +++ b/ui/src/dashboards/constants/gaugeColors.js @@ -122,31 +122,35 @@ export const validateSingleStatColors = (colors, coloration) => { let containsBaseColor = false - const filteredColors = colors.map(color => { + const formattedColors = colors.map(color => { if (color.id === SINGLE_STAT_BASE) { // Check for existance of base color containsBaseColor = true - return color + return {...color, type: coloration} } // Single stat colors should all have type of 'text' or 'background' return {...color, type: coloration} }) - const filteredColorsWithBase = [ - ...filteredColors, + const formattedColorsWithBase = [ + ...formattedColors, DEFAULT_SINGLESTAT_COLORS[0], ] - return containsBaseColor ? filteredColors : filteredColorsWithBase + return containsBaseColor ? formattedColors : formattedColorsWithBase } export const getSingleStatColoration = colors => { - if (!colors || colors.length === 0) { + if (!colors.length) { return SINGLE_STAT_TEXT - } else if (colors[0].type === (SINGLE_STAT_TEXT || SINGLE_STAT_BG)) { - return colors[0].type } + if ( + colors[0].type === SINGLE_STAT_TEXT || + colors[0].type === SINGLE_STAT_BG + ) { + return colors[0].type + } return SINGLE_STAT_TEXT } diff --git a/ui/src/shared/components/SingleStat.js b/ui/src/shared/components/SingleStat.js index 623a48dbd..9174f8852 100644 --- a/ui/src/shared/components/SingleStat.js +++ b/ui/src/shared/components/SingleStat.js @@ -1,5 +1,4 @@ import React, {PropTypes, PureComponent} from 'react' -import _ from 'lodash' import classnames from 'classnames' import lastValues from 'shared/parsing/lastValues' @@ -31,7 +30,7 @@ class SingleStat extends PureComponent { const lastValue = lastValues(data)[1] const precision = 100.0 const roundedValue = Math.round(+lastValue * precision) / precision - const colorizeText = _.some(colors, {type: SINGLE_STAT_TEXT}) + const colorizeText = !!colors.find(color => color.type === SINGLE_STAT_TEXT) const {bgColor, textColor} = generateSingleStatHexs( colors, diff --git a/ui/src/shared/constants/colorOperations.js b/ui/src/shared/constants/colorOperations.js index b313084f9..b1dbf6ba0 100644 --- a/ui/src/shared/constants/colorOperations.js +++ b/ui/src/shared/constants/colorOperations.js @@ -48,12 +48,15 @@ export const generateSingleStatHexs = ( lastValue ) => { const defaultColoring = {bgColor: null, textColor: GAUGE_COLORS[11].hex} - if (!colors.length) { + + if (!colors.length || !lastValue) { return defaultColoring } // baseColor is expected in all cases - const baseColor = colors.find(color => (color.id = SINGLE_STAT_BASE)) + const baseColor = colors.find(color => (color.id = SINGLE_STAT_BASE)) || { + hex: defaultColoring.textColor, + } // If the single stat is above a line graph never have a background color if (containsLineGraph) { From e5fe5cd0e6e3a2115b6ceb5b312a4a013604ec19 Mon Sep 17 00:00:00 2001 From: Iris Scholten Date: Thu, 8 Feb 2018 16:01:00 -0800 Subject: [PATCH 56/98] WIP remove public logic from organizations --- server/me.go | 80 +++++++------------ .../components/chronograf/ProvidersTable.js | 7 +- .../chronograf/ProvidersTableRow.js | 7 +- .../chronograf/ProvidersTableRowNew.js | 7 +- 4 files changed, 44 insertions(+), 57 deletions(-) diff --git a/server/me.go b/server/me.go index a0b60b5a2..af1789c3b 100644 --- a/server/me.go +++ b/server/me.go @@ -201,38 +201,37 @@ func (s *Service) Me(w http.ResponseWriter, r *http.Request) { serverCtx := serverContext(ctx) /* ALTERNATE 1 - If existing - If roles, let them in - If no roles, purgatory + If existing + If roles, let them in + If no roles, purgatory - If new - Build new user - Generate roles for user based on defined mappings - If roles, save and let them in - If no roles, tell them box is private + If new + Build new user + Generate roles for user based on defined mappings + If roles, save and let them in + If no roles, tell them box is private */ /* ALTERNATE 2 - Find user => (existing) - If no user => (new) - Build new user, with superadmin based on setting - Generate roles for user based on defined mappings + Find user => (existing) + If no user => (new) + Build new user, with superadmin based on setting + Generate roles for user based on defined mappings - If roles, - (existing) => let them in - (new) => save, let them in + If roles, + (existing) => let them in + (new) => save, let them in - If no roles, - (existing) => purgatory - (new) => tell them box is private + If no roles, + (existing) => purgatory + (new) => tell them box is private */ - defaultOrg, err := s.Store.Organizations(serverCtx).DefaultOrganization(serverCtx) - if err != nil { - unknownErrorWithMessage(w, err, s.Logger) - return - + if err != nil { + unknownErrorWithMessage(w, err, s.Logger) + return + } if p.Organization == "" { p.Organization = defaultOrg.ID @@ -248,29 +247,14 @@ func (s *Service) Me(w http.ResponseWriter, r *http.Request) { return } + // user exists if usr != nil { - - if defaultOrg.Public || usr.SuperAdmin == true { - // If the default organization is public, or the user is a super admin - // they will always have a role in the default organization - defaultOrgID := defaultOrg.ID - if !hasRoleInDefaultOrganization(usr, defaultOrgID) { - usr.Roles = append(usr.Roles, chronograf.Role{ - Organization: defaultOrgID, - Name: defaultOrg.DefaultRole, - }) - if err := s.Store.Users(serverCtx).Update(serverCtx, usr); err != nil { - unknownErrorWithMessage(w, err, s.Logger) - return - } - } - } - - // If the default org is private and the user has no roles, they should not have access - if !defaultOrg.Public && len(usr.Roles) == 0 { + // user has no roles + if len(usr.Roles) == 0 { Error(w, http.StatusForbidden, "This organization is private. To gain access, you must be explicitly added by an administrator.", s.Logger) return } + currentOrg, err := s.Store.Organizations(serverCtx).Get(serverCtx, chronograf.OrganizationQuery{ID: &p.Organization}) if err == chronograf.ErrOrganizationNotFound { // The intent is to force a the user to go through another auth flow @@ -294,13 +278,6 @@ func (s *Service) Me(w http.ResponseWriter, r *http.Request) { return } - // TODO(desa) get rid of this - // If users must be explicitly added to the default organization, respond with 403 - // forbidden - if !defaultOrg.Public { - Error(w, http.StatusForbidden, "This organization is private. To gain access, you must be explicitly added by an administrator.", s.Logger) - return - } // Because we didnt find a user, making a new one user := &chronograf.User{ Name: p.Subject, @@ -319,6 +296,11 @@ func (s *Service) Me(w http.ResponseWriter, r *http.Request) { return } + if len(roles) == 0 { + Error(w, http.StatusForbidden, "This organization is private. To gain access, you must be explicitly added by an administrator.", s.Logger) + return + } + user.Roles = roles newUser, err := s.Store.Users(serverCtx).Add(serverCtx, user) diff --git a/ui/src/admin/components/chronograf/ProvidersTable.js b/ui/src/admin/components/chronograf/ProvidersTable.js index 09ad954f1..c69e25f01 100644 --- a/ui/src/admin/components/chronograf/ProvidersTable.js +++ b/ui/src/admin/components/chronograf/ProvidersTable.js @@ -87,7 +87,7 @@ class ProvidersTable extends Component {
- {mappings.map(mapping => + {mappings.map((mapping, i) => )} {isCreatingMap @@ -103,13 +104,15 @@ class ProvidersTable extends Component { schemes={SCHEMES} onCreate={this.handleCreateMap} onCancel={this.handleCancelCreateMap} + rowIndex={mappings.length.toString()} /> : null}
:

- Looks like you have no mappings + Looks like you have no mappings
+ New users will not be able to sign in

-
- ) - } -} - -const {func, shape, string} = PropTypes - -OrganizationsTableRowDefault.propTypes = { - organization: shape({ - id: string, - name: string.isRequired, - }).isRequired, - onChooseDefaultRole: func.isRequired, -} - -export default OrganizationsTableRowDefault diff --git a/ui/src/admin/components/chronograf/ProvidersTable.js b/ui/src/admin/components/chronograf/ProvidersTable.js index ca25b2414..5644fa35f 100644 --- a/ui/src/admin/components/chronograf/ProvidersTable.js +++ b/ui/src/admin/components/chronograf/ProvidersTable.js @@ -107,7 +107,7 @@ class ProvidersTable extends Component {

Looks like you have no mappings
- New users will not be able to sign in + New users will not be able to sign up automatically