Sfoglia il codice sorgente

teams: hide tabs settings and groupsync for non team admins

Hugo Häggmark 6 anni fa
parent
commit
13ed10495a

+ 0 - 82
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);
-      });
-    });
-  });
 });

+ 7 - 15
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<Props, State> {
     );
   }
 
-  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 (
       <div>
         <div className="page-action-bar">
@@ -100,7 +92,7 @@ export class TeamMembers extends PureComponent<Props, State> {
           <button
             className="btn btn-primary pull-right"
             onClick={this.onToggleAdding}
-            disabled={isAdding || !this.isSignedInUserTeamAdmin()}
+            disabled={isAdding || !isTeamAdmin}
           >
             Add member
           </button>
@@ -145,7 +137,7 @@ export class TeamMembers extends PureComponent<Props, State> {
                     member={member}
                     syncEnabled={syncEnabled}
                     editorsCanAdmin={editorsCanAdmin}
-                    signedInUserIsTeamAdmin={this.isSignedInUserTeamAdmin()}
+                    signedInUserIsTeamAdmin={isTeamAdmin}
                   />
                 ))}
             </tbody>

+ 51 - 1
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();
+    });
+  });
 });

+ 24 - 8
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<Props, State> {
     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<Props, State> {
         return <TeamMembers syncEnabled={isSyncEnabled} />;
 
       case PageTypes.Settings:
-        return <TeamSettings />;
+        return isSignedInUserTeamAdmin && <TeamSettings />;
       case PageTypes.GroupSync:
-        return isSyncEnabled && <TeamGroupSync />;
+        return isSignedInUserTeamAdmin && isSyncEnabled && <TeamGroupSync />;
     }
 
     return null;
   }
 
   render() {
-    const { team, navModel } = this.props;
+    const { team, navModel, members, editorsCanAdmin, signedInUser } = this.props;
+    const isTeamAdmin = isSignedInUserTeamAdmin({ members, editorsCanAdmin, signedInUser });
 
     return (
-      <Page navModel={navModel}>
+      <Page navModel={this.hideTabsFromNonTeamAdmin(navModel, isTeamAdmin)}>
         <Page.Contents isLoading={this.state.isLoading}>
-          {team && Object.keys(team).length !== 0 && this.renderPage()}
+          {team && Object.keys(team).length !== 0 && this.renderPage(isTeamAdmin)}
         </Page.Contents>
       </Page>
     );
@@ -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,
   };
 }
 

+ 22 - 0
public/app/features/teams/__snapshots__/TeamPages.test.tsx.snap

@@ -47,3 +47,25 @@ exports[`Render should render settings and preferences page 1`] = `
   </PageContents>
 </Page>
 `;
+
+exports[`Render when feature toggle editorsCanAdmin is turned on should not render settings page if user is team member 1`] = `
+<Page
+  navModel={Object {}}
+>
+  <PageContents
+    isLoading={true}
+  />
+</Page>
+`;
+
+exports[`Render when feature toggle editorsCanAdmin is turned on should render settings page if user is team admin 1`] = `
+<Page
+  navModel={Object {}}
+>
+  <PageContents
+    isLoading={true}
+  >
+    <Connect(TeamSettings) />
+  </PageContents>
+</Page>
+`;

+ 94 - 2
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<Config>) => {
+  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);
+    });
+  });
+});

+ 17 - 1
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;
+};