diff --git a/CHANGELOG.md b/CHANGELOG.md index 517fa82779b..22e4f5482b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ - [Issue #1144](https://github.com/grafana/grafana/issues/1144). Templating: You can now select multiple template variables values at the same time. - [Issue #1888](https://github.com/grafana/grafana/issues/1144). Templating: Repeat panel or row for each selected template variable value +**User or Organization admin** +- [Issue #1899](https://github.com/grafana/grafana/issues/1899). Organization: You can now update the organization user role directly (without removing and readding the organization user). + **Backend** - [Issue #1905](https://github.com/grafana/grafana/issues/1905). Github OAuth: You can now configure a Github team membership requirement, thx @dewski - [Issue #1891](https://github.com/grafana/grafana/issues/1891). Security: New config option to disable the use of gravatar for profile images diff --git a/pkg/api/api.go b/pkg/api/api.go index 6482beb075c..d281d94419f 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -71,6 +71,7 @@ func Register(r *macaron.Macaron) { r.Put("/", bind(m.UpdateOrgCommand{}), UpdateOrg) r.Post("/users", bind(m.AddOrgUserCommand{}), AddOrgUser) r.Get("/users", GetOrgUsers) + r.Patch("/users/:id", bind(m.UpdateOrgUserCommand{}), UpdateOrgUser) r.Delete("/users/:id", RemoveOrgUser) }, reqAccountAdmin) diff --git a/pkg/api/org_users.go b/pkg/api/org_users.go index 123ba08c4c7..8a372c791b9 100644 --- a/pkg/api/org_users.go +++ b/pkg/api/org_users.go @@ -48,6 +48,23 @@ func GetOrgUsers(c *middleware.Context) { c.JSON(200, query.Result) } +func UpdateOrgUser(c *middleware.Context, cmd m.UpdateOrgUserCommand) { + if !cmd.Role.IsValid() { + c.JsonApiErr(400, "Invalid role specified", nil) + return + } + + cmd.UserId = c.ParamsInt64(":id") + cmd.OrgId = c.OrgId + + if err := bus.Dispatch(&cmd); err != nil { + c.JsonApiErr(500, "Failed update org user", err) + return + } + + c.JsonOK("Organization user updated") +} + func RemoveOrgUser(c *middleware.Context) { userId := c.ParamsInt64(":id") diff --git a/pkg/models/org_user.go b/pkg/models/org_user.go index 811d02e1afe..3e40fd24b68 100644 --- a/pkg/models/org_user.go +++ b/pkg/models/org_user.go @@ -9,6 +9,7 @@ import ( var ( ErrInvalidRoleType = errors.New("Invalid role type") ErrLastOrgAdmin = errors.New("Cannot remove last organization admin") + ErrOrgUserNotFound = errors.New("Cannot find the organization user") ) type RoleType string @@ -24,6 +25,7 @@ func (r RoleType) IsValid() bool { } type OrgUser struct { + Id int64 OrgId int64 UserId int64 Role RoleType @@ -47,6 +49,13 @@ type AddOrgUserCommand struct { UserId int64 `json:"-"` } +type UpdateOrgUserCommand struct { + Role RoleType `json:"role" binding:"Required"` + + OrgId int64 `json:"-"` + UserId int64 `json:"-"` +} + // ---------------------- // QUERIES diff --git a/pkg/services/sqlstore/org_test.go b/pkg/services/sqlstore/org_test.go index 0e20919709b..7d98da076e0 100644 --- a/pkg/services/sqlstore/org_test.go +++ b/pkg/services/sqlstore/org_test.go @@ -80,6 +80,19 @@ func TestAccountDataAccess(t *testing.T) { So(err, ShouldBeNil) }) + Convey("Can update org user role", func() { + updateCmd := m.UpdateOrgUserCommand{OrgId: ac1.OrgId, UserId: ac2.Id, Role: m.ROLE_ADMIN} + err = UpdateOrgUser(&updateCmd) + So(err, ShouldBeNil) + + orgUsersQuery := m.GetOrgUsersQuery{OrgId: ac1.OrgId} + err = GetOrgUsers(&orgUsersQuery) + So(err, ShouldBeNil) + + So(orgUsersQuery.Result[1].Role, ShouldEqual, m.ROLE_ADMIN) + + }) + Convey("Can get logged in user projection", func() { query := m.GetSignedInUserQuery{UserId: ac2.Id} err := GetSignedInUser(&query) diff --git a/pkg/services/sqlstore/org_users.go b/pkg/services/sqlstore/org_users.go index eaca01ce12c..3502cd97d60 100644 --- a/pkg/services/sqlstore/org_users.go +++ b/pkg/services/sqlstore/org_users.go @@ -14,6 +14,7 @@ func init() { bus.AddHandler("sql", AddOrgUser) bus.AddHandler("sql", RemoveOrgUser) bus.AddHandler("sql", GetOrgUsers) + bus.AddHandler("sql", UpdateOrgUser) } func AddOrgUser(cmd *m.AddOrgUserCommand) error { @@ -32,6 +33,25 @@ func AddOrgUser(cmd *m.AddOrgUserCommand) error { }) } +func UpdateOrgUser(cmd *m.UpdateOrgUserCommand) error { + return inTransaction(func(sess *xorm.Session) error { + var orgUser m.OrgUser + exists, err := sess.Where("org_id=? AND user_id=?", cmd.OrgId, cmd.UserId).Get(&orgUser) + if err != nil { + return err + } + + if !exists { + return m.ErrOrgUserNotFound + } + + orgUser.Role = cmd.Role + orgUser.Updated = time.Now() + _, err = sess.Id(orgUser.Id).Update(&orgUser) + return err + }) +} + func GetOrgUsers(query *m.GetOrgUsersQuery) error { query.Result = make([]*m.OrgUserDTO, 0) sess := x.Table("org_user") diff --git a/public/app/features/org/orgUsersCtrl.js b/public/app/features/org/orgUsersCtrl.js index d679a8dab98..9623e2566a7 100644 --- a/public/app/features/org/orgUsersCtrl.js +++ b/public/app/features/org/orgUsersCtrl.js @@ -23,6 +23,10 @@ function (angular) { }); }; + $scope.updateOrgUser = function(user) { + backendSrv.patch('/api/org/users/' + user.userId, user); + }; + $scope.removeUser = function(user) { backendSrv.delete('/api/org/users/' + user.userId).then($scope.get); }; diff --git a/public/app/features/org/partials/orgUsers.html b/public/app/features/org/partials/orgUsers.html index c14a44efef7..8b1829437c7 100644 --- a/public/app/features/org/partials/orgUsers.html +++ b/public/app/features/org/partials/orgUsers.html @@ -35,7 +35,7 @@
- +
@@ -46,7 +46,8 @@
Login Email{{user.login}} {{user.email}} - {{user.role}} + diff --git a/public/app/services/backendSrv.js b/public/app/services/backendSrv.js index c208fe3388e..7424d8fcd83 100644 --- a/public/app/services/backendSrv.js +++ b/public/app/services/backendSrv.js @@ -23,6 +23,10 @@ function (angular, _, config) { return this.request({ method: 'POST', url: url, data: data }); }; + this.patch = function(url, data) { + return this.request({ method: 'PATCH', url: url, data: data }); + }; + this.put = function(url, data) { return this.request({ method: 'PUT', url: url, data: data }); };