diff --git a/pkg/api/team_members.go b/pkg/api/team_members.go index 60a170a8c31..5b5970de6ad 100644 --- a/pkg/api/team_members.go +++ b/pkg/api/team_members.go @@ -4,6 +4,7 @@ import ( "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/bus" m "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/util" ) @@ -17,6 +18,11 @@ func GetTeamMembers(c *m.ReqContext) Response { for _, member := range query.Result { member.AvatarUrl = dtos.GetGravatarUrl(member.Email) + member.Labels = []string{} + + if setting.IsEnterprise && setting.LdapEnabled && member.External { + member.Labels = append(member.Labels, "LDAP") + } } return JSON(200, query.Result) diff --git a/pkg/models/team_member.go b/pkg/models/team_member.go index 9434dad8ecd..dd64787f465 100644 --- a/pkg/models/team_member.go +++ b/pkg/models/team_member.go @@ -12,10 +12,11 @@ var ( // TeamMember model type TeamMember struct { - Id int64 - OrgId int64 - TeamId int64 - UserId int64 + Id int64 + OrgId int64 + TeamId int64 + UserId int64 + External bool Created time.Time Updated time.Time @@ -25,9 +26,10 @@ type TeamMember struct { // COMMANDS type AddTeamMemberCommand struct { - UserId int64 `json:"userId" binding:"Required"` - OrgId int64 `json:"-"` - TeamId int64 `json:"-"` + UserId int64 `json:"userId" binding:"Required"` + OrgId int64 `json:"-"` + TeamId int64 `json:"-"` + External bool `json:"-"` } type RemoveTeamMemberCommand struct { @@ -40,20 +42,23 @@ type RemoveTeamMemberCommand struct { // QUERIES type GetTeamMembersQuery struct { - OrgId int64 - TeamId int64 - UserId int64 - Result []*TeamMemberDTO + OrgId int64 + TeamId int64 + UserId int64 + External bool + Result []*TeamMemberDTO } // ---------------------- // Projections and DTOs type TeamMemberDTO struct { - OrgId int64 `json:"orgId"` - TeamId int64 `json:"teamId"` - UserId int64 `json:"userId"` - Email string `json:"email"` - Login string `json:"login"` - AvatarUrl string `json:"avatarUrl"` + OrgId int64 `json:"orgId"` + TeamId int64 `json:"teamId"` + UserId int64 `json:"userId"` + External bool `json:"-"` + Email string `json:"email"` + Login string `json:"login"` + AvatarUrl string `json:"avatarUrl"` + Labels []string `json:"labels"` } diff --git a/pkg/services/sqlstore/migrations/team_mig.go b/pkg/services/sqlstore/migrations/team_mig.go index 9800d27f8ab..34c46ad13cf 100644 --- a/pkg/services/sqlstore/migrations/team_mig.go +++ b/pkg/services/sqlstore/migrations/team_mig.go @@ -51,4 +51,7 @@ func addTeamMigrations(mg *Migrator) { Name: "email", Type: DB_NVarchar, Nullable: true, Length: 190, })) + mg.AddMigration("Add column external to team_member table", NewAddColumnMigration(teamMemberV1, &Column{ + Name: "external", Type: DB_Bool, Nullable: true, + })) } diff --git a/pkg/services/sqlstore/team.go b/pkg/services/sqlstore/team.go index 72955df9a6a..e51bcb9b6e9 100644 --- a/pkg/services/sqlstore/team.go +++ b/pkg/services/sqlstore/team.go @@ -240,11 +240,12 @@ func AddTeamMember(cmd *m.AddTeamMemberCommand) error { } entity := m.TeamMember{ - OrgId: cmd.OrgId, - TeamId: cmd.TeamId, - UserId: cmd.UserId, - Created: time.Now(), - Updated: time.Now(), + OrgId: cmd.OrgId, + TeamId: cmd.TeamId, + UserId: cmd.UserId, + External: cmd.External, + Created: time.Now(), + Updated: time.Now(), } _, err := sess.Insert(&entity) @@ -289,7 +290,10 @@ func GetTeamMembers(query *m.GetTeamMembersQuery) error { if query.UserId != 0 { sess.Where("team_member.user_id=?", query.UserId) } - sess.Cols("user.org_id", "team_member.team_id", "team_member.user_id", "user.email", "user.login") + if query.External { + sess.Where("team_member.external=?", dialect.BooleanStr(true)) + } + sess.Cols("team_member.org_id", "team_member.team_id", "team_member.user_id", "user.email", "user.login", "team_member.external") sess.Asc("user.login", "user.email") err := sess.Find(&query.Result) diff --git a/pkg/services/sqlstore/team_test.go b/pkg/services/sqlstore/team_test.go index abaa973957d..8f243617262 100644 --- a/pkg/services/sqlstore/team_test.go +++ b/pkg/services/sqlstore/team_test.go @@ -50,13 +50,29 @@ func TestTeamCommandsAndQueries(t *testing.T) { err = AddTeamMember(&m.AddTeamMemberCommand{OrgId: testOrgId, TeamId: team1.Id, UserId: userIds[0]}) So(err, ShouldBeNil) + err = AddTeamMember(&m.AddTeamMemberCommand{OrgId: testOrgId, TeamId: team1.Id, UserId: userIds[1], External: true}) + So(err, ShouldBeNil) q1 := &m.GetTeamMembersQuery{OrgId: testOrgId, TeamId: team1.Id} err = GetTeamMembers(q1) So(err, ShouldBeNil) + So(q1.Result, ShouldHaveLength, 2) So(q1.Result[0].TeamId, ShouldEqual, team1.Id) So(q1.Result[0].Login, ShouldEqual, "loginuser0") So(q1.Result[0].OrgId, ShouldEqual, testOrgId) + So(q1.Result[1].TeamId, ShouldEqual, team1.Id) + So(q1.Result[1].Login, ShouldEqual, "loginuser1") + So(q1.Result[1].OrgId, ShouldEqual, testOrgId) + So(q1.Result[1].External, ShouldEqual, true) + + q2 := &m.GetTeamMembersQuery{OrgId: testOrgId, TeamId: team1.Id, External: true} + err = GetTeamMembers(q2) + So(err, ShouldBeNil) + So(q2.Result, ShouldHaveLength, 1) + So(q2.Result[0].TeamId, ShouldEqual, team1.Id) + So(q2.Result[0].Login, ShouldEqual, "loginuser1") + So(q2.Result[0].OrgId, ShouldEqual, testOrgId) + So(q2.Result[0].External, ShouldEqual, true) }) Convey("Should be able to search for teams", func() { diff --git a/public/app/features/teams/TeamMembers.test.tsx b/public/app/features/teams/TeamMembers.test.tsx index 8584edd86c8..696880bebd9 100644 --- a/public/app/features/teams/TeamMembers.test.tsx +++ b/public/app/features/teams/TeamMembers.test.tsx @@ -12,6 +12,7 @@ const setup = (propOverrides?: object) => { loadTeamMembers: jest.fn(), addTeamMember: jest.fn(), removeTeamMember: jest.fn(), + syncEnabled: false, }; Object.assign(props, propOverrides); @@ -39,6 +40,15 @@ describe('Render', () => { expect(wrapper).toMatchSnapshot(); }); + + it('should render team members when sync enabled', () => { + const { wrapper } = setup({ + members: getMockTeamMembers(5), + syncEnabled: true, + }); + + expect(wrapper).toMatchSnapshot(); + }); }); describe('Functions', () => { diff --git a/public/app/features/teams/TeamMembers.tsx b/public/app/features/teams/TeamMembers.tsx index 38e8c0a9aa0..cda175f4395 100644 --- a/public/app/features/teams/TeamMembers.tsx +++ b/public/app/features/teams/TeamMembers.tsx @@ -3,6 +3,7 @@ import { connect } from 'react-redux'; import SlideDown from 'app/core/components/Animations/SlideDown'; import { UserPicker, User } from 'app/core/components/Picker/UserPicker'; import DeleteButton from 'app/core/components/DeleteButton/DeleteButton'; +import { TagBadge } from 'app/core/components/TagFilter/TagBadge'; import { TeamMember } from '../../types'; import { loadTeamMembers, addTeamMember, removeTeamMember, setSearchMemberQuery } from './state/actions'; import { getSearchMemberQuery, getTeamMembers } from './state/selectors'; @@ -14,6 +15,7 @@ export interface Props { addTeamMember: typeof addTeamMember; removeTeamMember: typeof removeTeamMember; setSearchMemberQuery: typeof setSearchMemberQuery; + syncEnabled: boolean; } export interface State { @@ -52,7 +54,19 @@ export class TeamMembers extends PureComponent { this.setState({ newTeamMember: null }); }; - renderMember(member: TeamMember) { + renderLabels(labels: string[]) { + if (!labels) { + return ; + } + + return ( + + {labels.map(label => {}} />)} + + ); + } + + renderMember(member: TeamMember, syncEnabled: boolean) { return ( @@ -60,6 +74,7 @@ export class TeamMembers extends PureComponent { {member.login} {member.email} + {syncEnabled ? this.renderLabels(member.labels) : ''} this.onRemoveMember(member)} /> @@ -69,7 +84,7 @@ export class TeamMembers extends PureComponent { render() { const { newTeamMember, isAdding } = this.state; - const { searchMemberQuery, members } = this.props; + const { searchMemberQuery, members, syncEnabled } = this.props; const newTeamMemberValue = newTeamMember && newTeamMember.id.toString(); return ( @@ -120,10 +135,11 @@ export class TeamMembers extends PureComponent { Name Email + {syncEnabled ? : ''} - {members && members.map(member => this.renderMember(member))} + {members && members.map(member => this.renderMember(member, syncEnabled))} diff --git a/public/app/features/teams/TeamPages.tsx b/public/app/features/teams/TeamPages.tsx index bbc8b7013ca..3dc5a9f6f15 100644 --- a/public/app/features/teams/TeamPages.tsx +++ b/public/app/features/teams/TeamPages.tsx @@ -63,7 +63,7 @@ export class TeamPages extends PureComponent { switch (currentPage) { case PageTypes.Members: - return ; + return ; case PageTypes.Settings: return ; diff --git a/public/app/features/teams/__mocks__/teamMocks.ts b/public/app/features/teams/__mocks__/teamMocks.ts index c9e9a27bee0..34fa06b2d09 100644 --- a/public/app/features/teams/__mocks__/teamMocks.ts +++ b/public/app/features/teams/__mocks__/teamMocks.ts @@ -35,6 +35,7 @@ export const getMockTeamMembers = (amount: number): TeamMember[] => { avatarUrl: 'some/url/', email: 'test@test.com', login: `testUser-${i}`, + labels: ['label 1', 'label 2'], }); } @@ -48,6 +49,7 @@ export const getMockTeamMember = (): TeamMember => { avatarUrl: 'some/url/', email: 'test@test.com', login: 'testUser', + labels: [], }; }; diff --git a/public/app/features/teams/__snapshots__/TeamMembers.test.tsx.snap b/public/app/features/teams/__snapshots__/TeamMembers.test.tsx.snap index 2a42897e2b9..93e6f2131aa 100644 --- a/public/app/features/teams/__snapshots__/TeamMembers.test.tsx.snap +++ b/public/app/features/teams/__snapshots__/TeamMembers.test.tsx.snap @@ -315,3 +315,305 @@ exports[`Render should render team members 1`] = ` `; + +exports[`Render should render team members when sync enabled 1`] = ` +
+
+
+ +
+
+ +
+ +
+ +
+ Add Team Member +
+
+ +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + Name + + Email + + +
+ + + testUser-1 + + test@test.com + + + + + +
+ + + testUser-2 + + test@test.com + + + + + +
+ + + testUser-3 + + test@test.com + + + + + +
+ + + testUser-4 + + test@test.com + + + + + +
+ + + testUser-5 + + test@test.com + + + + + +
+
+
+`; diff --git a/public/app/features/teams/__snapshots__/TeamPages.test.tsx.snap b/public/app/features/teams/__snapshots__/TeamPages.test.tsx.snap index 4ce4df4acb2..f32b8211d2c 100644 --- a/public/app/features/teams/__snapshots__/TeamPages.test.tsx.snap +++ b/public/app/features/teams/__snapshots__/TeamPages.test.tsx.snap @@ -29,7 +29,9 @@ exports[`Render should render member page if team not empty 1`] = `
- +
`; diff --git a/public/app/types/teams.ts b/public/app/types/teams.ts index bcf752c86c0..b85ff3833d6 100644 --- a/public/app/types/teams.ts +++ b/public/app/types/teams.ts @@ -12,6 +12,7 @@ export interface TeamMember { avatarUrl: string; email: string; login: string; + labels: string[]; } export interface TeamGroup {