Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
N
nexpie-grafana-theme
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Registry
Registry
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Kornkitt Poolsup
nexpie-grafana-theme
Commits
b2833daf
Unverified
Commit
b2833daf
authored
Sep 17, 2018
by
Marcus Efraimsson
Committed by
GitHub
Sep 17, 2018
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #13285 from marefr/team_member_ext
Team member labels
parents
dd0b1d84
da68b858
Show whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
373 additions
and
6 deletions
+373
-6
pkg/api/team_members.go
+6
-0
pkg/models/team_member.go
+5
-0
pkg/services/sqlstore/migrations/team_mig.go
+3
-0
pkg/services/sqlstore/team.go
+5
-1
pkg/services/sqlstore/team_test.go
+16
-0
public/app/features/teams/TeamMembers.test.tsx
+10
-0
public/app/features/teams/TeamMembers.tsx
+19
-3
public/app/features/teams/TeamPages.tsx
+1
-1
public/app/features/teams/__mocks__/teamMocks.ts
+2
-0
public/app/features/teams/__snapshots__/TeamMembers.test.tsx.snap
+302
-0
public/app/features/teams/__snapshots__/TeamPages.test.tsx.snap
+3
-1
public/app/types/teams.ts
+1
-0
No files found.
pkg/api/team_members.go
View file @
b2833daf
...
...
@@ -4,6 +4,7 @@ import (
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/bus"
m
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
)
...
...
@@ -17,6 +18,11 @@ func GetTeamMembers(c *m.ReqContext) Response {
for
_
,
member
:=
range
query
.
Result
{
member
.
AvatarUrl
=
dtos
.
GetGravatarUrl
(
member
.
Email
)
member
.
Labels
=
[]
string
{}
if
setting
.
IsEnterprise
&&
setting
.
LdapEnabled
&&
member
.
External
{
member
.
Labels
=
append
(
member
.
Labels
,
"LDAP"
)
}
}
return
JSON
(
200
,
query
.
Result
)
...
...
pkg/models/team_member.go
View file @
b2833daf
...
...
@@ -16,6 +16,7 @@ type TeamMember struct {
OrgId
int64
TeamId
int64
UserId
int64
External
bool
Created
time
.
Time
Updated
time
.
Time
...
...
@@ -28,6 +29,7 @@ type AddTeamMemberCommand struct {
UserId
int64
`json:"userId" binding:"Required"`
OrgId
int64
`json:"-"`
TeamId
int64
`json:"-"`
External
bool
`json:"-"`
}
type
RemoveTeamMemberCommand
struct
{
...
...
@@ -43,6 +45,7 @@ type GetTeamMembersQuery struct {
OrgId
int64
TeamId
int64
UserId
int64
External
bool
Result
[]
*
TeamMemberDTO
}
...
...
@@ -53,7 +56,9 @@ type TeamMemberDTO struct {
OrgId
int64
`json:"orgId"`
TeamId
int64
`json:"teamId"`
UserId
int64
`json:"userId"`
External
bool
`json:"-"`
Email
string
`json:"email"`
Login
string
`json:"login"`
AvatarUrl
string
`json:"avatarUrl"`
Labels
[]
string
`json:"labels"`
}
pkg/services/sqlstore/migrations/team_mig.go
View file @
b2833daf
...
...
@@ -51,4 +51,7 @@ func addTeamMigrations(mg *Migrator) {
Name
:
"email"
,
Type
:
DB_NVarchar
,
Nullable
:
true
,
Length
:
190
,
}))
mg
.
AddMigration
(
"Add column external to team_member table"
,
NewAddColumnMigration
(
teamMemberV1
,
&
Column
{
Name
:
"external"
,
Type
:
DB_Bool
,
Nullable
:
true
,
}))
}
pkg/services/sqlstore/team.go
View file @
b2833daf
...
...
@@ -243,6 +243,7 @@ func AddTeamMember(cmd *m.AddTeamMemberCommand) error {
OrgId
:
cmd
.
OrgId
,
TeamId
:
cmd
.
TeamId
,
UserId
:
cmd
.
UserId
,
External
:
cmd
.
External
,
Created
:
time
.
Now
(),
Updated
:
time
.
Now
(),
}
...
...
@@ -289,7 +290,10 @@ func GetTeamMembers(query *m.GetTeamMembersQuery) error {
if
query
.
UserId
!=
0
{
sess
.
Where
(
"team_member.user_id=?"
,
query
.
UserId
)
}
sess
.
Cols
(
"user.org_id"
,
"team_member.team_id"
,
"team_member.user_id"
,
"user.email"
,
"user.login"
)
if
query
.
External
{
sess
.
Where
(
"team_member.external=?"
,
dialect
.
BooleanStr
(
true
))
}
sess
.
Cols
(
"team_member.org_id"
,
"team_member.team_id"
,
"team_member.user_id"
,
"user.email"
,
"user.login"
,
"team_member.external"
)
sess
.
Asc
(
"user.login"
,
"user.email"
)
err
:=
sess
.
Find
(
&
query
.
Result
)
...
...
pkg/services/sqlstore/team_test.go
View file @
b2833daf
...
...
@@ -50,13 +50,29 @@ func TestTeamCommandsAndQueries(t *testing.T) {
err
=
AddTeamMember
(
&
m
.
AddTeamMemberCommand
{
OrgId
:
testOrgId
,
TeamId
:
team1
.
Id
,
UserId
:
userIds
[
0
]})
So
(
err
,
ShouldBeNil
)
err
=
AddTeamMember
(
&
m
.
AddTeamMemberCommand
{
OrgId
:
testOrgId
,
TeamId
:
team1
.
Id
,
UserId
:
userIds
[
1
],
External
:
true
})
So
(
err
,
ShouldBeNil
)
q1
:=
&
m
.
GetTeamMembersQuery
{
OrgId
:
testOrgId
,
TeamId
:
team1
.
Id
}
err
=
GetTeamMembers
(
q1
)
So
(
err
,
ShouldBeNil
)
So
(
q1
.
Result
,
ShouldHaveLength
,
2
)
So
(
q1
.
Result
[
0
]
.
TeamId
,
ShouldEqual
,
team1
.
Id
)
So
(
q1
.
Result
[
0
]
.
Login
,
ShouldEqual
,
"loginuser0"
)
So
(
q1
.
Result
[
0
]
.
OrgId
,
ShouldEqual
,
testOrgId
)
So
(
q1
.
Result
[
1
]
.
TeamId
,
ShouldEqual
,
team1
.
Id
)
So
(
q1
.
Result
[
1
]
.
Login
,
ShouldEqual
,
"loginuser1"
)
So
(
q1
.
Result
[
1
]
.
OrgId
,
ShouldEqual
,
testOrgId
)
So
(
q1
.
Result
[
1
]
.
External
,
ShouldEqual
,
true
)
q2
:=
&
m
.
GetTeamMembersQuery
{
OrgId
:
testOrgId
,
TeamId
:
team1
.
Id
,
External
:
true
}
err
=
GetTeamMembers
(
q2
)
So
(
err
,
ShouldBeNil
)
So
(
q2
.
Result
,
ShouldHaveLength
,
1
)
So
(
q2
.
Result
[
0
]
.
TeamId
,
ShouldEqual
,
team1
.
Id
)
So
(
q2
.
Result
[
0
]
.
Login
,
ShouldEqual
,
"loginuser1"
)
So
(
q2
.
Result
[
0
]
.
OrgId
,
ShouldEqual
,
testOrgId
)
So
(
q2
.
Result
[
0
]
.
External
,
ShouldEqual
,
true
)
})
Convey
(
"Should be able to search for teams"
,
func
()
{
...
...
public/app/features/teams/TeamMembers.test.tsx
View file @
b2833daf
...
...
@@ -12,6 +12,7 @@ const setup = (propOverrides?: object) => {
loadTeamMembers
:
jest
.
fn
(),
addTeamMember
:
jest
.
fn
(),
removeTeamMember
:
jest
.
fn
(),
syncEnabled
:
false
,
};
Object
.
assign
(
props
,
propOverrides
);
...
...
@@ -39,6 +40,15 @@ describe('Render', () => {
expect
(
wrapper
).
toMatchSnapshot
();
});
it
(
'should render team members when sync enabled'
,
()
=>
{
const
{
wrapper
}
=
setup
({
members
:
getMockTeamMembers
(
5
),
syncEnabled
:
true
,
});
expect
(
wrapper
).
toMatchSnapshot
();
});
});
describe
(
'Functions'
,
()
=>
{
...
...
public/app/features/teams/TeamMembers.tsx
View file @
b2833daf
...
...
@@ -3,6 +3,7 @@ import { connect } from 'react-redux';
import
SlideDown
from
'app/core/components/Animations/SlideDown'
;
import
{
UserPicker
,
User
}
from
'app/core/components/Picker/UserPicker'
;
import
DeleteButton
from
'app/core/components/DeleteButton/DeleteButton'
;
import
{
TagBadge
}
from
'app/core/components/TagFilter/TagBadge'
;
import
{
TeamMember
}
from
'../../types'
;
import
{
loadTeamMembers
,
addTeamMember
,
removeTeamMember
,
setSearchMemberQuery
}
from
'./state/actions'
;
import
{
getSearchMemberQuery
,
getTeamMembers
}
from
'./state/selectors'
;
...
...
@@ -14,6 +15,7 @@ export interface Props {
addTeamMember
:
typeof
addTeamMember
;
removeTeamMember
:
typeof
removeTeamMember
;
setSearchMemberQuery
:
typeof
setSearchMemberQuery
;
syncEnabled
:
boolean
;
}
export
interface
State
{
...
...
@@ -52,7 +54,19 @@ export class TeamMembers extends PureComponent<Props, State> {
this
.
setState
({
newTeamMember
:
null
});
};
renderMember
(
member
:
TeamMember
)
{
renderLabels
(
labels
:
string
[])
{
if
(
!
labels
)
{
return
<
td
/>;
}
return
(
<
td
>
{
labels
.
map
(
label
=>
<
TagBadge
key=
{
label
}
label=
{
label
}
removeIcon=
{
false
}
count=
{
0
}
onClick=
{
()
=>
{}
}
/>)
}
</
td
>
);
}
renderMember
(
member
:
TeamMember
,
syncEnabled
:
boolean
)
{
return
(
<
tr
key=
{
member
.
userId
}
>
<
td
className=
"width-4 text-center"
>
...
...
@@ -60,6 +74,7 @@ export class TeamMembers extends PureComponent<Props, State> {
</
td
>
<
td
>
{
member
.
login
}
</
td
>
<
td
>
{
member
.
email
}
</
td
>
{
syncEnabled
?
this
.
renderLabels
(
member
.
labels
)
:
''
}
<
td
className=
"text-right"
>
<
DeleteButton
onConfirmDelete=
{
()
=>
this
.
onRemoveMember
(
member
)
}
/>
</
td
>
...
...
@@ -69,7 +84,7 @@ export class TeamMembers extends PureComponent<Props, State> {
render
()
{
const
{
newTeamMember
,
isAdding
}
=
this
.
state
;
const
{
searchMemberQuery
,
members
}
=
this
.
props
;
const
{
searchMemberQuery
,
members
,
syncEnabled
}
=
this
.
props
;
const
newTeamMemberValue
=
newTeamMember
&&
newTeamMember
.
id
.
toString
();
return
(
...
...
@@ -120,10 +135,11 @@ export class TeamMembers extends PureComponent<Props, State> {
<
th
/>
<
th
>
Name
</
th
>
<
th
>
Email
</
th
>
{
syncEnabled
?
<
th
/>
:
''
}
<
th
style=
{
{
width
:
'1%'
}
}
/>
</
tr
>
</
thead
>
<
tbody
>
{
members
&&
members
.
map
(
member
=>
this
.
renderMember
(
member
))
}
</
tbody
>
<
tbody
>
{
members
&&
members
.
map
(
member
=>
this
.
renderMember
(
member
,
syncEnabled
))
}
</
tbody
>
</
table
>
</
div
>
</
div
>
...
...
public/app/features/teams/TeamPages.tsx
View file @
b2833daf
...
...
@@ -63,7 +63,7 @@ export class TeamPages extends PureComponent<Props, State> {
switch
(
currentPage
)
{
case
PageTypes
.
Members
:
return
<
TeamMembers
/>;
return
<
TeamMembers
syncEnabled=
{
isSyncEnabled
}
/>;
case
PageTypes
.
Settings
:
return
<
TeamSettings
/>;
...
...
public/app/features/teams/__mocks__/teamMocks.ts
View file @
b2833daf
...
...
@@ -35,6 +35,7 @@ export const getMockTeamMembers = (amount: number): TeamMember[] => {
avatarUrl
:
'some/url/'
,
email
:
'test@test.com'
,
login
:
`testUser-
${
i
}
`
,
labels
:
[
'label 1'
,
'label 2'
],
});
}
...
...
@@ -48,6 +49,7 @@ export const getMockTeamMember = (): TeamMember => {
avatarUrl
:
'some/url/'
,
email
:
'test@test.com'
,
login
:
'testUser'
,
labels
:
[],
};
};
...
...
public/app/features/teams/__snapshots__/TeamMembers.test.tsx.snap
View file @
b2833daf
...
...
@@ -315,3 +315,305 @@ exports[`Render should render team members 1`] = `
</div>
</div>
`;
exports[`Render should render team members when sync enabled 1`] = `
<div>
<div
className="page-action-bar"
>
<div
className="gf-form gf-form--grow"
>
<label
className="gf-form--has-input-icon gf-form--grow"
>
<input
className="gf-form-input"
onChange={[Function]}
placeholder="Search members"
type="text"
value=""
/>
<i
className="gf-form-input-icon fa fa-search"
/>
</label>
</div>
<div
className="page-action-bar__spacer"
/>
<button
className="btn btn-success pull-right"
disabled={false}
onClick={[Function]}
>
<i
className="fa fa-plus"
/>
Add a member
</button>
</div>
<Component
in={false}
>
<div
className="cta-form"
>
<button
className="cta-form__close btn btn-transparent"
onClick={[Function]}
>
<i
className="fa fa-close"
/>
</button>
<h5>
Add Team Member
</h5>
<div
className="gf-form-inline"
>
<UserPicker
className="width-30"
onSelected={[Function]}
value={null}
/>
</div>
</div>
</Component>
<div
className="admin-list-table"
>
<table
className="filter-table filter-table--hover form-inline"
>
<thead>
<tr>
<th />
<th>
Name
</th>
<th>
Email
</th>
<th />
<th
style={
Object {
"width": "1%",
}
}
/>
</tr>
</thead>
<tbody>
<tr
key="1"
>
<td
className="width-4 text-center"
>
<img
className="filter-table__avatar"
src="some/url/"
/>
</td>
<td>
testUser-1
</td>
<td>
test@test.com
</td>
<td>
<TagBadge
count={0}
key="label 1"
label="label 1"
onClick={[Function]}
removeIcon={false}
/>
<TagBadge
count={0}
key="label 2"
label="label 2"
onClick={[Function]}
removeIcon={false}
/>
</td>
<td
className="text-right"
>
<DeleteButton
onConfirmDelete={[Function]}
/>
</td>
</tr>
<tr
key="2"
>
<td
className="width-4 text-center"
>
<img
className="filter-table__avatar"
src="some/url/"
/>
</td>
<td>
testUser-2
</td>
<td>
test@test.com
</td>
<td>
<TagBadge
count={0}
key="label 1"
label="label 1"
onClick={[Function]}
removeIcon={false}
/>
<TagBadge
count={0}
key="label 2"
label="label 2"
onClick={[Function]}
removeIcon={false}
/>
</td>
<td
className="text-right"
>
<DeleteButton
onConfirmDelete={[Function]}
/>
</td>
</tr>
<tr
key="3"
>
<td
className="width-4 text-center"
>
<img
className="filter-table__avatar"
src="some/url/"
/>
</td>
<td>
testUser-3
</td>
<td>
test@test.com
</td>
<td>
<TagBadge
count={0}
key="label 1"
label="label 1"
onClick={[Function]}
removeIcon={false}
/>
<TagBadge
count={0}
key="label 2"
label="label 2"
onClick={[Function]}
removeIcon={false}
/>
</td>
<td
className="text-right"
>
<DeleteButton
onConfirmDelete={[Function]}
/>
</td>
</tr>
<tr
key="4"
>
<td
className="width-4 text-center"
>
<img
className="filter-table__avatar"
src="some/url/"
/>
</td>
<td>
testUser-4
</td>
<td>
test@test.com
</td>
<td>
<TagBadge
count={0}
key="label 1"
label="label 1"
onClick={[Function]}
removeIcon={false}
/>
<TagBadge
count={0}
key="label 2"
label="label 2"
onClick={[Function]}
removeIcon={false}
/>
</td>
<td
className="text-right"
>
<DeleteButton
onConfirmDelete={[Function]}
/>
</td>
</tr>
<tr
key="5"
>
<td
className="width-4 text-center"
>
<img
className="filter-table__avatar"
src="some/url/"
/>
</td>
<td>
testUser-5
</td>
<td>
test@test.com
</td>
<td>
<TagBadge
count={0}
key="label 1"
label="label 1"
onClick={[Function]}
removeIcon={false}
/>
<TagBadge
count={0}
key="label 2"
label="label 2"
onClick={[Function]}
removeIcon={false}
/>
</td>
<td
className="text-right"
>
<DeleteButton
onConfirmDelete={[Function]}
/>
</td>
</tr>
</tbody>
</table>
</div>
</div>
`;
public/app/features/teams/__snapshots__/TeamPages.test.tsx.snap
View file @
b2833daf
...
...
@@ -29,7 +29,9 @@ exports[`Render should render member page if team not empty 1`] = `
<div
className="page-container page-body"
>
<Connect(TeamMembers) />
<Connect(TeamMembers)
syncEnabled={true}
/>
</div>
</div>
`;
...
...
public/app/types/teams.ts
View file @
b2833daf
...
...
@@ -12,6 +12,7 @@ export interface TeamMember {
avatarUrl
:
string
;
email
:
string
;
login
:
string
;
labels
:
string
[];
}
export
interface
TeamGroup
{
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment