Commit 53c74fa2 by Hugo Häggmark Committed by Leonard Gram

teams: refactor so that you can only delete teams if you are team admin

parent a615b78f
...@@ -79,6 +79,7 @@ type TeamDTO struct { ...@@ -79,6 +79,7 @@ type TeamDTO struct {
Email string `json:"email"` Email string `json:"email"`
AvatarUrl string `json:"avatarUrl"` AvatarUrl string `json:"avatarUrl"`
MemberCount int64 `json:"memberCount"` MemberCount int64 `json:"memberCount"`
Permission PermissionType `json:"permission"`
} }
type SearchTeamQueryResult struct { type SearchTeamQueryResult struct {
......
...@@ -23,6 +23,18 @@ func init() { ...@@ -23,6 +23,18 @@ func init() {
bus.AddHandler("sql", GetTeamMembers) bus.AddHandler("sql", GetTeamMembers)
} }
func getTeamSearchSqlBase() string {
return `SELECT
team.id as id,
team.org_id,
team.name as name,
team.email as email,
(SELECT COUNT(*) from team_member where team_member.team_id = team.id) as member_count,
team_member.permission
FROM team as team
INNER JOIN team_member on team.id = team_member.team_id AND team_member.user_id = ? `
}
func getTeamSelectSqlBase() string { func getTeamSelectSqlBase() string {
return `SELECT return `SELECT
team.id as id, team.id as id,
...@@ -146,10 +158,11 @@ func SearchTeams(query *m.SearchTeamsQuery) error { ...@@ -146,10 +158,11 @@ func SearchTeams(query *m.SearchTeamsQuery) error {
var sql bytes.Buffer var sql bytes.Buffer
params := make([]interface{}, 0) params := make([]interface{}, 0)
sql.WriteString(getTeamSelectSqlBase())
if query.UserIdFilter > 0 { if query.UserIdFilter > 0 {
sql.WriteString(`INNER JOIN team_member on team.id = team_member.team_id AND team_member.user_id = ?`) sql.WriteString(getTeamSearchSqlBase())
params = append(params, query.UserIdFilter) params = append(params, query.UserIdFilter)
} else {
sql.WriteString(getTeamSelectSqlBase())
} }
sql.WriteString(` WHERE team.org_id = ?`) sql.WriteString(` WHERE team.org_id = ?`)
......
...@@ -6,7 +6,7 @@ import { DeleteButton } from '@grafana/ui'; ...@@ -6,7 +6,7 @@ import { DeleteButton } from '@grafana/ui';
import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA'; import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA';
import { NavModel, Team, OrgRole } from 'app/types'; import { NavModel, Team, OrgRole } from 'app/types';
import { loadTeams, deleteTeam, setSearchQuery } from './state/actions'; import { loadTeams, deleteTeam, setSearchQuery } from './state/actions';
import { getSearchQuery, getTeams, getTeamsCount } from './state/selectors'; import { getSearchQuery, getTeams, getTeamsCount, isPermissionTeamAdmin } from './state/selectors';
import { getNavModel } from 'app/core/selectors/navModel'; import { getNavModel } from 'app/core/selectors/navModel';
import { FilterInput } from 'app/core/components/FilterInput/FilterInput'; import { FilterInput } from 'app/core/components/FilterInput/FilterInput';
import { config } from 'app/core/config'; import { config } from 'app/core/config';
...@@ -43,7 +43,10 @@ export class TeamList extends PureComponent<Props, any> { ...@@ -43,7 +43,10 @@ export class TeamList extends PureComponent<Props, any> {
}; };
renderTeam(team: Team) { renderTeam(team: Team) {
const { editorsCanAdmin, signedInUser } = this.props;
const permission = team.permission;
const teamUrl = `org/teams/edit/${team.id}`; const teamUrl = `org/teams/edit/${team.id}`;
const canDelete = isPermissionTeamAdmin({ permission, editorsCanAdmin, signedInUser });
return ( return (
<tr key={team.id}> <tr key={team.id}>
...@@ -62,7 +65,7 @@ export class TeamList extends PureComponent<Props, any> { ...@@ -62,7 +65,7 @@ export class TeamList extends PureComponent<Props, any> {
<a href={teamUrl}>{team.memberCount}</a> <a href={teamUrl}>{team.memberCount}</a>
</td> </td>
<td className="text-right"> <td className="text-right">
<DeleteButton onConfirm={() => this.deleteTeam(team)} /> <DeleteButton onConfirm={() => this.deleteTeam(team)} disabled={!canDelete} />
</td> </td>
</tr> </tr>
); );
......
...@@ -9,6 +9,7 @@ export const getMultipleMockTeams = (numberOfTeams: number): Team[] => { ...@@ -9,6 +9,7 @@ export const getMultipleMockTeams = (numberOfTeams: number): Team[] => {
avatarUrl: 'some/url/', avatarUrl: 'some/url/',
email: `test-${i}@test.com`, email: `test-${i}@test.com`,
memberCount: i, memberCount: i,
permission: TeamPermissionLevel.Member,
}); });
} }
...@@ -22,6 +23,7 @@ export const getMockTeam = (): Team => { ...@@ -22,6 +23,7 @@ export const getMockTeam = (): Team => {
avatarUrl: 'some/url/', avatarUrl: 'some/url/',
email: 'test@test.com', email: 'test@test.com',
memberCount: 1, memberCount: 1,
permission: TeamPermissionLevel.Member,
}; };
}; };
......
...@@ -133,6 +133,7 @@ exports[`Render should render teams table 1`] = ` ...@@ -133,6 +133,7 @@ exports[`Render should render teams table 1`] = `
className="text-right" className="text-right"
> >
<DeleteButton <DeleteButton
disabled={false}
onConfirm={[Function]} onConfirm={[Function]}
/> />
</td> </td>
...@@ -183,6 +184,7 @@ exports[`Render should render teams table 1`] = ` ...@@ -183,6 +184,7 @@ exports[`Render should render teams table 1`] = `
className="text-right" className="text-right"
> >
<DeleteButton <DeleteButton
disabled={false}
onConfirm={[Function]} onConfirm={[Function]}
/> />
</td> </td>
...@@ -233,6 +235,7 @@ exports[`Render should render teams table 1`] = ` ...@@ -233,6 +235,7 @@ exports[`Render should render teams table 1`] = `
className="text-right" className="text-right"
> >
<DeleteButton <DeleteButton
disabled={false}
onConfirm={[Function]} onConfirm={[Function]}
/> />
</td> </td>
...@@ -283,6 +286,7 @@ exports[`Render should render teams table 1`] = ` ...@@ -283,6 +286,7 @@ exports[`Render should render teams table 1`] = `
className="text-right" className="text-right"
> >
<DeleteButton <DeleteButton
disabled={false}
onConfirm={[Function]} onConfirm={[Function]}
/> />
</td> </td>
...@@ -333,6 +337,7 @@ exports[`Render should render teams table 1`] = ` ...@@ -333,6 +337,7 @@ exports[`Render should render teams table 1`] = `
className="text-right" className="text-right"
> >
<DeleteButton <DeleteButton
disabled={false}
onConfirm={[Function]} onConfirm={[Function]}
/> />
</td> </td>
...@@ -458,6 +463,7 @@ exports[`Render when feature toggle editorsCanAdmin is turned on and signedin us ...@@ -458,6 +463,7 @@ exports[`Render when feature toggle editorsCanAdmin is turned on and signedin us
className="text-right" className="text-right"
> >
<DeleteButton <DeleteButton
disabled={true}
onConfirm={[Function]} onConfirm={[Function]}
/> />
</td> </td>
...@@ -583,6 +589,7 @@ exports[`Render when feature toggle editorsCanAdmin is turned on and signedin us ...@@ -583,6 +589,7 @@ exports[`Render when feature toggle editorsCanAdmin is turned on and signedin us
className="text-right" className="text-right"
> >
<DeleteButton <DeleteButton
disabled={true}
onConfirm={[Function]} onConfirm={[Function]}
/> />
</td> </td>
......
import { Team, NavModelItem, NavModel } from 'app/types'; import { Team, NavModelItem, NavModel, TeamPermissionLevel } from 'app/types';
import config from 'app/core/config'; import config from 'app/core/config';
export function buildNavModel(team: Team): NavModelItem { export function buildNavModel(team: Team): NavModelItem {
...@@ -47,6 +47,7 @@ export function getTeamLoadingNav(pageName: string): NavModel { ...@@ -47,6 +47,7 @@ export function getTeamLoadingNav(pageName: string): NavModel {
name: 'Loading', name: 'Loading',
email: 'loading', email: 'loading',
memberCount: 0, memberCount: 0,
permission: TeamPermissionLevel.Member,
}); });
let node: NavModelItem; let node: NavModelItem;
......
...@@ -37,10 +37,24 @@ export interface Config { ...@@ -37,10 +37,24 @@ export interface Config {
} }
export const isSignedInUserTeamAdmin = (config: Config): boolean => { export const isSignedInUserTeamAdmin = (config: Config): boolean => {
const userInMembers = config.members.find(m => m.userId === config.signedInUser.id); const { members, signedInUser, editorsCanAdmin } = config;
const isAdmin = config.signedInUser.isGrafanaAdmin || config.signedInUser.orgRole === OrgRole.Admin; const userInMembers = members.find(m => m.userId === signedInUser.id);
const userIsTeamAdmin = userInMembers && userInMembers.permission === TeamPermissionLevel.Admin; const permission = userInMembers ? userInMembers.permission : TeamPermissionLevel.Member;
return isPermissionTeamAdmin({ permission, signedInUser, editorsCanAdmin });
};
export interface PermissionConfig {
permission: TeamPermissionLevel;
editorsCanAdmin: boolean;
signedInUser: User;
}
export const isPermissionTeamAdmin = (config: PermissionConfig): boolean => {
const { permission, signedInUser, editorsCanAdmin } = config;
const isAdmin = signedInUser.isGrafanaAdmin || signedInUser.orgRole === OrgRole.Admin;
const userIsTeamAdmin = permission === TeamPermissionLevel.Admin;
const isSignedInUserTeamAdmin = isAdmin || userIsTeamAdmin; const isSignedInUserTeamAdmin = isAdmin || userIsTeamAdmin;
return isSignedInUserTeamAdmin || !config.editorsCanAdmin; return isSignedInUserTeamAdmin || !editorsCanAdmin;
}; };
import { TeamPermissionLevel } from './acl';
export interface Team { export interface Team {
id: number; id: number;
name: string; name: string;
avatarUrl: string; avatarUrl: string;
email: string; email: string;
memberCount: number; memberCount: number;
permission: TeamPermissionLevel;
} }
export interface TeamMember { export interface TeamMember {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment