diff --git a/pkg/api/api.go b/pkg/api/api.go
index 3f6d8d4d954..ea082ff4741 100644
--- a/pkg/api/api.go
+++ b/pkg/api/api.go
@@ -40,8 +40,11 @@ func (hs *HttpServer) registerRoutes() {
r.Get("/datasources/", reqSignedIn, Index)
r.Get("/datasources/new", reqSignedIn, Index)
r.Get("/datasources/edit/*", reqSignedIn, Index)
+ r.Get("/org/users", reqSignedIn, Index)
r.Get("/org/users/new", reqSignedIn, Index)
r.Get("/org/users/invite", reqSignedIn, Index)
+ r.Get("/org/teams", reqSignedIn, Index)
+ r.Get("/org/teams/*", reqSignedIn, Index)
r.Get("/org/apikeys/", reqSignedIn, Index)
r.Get("/dashboard/import/", reqSignedIn, Index)
r.Get("/configuration", reqGrafanaAdmin, Index)
diff --git a/pkg/api/dtos/models.go b/pkg/api/dtos/models.go
index 2e9aa78d7d5..a702b06fad5 100644
--- a/pkg/api/dtos/models.go
+++ b/pkg/api/dtos/models.go
@@ -3,6 +3,7 @@ package dtos
import (
"crypto/md5"
"fmt"
+ "regexp"
"strings"
"github.com/grafana/grafana/pkg/components/simplejson"
@@ -57,3 +58,19 @@ func GetGravatarUrl(text string) string {
hasher.Write([]byte(strings.ToLower(text)))
return fmt.Sprintf(setting.AppSubUrl+"/avatar/%x", hasher.Sum(nil))
}
+
+func GetGravatarUrlWithDefault(text string, defaultText string) string {
+ if text != "" {
+ return GetGravatarUrl(text)
+ }
+
+ reg, err := regexp.Compile("[^a-zA-Z0-9]+")
+
+ if err != nil {
+ return ""
+ }
+
+ text = reg.ReplaceAllString(defaultText, "") + "@localhost"
+
+ return GetGravatarUrl(text)
+}
diff --git a/pkg/api/team.go b/pkg/api/team.go
index 31e465d3232..af537224d41 100644
--- a/pkg/api/team.go
+++ b/pkg/api/team.go
@@ -1,6 +1,7 @@
package api
import (
+ "github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/middleware"
m "github.com/grafana/grafana/pkg/models"
@@ -70,6 +71,10 @@ func SearchTeams(c *middleware.Context) Response {
return ApiError(500, "Failed to search Teams", err)
}
+ for _, team := range query.Result.Teams {
+ team.AvatarUrl = dtos.GetGravatarUrlWithDefault(team.Email, team.Name)
+ }
+
query.Result.Page = page
query.Result.PerPage = perPage
diff --git a/pkg/api/team_members.go b/pkg/api/team_members.go
index 0999c9573a5..412e142edb7 100644
--- a/pkg/api/team_members.go
+++ b/pkg/api/team_members.go
@@ -1,6 +1,7 @@
package api
import (
+ "github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/middleware"
m "github.com/grafana/grafana/pkg/models"
@@ -15,6 +16,10 @@ func GetTeamMembers(c *middleware.Context) Response {
return ApiError(500, "Failed to get Team Members", err)
}
+ for _, member := range query.Result {
+ member.AvatarUrl = dtos.GetGravatarUrl(member.Email)
+ }
+
return Json(200, query.Result)
}
diff --git a/pkg/models/team.go b/pkg/models/team.go
index b9759f059cf..d2912f431b8 100644
--- a/pkg/models/team.go
+++ b/pkg/models/team.go
@@ -16,6 +16,7 @@ type Team struct {
Id int64 `json:"id"`
OrgId int64 `json:"orgId"`
Name string `json:"name"`
+ Email string `json:"email"`
Created time.Time `json:"created"`
Updated time.Time `json:"updated"`
@@ -26,14 +27,16 @@ type Team struct {
type CreateTeamCommand struct {
Name string `json:"name" binding:"Required"`
+ Email string `json:"email"`
OrgId int64 `json:"-"`
Result Team `json:"-"`
}
type UpdateTeamCommand struct {
- Id int64
- Name string
+ Id int64
+ Name string
+ Email string
}
type DeleteTeamCommand struct {
@@ -64,6 +67,8 @@ type SearchTeamDto struct {
Id int64 `json:"id"`
OrgId int64 `json:"orgId"`
Name string `json:"name"`
+ Email string `json:"email"`
+ AvatarUrl string `json:"avatarUrl"`
MemberCount int64 `json:"memberCount"`
}
diff --git a/pkg/models/team_member.go b/pkg/models/team_member.go
index 71e5cd4ba12..9970678a1ae 100644
--- a/pkg/models/team_member.go
+++ b/pkg/models/team_member.go
@@ -47,9 +47,10 @@ type GetTeamMembersQuery struct {
// Projections and DTOs
type TeamMemberDTO struct {
- OrgId int64 `json:"orgId"`
- TeamId int64 `json:"teamId"`
- UserId int64 `json:"userId"`
- Email string `json:"email"`
- Login string `json:"login"`
+ OrgId int64 `json:"orgId"`
+ TeamId int64 `json:"teamId"`
+ UserId int64 `json:"userId"`
+ Email string `json:"email"`
+ Login string `json:"login"`
+ AvatarUrl string `json:"avatarUrl"`
}
diff --git a/pkg/services/sqlstore/migrations/team_mig.go b/pkg/services/sqlstore/migrations/team_mig.go
index 374972e5449..eb0641fbc32 100644
--- a/pkg/services/sqlstore/migrations/team_mig.go
+++ b/pkg/services/sqlstore/migrations/team_mig.go
@@ -45,4 +45,9 @@ func addTeamMigrations(mg *Migrator) {
//------- indexes ------------------
mg.AddMigration("add index team_member.org_id", NewAddIndexMigration(teamMemberV1, teamMemberV1.Indices[0]))
mg.AddMigration("add unique index team_member_org_id_team_id_user_id", NewAddIndexMigration(teamMemberV1, teamMemberV1.Indices[1]))
+
+ // add column email
+ mg.AddMigration("Add column email to team table", NewAddColumnMigration(teamV1, &Column{
+ Name: "email", Type: DB_NVarchar, Nullable: true, Length: 190,
+ }))
}
diff --git a/pkg/services/sqlstore/team.go b/pkg/services/sqlstore/team.go
index 3e9a6e6ec56..98bb1a36eb9 100644
--- a/pkg/services/sqlstore/team.go
+++ b/pkg/services/sqlstore/team.go
@@ -33,6 +33,7 @@ func CreateTeam(cmd *m.CreateTeamCommand) error {
team := m.Team{
Name: cmd.Name,
+ Email: cmd.Email,
OrgId: cmd.OrgId,
Created: time.Now(),
Updated: time.Now(),
@@ -57,9 +58,12 @@ func UpdateTeam(cmd *m.UpdateTeamCommand) error {
team := m.Team{
Name: cmd.Name,
+ Email: cmd.Email,
Updated: time.Now(),
}
+ sess.MustCols("email")
+
affectedRows, err := sess.Id(cmd.Id).Update(&team)
if err != nil {
@@ -125,6 +129,7 @@ func SearchTeams(query *m.SearchTeamsQuery) error {
sql.WriteString(`select
team.id as id,
team.name as name,
+ team.email as email,
(select count(*) from team_member where team_member.team_id = team.id) as member_count
from team as team
where team.org_id = ?`)
diff --git a/pkg/services/sqlstore/team_test.go b/pkg/services/sqlstore/team_test.go
index 4a099db14ff..dbae4545266 100644
--- a/pkg/services/sqlstore/team_test.go
+++ b/pkg/services/sqlstore/team_test.go
@@ -27,8 +27,8 @@ func TestTeamCommandsAndQueries(t *testing.T) {
userIds = append(userIds, userCmd.Result.Id)
}
- group1 := m.CreateTeamCommand{Name: "group1 name"}
- group2 := m.CreateTeamCommand{Name: "group2 name"}
+ group1 := m.CreateTeamCommand{Name: "group1 name", Email: "test1@test.com"}
+ group2 := m.CreateTeamCommand{Name: "group2 name", Email: "test2@test.com"}
err := CreateTeam(&group1)
So(err, ShouldBeNil)
@@ -43,6 +43,7 @@ func TestTeamCommandsAndQueries(t *testing.T) {
team1 := query.Result.Teams[0]
So(team1.Name, ShouldEqual, "group1 name")
+ So(team1.Email, ShouldEqual, "test1@test.com")
err = AddTeamMember(&m.AddTeamMemberCommand{OrgId: 1, TeamId: team1.Id, UserId: userIds[0]})
So(err, ShouldBeNil)
@@ -76,6 +77,7 @@ func TestTeamCommandsAndQueries(t *testing.T) {
So(err, ShouldBeNil)
So(len(query.Result), ShouldEqual, 1)
So(query.Result[0].Name, ShouldEqual, "group2 name")
+ So(query.Result[0].Email, ShouldEqual, "test2@test.com")
})
Convey("Should be able to remove users from a group", func() {
diff --git a/public/app/core/routes/routes.ts b/public/app/core/routes/routes.ts
index 2e4950cac6f..b8b157b5ad0 100644
--- a/public/app/core/routes/routes.ts
+++ b/public/app/core/routes/routes.ts
@@ -145,6 +145,12 @@ function setupAngularRoutes($routeProvider, $locationProvider) {
controllerAs: 'ctrl',
resolve: loadOrgBundle,
})
+ .when('/org/teams/new', {
+ templateUrl: 'public/app/features/org/partials/create_team.html',
+ controller: 'CreateTeamCtrl',
+ controllerAs: 'ctrl',
+ resolve: loadOrgBundle,
+ })
.when('/org/teams/edit/:id', {
templateUrl: 'public/app/features/org/partials/team_details.html',
controller: 'TeamDetailsCtrl',
diff --git a/public/app/features/org/all.ts b/public/app/features/org/all.ts
index 11a362d728a..50b81356c70 100644
--- a/public/app/features/org/all.ts
+++ b/public/app/features/org/all.ts
@@ -1,13 +1,13 @@
-import './org_users_ctrl';
-import './profile_ctrl';
-import './org_users_ctrl';
-import './select_org_ctrl';
-import './change_password_ctrl';
-import './new_org_ctrl';
-import './user_invite_ctrl';
-import './teams_ctrl';
-import './team_details_ctrl';
-import './create_team_modal';
-import './org_api_keys_ctrl';
-import './org_details_ctrl';
-import './prefs_control';
+import "./org_users_ctrl";
+import "./profile_ctrl";
+import "./org_users_ctrl";
+import "./select_org_ctrl";
+import "./change_password_ctrl";
+import "./new_org_ctrl";
+import "./user_invite_ctrl";
+import "./teams_ctrl";
+import "./team_details_ctrl";
+import "./create_team_ctrl";
+import "./org_api_keys_ctrl";
+import "./org_details_ctrl";
+import "./prefs_control";
diff --git a/public/app/features/org/create_team_ctrl.ts b/public/app/features/org/create_team_ctrl.ts
new file mode 100644
index 00000000000..08184076a1c
--- /dev/null
+++ b/public/app/features/org/create_team_ctrl.ts
@@ -0,0 +1,26 @@
+import coreModule from "app/core/core_module";
+
+export default class CreateTeamCtrl {
+ name: string;
+ email: string;
+ navModel: any;
+
+ /** @ngInject **/
+ constructor(private backendSrv, private $location, navModelSrv) {
+ this.navModel = navModelSrv.getNav("cfg", "teams", 0);
+ }
+
+ create() {
+ const payload = {
+ name: this.name,
+ email: this.email
+ };
+ this.backendSrv.post("/api/teams", payload).then(result => {
+ if (result.teamId) {
+ this.$location.path("/org/teams/edit/" + result.teamId);
+ }
+ });
+ }
+}
+
+coreModule.controller("CreateTeamCtrl", CreateTeamCtrl);
diff --git a/public/app/features/org/create_team_modal.ts b/public/app/features/org/create_team_modal.ts
deleted file mode 100644
index acac782fadc..00000000000
--- a/public/app/features/org/create_team_modal.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-///