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 @@
-