From 13ed10495a96927bb31e26fe72d7aec579c4ce66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20H=C3=A4ggmark?= Date: Thu, 14 Mar 2019 09:49:35 +0100 Subject: [PATCH] teams: hide tabs settings and groupsync for non team admins --- .../app/features/teams/TeamMembers.test.tsx | 82 ---------------- public/app/features/teams/TeamMembers.tsx | 22 ++--- public/app/features/teams/TeamPages.test.tsx | 52 +++++++++- public/app/features/teams/TeamPages.tsx | 32 +++++-- .../__snapshots__/TeamPages.test.tsx.snap | 22 +++++ .../features/teams/state/selectors.test.ts | 96 ++++++++++++++++++- public/app/features/teams/state/selectors.ts | 18 +++- 7 files changed, 215 insertions(+), 109 deletions(-) diff --git a/public/app/features/teams/TeamMembers.test.tsx b/public/app/features/teams/TeamMembers.test.tsx index 02bfc4149f0..64e679f92ef 100644 --- a/public/app/features/teams/TeamMembers.test.tsx +++ b/public/app/features/teams/TeamMembers.test.tsx @@ -74,86 +74,4 @@ describe('Functions', () => { expect(instance.props.addTeamMember).toHaveBeenCalledWith(1); }); - - describe('isSignedInUserTeamAdmin', () => { - describe('when feature toggle editorsCanAdmin is turned off', () => { - it('should return true', () => { - const { instance } = setup({ editorsCanAdmin: false }); - - const result = instance.isSignedInUserTeamAdmin(); - - expect(result).toBe(true); - }); - }); - - describe('when feature toggle editorsCanAdmin is turned on', () => { - it('should return true if signed in user is grafanaAdmin', () => { - const members = getMockTeamMembers(5, 5); - const { instance } = setup({ - members, - editorsCanAdmin: true, - signedInUser: { - id: signedInUserId, - isGrafanaAdmin: true, - orgRole: OrgRole.Viewer, - }, - }); - - const result = instance.isSignedInUserTeamAdmin(); - - expect(result).toBe(true); - }); - - it('should return true if signed in user is org admin', () => { - const members = getMockTeamMembers(5, 5); - const { instance } = setup({ - members, - editorsCanAdmin: true, - signedInUser: { - id: signedInUserId, - isGrafanaAdmin: false, - orgRole: OrgRole.Admin, - }, - }); - - const result = instance.isSignedInUserTeamAdmin(); - - expect(result).toBe(true); - }); - - it('should return true if signed in user is team admin', () => { - const members = getMockTeamMembers(5, signedInUserId); - const { instance } = setup({ - members, - editorsCanAdmin: true, - signedInUser: { - id: signedInUserId, - isGrafanaAdmin: false, - orgRole: OrgRole.Viewer, - }, - }); - - const result = instance.isSignedInUserTeamAdmin(); - - expect(result).toBe(true); - }); - - it('should return false if signed in user is not grafanaAdmin, org admin or team admin', () => { - const members = getMockTeamMembers(5, 5); - const { instance } = setup({ - members, - editorsCanAdmin: true, - signedInUser: { - id: signedInUserId, - isGrafanaAdmin: false, - orgRole: OrgRole.Viewer, - }, - }); - - const result = instance.isSignedInUserTeamAdmin(); - - expect(result).toBe(false); - }); - }); - }); }); diff --git a/public/app/features/teams/TeamMembers.tsx b/public/app/features/teams/TeamMembers.tsx index db2b9024a1f..76fbdc87f1c 100644 --- a/public/app/features/teams/TeamMembers.tsx +++ b/public/app/features/teams/TeamMembers.tsx @@ -3,9 +3,9 @@ import { connect } from 'react-redux'; import SlideDown from 'app/core/components/Animations/SlideDown'; import { UserPicker } from 'app/core/components/Select/UserPicker'; import { TagBadge } from 'app/core/components/TagFilter/TagBadge'; -import { TeamMember, User, TeamPermissionLevel, OrgRole } from 'app/types'; +import { TeamMember, User } from 'app/types'; import { loadTeamMembers, addTeamMember, setSearchMemberQuery } from './state/actions'; -import { getSearchMemberQuery, getTeamMembers } from './state/selectors'; +import { getSearchMemberQuery, getTeamMembers, isSignedInUserTeamAdmin } from './state/selectors'; import { FilterInput } from 'app/core/components/FilterInput/FilterInput'; import { WithFeatureToggle } from 'app/core/components/WithFeatureToggle'; import { config } from 'app/core/config'; @@ -69,19 +69,11 @@ export class TeamMembers extends PureComponent { ); } - isSignedInUserTeamAdmin = (): boolean => { - const { members, editorsCanAdmin, signedInUser } = this.props; - const userInMembers = members.find(m => m.userId === signedInUser.id); - const isAdmin = signedInUser.isGrafanaAdmin || signedInUser.orgRole === OrgRole.Admin; - const userIsTeamAdmin = userInMembers && userInMembers.permission === TeamPermissionLevel.Admin; - const isSignedInUserTeamAdmin = isAdmin || userIsTeamAdmin; - - return isSignedInUserTeamAdmin || !editorsCanAdmin; - }; - render() { const { isAdding } = this.state; - const { searchMemberQuery, members, syncEnabled, editorsCanAdmin } = this.props; + const { searchMemberQuery, members, syncEnabled, editorsCanAdmin, signedInUser } = this.props; + const isTeamAdmin = isSignedInUserTeamAdmin({ members, editorsCanAdmin, signedInUser }); + return (
@@ -100,7 +92,7 @@ export class TeamMembers extends PureComponent { @@ -145,7 +137,7 @@ export class TeamMembers extends PureComponent { member={member} syncEnabled={syncEnabled} editorsCanAdmin={editorsCanAdmin} - signedInUserIsTeamAdmin={this.isSignedInUserTeamAdmin()} + signedInUserIsTeamAdmin={isTeamAdmin} /> ))} diff --git a/public/app/features/teams/TeamPages.test.tsx b/public/app/features/teams/TeamPages.test.tsx index 5d751f46989..74e9dfa24f2 100644 --- a/public/app/features/teams/TeamPages.test.tsx +++ b/public/app/features/teams/TeamPages.test.tsx @@ -1,8 +1,9 @@ import React from 'react'; import { shallow } from 'enzyme'; import { TeamPages, Props } from './TeamPages'; -import { NavModel, Team } from '../../types'; +import { NavModel, Team, TeamMember, OrgRole } from '../../types'; import { getMockTeam } from './__mocks__/teamMocks'; +import { User } from 'app/core/services/context_srv'; jest.mock('app/core/config', () => ({ buildInfo: { isEnterprise: true }, @@ -15,6 +16,13 @@ const setup = (propOverrides?: object) => { loadTeam: jest.fn(), pageName: 'members', team: {} as Team, + members: [] as TeamMember[], + editorsCanAdmin: false, + signedInUser: { + id: 1, + isGrafanaAdmin: false, + orgRole: OrgRole.Viewer, + } as User, }; Object.assign(props, propOverrides); @@ -65,4 +73,46 @@ describe('Render', () => { expect(wrapper).toMatchSnapshot(); }); + + describe('when feature toggle editorsCanAdmin is turned on', () => { + it('should render settings page if user is team admin', () => { + const { wrapper } = setup({ + team: getMockTeam(), + pageName: 'settings', + preferences: { + homeDashboardId: 1, + theme: 'Default', + timezone: 'Default', + }, + editorsCanAdmin: true, + signedInUser: { + id: 1, + isGrafanaAdmin: false, + orgRole: OrgRole.Admin, + } as User, + }); + + expect(wrapper).toMatchSnapshot(); + }); + + it('should not render settings page if user is team member', () => { + const { wrapper } = setup({ + team: getMockTeam(), + pageName: 'settings', + preferences: { + homeDashboardId: 1, + theme: 'Default', + timezone: 'Default', + }, + editorsCanAdmin: true, + signedInUser: { + id: 1, + isGrafanaAdmin: false, + orgRole: OrgRole.Viewer, + } as User, + }); + + expect(wrapper).toMatchSnapshot(); + }); + }); }); diff --git a/public/app/features/teams/TeamPages.tsx b/public/app/features/teams/TeamPages.tsx index 15adc8b3856..a64fe61612d 100644 --- a/public/app/features/teams/TeamPages.tsx +++ b/public/app/features/teams/TeamPages.tsx @@ -7,12 +7,13 @@ import Page from 'app/core/components/Page/Page'; import TeamMembers from './TeamMembers'; import TeamSettings from './TeamSettings'; import TeamGroupSync from './TeamGroupSync'; -import { NavModel, Team } from 'app/types'; +import { NavModel, Team, TeamMember } from 'app/types'; import { loadTeam } from './state/actions'; -import { getTeam } from './state/selectors'; +import { getTeam, getTeamMembers, isSignedInUserTeamAdmin } from './state/selectors'; import { getTeamLoadingNav } from './state/navModel'; import { getNavModel } from 'app/core/selectors/navModel'; import { getRouteParamsId, getRouteParamsPage } from '../../core/selectors/location'; +import { contextSrv, User } from 'app/core/services/context_srv'; export interface Props { team: Team; @@ -20,6 +21,9 @@ export interface Props { teamId: number; pageName: string; navModel: NavModel; + members?: TeamMember[]; + editorsCanAdmin?: boolean; + signedInUser?: User; } interface State { @@ -61,7 +65,15 @@ export class TeamPages extends PureComponent { return _.includes(pages, currentPage) ? currentPage : pages[0]; } - renderPage() { + hideTabsFromNonTeamAdmin = (navModel: NavModel, isSignedInUserTeamAdmin: boolean) => { + if (!isSignedInUserTeamAdmin && navModel.main && navModel.main.children) { + navModel.main.children = navModel.main.children.filter(navItem => navItem.text === 'Members'); + } + + return navModel; + }; + + renderPage(isSignedInUserTeamAdmin: boolean) { const { isSyncEnabled } = this.state; const currentPage = this.getCurrentPage(); @@ -70,21 +82,22 @@ export class TeamPages extends PureComponent { return ; case PageTypes.Settings: - return ; + return isSignedInUserTeamAdmin && ; case PageTypes.GroupSync: - return isSyncEnabled && ; + return isSignedInUserTeamAdmin && isSyncEnabled && ; } return null; } render() { - const { team, navModel } = this.props; + const { team, navModel, members, editorsCanAdmin, signedInUser } = this.props; + const isTeamAdmin = isSignedInUserTeamAdmin({ members, editorsCanAdmin, signedInUser }); return ( - + - {team && Object.keys(team).length !== 0 && this.renderPage()} + {team && Object.keys(team).length !== 0 && this.renderPage(isTeamAdmin)} ); @@ -101,6 +114,9 @@ function mapStateToProps(state) { teamId: teamId, pageName: pageName, team: getTeam(state.team, teamId), + members: getTeamMembers(state.team), + editorsCanAdmin: config.editorsCanAdmin, // this makes the feature toggle mockable/controllable from tests, + signedInUser: contextSrv.user, // this makes the feature toggle mockable/controllable from tests, }; } diff --git a/public/app/features/teams/__snapshots__/TeamPages.test.tsx.snap b/public/app/features/teams/__snapshots__/TeamPages.test.tsx.snap index 70f37cea4c5..6fdf7d063ba 100644 --- a/public/app/features/teams/__snapshots__/TeamPages.test.tsx.snap +++ b/public/app/features/teams/__snapshots__/TeamPages.test.tsx.snap @@ -47,3 +47,25 @@ exports[`Render should render settings and preferences page 1`] = ` `; + +exports[`Render when feature toggle editorsCanAdmin is turned on should not render settings page if user is team member 1`] = ` + + + +`; + +exports[`Render when feature toggle editorsCanAdmin is turned on should render settings page if user is team admin 1`] = ` + + + + + +`; diff --git a/public/app/features/teams/state/selectors.test.ts b/public/app/features/teams/state/selectors.test.ts index 1721437c929..5d9981e0403 100644 --- a/public/app/features/teams/state/selectors.test.ts +++ b/public/app/features/teams/state/selectors.test.ts @@ -1,6 +1,7 @@ -import { getTeam, getTeamMembers, getTeams } from './selectors'; +import { getTeam, getTeamMembers, getTeams, isSignedInUserTeamAdmin, Config } from './selectors'; import { getMockTeam, getMockTeamMembers, getMultipleMockTeams } from '../__mocks__/teamMocks'; -import { Team, TeamGroup, TeamsState, TeamState } from '../../../types'; +import { Team, TeamGroup, TeamsState, TeamState, OrgRole } from '../../../types'; +import { User } from 'app/core/services/context_srv'; describe('Teams selectors', () => { describe('Get teams', () => { @@ -55,3 +56,94 @@ describe('Team selectors', () => { }); }); }); + +const signedInUserId = 1; + +const setup = (configOverrides?: Partial) => { + const defaultConfig: Config = { + editorsCanAdmin: false, + members: getMockTeamMembers(5, 5), + signedInUser: { + id: signedInUserId, + isGrafanaAdmin: false, + orgRole: OrgRole.Viewer, + } as User, + }; + + return { ...defaultConfig, ...configOverrides }; +}; + +describe('isSignedInUserTeamAdmin', () => { + describe('when feature toggle editorsCanAdmin is turned off', () => { + it('should return true', () => { + const config = setup(); + + const result = isSignedInUserTeamAdmin(config); + + expect(result).toBe(true); + }); + }); + + describe('when feature toggle editorsCanAdmin is turned on', () => { + it('should return true if signed in user is grafanaAdmin', () => { + const config = setup({ + editorsCanAdmin: true, + signedInUser: { + id: signedInUserId, + isGrafanaAdmin: true, + orgRole: OrgRole.Viewer, + } as User, + }); + + const result = isSignedInUserTeamAdmin(config); + + expect(result).toBe(true); + }); + + it('should return true if signed in user is org admin', () => { + const config = setup({ + editorsCanAdmin: true, + signedInUser: { + id: signedInUserId, + isGrafanaAdmin: false, + orgRole: OrgRole.Admin, + } as User, + }); + + const result = isSignedInUserTeamAdmin(config); + + expect(result).toBe(true); + }); + + it('should return true if signed in user is team admin', () => { + const config = setup({ + members: getMockTeamMembers(5, signedInUserId), + editorsCanAdmin: true, + signedInUser: { + id: signedInUserId, + isGrafanaAdmin: false, + orgRole: OrgRole.Viewer, + } as User, + }); + + const result = isSignedInUserTeamAdmin(config); + + expect(result).toBe(true); + }); + + it('should return false if signed in user is not grafanaAdmin, org admin or team admin', () => { + const config = setup({ + editorsCanAdmin: true, + signedInUser: { + id: signedInUserId, + isGrafanaAdmin: false, + orgRole: OrgRole.Viewer, + } as User, + }); + + const result = isSignedInUserTeamAdmin(config); + + expect(result).toBe(false); + }); + }); +}); diff --git a/public/app/features/teams/state/selectors.ts b/public/app/features/teams/state/selectors.ts index 9201993bf0d..d8b8220bb44 100644 --- a/public/app/features/teams/state/selectors.ts +++ b/public/app/features/teams/state/selectors.ts @@ -1,4 +1,5 @@ -import { Team, TeamsState, TeamState } from 'app/types'; +import { Team, TeamsState, TeamState, TeamMember, OrgRole, TeamPermissionLevel } from 'app/types'; +import { User } from 'app/core/services/context_srv'; export const getSearchQuery = (state: TeamsState) => state.searchQuery; export const getSearchMemberQuery = (state: TeamState) => state.searchMemberQuery; @@ -28,3 +29,18 @@ export const getTeamMembers = (state: TeamState) => { return regex.test(member.login) || regex.test(member.email); }); }; + +export interface Config { + members: TeamMember[]; + editorsCanAdmin: boolean; + signedInUser: User; +} + +export const isSignedInUserTeamAdmin = (config: Config): boolean => { + const userInMembers = config.members.find(m => m.userId === config.signedInUser.id); + const isAdmin = config.signedInUser.isGrafanaAdmin || config.signedInUser.orgRole === OrgRole.Admin; + const userIsTeamAdmin = userInMembers && userInMembers.permission === TeamPermissionLevel.Admin; + const isSignedInUserTeamAdmin = isAdmin || userIsTeamAdmin; + + return isSignedInUserTeamAdmin || !config.editorsCanAdmin; +};