diff --git a/README.md b/README.md
index 80a80b694c1..81fb1f8d42b 100644
--- a/README.md
+++ b/README.md
@@ -80,8 +80,11 @@ In your custom.ini uncomment (remove the leading `;`) sign. And set `app_mode =
### Running tests
-- You can run backend Golang tests using "go test ./pkg/...".
-- Execute all frontend tests with "npm run test"
+#### Frontend
+Execute all frontend tests
+```bash
+npm run test
+```
Writing & watching frontend tests (we have two test runners)
@@ -92,6 +95,18 @@ Writing & watching frontend tests (we have two test runners)
- Start watcher: `npm run karma`
- Karma+Mocha runs all files that end with the name "_specs.ts".
+#### Backend
+```bash
+# Run Golang tests using sqlite3 as database (default)
+go test ./pkg/...
+
+# Run Golang tests using mysql as database - convenient to use /docker/blocks/mysql_tests
+GRAFANA_TEST_DB=mysql go test ./pkg/...
+
+# Run Golang tests using postgres as database - convenient to use /docker/blocks/postgres_tests
+GRAFANA_TEST_DB=postgres go test ./pkg/...
+```
+
## Contribute
If you have any idea for an improvement or found a bug, do not hesitate to open an issue.
diff --git a/docs/sources/index.md b/docs/sources/index.md
index c1072db47a5..3c59b9baba0 100644
--- a/docs/sources/index.md
+++ b/docs/sources/index.md
@@ -1,49 +1,107 @@
+++
-title = "Docs Home"
-description = "Install guide for Grafana"
+title = "Grafana documentation"
+description = "Guides, Installation & Feature Documentation"
keywords = ["grafana", "installation", "documentation"]
type = "docs"
aliases = ["v1.1", "guides/reference/admin"]
+++
-# Welcome to the Grafana Documentation
+# Grafana Documentation
-Grafana is an open source metric analytics & visualization suite. It is most commonly used for
-visualizing time series data for infrastructure and application analytics but many use it in
-other domains including industrial sensors, home automation, weather, and process control.
+
Installing Grafana
+
-## Installing Grafana
-- [Installing on Debian / Ubuntu](installation/debian)
-- [Installing on RPM-based Linux (CentOS, Fedora, OpenSuse, RedHat)](installation/rpm)
-- [Installing on Mac OS X](installation/mac)
-- [Installing on Windows](installation/windows)
-- [Installing on Docker](installation/docker)
-- [Installing using Provisioning (Chef, Puppet, Salt, Ansible, etc)](administration/provisioning#configuration-management-tools)
-- [Nightly Builds](https://grafana.com/grafana/download)
+Guides
-For other platforms Read the [build from source]({{< relref "project/building_from_source.md" >}})
-instructions for more information.
+
-## Configuring Grafana
-
-The back-end web server has a number of configuration options. Go the
-[Configuration]({{< relref "installation/configuration.md" >}}) page for details on all
-those options.
-
-
-## Getting Started
-
-- [Getting Started]({{< relref "guides/getting_started.md" >}})
-- [Basic Concepts]({{< relref "guides/basic_concepts.md" >}})
-- [Screencasts]({{< relref "tutorials/screencasts.md" >}})
-
-## Data Source Guides
-
-- [Graphite]({{< relref "features/datasources/graphite.md" >}})
-- [Elasticsearch]({{< relref "features/datasources/elasticsearch.md" >}})
-- [InfluxDB]({{< relref "features/datasources/influxdb.md" >}})
-- [Prometheus]({{< relref "features/datasources/prometheus.md" >}})
-- [OpenTSDB]({{< relref "features/datasources/opentsdb.md" >}})
-- [MySQL]({{< relref "features/datasources/mysql.md" >}})
-- [Postgres]({{< relref "features/datasources/postgres.md" >}})
-- [Cloudwatch]({{< relref "features/datasources/cloudwatch.md" >}})
+Data Source Guides
+
diff --git a/pkg/api/api.go b/pkg/api/api.go
index 752af7602f5..ad77e410dbf 100644
--- a/pkg/api/api.go
+++ b/pkg/api/api.go
@@ -150,13 +150,13 @@ func (hs *HttpServer) registerRoutes() {
apiRoute.Group("/teams", func(teamsRoute RouteRegister) {
teamsRoute.Get("/:teamId", wrap(GetTeamById))
teamsRoute.Get("/search", wrap(SearchTeams))
- teamsRoute.Post("/", quota("teams"), reqOrgAdmin, bind(m.CreateTeamCommand{}), wrap(CreateTeam))
- teamsRoute.Put("/:teamId", reqOrgAdmin, bind(m.UpdateTeamCommand{}), wrap(UpdateTeam))
- teamsRoute.Delete("/:teamId", reqOrgAdmin, wrap(DeleteTeamById))
- teamsRoute.Get("/:teamId/members", reqOrgAdmin, wrap(GetTeamMembers))
- teamsRoute.Post("/:teamId/members", reqOrgAdmin, quota("teams"), bind(m.AddTeamMemberCommand{}), wrap(AddTeamMember))
- teamsRoute.Delete("/:teamId/members/:userId", reqOrgAdmin, wrap(RemoveTeamMember))
- })
+ teamsRoute.Post("/", quota("teams"), bind(m.CreateTeamCommand{}), wrap(CreateTeam))
+ teamsRoute.Put("/:teamId", bind(m.UpdateTeamCommand{}), wrap(UpdateTeam))
+ teamsRoute.Delete("/:teamId", wrap(DeleteTeamById))
+ teamsRoute.Get("/:teamId/members", wrap(GetTeamMembers))
+ teamsRoute.Post("/:teamId/members", quota("teams"), bind(m.AddTeamMemberCommand{}), wrap(AddTeamMember))
+ teamsRoute.Delete("/:teamId/members/:userId", wrap(RemoveTeamMember))
+ }, reqOrgAdmin)
// org information available to all users.
apiRoute.Group("/org", func(orgRoute RouteRegister) {
diff --git a/pkg/api/dashboard_acl.go b/pkg/api/dashboard_acl.go
index b5d912d25f1..45f121dd0d0 100644
--- a/pkg/api/dashboard_acl.go
+++ b/pkg/api/dashboard_acl.go
@@ -13,6 +13,11 @@ import (
func GetDashboardAclList(c *middleware.Context) Response {
dashId := c.ParamsInt64(":dashboardId")
+ _, rsp := getDashboardHelper(c.OrgId, "", dashId, "")
+ if rsp != nil {
+ return rsp
+ }
+
guardian := guardian.NewDashboardGuardian(dashId, c.OrgId, c.SignedInUser)
if canAdmin, err := guardian.CanAdmin(); err != nil || !canAdmin {
@@ -36,6 +41,11 @@ func GetDashboardAclList(c *middleware.Context) Response {
func UpdateDashboardAcl(c *middleware.Context, apiCmd dtos.UpdateDashboardAclCommand) Response {
dashId := c.ParamsInt64(":dashboardId")
+ _, rsp := getDashboardHelper(c.OrgId, "", dashId, "")
+ if rsp != nil {
+ return rsp
+ }
+
guardian := guardian.NewDashboardGuardian(dashId, c.OrgId, c.SignedInUser)
if canAdmin, err := guardian.CanAdmin(); err != nil || !canAdmin {
return dashboardGuardianResponse(err)
@@ -79,6 +89,11 @@ func DeleteDashboardAcl(c *middleware.Context) Response {
dashId := c.ParamsInt64(":dashboardId")
aclId := c.ParamsInt64(":aclId")
+ _, rsp := getDashboardHelper(c.OrgId, "", dashId, "")
+ if rsp != nil {
+ return rsp
+ }
+
guardian := guardian.NewDashboardGuardian(dashId, c.OrgId, c.SignedInUser)
if canAdmin, err := guardian.CanAdmin(); err != nil || !canAdmin {
return dashboardGuardianResponse(err)
diff --git a/pkg/api/dashboard_acl_test.go b/pkg/api/dashboard_acl_test.go
index 467045e360a..e43e57ed5c0 100644
--- a/pkg/api/dashboard_acl_test.go
+++ b/pkg/api/dashboard_acl_test.go
@@ -23,6 +23,14 @@ func TestDashboardAclApiEndpoint(t *testing.T) {
}
dtoRes := transformDashboardAclsToDTOs(mockResult)
+ getDashboardQueryResult := m.NewDashboard("Dash")
+ var getDashboardNotFoundError error
+
+ bus.AddHandler("test", func(query *m.GetDashboardQuery) error {
+ query.Result = getDashboardQueryResult
+ return getDashboardNotFoundError
+ })
+
bus.AddHandler("test", func(query *m.GetDashboardAclInfoListQuery) error {
query.Result = dtoRes
return nil
@@ -60,6 +68,40 @@ func TestDashboardAclApiEndpoint(t *testing.T) {
So(respJSON.GetIndex(0).Get("permission").MustInt(), ShouldEqual, m.PERMISSION_VIEW)
})
})
+
+ loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/2/acl", "/api/dashboards/id/:dashboardId/acl", m.ROLE_ADMIN, func(sc *scenarioContext) {
+ getDashboardNotFoundError = m.ErrDashboardNotFound
+ sc.handlerFunc = GetDashboardAclList
+ sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
+
+ Convey("Should not be able to access ACL", func() {
+ So(sc.resp.Code, ShouldEqual, 404)
+ })
+ })
+
+ Convey("Should not be able to update permissions for non-existing dashboard", func() {
+ cmd := dtos.UpdateDashboardAclCommand{
+ Items: []dtos.DashboardAclUpdateItem{
+ {UserId: 1000, Permission: m.PERMISSION_ADMIN},
+ },
+ }
+
+ postAclScenario("When calling POST on", "/api/dashboards/id/1/acl", "/api/dashboards/id/:dashboardId/acl", m.ROLE_ADMIN, cmd, func(sc *scenarioContext) {
+ getDashboardNotFoundError = m.ErrDashboardNotFound
+ CallPostAcl(sc)
+ So(sc.resp.Code, ShouldEqual, 404)
+ })
+ })
+
+ loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/id/2/acl/6", "/api/dashboards/id/:dashboardId/acl/:aclId", m.ROLE_ADMIN, func(sc *scenarioContext) {
+ getDashboardNotFoundError = m.ErrDashboardNotFound
+ sc.handlerFunc = DeleteDashboardAcl
+ sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec()
+
+ Convey("Should not be able to delete non-existing dashboard", func() {
+ So(sc.resp.Code, ShouldEqual, 404)
+ })
+ })
})
Convey("When user is org editor and has admin permission in the ACL", func() {
diff --git a/pkg/api/org_users.go b/pkg/api/org_users.go
index 57a15bd8db5..433b9f2bd66 100644
--- a/pkg/api/org_users.go
+++ b/pkg/api/org_users.go
@@ -46,26 +46,30 @@ func addOrgUserHelper(cmd m.AddOrgUserCommand) Response {
// GET /api/org/users
func GetOrgUsersForCurrentOrg(c *middleware.Context) Response {
- return getOrgUsersHelper(c.OrgId)
+ return getOrgUsersHelper(c.OrgId, c.Params("query"), c.ParamsInt("limit"))
}
// GET /api/orgs/:orgId/users
func GetOrgUsers(c *middleware.Context) Response {
- return getOrgUsersHelper(c.ParamsInt64(":orgId"))
+ return getOrgUsersHelper(c.ParamsInt64(":orgId"), "", 0)
}
-func getOrgUsersHelper(orgId int64) Response {
- query := m.GetOrgUsersQuery{OrgId: orgId}
+func getOrgUsersHelper(orgId int64, query string, limit int) Response {
+ q := m.GetOrgUsersQuery{
+ OrgId: orgId,
+ Query: query,
+ Limit: limit,
+ }
- if err := bus.Dispatch(&query); err != nil {
+ if err := bus.Dispatch(&q); err != nil {
return ApiError(500, "Failed to get account user", err)
}
- for _, user := range query.Result {
+ for _, user := range q.Result {
user.AvatarUrl = dtos.GetGravatarUrl(user.Email)
}
- return Json(200, query.Result)
+ return Json(200, q.Result)
}
// PATCH /api/org/users/:userId
diff --git a/pkg/api/team.go b/pkg/api/team.go
index af537224d41..f11eca68b91 100644
--- a/pkg/api/team.go
+++ b/pkg/api/team.go
@@ -26,6 +26,7 @@ func CreateTeam(c *middleware.Context, cmd m.CreateTeamCommand) Response {
// PUT /api/teams/:teamId
func UpdateTeam(c *middleware.Context, cmd m.UpdateTeamCommand) Response {
+ cmd.OrgId = c.OrgId
cmd.Id = c.ParamsInt64(":teamId")
if err := bus.Dispatch(&cmd); err != nil {
if err == m.ErrTeamNameTaken {
@@ -39,7 +40,7 @@ func UpdateTeam(c *middleware.Context, cmd m.UpdateTeamCommand) Response {
// DELETE /api/teams/:teamId
func DeleteTeamById(c *middleware.Context) Response {
- if err := bus.Dispatch(&m.DeleteTeamCommand{Id: c.ParamsInt64(":teamId")}); err != nil {
+ if err := bus.Dispatch(&m.DeleteTeamCommand{OrgId: c.OrgId, Id: c.ParamsInt64(":teamId")}); err != nil {
if err == m.ErrTeamNotFound {
return ApiError(404, "Failed to delete Team. ID not found", nil)
}
@@ -60,11 +61,11 @@ func SearchTeams(c *middleware.Context) Response {
}
query := m.SearchTeamsQuery{
+ OrgId: c.OrgId,
Query: c.Query("query"),
Name: c.Query("name"),
Page: page,
Limit: perPage,
- OrgId: c.OrgId,
}
if err := bus.Dispatch(&query); err != nil {
@@ -83,7 +84,7 @@ func SearchTeams(c *middleware.Context) Response {
// GET /api/teams/:teamId
func GetTeamById(c *middleware.Context) Response {
- query := m.GetTeamByIdQuery{Id: c.ParamsInt64(":teamId")}
+ query := m.GetTeamByIdQuery{OrgId: c.OrgId, Id: c.ParamsInt64(":teamId")}
if err := bus.Dispatch(&query); err != nil {
if err == m.ErrTeamNotFound {
diff --git a/pkg/api/team_members.go b/pkg/api/team_members.go
index 412e142edb7..59dfc20b791 100644
--- a/pkg/api/team_members.go
+++ b/pkg/api/team_members.go
@@ -10,7 +10,7 @@ import (
// GET /api/teams/:teamId/members
func GetTeamMembers(c *middleware.Context) Response {
- query := m.GetTeamMembersQuery{TeamId: c.ParamsInt64(":teamId")}
+ query := m.GetTeamMembersQuery{OrgId: c.OrgId, TeamId: c.ParamsInt64(":teamId")}
if err := bus.Dispatch(&query); err != nil {
return ApiError(500, "Failed to get Team Members", err)
@@ -42,7 +42,7 @@ func AddTeamMember(c *middleware.Context, cmd m.AddTeamMemberCommand) Response {
// DELETE /api/teams/:teamId/members/:userId
func RemoveTeamMember(c *middleware.Context) Response {
- if err := bus.Dispatch(&m.RemoveTeamMemberCommand{TeamId: c.ParamsInt64(":teamId"), UserId: c.ParamsInt64(":userId")}); err != nil {
+ if err := bus.Dispatch(&m.RemoveTeamMemberCommand{OrgId: c.OrgId, TeamId: c.ParamsInt64(":teamId"), UserId: c.ParamsInt64(":userId")}); err != nil {
return ApiError(500, "Failed to remove Member from Team", err)
}
return ApiSuccess("Team Member removed")
diff --git a/pkg/models/org_user.go b/pkg/models/org_user.go
index 9379625d458..ca32cc50060 100644
--- a/pkg/models/org_user.go
+++ b/pkg/models/org_user.go
@@ -95,7 +95,10 @@ type UpdateOrgUserCommand struct {
// QUERIES
type GetOrgUsersQuery struct {
- OrgId int64
+ OrgId int64
+ Query string
+ Limit int
+
Result []*OrgUserDTO
}
diff --git a/pkg/models/team.go b/pkg/models/team.go
index d2912f431b8..f789f125aa1 100644
--- a/pkg/models/team.go
+++ b/pkg/models/team.go
@@ -37,18 +37,22 @@ type UpdateTeamCommand struct {
Id int64
Name string
Email string
+ OrgId int64 `json:"-"`
}
type DeleteTeamCommand struct {
- Id int64
+ OrgId int64
+ Id int64
}
type GetTeamByIdQuery struct {
+ OrgId int64
Id int64
Result *Team
}
type GetTeamsByUserQuery struct {
+ OrgId int64
UserId int64 `json:"userId"`
Result []*Team `json:"teams"`
}
diff --git a/pkg/models/team_member.go b/pkg/models/team_member.go
index 9970678a1ae..19cf657292d 100644
--- a/pkg/models/team_member.go
+++ b/pkg/models/team_member.go
@@ -31,6 +31,7 @@ type AddTeamMemberCommand struct {
}
type RemoveTeamMemberCommand struct {
+ OrgId int64 `json:"-"`
UserId int64
TeamId int64
}
@@ -39,6 +40,7 @@ type RemoveTeamMemberCommand struct {
// QUERIES
type GetTeamMembersQuery struct {
+ OrgId int64
TeamId int64
Result []*TeamMemberDTO
}
diff --git a/pkg/services/guardian/guardian.go b/pkg/services/guardian/guardian.go
index f4056841c33..b448561494d 100644
--- a/pkg/services/guardian/guardian.go
+++ b/pkg/services/guardian/guardian.go
@@ -160,7 +160,7 @@ func (g *DashboardGuardian) getTeams() ([]*m.Team, error) {
return g.groups, nil
}
- query := m.GetTeamsByUserQuery{UserId: g.user.UserId}
+ query := m.GetTeamsByUserQuery{OrgId: g.orgId, UserId: g.user.UserId}
err := bus.Dispatch(&query)
g.groups = query.Result
diff --git a/pkg/services/sqlstore/datasource_test.go b/pkg/services/sqlstore/datasource_test.go
index e6f0114ab4d..28f5b8eba9d 100644
--- a/pkg/services/sqlstore/datasource_test.go
+++ b/pkg/services/sqlstore/datasource_test.go
@@ -1,6 +1,8 @@
package sqlstore
import (
+ "os"
+ "strings"
"testing"
"github.com/go-xorm/xorm"
@@ -11,10 +13,33 @@ import (
"github.com/grafana/grafana/pkg/services/sqlstore/sqlutil"
)
+var (
+ dbSqlite = "sqlite"
+ dbMySql = "mysql"
+ dbPostgres = "postgres"
+)
+
func InitTestDB(t *testing.T) *xorm.Engine {
- x, err := xorm.NewEngine(sqlutil.TestDB_Sqlite3.DriverName, sqlutil.TestDB_Sqlite3.ConnStr)
- //x, err := xorm.NewEngine(sqlutil.TestDB_Mysql.DriverName, sqlutil.TestDB_Mysql.ConnStr)
- //x, err := xorm.NewEngine(sqlutil.TestDB_Postgres.DriverName, sqlutil.TestDB_Postgres.ConnStr)
+ selectedDb := dbSqlite
+ //selectedDb := dbMySql
+ //selectedDb := dbPostgres
+
+ var x *xorm.Engine
+ var err error
+
+ // environment variable present for test db?
+ if db, present := os.LookupEnv("GRAFANA_TEST_DB"); present {
+ selectedDb = db
+ }
+
+ switch strings.ToLower(selectedDb) {
+ case dbMySql:
+ x, err = xorm.NewEngine(sqlutil.TestDB_Mysql.DriverName, sqlutil.TestDB_Mysql.ConnStr)
+ case dbPostgres:
+ x, err = xorm.NewEngine(sqlutil.TestDB_Postgres.DriverName, sqlutil.TestDB_Postgres.ConnStr)
+ default:
+ x, err = xorm.NewEngine(sqlutil.TestDB_Sqlite3.DriverName, sqlutil.TestDB_Sqlite3.ConnStr)
+ }
// x.ShowSQL()
diff --git a/pkg/services/sqlstore/org_test.go b/pkg/services/sqlstore/org_test.go
index 59d96c4f8ca..5322dfd4748 100644
--- a/pkg/services/sqlstore/org_test.go
+++ b/pkg/services/sqlstore/org_test.go
@@ -123,6 +123,31 @@ func TestAccountDataAccess(t *testing.T) {
So(query.Result[0].Role, ShouldEqual, "Admin")
})
+ Convey("Can get organization users with query", func() {
+ query := m.GetOrgUsersQuery{
+ OrgId: ac1.OrgId,
+ Query: "ac1",
+ }
+ err := GetOrgUsers(&query)
+
+ So(err, ShouldBeNil)
+ So(len(query.Result), ShouldEqual, 1)
+ So(query.Result[0].Email, ShouldEqual, ac1.Email)
+ })
+
+ Convey("Can get organization users with query and limit", func() {
+ query := m.GetOrgUsersQuery{
+ OrgId: ac1.OrgId,
+ Query: "ac",
+ Limit: 1,
+ }
+ err := GetOrgUsers(&query)
+
+ So(err, ShouldBeNil)
+ So(len(query.Result), ShouldEqual, 1)
+ So(query.Result[0].Email, ShouldEqual, ac1.Email)
+ })
+
Convey("Can set using org", func() {
cmd := m.SetUsingOrgCommand{UserId: ac2.Id, OrgId: ac1.Id}
err := SetUsingOrg(&cmd)
diff --git a/pkg/services/sqlstore/org_users.go b/pkg/services/sqlstore/org_users.go
index 2c2a51fd362..0b991c73c55 100644
--- a/pkg/services/sqlstore/org_users.go
+++ b/pkg/services/sqlstore/org_users.go
@@ -2,6 +2,7 @@ package sqlstore
import (
"fmt"
+ "strings"
"time"
"github.com/grafana/grafana/pkg/bus"
@@ -69,9 +70,30 @@ func UpdateOrgUser(cmd *m.UpdateOrgUserCommand) error {
func GetOrgUsers(query *m.GetOrgUsersQuery) error {
query.Result = make([]*m.OrgUserDTO, 0)
+
sess := x.Table("org_user")
sess.Join("INNER", "user", fmt.Sprintf("org_user.user_id=%s.id", x.Dialect().Quote("user")))
- sess.Where("org_user.org_id=?", query.OrgId)
+
+ whereConditions := make([]string, 0)
+ whereParams := make([]interface{}, 0)
+
+ whereConditions = append(whereConditions, "org_user.org_id = ?")
+ whereParams = append(whereParams, query.OrgId)
+
+ if query.Query != "" {
+ queryWithWildcards := "%" + query.Query + "%"
+ whereConditions = append(whereConditions, "(email "+dialect.LikeStr()+" ? OR name "+dialect.LikeStr()+" ? OR login "+dialect.LikeStr()+" ?)")
+ whereParams = append(whereParams, queryWithWildcards, queryWithWildcards, queryWithWildcards)
+ }
+
+ if len(whereConditions) > 0 {
+ sess.Where(strings.Join(whereConditions, " AND "), whereParams...)
+ }
+
+ if query.Limit > 0 {
+ sess.Limit(query.Limit, 0)
+ }
+
sess.Cols("org_user.org_id", "org_user.user_id", "user.email", "user.login", "org_user.role", "user.last_seen_at")
sess.Asc("user.email", "user.login")
diff --git a/pkg/services/sqlstore/team.go b/pkg/services/sqlstore/team.go
index 98bb1a36eb9..ecb34ad927b 100644
--- a/pkg/services/sqlstore/team.go
+++ b/pkg/services/sqlstore/team.go
@@ -25,7 +25,7 @@ func init() {
func CreateTeam(cmd *m.CreateTeamCommand) error {
return inTransaction(func(sess *DBSession) error {
- if isNameTaken, err := isTeamNameTaken(cmd.Name, 0, sess); err != nil {
+ if isNameTaken, err := isTeamNameTaken(cmd.OrgId, cmd.Name, 0, sess); err != nil {
return err
} else if isNameTaken {
return m.ErrTeamNameTaken
@@ -50,7 +50,7 @@ func CreateTeam(cmd *m.CreateTeamCommand) error {
func UpdateTeam(cmd *m.UpdateTeamCommand) error {
return inTransaction(func(sess *DBSession) error {
- if isNameTaken, err := isTeamNameTaken(cmd.Name, cmd.Id, sess); err != nil {
+ if isNameTaken, err := isTeamNameTaken(cmd.OrgId, cmd.Name, cmd.Id, sess); err != nil {
return err
} else if isNameTaken {
return m.ErrTeamNameTaken
@@ -80,20 +80,20 @@ func UpdateTeam(cmd *m.UpdateTeamCommand) error {
func DeleteTeam(cmd *m.DeleteTeamCommand) error {
return inTransaction(func(sess *DBSession) error {
- if res, err := sess.Query("SELECT 1 from team WHERE id=?", cmd.Id); err != nil {
+ if res, err := sess.Query("SELECT 1 from team WHERE org_id=? and id=?", cmd.OrgId, cmd.Id); err != nil {
return err
} else if len(res) != 1 {
return m.ErrTeamNotFound
}
deletes := []string{
- "DELETE FROM team_member WHERE team_id = ?",
- "DELETE FROM team WHERE id = ?",
- "DELETE FROM dashboard_acl WHERE team_id = ?",
+ "DELETE FROM team_member WHERE org_id=? and team_id = ?",
+ "DELETE FROM team WHERE org_id=? and id = ?",
+ "DELETE FROM dashboard_acl WHERE org_id=? and team_id = ?",
}
for _, sql := range deletes {
- _, err := sess.Exec(sql, cmd.Id)
+ _, err := sess.Exec(sql, cmd.OrgId, cmd.Id)
if err != nil {
return err
}
@@ -102,9 +102,9 @@ func DeleteTeam(cmd *m.DeleteTeamCommand) error {
})
}
-func isTeamNameTaken(name string, existingId int64, sess *DBSession) (bool, error) {
+func isTeamNameTaken(orgId int64, name string, existingId int64, sess *DBSession) (bool, error) {
var team m.Team
- exists, err := sess.Where("name=?", name).Get(&team)
+ exists, err := sess.Where("org_id=? and name=?", orgId, name).Get(&team)
if err != nil {
return false, nil
@@ -128,6 +128,7 @@ func SearchTeams(query *m.SearchTeamsQuery) error {
sql.WriteString(`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
@@ -176,7 +177,7 @@ func SearchTeams(query *m.SearchTeamsQuery) error {
func GetTeamById(query *m.GetTeamByIdQuery) error {
var team m.Team
- exists, err := x.Id(query.Id).Get(&team)
+ exists, err := x.Where("org_id=? and id=?", query.OrgId, query.Id).Get(&team)
if err != nil {
return err
}
@@ -194,7 +195,7 @@ func GetTeamsByUser(query *m.GetTeamsByUserQuery) error {
sess := x.Table("team")
sess.Join("INNER", "team_member", "team.id=team_member.team_id")
- sess.Where("team_member.user_id=?", query.UserId)
+ sess.Where("team.org_id=? and team_member.user_id=?", query.OrgId, query.UserId)
err := sess.Find(&query.Result)
if err != nil {
@@ -206,13 +207,13 @@ func GetTeamsByUser(query *m.GetTeamsByUserQuery) error {
func AddTeamMember(cmd *m.AddTeamMemberCommand) error {
return inTransaction(func(sess *DBSession) error {
- if res, err := sess.Query("SELECT 1 from team_member WHERE team_id=? and user_id=?", cmd.TeamId, cmd.UserId); err != nil {
+ if res, err := sess.Query("SELECT 1 from team_member WHERE org_id=? and team_id=? and user_id=?", cmd.OrgId, cmd.TeamId, cmd.UserId); err != nil {
return err
} else if len(res) == 1 {
return m.ErrTeamMemberAlreadyAdded
}
- if res, err := sess.Query("SELECT 1 from team WHERE id=?", cmd.TeamId); err != nil {
+ if res, err := sess.Query("SELECT 1 from team WHERE org_id=? and id=?", cmd.OrgId, cmd.TeamId); err != nil {
return err
} else if len(res) != 1 {
return m.ErrTeamNotFound
@@ -233,8 +234,8 @@ func AddTeamMember(cmd *m.AddTeamMemberCommand) error {
func RemoveTeamMember(cmd *m.RemoveTeamMemberCommand) error {
return inTransaction(func(sess *DBSession) error {
- var rawSql = "DELETE FROM team_member WHERE team_id=? and user_id=?"
- _, err := sess.Exec(rawSql, cmd.TeamId, cmd.UserId)
+ var rawSql = "DELETE FROM team_member WHERE org_id=? and team_id=? and user_id=?"
+ _, err := sess.Exec(rawSql, cmd.OrgId, cmd.TeamId, cmd.UserId)
if err != nil {
return err
}
@@ -247,7 +248,7 @@ func GetTeamMembers(query *m.GetTeamMembersQuery) error {
query.Result = make([]*m.TeamMemberDTO, 0)
sess := x.Table("team_member")
sess.Join("INNER", "user", fmt.Sprintf("team_member.user_id=%s.id", x.Dialect().Quote("user")))
- sess.Where("team_member.team_id=?", query.TeamId)
+ sess.Where("team_member.org_id=? and team_member.team_id=?", query.OrgId, query.TeamId)
sess.Cols("user.org_id", "team_member.team_id", "team_member.user_id", "user.email", "user.login")
sess.Asc("user.login", "user.email")
diff --git a/pkg/services/sqlstore/team_test.go b/pkg/services/sqlstore/team_test.go
index dbae4545266..bebe59f4238 100644
--- a/pkg/services/sqlstore/team_test.go
+++ b/pkg/services/sqlstore/team_test.go
@@ -27,8 +27,9 @@ func TestTeamCommandsAndQueries(t *testing.T) {
userIds = append(userIds, userCmd.Result.Id)
}
- group1 := m.CreateTeamCommand{Name: "group1 name", Email: "test1@test.com"}
- group2 := m.CreateTeamCommand{Name: "group2 name", Email: "test2@test.com"}
+ var testOrgId int64 = 1
+ group1 := m.CreateTeamCommand{OrgId: testOrgId, Name: "group1 name", Email: "test1@test.com"}
+ group2 := m.CreateTeamCommand{OrgId: testOrgId, Name: "group2 name", Email: "test2@test.com"}
err := CreateTeam(&group1)
So(err, ShouldBeNil)
@@ -36,7 +37,7 @@ func TestTeamCommandsAndQueries(t *testing.T) {
So(err, ShouldBeNil)
Convey("Should be able to create teams and add users", func() {
- query := &m.SearchTeamsQuery{Name: "group1 name", Page: 1, Limit: 10}
+ query := &m.SearchTeamsQuery{OrgId: testOrgId, Name: "group1 name", Page: 1, Limit: 10}
err = SearchTeams(query)
So(err, ShouldBeNil)
So(query.Page, ShouldEqual, 1)
@@ -44,25 +45,27 @@ func TestTeamCommandsAndQueries(t *testing.T) {
team1 := query.Result.Teams[0]
So(team1.Name, ShouldEqual, "group1 name")
So(team1.Email, ShouldEqual, "test1@test.com")
+ So(team1.OrgId, ShouldEqual, testOrgId)
- err = AddTeamMember(&m.AddTeamMemberCommand{OrgId: 1, TeamId: team1.Id, UserId: userIds[0]})
+ err = AddTeamMember(&m.AddTeamMemberCommand{OrgId: testOrgId, TeamId: team1.Id, UserId: userIds[0]})
So(err, ShouldBeNil)
- q1 := &m.GetTeamMembersQuery{TeamId: team1.Id}
+ q1 := &m.GetTeamMembersQuery{OrgId: testOrgId, TeamId: team1.Id}
err = GetTeamMembers(q1)
So(err, ShouldBeNil)
So(q1.Result[0].TeamId, ShouldEqual, team1.Id)
So(q1.Result[0].Login, ShouldEqual, "loginuser0")
+ So(q1.Result[0].OrgId, ShouldEqual, testOrgId)
})
Convey("Should be able to search for teams", func() {
- query := &m.SearchTeamsQuery{Query: "group", Page: 1}
+ query := &m.SearchTeamsQuery{OrgId: testOrgId, Query: "group", Page: 1}
err = SearchTeams(query)
So(err, ShouldBeNil)
So(len(query.Result.Teams), ShouldEqual, 2)
So(query.Result.TotalCount, ShouldEqual, 2)
- query2 := &m.SearchTeamsQuery{Query: ""}
+ query2 := &m.SearchTeamsQuery{OrgId: testOrgId, Query: ""}
err = SearchTeams(query2)
So(err, ShouldBeNil)
So(len(query2.Result.Teams), ShouldEqual, 2)
@@ -70,9 +73,9 @@ func TestTeamCommandsAndQueries(t *testing.T) {
Convey("Should be able to return all teams a user is member of", func() {
groupId := group2.Result.Id
- err := AddTeamMember(&m.AddTeamMemberCommand{OrgId: 1, TeamId: groupId, UserId: userIds[0]})
+ err := AddTeamMember(&m.AddTeamMemberCommand{OrgId: testOrgId, TeamId: groupId, UserId: userIds[0]})
- query := &m.GetTeamsByUserQuery{UserId: userIds[0]}
+ query := &m.GetTeamsByUserQuery{OrgId: testOrgId, UserId: userIds[0]}
err = GetTeamsByUser(query)
So(err, ShouldBeNil)
So(len(query.Result), ShouldEqual, 1)
@@ -81,7 +84,7 @@ func TestTeamCommandsAndQueries(t *testing.T) {
})
Convey("Should be able to remove users from a group", func() {
- err = RemoveTeamMember(&m.RemoveTeamMemberCommand{TeamId: group1.Result.Id, UserId: userIds[0]})
+ err = RemoveTeamMember(&m.RemoveTeamMemberCommand{OrgId: testOrgId, TeamId: group1.Result.Id, UserId: userIds[0]})
So(err, ShouldBeNil)
q1 := &m.GetTeamMembersQuery{TeamId: group1.Result.Id}
@@ -92,20 +95,20 @@ func TestTeamCommandsAndQueries(t *testing.T) {
Convey("Should be able to remove a group with users and permissions", func() {
groupId := group2.Result.Id
- err := AddTeamMember(&m.AddTeamMemberCommand{OrgId: 1, TeamId: groupId, UserId: userIds[1]})
+ err := AddTeamMember(&m.AddTeamMemberCommand{OrgId: testOrgId, TeamId: groupId, UserId: userIds[1]})
So(err, ShouldBeNil)
- err = AddTeamMember(&m.AddTeamMemberCommand{OrgId: 1, TeamId: groupId, UserId: userIds[2]})
+ err = AddTeamMember(&m.AddTeamMemberCommand{OrgId: testOrgId, TeamId: groupId, UserId: userIds[2]})
So(err, ShouldBeNil)
- err = SetDashboardAcl(&m.SetDashboardAclCommand{DashboardId: 1, OrgId: 1, Permission: m.PERMISSION_EDIT, TeamId: groupId})
+ err = SetDashboardAcl(&m.SetDashboardAclCommand{DashboardId: 1, OrgId: testOrgId, Permission: m.PERMISSION_EDIT, TeamId: groupId})
- err = DeleteTeam(&m.DeleteTeamCommand{Id: groupId})
+ err = DeleteTeam(&m.DeleteTeamCommand{OrgId: testOrgId, Id: groupId})
So(err, ShouldBeNil)
- query := &m.GetTeamByIdQuery{Id: groupId}
+ query := &m.GetTeamByIdQuery{OrgId: testOrgId, Id: groupId}
err = GetTeamById(query)
So(err, ShouldEqual, m.ErrTeamNotFound)
- permQuery := &m.GetDashboardAclInfoListQuery{DashboardId: 1, OrgId: 1}
+ permQuery := &m.GetDashboardAclInfoListQuery{DashboardId: 1, OrgId: testOrgId}
err = GetDashboardAclInfoList(permQuery)
So(err, ShouldBeNil)
diff --git a/pkg/social/github_oauth.go b/pkg/social/github_oauth.go
index 6f00cd2cd9e..c74e8825bc1 100644
--- a/pkg/social/github_oauth.go
+++ b/pkg/social/github_oauth.go
@@ -210,7 +210,7 @@ func (s *SocialGithub) UserInfo(client *http.Client, token *oauth2.Token) (*Basi
if err != nil {
return nil, fmt.Errorf("Error getting user info: %s", err)
}
-
+ data.OrganizationsUrl = s.apiUrl + "/user/orgs"
userInfo := &BasicUserInfo{
Name: data.Login,
Login: data.Login,
diff --git a/public/app/core/components/Picker/UserPicker.tsx b/public/app/core/components/Picker/UserPicker.tsx
index 5c36505aeaa..77bf6c1fe15 100644
--- a/public/app/core/components/Picker/UserPicker.tsx
+++ b/public/app/core/components/Picker/UserPicker.tsx
@@ -31,7 +31,7 @@ class UserPicker extends Component {
this.debouncedSearch = debounce(this.search, 300, {
leading: true,
- trailing: false,
+ trailing: true,
});
}
@@ -39,10 +39,10 @@ class UserPicker extends Component {
const { toggleLoading, backendSrv } = this.props;
toggleLoading(true);
- return backendSrv.get(`/api/users/search?perpage=10&page=1&query=${query}`).then(result => {
- const users = result.users.map(user => {
+ return backendSrv.get(`/api/org/users?query=${query}&limit=10`).then(result => {
+ const users = result.map(user => {
return {
- id: user.id,
+ id: user.userId,
label: `${user.login} - ${user.email}`,
avatarUrl: user.avatarUrl,
login: user.login,
diff --git a/public/app/core/components/form_dropdown/form_dropdown.ts b/public/app/core/components/form_dropdown/form_dropdown.ts
index cf9c6b5efe0..7ac55e54cf1 100644
--- a/public/app/core/components/form_dropdown/form_dropdown.ts
+++ b/public/app/core/components/form_dropdown/form_dropdown.ts
@@ -34,6 +34,7 @@ export class FormDropdownCtrl {
lookupText: boolean;
placeholder: any;
startOpen: any;
+ debounce: number;
/** @ngInject **/
constructor(private $scope, $element, private $sce, private templateSrv, private $q) {
@@ -72,6 +73,10 @@ export class FormDropdownCtrl {
this.source(this.query, this.process.bind(this));
};
+ if (this.debounce) {
+ typeahead.lookup = _.debounce(typeahead.lookup, 500, { leading: true });
+ }
+
this.linkElement.keydown(evt => {
// trigger typeahead on down arrow or enter key
if (evt.keyCode === 40 || evt.keyCode === 13) {
@@ -263,6 +268,7 @@ export function formDropdownDirective() {
lookupText: '@',
placeholder: '@',
startOpen: '@',
+ debounce: '@',
},
};
}
diff --git a/public/app/core/components/query_part/query_part_editor.ts b/public/app/core/components/query_part/query_part_editor.ts
index 138da186238..5fd6e16c466 100644
--- a/public/app/core/components/query_part/query_part_editor.ts
+++ b/public/app/core/components/query_part/query_part_editor.ts
@@ -23,11 +23,13 @@ export function queryPartEditorDirective($compile, templateSrv) {
scope: {
part: '=',
handleEvent: '&',
+ debounce: '@',
},
link: function postLink($scope, elem) {
var part = $scope.part;
var partDef = part.def;
var $paramsContainer = elem.find('.query-part-parameters');
+ var debounceLookup = $scope.debounce;
$scope.partActions = [];
@@ -128,6 +130,10 @@ export function queryPartEditorDirective($compile, templateSrv) {
var items = this.source(this.query, $.proxy(this.process, this));
return items ? this.process(items) : items;
};
+
+ if (debounceLookup) {
+ typeahead.lookup = _.debounce(typeahead.lookup, 500, { leading: true });
+ }
}
$scope.showActionsMenu = function() {
diff --git a/public/app/core/directives/metric_segment.js b/public/app/core/directives/metric_segment.js
index 2754f8d8c6e..7ba4a5a5259 100644
--- a/public/app/core/directives/metric_segment.js
+++ b/public/app/core/directives/metric_segment.js
@@ -22,6 +22,7 @@ function (_, $, coreModule) {
segment: "=",
getOptions: "&",
onChange: "&",
+ debounce: "@",
},
link: function($scope, elem) {
var $input = $(inputTemplate);
@@ -30,6 +31,7 @@ function (_, $, coreModule) {
var options = null;
var cancelBlur = null;
var linkMode = true;
+ var debounceLookup = $scope.debounce;
$input.appendTo(elem);
$button.appendTo(elem);
@@ -135,6 +137,10 @@ function (_, $, coreModule) {
return items ? this.process(items) : items;
};
+ if (debounceLookup) {
+ typeahead.lookup = _.debounce(typeahead.lookup, 500, {leading: true});
+ }
+
$button.keydown(function(evt) {
// trigger typeahead on down arrow or enter key
if (evt.keyCode === 40 || evt.keyCode === 13) {
diff --git a/public/app/plugins/datasource/graphite/partials/query.editor.html b/public/app/plugins/datasource/graphite/partials/query.editor.html
index 6414a39c94f..51c25100c1e 100755
--- a/public/app/plugins/datasource/graphite/partials/query.editor.html
+++ b/public/app/plugins/datasource/graphite/partials/query.editor.html
@@ -13,9 +13,9 @@