mirror of
https://github.com/grafana/grafana.git
synced 2025-01-21 22:13:38 -06:00
teams: hide tabs settings and groupsync for non team admins
This commit is contained in:
parent
d1481cac50
commit
13ed10495a
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -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>
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -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>
|
||||
`;
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -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;
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user