mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
refactor: splitted TeamMembers to TeamMemberRow
This commit is contained in:
parent
6a63725df0
commit
178d637b4e
82
public/app/features/teams/TeamMemberRow.test.tsx
Normal file
82
public/app/features/teams/TeamMemberRow.test.tsx
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { shallow } from 'enzyme';
|
||||||
|
import { TeamMember, TeamPermissionLevel } from '../../types';
|
||||||
|
import { getMockTeamMember } from './__mocks__/teamMocks';
|
||||||
|
import { TeamMemberRow, Props } from './TeamMemberRow';
|
||||||
|
import { SelectOptionItem } from '@grafana/ui';
|
||||||
|
|
||||||
|
const setup = (propOverrides?: object) => {
|
||||||
|
const props: Props = {
|
||||||
|
member: getMockTeamMember(),
|
||||||
|
syncEnabled: false,
|
||||||
|
editorsCanAdmin: false,
|
||||||
|
signedInUserIsTeamAdmin: false,
|
||||||
|
updateTeamMember: jest.fn(),
|
||||||
|
removeTeamMember: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
Object.assign(props, propOverrides);
|
||||||
|
|
||||||
|
const wrapper = shallow(<TeamMemberRow {...props} />);
|
||||||
|
const instance = wrapper.instance() as TeamMemberRow;
|
||||||
|
|
||||||
|
return {
|
||||||
|
wrapper,
|
||||||
|
instance,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('Render', () => {
|
||||||
|
describe('when feature toggle editorsCanAdmin is turned on', () => {
|
||||||
|
it('should render permissions select if user is team admin', () => {
|
||||||
|
const { wrapper } = setup({ editorsCanAdmin: true, signedInUserIsTeamAdmin: true });
|
||||||
|
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render span and disable buttons if user is team member', () => {
|
||||||
|
const { wrapper } = setup({ editorsCanAdmin: true, signedInUserIsTeamAdmin: false });
|
||||||
|
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when feature toggle editorsCanAdmin is turned off', () => {
|
||||||
|
it('should not render permissions', () => {
|
||||||
|
const { wrapper } = setup({ editorsCanAdmin: false, signedInUserIsTeamAdmin: true });
|
||||||
|
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Functions', () => {
|
||||||
|
describe('on remove member', () => {
|
||||||
|
const member = getMockTeamMember();
|
||||||
|
const { instance } = setup({ member });
|
||||||
|
|
||||||
|
instance.onRemoveMember(member);
|
||||||
|
|
||||||
|
expect(instance.props.removeTeamMember).toHaveBeenCalledWith(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('on update permision for user in team', () => {
|
||||||
|
const member: TeamMember = {
|
||||||
|
userId: 3,
|
||||||
|
teamId: 2,
|
||||||
|
avatarUrl: '',
|
||||||
|
email: 'user@user.org',
|
||||||
|
labels: [],
|
||||||
|
login: 'member',
|
||||||
|
permission: TeamPermissionLevel.Member,
|
||||||
|
};
|
||||||
|
const { instance } = setup({ member });
|
||||||
|
const permission = TeamPermissionLevel.Admin;
|
||||||
|
const item: SelectOptionItem = { value: permission };
|
||||||
|
const expectedTeamMemeber = { ...member, permission };
|
||||||
|
|
||||||
|
instance.onPermissionChange(item, member);
|
||||||
|
|
||||||
|
expect(instance.props.updateTeamMember).toHaveBeenCalledWith(expectedTeamMemeber);
|
||||||
|
});
|
||||||
|
});
|
106
public/app/features/teams/TeamMemberRow.tsx
Normal file
106
public/app/features/teams/TeamMemberRow.tsx
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
import React, { PureComponent } from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { DeleteButton, Select, SelectOptionItem } from '@grafana/ui';
|
||||||
|
|
||||||
|
import { TeamMember, teamsPermissionLevels } from 'app/types';
|
||||||
|
import { WithFeatureToggle } from 'app/core/components/WithFeatureToggle';
|
||||||
|
import { updateTeamMember, removeTeamMember } from './state/actions';
|
||||||
|
import { TagBadge } from 'app/core/components/TagFilter/TagBadge';
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
member: TeamMember;
|
||||||
|
syncEnabled: boolean;
|
||||||
|
editorsCanAdmin: boolean;
|
||||||
|
signedInUserIsTeamAdmin: boolean;
|
||||||
|
removeTeamMember?: typeof removeTeamMember;
|
||||||
|
updateTeamMember?: typeof updateTeamMember;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TeamMemberRow extends PureComponent<Props> {
|
||||||
|
constructor(props: Props) {
|
||||||
|
super(props);
|
||||||
|
this.renderLabels = this.renderLabels.bind(this);
|
||||||
|
this.renderPermissions = this.renderPermissions.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
onRemoveMember(member: TeamMember) {
|
||||||
|
this.props.removeTeamMember(member.userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
onPermissionChange = (item: SelectOptionItem, member: TeamMember) => {
|
||||||
|
const permission = item.value;
|
||||||
|
const updatedTeamMember = { ...member, permission };
|
||||||
|
|
||||||
|
this.props.updateTeamMember(updatedTeamMember);
|
||||||
|
};
|
||||||
|
|
||||||
|
renderPermissions(member: TeamMember) {
|
||||||
|
const { editorsCanAdmin, signedInUserIsTeamAdmin } = this.props;
|
||||||
|
const value = teamsPermissionLevels.find(dp => dp.value === member.permission);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<WithFeatureToggle featureToggle={editorsCanAdmin}>
|
||||||
|
<td>
|
||||||
|
<div className="gf-form">
|
||||||
|
{signedInUserIsTeamAdmin && (
|
||||||
|
<Select
|
||||||
|
isSearchable={false}
|
||||||
|
options={teamsPermissionLevels}
|
||||||
|
onChange={item => this.onPermissionChange(item, member)}
|
||||||
|
className="gf-form-select-box__control--menu-right"
|
||||||
|
value={value}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{!signedInUserIsTeamAdmin && <span>{value.label}</span>}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</WithFeatureToggle>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderLabels(labels: string[]) {
|
||||||
|
if (!labels) {
|
||||||
|
return <td />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<td>
|
||||||
|
{labels.map(label => (
|
||||||
|
<TagBadge key={label} label={label} removeIcon={false} count={0} onClick={() => {}} />
|
||||||
|
))}
|
||||||
|
</td>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { member, syncEnabled, signedInUserIsTeamAdmin } = this.props;
|
||||||
|
return (
|
||||||
|
<tr key={member.userId}>
|
||||||
|
<td className="width-4 text-center">
|
||||||
|
<img className="filter-table__avatar" src={member.avatarUrl} />
|
||||||
|
</td>
|
||||||
|
<td>{member.login}</td>
|
||||||
|
<td>{member.email}</td>
|
||||||
|
{this.renderPermissions(member)}
|
||||||
|
{syncEnabled && this.renderLabels(member.labels)}
|
||||||
|
<td className="text-right">
|
||||||
|
<DeleteButton onConfirm={() => this.onRemoveMember(member)} disabled={!signedInUserIsTeamAdmin} />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapStateToProps(state) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapDispatchToProps = {
|
||||||
|
removeTeamMember,
|
||||||
|
updateTeamMember,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(TeamMemberRow);
|
@ -1,45 +1,29 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { shallow } from 'enzyme';
|
import { shallow } from 'enzyme';
|
||||||
import { TeamMembers, Props, State } from './TeamMembers';
|
import { TeamMembers, Props, State } from './TeamMembers';
|
||||||
import { TeamMember, TeamPermissionLevel } from '../../types';
|
import { TeamMember, OrgRole } from '../../types';
|
||||||
import { getMockTeamMember, getMockTeamMembers } from './__mocks__/teamMocks';
|
import { getMockTeamMembers } from './__mocks__/teamMocks';
|
||||||
import { SelectOptionItem } from '@grafana/ui';
|
import { User } from 'app/core/services/context_srv';
|
||||||
import { contextSrv } from 'app/core/services/context_srv';
|
|
||||||
|
|
||||||
const signedInUserId = 1;
|
const signedInUserId = 1;
|
||||||
const originalContextSrv = contextSrv;
|
|
||||||
|
|
||||||
jest.mock('app/core/services/context_srv', () => ({
|
const setup = (propOverrides?: object) => {
|
||||||
contextSrv: {
|
|
||||||
isGrafanaAdmin: false,
|
|
||||||
hasRole: role => false,
|
|
||||||
user: { id: signedInUserId },
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
interface SetupProps {
|
|
||||||
propOverrides?: object;
|
|
||||||
isGrafanaAdmin?: boolean;
|
|
||||||
isOrgAdmin?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const setup = (setupProps: SetupProps) => {
|
|
||||||
const props: Props = {
|
const props: Props = {
|
||||||
members: [] as TeamMember[],
|
members: [] as TeamMember[],
|
||||||
searchMemberQuery: '',
|
searchMemberQuery: '',
|
||||||
setSearchMemberQuery: jest.fn(),
|
setSearchMemberQuery: jest.fn(),
|
||||||
loadTeamMembers: jest.fn(),
|
loadTeamMembers: jest.fn(),
|
||||||
addTeamMember: jest.fn(),
|
addTeamMember: jest.fn(),
|
||||||
removeTeamMember: jest.fn(),
|
|
||||||
updateTeamMember: jest.fn(),
|
|
||||||
syncEnabled: false,
|
syncEnabled: false,
|
||||||
editorsCanAdmin: false,
|
editorsCanAdmin: false,
|
||||||
|
signedInUser: {
|
||||||
|
id: signedInUserId,
|
||||||
|
isGrafanaAdmin: false,
|
||||||
|
orgRole: OrgRole.Viewer,
|
||||||
|
} as User,
|
||||||
};
|
};
|
||||||
|
|
||||||
contextSrv.isGrafanaAdmin = setupProps.isGrafanaAdmin || false;
|
Object.assign(props, propOverrides);
|
||||||
contextSrv.hasRole = role => setupProps.isOrgAdmin || false;
|
|
||||||
|
|
||||||
Object.assign(props, setupProps.propOverrides);
|
|
||||||
|
|
||||||
const wrapper = shallow(<TeamMembers {...props} />);
|
const wrapper = shallow(<TeamMembers {...props} />);
|
||||||
const instance = wrapper.instance() as TeamMembers;
|
const instance = wrapper.instance() as TeamMembers;
|
||||||
@ -51,11 +35,6 @@ const setup = (setupProps: SetupProps) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
describe('Render', () => {
|
describe('Render', () => {
|
||||||
beforeEach(() => {
|
|
||||||
contextSrv.isGrafanaAdmin = originalContextSrv.isGrafanaAdmin;
|
|
||||||
contextSrv.hasRole = originalContextSrv.hasRole;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should render component', () => {
|
it('should render component', () => {
|
||||||
const { wrapper } = setup({});
|
const { wrapper } = setup({});
|
||||||
|
|
||||||
@ -63,74 +42,16 @@ describe('Render', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should render team members', () => {
|
it('should render team members', () => {
|
||||||
const { wrapper } = setup({
|
const { wrapper } = setup({ members: getMockTeamMembers(5, 5) });
|
||||||
propOverrides: {
|
|
||||||
members: getMockTeamMembers(5, 5),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(wrapper).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should render team members when sync enabled', () => {
|
it('should render team members when sync enabled', () => {
|
||||||
const { wrapper } = setup({
|
const { wrapper } = setup({ members: getMockTeamMembers(5, 5), syncEnabled: true });
|
||||||
propOverrides: {
|
|
||||||
members: getMockTeamMembers(5, 5),
|
|
||||||
syncEnabled: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(wrapper).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('when feature toggle editorsCanAdmin is turned on', () => {
|
|
||||||
it('should render permissions select if user is Grafana Admin', () => {
|
|
||||||
const members = getMockTeamMembers(5, 5);
|
|
||||||
const { wrapper } = setup({
|
|
||||||
propOverrides: { members, editorsCanAdmin: true },
|
|
||||||
isGrafanaAdmin: true,
|
|
||||||
isOrgAdmin: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(wrapper).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should render permissions select if user is Org Admin', () => {
|
|
||||||
const members = getMockTeamMembers(5, 5);
|
|
||||||
const { wrapper } = setup({
|
|
||||||
propOverrides: { members, editorsCanAdmin: true },
|
|
||||||
isGrafanaAdmin: false,
|
|
||||||
isOrgAdmin: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(wrapper).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should render permissions select if user is team admin', () => {
|
|
||||||
const members = getMockTeamMembers(5, signedInUserId);
|
|
||||||
const { wrapper } = setup({
|
|
||||||
propOverrides: { members, editorsCanAdmin: true },
|
|
||||||
isGrafanaAdmin: false,
|
|
||||||
isOrgAdmin: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(wrapper).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should render span and disable buttons if user is team member', () => {
|
|
||||||
const members = getMockTeamMembers(5, 5);
|
|
||||||
const { wrapper } = setup({
|
|
||||||
propOverrides: {
|
|
||||||
members,
|
|
||||||
editorsCanAdmin: true,
|
|
||||||
},
|
|
||||||
isGrafanaAdmin: false,
|
|
||||||
isOrgAdmin: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(wrapper).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Functions', () => {
|
describe('Functions', () => {
|
||||||
@ -144,15 +65,6 @@ describe('Functions', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('on remove member', () => {
|
|
||||||
const { instance } = setup({});
|
|
||||||
const mockTeamMember = getMockTeamMember();
|
|
||||||
|
|
||||||
instance.onRemoveMember(mockTeamMember);
|
|
||||||
|
|
||||||
expect(instance.props.removeTeamMember).toHaveBeenCalledWith(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('on add user to team', () => {
|
describe('on add user to team', () => {
|
||||||
const { wrapper, instance } = setup({});
|
const { wrapper, instance } = setup({});
|
||||||
const state = wrapper.state() as State;
|
const state = wrapper.state() as State;
|
||||||
@ -169,23 +81,85 @@ describe('Functions', () => {
|
|||||||
expect(instance.props.addTeamMember).toHaveBeenCalledWith(1);
|
expect(instance.props.addTeamMember).toHaveBeenCalledWith(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('on update permision for user in team', () => {
|
describe('isSignedInUserTeamAdmin', () => {
|
||||||
const { instance } = setup({});
|
describe('when feature toggle editorsCanAdmin is turned off', () => {
|
||||||
const permission = TeamPermissionLevel.Admin;
|
it('should return true', () => {
|
||||||
const item: SelectOptionItem = { value: permission };
|
const { instance } = setup({ editorsCanAdmin: false });
|
||||||
const member: TeamMember = {
|
|
||||||
userId: 3,
|
|
||||||
teamId: 2,
|
|
||||||
avatarUrl: '',
|
|
||||||
email: 'user@user.org',
|
|
||||||
labels: [],
|
|
||||||
login: 'member',
|
|
||||||
permission: TeamPermissionLevel.Member,
|
|
||||||
};
|
|
||||||
const expectedTeamMemeber = { ...member, permission };
|
|
||||||
|
|
||||||
instance.onPermissionChange(item, member);
|
const result = instance.isSignedInUserTeamAdmin();
|
||||||
|
|
||||||
expect(instance.props.updateTeamMember).toHaveBeenCalledWith(expectedTeamMemeber);
|
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);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -2,32 +2,25 @@ import React, { PureComponent } from 'react';
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import SlideDown from 'app/core/components/Animations/SlideDown';
|
import SlideDown from 'app/core/components/Animations/SlideDown';
|
||||||
import { UserPicker } from 'app/core/components/Select/UserPicker';
|
import { UserPicker } from 'app/core/components/Select/UserPicker';
|
||||||
import { DeleteButton, Select, SelectOptionItem } from '@grafana/ui';
|
|
||||||
import { TagBadge } from 'app/core/components/TagFilter/TagBadge';
|
import { TagBadge } from 'app/core/components/TagFilter/TagBadge';
|
||||||
import { TeamMember, User, teamsPermissionLevels, TeamPermissionLevel, OrgRole } from 'app/types';
|
import { TeamMember, User, TeamPermissionLevel, OrgRole } from 'app/types';
|
||||||
import {
|
import { loadTeamMembers, addTeamMember, setSearchMemberQuery } from './state/actions';
|
||||||
loadTeamMembers,
|
|
||||||
addTeamMember,
|
|
||||||
removeTeamMember,
|
|
||||||
setSearchMemberQuery,
|
|
||||||
updateTeamMember,
|
|
||||||
} from './state/actions';
|
|
||||||
import { getSearchMemberQuery, getTeamMembers } from './state/selectors';
|
import { getSearchMemberQuery, getTeamMembers } from './state/selectors';
|
||||||
import { FilterInput } from 'app/core/components/FilterInput/FilterInput';
|
import { FilterInput } from 'app/core/components/FilterInput/FilterInput';
|
||||||
import { WithFeatureToggle } from 'app/core/components/WithFeatureToggle';
|
import { WithFeatureToggle } from 'app/core/components/WithFeatureToggle';
|
||||||
import { config } from 'app/core/config';
|
import { config } from 'app/core/config';
|
||||||
import { contextSrv } from 'app/core/services/context_srv';
|
import { contextSrv, User as SignedInUser } from 'app/core/services/context_srv';
|
||||||
|
import TeamMemberRow from './TeamMemberRow';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
members: TeamMember[];
|
members: TeamMember[];
|
||||||
searchMemberQuery: string;
|
searchMemberQuery: string;
|
||||||
loadTeamMembers: typeof loadTeamMembers;
|
loadTeamMembers: typeof loadTeamMembers;
|
||||||
addTeamMember: typeof addTeamMember;
|
addTeamMember: typeof addTeamMember;
|
||||||
removeTeamMember: typeof removeTeamMember;
|
|
||||||
setSearchMemberQuery: typeof setSearchMemberQuery;
|
setSearchMemberQuery: typeof setSearchMemberQuery;
|
||||||
updateTeamMember: typeof updateTeamMember;
|
|
||||||
syncEnabled: boolean;
|
syncEnabled: boolean;
|
||||||
editorsCanAdmin?: boolean;
|
editorsCanAdmin?: boolean;
|
||||||
|
signedInUser?: SignedInUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface State {
|
export interface State {
|
||||||
@ -39,7 +32,6 @@ export class TeamMembers extends PureComponent<Props, State> {
|
|||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = { isAdding: false, newTeamMember: null };
|
this.state = { isAdding: false, newTeamMember: null };
|
||||||
this.renderPermissions = this.renderPermissions.bind(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
@ -50,10 +42,6 @@ export class TeamMembers extends PureComponent<Props, State> {
|
|||||||
this.props.setSearchMemberQuery(value);
|
this.props.setSearchMemberQuery(value);
|
||||||
};
|
};
|
||||||
|
|
||||||
onRemoveMember(member: TeamMember) {
|
|
||||||
this.props.removeTeamMember(member.userId);
|
|
||||||
}
|
|
||||||
|
|
||||||
onToggleAdding = () => {
|
onToggleAdding = () => {
|
||||||
this.setState({ isAdding: !this.state.isAdding });
|
this.setState({ isAdding: !this.state.isAdding });
|
||||||
};
|
};
|
||||||
@ -81,65 +69,16 @@ export class TeamMembers extends PureComponent<Props, State> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
onPermissionChange = (item: SelectOptionItem, member: TeamMember) => {
|
isSignedInUserTeamAdmin = (): boolean => {
|
||||||
const permission = item.value;
|
const { members, editorsCanAdmin, signedInUser } = this.props;
|
||||||
const updatedTeamMember = { ...member, permission };
|
const userInMembers = members.find(m => m.userId === signedInUser.id);
|
||||||
|
const isAdmin = signedInUser.isGrafanaAdmin || signedInUser.orgRole === OrgRole.Admin;
|
||||||
this.props.updateTeamMember(updatedTeamMember);
|
|
||||||
};
|
|
||||||
|
|
||||||
private isSignedInUserTeamAdmin = () => {
|
|
||||||
const { members, editorsCanAdmin } = this.props;
|
|
||||||
const userInMembers = members.find(m => m.userId === contextSrv.user.id);
|
|
||||||
const isAdmin = contextSrv.isGrafanaAdmin || contextSrv.hasRole(OrgRole.Admin);
|
|
||||||
const userIsTeamAdmin = userInMembers && userInMembers.permission === TeamPermissionLevel.Admin;
|
const userIsTeamAdmin = userInMembers && userInMembers.permission === TeamPermissionLevel.Admin;
|
||||||
const isSignedInUserTeamAdmin = isAdmin || userIsTeamAdmin;
|
const isSignedInUserTeamAdmin = isAdmin || userIsTeamAdmin;
|
||||||
|
|
||||||
return isSignedInUserTeamAdmin || !editorsCanAdmin;
|
return isSignedInUserTeamAdmin || !editorsCanAdmin;
|
||||||
};
|
};
|
||||||
|
|
||||||
renderPermissions(member: TeamMember) {
|
|
||||||
const { editorsCanAdmin } = this.props;
|
|
||||||
const isUserTeamAdmin = this.isSignedInUserTeamAdmin();
|
|
||||||
const value = teamsPermissionLevels.find(dp => dp.value === member.permission);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<WithFeatureToggle featureToggle={editorsCanAdmin}>
|
|
||||||
<td>
|
|
||||||
<div className="gf-form">
|
|
||||||
{isUserTeamAdmin && (
|
|
||||||
<Select
|
|
||||||
isSearchable={false}
|
|
||||||
options={teamsPermissionLevels}
|
|
||||||
onChange={item => this.onPermissionChange(item, member)}
|
|
||||||
className="gf-form-select-box__control--menu-right"
|
|
||||||
value={value}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{!isUserTeamAdmin && <span>{value.label}</span>}
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</WithFeatureToggle>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderMember(member: TeamMember, syncEnabled: boolean) {
|
|
||||||
return (
|
|
||||||
<tr key={member.userId}>
|
|
||||||
<td className="width-4 text-center">
|
|
||||||
<img className="filter-table__avatar" src={member.avatarUrl} />
|
|
||||||
</td>
|
|
||||||
<td>{member.login}</td>
|
|
||||||
<td>{member.email}</td>
|
|
||||||
{this.renderPermissions(member)}
|
|
||||||
{syncEnabled && this.renderLabels(member.labels)}
|
|
||||||
<td className="text-right">
|
|
||||||
<DeleteButton onConfirm={() => this.onRemoveMember(member)} disabled={!this.isSignedInUserTeamAdmin()} />
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { isAdding } = this.state;
|
const { isAdding } = this.state;
|
||||||
const { searchMemberQuery, members, syncEnabled, editorsCanAdmin } = this.props;
|
const { searchMemberQuery, members, syncEnabled, editorsCanAdmin } = this.props;
|
||||||
@ -198,7 +137,18 @@ export class TeamMembers extends PureComponent<Props, State> {
|
|||||||
<th style={{ width: '1%' }} />
|
<th style={{ width: '1%' }} />
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>{members && members.map(member => this.renderMember(member, syncEnabled))}</tbody>
|
<tbody>
|
||||||
|
{members &&
|
||||||
|
members.map(member => (
|
||||||
|
<TeamMemberRow
|
||||||
|
key={member.userId}
|
||||||
|
member={member}
|
||||||
|
syncEnabled={syncEnabled}
|
||||||
|
editorsCanAdmin={editorsCanAdmin}
|
||||||
|
signedInUserIsTeamAdmin={this.isSignedInUserTeamAdmin()}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -211,15 +161,14 @@ function mapStateToProps(state) {
|
|||||||
members: getTeamMembers(state.team),
|
members: getTeamMembers(state.team),
|
||||||
searchMemberQuery: getSearchMemberQuery(state.team),
|
searchMemberQuery: getSearchMemberQuery(state.team),
|
||||||
editorsCanAdmin: config.editorsCanAdmin, // this makes the feature toggle mockable/controllable from tests,
|
editorsCanAdmin: config.editorsCanAdmin, // this makes the feature toggle mockable/controllable from tests,
|
||||||
|
signedInUser: contextSrv.user, // this makes the feature toggle mockable/controllable from tests,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapDispatchToProps = {
|
const mapDispatchToProps = {
|
||||||
loadTeamMembers,
|
loadTeamMembers,
|
||||||
addTeamMember,
|
addTeamMember,
|
||||||
removeTeamMember,
|
|
||||||
setSearchMemberQuery,
|
setSearchMemberQuery,
|
||||||
updateTeamMember,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connect(
|
export default connect(
|
||||||
|
@ -0,0 +1,191 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`Render when feature toggle editorsCanAdmin is turned off should not render permissions 1`] = `
|
||||||
|
<tr
|
||||||
|
key="1"
|
||||||
|
>
|
||||||
|
<td
|
||||||
|
className="width-4 text-center"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
className="filter-table__avatar"
|
||||||
|
src="some/url/"
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
testUser
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
test@test.com
|
||||||
|
</td>
|
||||||
|
<Component
|
||||||
|
featureToggle={false}
|
||||||
|
>
|
||||||
|
<td>
|
||||||
|
<div
|
||||||
|
className="gf-form"
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
autoFocus={false}
|
||||||
|
backspaceRemovesValue={true}
|
||||||
|
className="gf-form-select-box__control--menu-right"
|
||||||
|
isClearable={false}
|
||||||
|
isDisabled={false}
|
||||||
|
isLoading={false}
|
||||||
|
isMulti={false}
|
||||||
|
isSearchable={false}
|
||||||
|
maxMenuHeight={300}
|
||||||
|
onChange={[Function]}
|
||||||
|
openMenuOnFocus={false}
|
||||||
|
options={
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"description": "Is team member",
|
||||||
|
"label": "Member",
|
||||||
|
"value": 0,
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"description": "Can add/remove permissions, members and delete team.",
|
||||||
|
"label": "Admin",
|
||||||
|
"value": 4,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
value={
|
||||||
|
Object {
|
||||||
|
"description": "Is team member",
|
||||||
|
"label": "Member",
|
||||||
|
"value": 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
width={null}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</Component>
|
||||||
|
<td
|
||||||
|
className="text-right"
|
||||||
|
>
|
||||||
|
<DeleteButton
|
||||||
|
disabled={false}
|
||||||
|
onConfirm={[Function]}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Render when feature toggle editorsCanAdmin is turned on should render permissions select if user is team admin 1`] = `
|
||||||
|
<tr
|
||||||
|
key="1"
|
||||||
|
>
|
||||||
|
<td
|
||||||
|
className="width-4 text-center"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
className="filter-table__avatar"
|
||||||
|
src="some/url/"
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
testUser
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
test@test.com
|
||||||
|
</td>
|
||||||
|
<Component
|
||||||
|
featureToggle={true}
|
||||||
|
>
|
||||||
|
<td>
|
||||||
|
<div
|
||||||
|
className="gf-form"
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
autoFocus={false}
|
||||||
|
backspaceRemovesValue={true}
|
||||||
|
className="gf-form-select-box__control--menu-right"
|
||||||
|
isClearable={false}
|
||||||
|
isDisabled={false}
|
||||||
|
isLoading={false}
|
||||||
|
isMulti={false}
|
||||||
|
isSearchable={false}
|
||||||
|
maxMenuHeight={300}
|
||||||
|
onChange={[Function]}
|
||||||
|
openMenuOnFocus={false}
|
||||||
|
options={
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"description": "Is team member",
|
||||||
|
"label": "Member",
|
||||||
|
"value": 0,
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"description": "Can add/remove permissions, members and delete team.",
|
||||||
|
"label": "Admin",
|
||||||
|
"value": 4,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
value={
|
||||||
|
Object {
|
||||||
|
"description": "Is team member",
|
||||||
|
"label": "Member",
|
||||||
|
"value": 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
width={null}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</Component>
|
||||||
|
<td
|
||||||
|
className="text-right"
|
||||||
|
>
|
||||||
|
<DeleteButton
|
||||||
|
disabled={false}
|
||||||
|
onConfirm={[Function]}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Render when feature toggle editorsCanAdmin is turned on should render span and disable buttons if user is team member 1`] = `
|
||||||
|
<tr
|
||||||
|
key="1"
|
||||||
|
>
|
||||||
|
<td
|
||||||
|
className="width-4 text-center"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
className="filter-table__avatar"
|
||||||
|
src="some/url/"
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
testUser
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
test@test.com
|
||||||
|
</td>
|
||||||
|
<Component
|
||||||
|
featureToggle={true}
|
||||||
|
>
|
||||||
|
<td>
|
||||||
|
<div
|
||||||
|
className="gf-form"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
Member
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</Component>
|
||||||
|
<td
|
||||||
|
className="text-right"
|
||||||
|
>
|
||||||
|
<DeleteButton
|
||||||
|
disabled={true}
|
||||||
|
onConfirm={[Function]}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
`;
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user