mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Merge branch 'master' into dashboard-search-permissions-filter
This commit is contained in:
19
README.md
19
README.md
@@ -80,8 +80,11 @@ In your custom.ini uncomment (remove the leading `;`) sign. And set `app_mode =
|
|||||||
|
|
||||||
### Running tests
|
### Running tests
|
||||||
|
|
||||||
- You can run backend Golang tests using "go test ./pkg/...".
|
#### Frontend
|
||||||
- Execute all frontend tests with "npm run test"
|
Execute all frontend tests
|
||||||
|
```bash
|
||||||
|
npm run test
|
||||||
|
```
|
||||||
|
|
||||||
Writing & watching frontend tests (we have two test runners)
|
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`
|
- Start watcher: `npm run karma`
|
||||||
- Karma+Mocha runs all files that end with the name "_specs.ts".
|
- 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
|
## Contribute
|
||||||
|
|
||||||
If you have any idea for an improvement or found a bug, do not hesitate to open an issue.
|
If you have any idea for an improvement or found a bug, do not hesitate to open an issue.
|
||||||
|
|||||||
@@ -1,49 +1,107 @@
|
|||||||
+++
|
+++
|
||||||
title = "Docs Home"
|
title = "Grafana documentation"
|
||||||
description = "Install guide for Grafana"
|
description = "Guides, Installation & Feature Documentation"
|
||||||
keywords = ["grafana", "installation", "documentation"]
|
keywords = ["grafana", "installation", "documentation"]
|
||||||
type = "docs"
|
type = "docs"
|
||||||
aliases = ["v1.1", "guides/reference/admin"]
|
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
|
<h2>Installing Grafana</h2>
|
||||||
visualizing time series data for infrastructure and application analytics but many use it in
|
<div class="nav-cards">
|
||||||
other domains including industrial sensors, home automation, weather, and process control.
|
<a href="{{< relref "installation/debian.md" >}}" class="nav-cards__item nav-cards__item--install">
|
||||||
|
<div class="nav-cards__icon fa fa-linux">
|
||||||
|
</div>
|
||||||
|
<h5>Installing on Linux</h5>
|
||||||
|
</a>
|
||||||
|
<a href="{{< relref "installation/mac.md" >}}" class="nav-cards__item nav-cards__item--install">
|
||||||
|
<div class="nav-cards__icon fa fa-apple">
|
||||||
|
</div>
|
||||||
|
<h5>Installing on Mac OS X</h5>
|
||||||
|
</a>
|
||||||
|
<a href="{{< relref "installation/windows.md" >}}" class="nav-cards__item nav-cards__item--install">
|
||||||
|
<div class="nav-cards__icon fa fa-windows">
|
||||||
|
</div>
|
||||||
|
<h5>Installing on Windows</h5>
|
||||||
|
</a>
|
||||||
|
<a href="https://grafana.com/cloud/grafana" class="nav-cards__item nav-cards__item--install">
|
||||||
|
<div class="nav-cards__icon fa fa-cloud">
|
||||||
|
</div>
|
||||||
|
<h5>Grafana Cloud</h5>
|
||||||
|
</a>
|
||||||
|
<a href="https://grafana.com/grafana/download" class="nav-cards__item nav-cards__item--install">
|
||||||
|
<div class="nav-cards__icon fa fa-moon-o">
|
||||||
|
</div>
|
||||||
|
<h5>Nightly Builds</h5>
|
||||||
|
</a>
|
||||||
|
<div class="nav-cards__item nav-cards__item--install">
|
||||||
|
<h5>For other platforms Read the <a href="{{< relref "project/building_from_source.md" >}}">build from source</a>
|
||||||
|
instructions for more information.</h5>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
## Installing Grafana
|
<h2>Guides</h2>
|
||||||
- [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)
|
|
||||||
|
|
||||||
For other platforms Read the [build from source]({{< relref "project/building_from_source.md" >}})
|
<div class="nav-cards">
|
||||||
instructions for more information.
|
<a href="https://grafana.com/grafana" class="nav-cards__item nav-cards__item--guide">
|
||||||
|
<h4>What is Grafana?</h4>
|
||||||
|
<p>Grafana feature highlights.</p>
|
||||||
|
</a>
|
||||||
|
<a href="{{< relref "installation/configuration.md" >}}" class="nav-cards__item nav-cards__item--guide">
|
||||||
|
<h4>Configure Grafana</h4>
|
||||||
|
<p>Article on all the Grafana configuration and setup options.</p>
|
||||||
|
</a>
|
||||||
|
<a href="{{< relref "guides/getting_started.md" >}}" class="nav-cards__item nav-cards__item--guide">
|
||||||
|
<h4>Getting Started</h4>
|
||||||
|
<p>A guide that walks you through the basics of using Grafana</p>
|
||||||
|
</a>
|
||||||
|
<a href="{{< relref "administration/provisioning.md" >}}" class="nav-cards__item nav-cards__item--guide">
|
||||||
|
<h4>Provisioning</h4>
|
||||||
|
<p>A guide to help you automate your Grafana setup & configuration.</p>
|
||||||
|
</a>
|
||||||
|
<a href="{{< relref "guides/whats-new-in-v5.md" >}}" class="nav-cards__item nav-cards__item--guide">
|
||||||
|
<h4>What's new in v5.0</h4>
|
||||||
|
<p>Article on all the new cool features and enhancements in v5.0</p>
|
||||||
|
</a>
|
||||||
|
<a href="{{< relref "tutorials/screencasts.md" >}}" class="nav-cards__item nav-cards__item--guide">
|
||||||
|
<h4>Screencasts</h4>
|
||||||
|
<p>Video tutorials & guides</p>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
## Configuring Grafana
|
<h2>Data Source Guides</h2>
|
||||||
|
<div class="nav-cards">
|
||||||
The back-end web server has a number of configuration options. Go the
|
<a href="{{< relref "features/datasources/graphite.md" >}}" class="nav-cards__item nav-cards__item--ds">
|
||||||
[Configuration]({{< relref "installation/configuration.md" >}}) page for details on all
|
<img src="/img/docs/logos/icon_graphite.svg" >
|
||||||
those options.
|
<h5>Graphite</h5>
|
||||||
|
</a>
|
||||||
|
<a href="{{< relref "features/datasources/elasticsearch.md" >}}" class="nav-cards__item nav-cards__item--ds">
|
||||||
## Getting Started
|
<img src="/img/docs/logos/icon_elasticsearch.svg" >
|
||||||
|
<h5>Elasticsearch</h5>
|
||||||
- [Getting Started]({{< relref "guides/getting_started.md" >}})
|
</a>
|
||||||
- [Basic Concepts]({{< relref "guides/basic_concepts.md" >}})
|
<a href="{{< relref "features/datasources/influxdb.md" >}}" class="nav-cards__item nav-cards__item--ds">
|
||||||
- [Screencasts]({{< relref "tutorials/screencasts.md" >}})
|
<img src="/img/docs/logos/icon_influxdb.svg" >
|
||||||
|
<h5>InfluxDB</h5>
|
||||||
## Data Source Guides
|
</a>
|
||||||
|
<a href="{{< relref "features/datasources/prometheus.md" >}}" class="nav-cards__item nav-cards__item--ds">
|
||||||
- [Graphite]({{< relref "features/datasources/graphite.md" >}})
|
<img src="/img/docs/logos/icon_prometheus.svg" >
|
||||||
- [Elasticsearch]({{< relref "features/datasources/elasticsearch.md" >}})
|
<h5>Prometheus</h5>
|
||||||
- [InfluxDB]({{< relref "features/datasources/influxdb.md" >}})
|
</a>
|
||||||
- [Prometheus]({{< relref "features/datasources/prometheus.md" >}})
|
<a href="{{< relref "features/datasources/opentsdb.md" >}}" class="nav-cards__item nav-cards__item--ds">
|
||||||
- [OpenTSDB]({{< relref "features/datasources/opentsdb.md" >}})
|
<img src="/img/docs/logos/icon_opentsdb.png" >
|
||||||
- [MySQL]({{< relref "features/datasources/mysql.md" >}})
|
<h5>OpenTSDB</h5>
|
||||||
- [Postgres]({{< relref "features/datasources/postgres.md" >}})
|
</a>
|
||||||
- [Cloudwatch]({{< relref "features/datasources/cloudwatch.md" >}})
|
<a href="{{< relref "features/datasources/mysql.md" >}}" class="nav-cards__item nav-cards__item--ds">
|
||||||
|
<img src="/img/docs/logos/icon_mysql.png" >
|
||||||
|
<h5>MySQL</h5>
|
||||||
|
</a>
|
||||||
|
<a href="{{< relref "features/datasources/postgres.md" >}}" class="nav-cards__item nav-cards__item--ds">
|
||||||
|
<img src="/img/docs/logos/icon_postgres.svg" >
|
||||||
|
<h5>Postgres</h5>
|
||||||
|
</a>
|
||||||
|
<a href="{{< relref "features/datasources/cloudwatch.md" >}}" class="nav-cards__item nav-cards__item--ds">
|
||||||
|
<img src="/img/docs/logos/icon_cloudwatch.svg">
|
||||||
|
<h5>Cloudwatch</h5>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||||
|
|
||||||
@@ -217,6 +218,10 @@ func PostDashboard(c *middleware.Context, cmd m.SaveDashboardCommand) Response {
|
|||||||
return ApiError(400, m.ErrDashboardTitleEmpty.Error(), nil)
|
return ApiError(400, m.ErrDashboardTitleEmpty.Error(), nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if dash.IsFolder && strings.ToLower(dash.Title) == strings.ToLower(m.RootFolderName) {
|
||||||
|
return ApiError(400, "A folder already exists with that name", nil)
|
||||||
|
}
|
||||||
|
|
||||||
if dash.Id == 0 {
|
if dash.Id == 0 {
|
||||||
limitReached, err := middleware.QuotaReached(c, "dashboard")
|
limitReached, err := middleware.QuotaReached(c, "dashboard")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -237,8 +242,11 @@ func PostDashboard(c *middleware.Context, cmd m.SaveDashboardCommand) Response {
|
|||||||
|
|
||||||
dashboard, err := dashboards.GetRepository().SaveDashboard(dashItem)
|
dashboard, err := dashboards.GetRepository().SaveDashboard(dashItem)
|
||||||
|
|
||||||
if err == m.ErrDashboardTitleEmpty {
|
if err == m.ErrDashboardTitleEmpty ||
|
||||||
return ApiError(400, m.ErrDashboardTitleEmpty.Error(), nil)
|
err == m.ErrDashboardWithSameNameAsFolder ||
|
||||||
|
err == m.ErrDashboardFolderWithSameNameAsDashboard ||
|
||||||
|
err == m.ErrDashboardTypeMismatch {
|
||||||
|
return ApiError(400, err.Error(), nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err == m.ErrDashboardContainsInvalidAlertData {
|
if err == m.ErrDashboardContainsInvalidAlertData {
|
||||||
|
|||||||
@@ -46,26 +46,30 @@ func addOrgUserHelper(cmd m.AddOrgUserCommand) Response {
|
|||||||
|
|
||||||
// GET /api/org/users
|
// GET /api/org/users
|
||||||
func GetOrgUsersForCurrentOrg(c *middleware.Context) Response {
|
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
|
// GET /api/orgs/:orgId/users
|
||||||
func GetOrgUsers(c *middleware.Context) Response {
|
func GetOrgUsers(c *middleware.Context) Response {
|
||||||
return getOrgUsersHelper(c.ParamsInt64(":orgId"))
|
return getOrgUsersHelper(c.ParamsInt64(":orgId"), "", 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getOrgUsersHelper(orgId int64) Response {
|
func getOrgUsersHelper(orgId int64, query string, limit int) Response {
|
||||||
query := m.GetOrgUsersQuery{OrgId: orgId}
|
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)
|
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)
|
user.AvatarUrl = dtos.GetGravatarUrl(user.Email)
|
||||||
}
|
}
|
||||||
|
|
||||||
return Json(200, query.Result)
|
return Json(200, q.Result)
|
||||||
}
|
}
|
||||||
|
|
||||||
// PATCH /api/org/users/:userId
|
// PATCH /api/org/users/:userId
|
||||||
|
|||||||
@@ -13,17 +13,22 @@ import (
|
|||||||
|
|
||||||
// Typed errors
|
// Typed errors
|
||||||
var (
|
var (
|
||||||
ErrDashboardNotFound = errors.New("Dashboard not found")
|
ErrDashboardNotFound = errors.New("Dashboard not found")
|
||||||
ErrDashboardSnapshotNotFound = errors.New("Dashboard snapshot not found")
|
ErrDashboardSnapshotNotFound = errors.New("Dashboard snapshot not found")
|
||||||
ErrDashboardWithSameUIDExists = errors.New("A dashboard with the same uid already exists")
|
ErrDashboardWithSameUIDExists = errors.New("A dashboard with the same uid already exists")
|
||||||
ErrDashboardWithSameNameInFolderExists = errors.New("A dashboard with the same name in the folder already exists")
|
ErrDashboardWithSameNameInFolderExists = errors.New("A dashboard with the same name in the folder already exists")
|
||||||
ErrDashboardVersionMismatch = errors.New("The dashboard has been changed by someone else")
|
ErrDashboardVersionMismatch = errors.New("The dashboard has been changed by someone else")
|
||||||
ErrDashboardTitleEmpty = errors.New("Dashboard title cannot be empty")
|
ErrDashboardTitleEmpty = errors.New("Dashboard title cannot be empty")
|
||||||
ErrDashboardFolderCannotHaveParent = errors.New("A Dashboard Folder cannot be added to another folder")
|
ErrDashboardFolderCannotHaveParent = errors.New("A Dashboard Folder cannot be added to another folder")
|
||||||
ErrDashboardContainsInvalidAlertData = errors.New("Invalid alert data. Cannot save dashboard")
|
ErrDashboardContainsInvalidAlertData = errors.New("Invalid alert data. Cannot save dashboard")
|
||||||
ErrDashboardFailedToUpdateAlertData = errors.New("Failed to save alert data")
|
ErrDashboardFailedToUpdateAlertData = errors.New("Failed to save alert data")
|
||||||
ErrDashboardsWithSameSlugExists = errors.New("Multiple dashboards with the same slug exists")
|
ErrDashboardsWithSameSlugExists = errors.New("Multiple dashboards with the same slug exists")
|
||||||
ErrDashboardFailedGenerateUniqueUid = errors.New("Failed to generate unique dashboard id")
|
ErrDashboardFailedGenerateUniqueUid = errors.New("Failed to generate unique dashboard id")
|
||||||
|
ErrDashboardExistingCannotChangeToDashboard = errors.New("An existing folder cannot be changed to a dashboard")
|
||||||
|
ErrDashboardTypeMismatch = errors.New("Dashboard cannot be changed to a folder")
|
||||||
|
ErrDashboardFolderWithSameNameAsDashboard = errors.New("Folder name cannot be the same as one of its dashboards")
|
||||||
|
ErrDashboardWithSameNameAsFolder = errors.New("Dashboard name cannot be the same as folder")
|
||||||
|
RootFolderName = "General"
|
||||||
)
|
)
|
||||||
|
|
||||||
type UpdatePluginDashboardError struct {
|
type UpdatePluginDashboardError struct {
|
||||||
@@ -95,14 +100,21 @@ func NewDashboardFromJson(data *simplejson.Json) *Dashboard {
|
|||||||
dash.Data = data
|
dash.Data = data
|
||||||
dash.Title = dash.Data.Get("title").MustString()
|
dash.Title = dash.Data.Get("title").MustString()
|
||||||
dash.UpdateSlug()
|
dash.UpdateSlug()
|
||||||
|
update := false
|
||||||
|
|
||||||
if id, err := dash.Data.Get("id").Float64(); err == nil {
|
if id, err := dash.Data.Get("id").Float64(); err == nil {
|
||||||
dash.Id = int64(id)
|
dash.Id = int64(id)
|
||||||
|
update = true
|
||||||
|
}
|
||||||
|
|
||||||
if version, err := dash.Data.Get("version").Float64(); err == nil {
|
if uid, err := dash.Data.Get("uid").String(); err == nil {
|
||||||
dash.Version = int(version)
|
dash.Uid = uid
|
||||||
dash.Updated = time.Now()
|
update = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if version, err := dash.Data.Get("version").Float64(); err == nil && update {
|
||||||
|
dash.Version = int(version)
|
||||||
|
dash.Updated = time.Now()
|
||||||
} else {
|
} else {
|
||||||
dash.Data.Set("version", 0)
|
dash.Data.Set("version", 0)
|
||||||
dash.Created = time.Now()
|
dash.Created = time.Now()
|
||||||
@@ -113,10 +125,6 @@ func NewDashboardFromJson(data *simplejson.Json) *Dashboard {
|
|||||||
dash.GnetId = int64(gnetId)
|
dash.GnetId = int64(gnetId)
|
||||||
}
|
}
|
||||||
|
|
||||||
if uid, err := dash.Data.Get("uid").String(); err == nil {
|
|
||||||
dash.Uid = uid
|
|
||||||
}
|
|
||||||
|
|
||||||
return dash
|
return dash
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -95,7 +95,10 @@ type UpdateOrgUserCommand struct {
|
|||||||
// QUERIES
|
// QUERIES
|
||||||
|
|
||||||
type GetOrgUsersQuery struct {
|
type GetOrgUsersQuery struct {
|
||||||
OrgId int64
|
OrgId int64
|
||||||
|
Query string
|
||||||
|
Limit int
|
||||||
|
|
||||||
Result []*OrgUserDTO
|
Result []*OrgUserDTO
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ func ImportDashboard(cmd *ImportDashboardCommand) error {
|
|||||||
Path: cmd.Path,
|
Path: cmd.Path,
|
||||||
Revision: dashboard.Data.Get("revision").MustInt64(1),
|
Revision: dashboard.Data.Get("revision").MustInt64(1),
|
||||||
ImportedUri: "db/" + saveCmd.Result.Slug,
|
ImportedUri: "db/" + saveCmd.Result.Slug,
|
||||||
|
ImportedUrl: saveCmd.Result.GetUrl(),
|
||||||
ImportedRevision: dashboard.Data.Get("revision").MustInt64(1),
|
ImportedRevision: dashboard.Data.Get("revision").MustInt64(1),
|
||||||
Imported: true,
|
Imported: true,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ type PluginDashboardInfoDTO struct {
|
|||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Imported bool `json:"imported"`
|
Imported bool `json:"imported"`
|
||||||
ImportedUri string `json:"importedUri"`
|
ImportedUri string `json:"importedUri"`
|
||||||
|
ImportedUrl string `json:"importedUrl"`
|
||||||
Slug string `json:"slug"`
|
Slug string `json:"slug"`
|
||||||
DashboardId int64 `json:"dashboardId"`
|
DashboardId int64 `json:"dashboardId"`
|
||||||
ImportedRevision int64 `json:"importedRevision"`
|
ImportedRevision int64 `json:"importedRevision"`
|
||||||
@@ -64,6 +65,7 @@ func GetPluginDashboards(orgId int64, pluginId string) ([]*PluginDashboardInfoDT
|
|||||||
res.DashboardId = existingDash.Id
|
res.DashboardId = existingDash.Id
|
||||||
res.Imported = true
|
res.Imported = true
|
||||||
res.ImportedUri = "db/" + existingDash.Slug
|
res.ImportedUri = "db/" + existingDash.Slug
|
||||||
|
res.ImportedUrl = existingDash.GetUrl()
|
||||||
res.ImportedRevision = existingDash.Data.Get("revision").MustInt64(1)
|
res.ImportedRevision = existingDash.Data.Get("revision").MustInt64(1)
|
||||||
existingMatches[existingDash.Id] = true
|
existingMatches[existingDash.Id] = true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,47 +32,36 @@ func SaveDashboard(cmd *m.SaveDashboardCommand) error {
|
|||||||
return inTransaction(func(sess *DBSession) error {
|
return inTransaction(func(sess *DBSession) error {
|
||||||
dash := cmd.GetDashboardModel()
|
dash := cmd.GetDashboardModel()
|
||||||
|
|
||||||
// try get existing dashboard
|
if err := getExistingDashboardForUpdate(sess, dash, cmd); err != nil {
|
||||||
var existing m.Dashboard
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if dash.Id != 0 {
|
var existingByTitleAndFolder m.Dashboard
|
||||||
dashWithIdExists, err := sess.Where("id=? AND org_id=?", dash.Id, dash.OrgId).Get(&existing)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !dashWithIdExists {
|
|
||||||
return m.ErrDashboardNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
// check for is someone else has written in between
|
dashWithTitleAndFolderExists, err := sess.Where("org_id=? AND slug=? AND (is_folder=? OR folder_id=?)", dash.OrgId, dash.Slug, dialect.BooleanStr(true), dash.FolderId).Get(&existingByTitleAndFolder)
|
||||||
if dash.Version != existing.Version {
|
if err != nil {
|
||||||
if cmd.Overwrite {
|
return err
|
||||||
dash.Version = existing.Version
|
}
|
||||||
} else {
|
|
||||||
return m.ErrDashboardVersionMismatch
|
if dashWithTitleAndFolderExists {
|
||||||
|
if dash.Id != existingByTitleAndFolder.Id {
|
||||||
|
if existingByTitleAndFolder.IsFolder && !cmd.IsFolder {
|
||||||
|
return m.ErrDashboardWithSameNameAsFolder
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// do not allow plugin dashboard updates without overwrite flag
|
if !existingByTitleAndFolder.IsFolder && cmd.IsFolder {
|
||||||
if existing.PluginId != "" && cmd.Overwrite == false {
|
return m.ErrDashboardFolderWithSameNameAsDashboard
|
||||||
return m.UpdatePluginDashboardError{PluginId: existing.PluginId}
|
}
|
||||||
}
|
|
||||||
} else if dash.Uid != "" {
|
|
||||||
var sameUid m.Dashboard
|
|
||||||
sameUidExists, err := sess.Where("org_id=? AND uid=?", dash.OrgId, dash.Uid).Get(&sameUid)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if sameUidExists {
|
if cmd.Overwrite {
|
||||||
// another dashboard with same uid
|
dash.Id = existingByTitleAndFolder.Id
|
||||||
if dash.Id != sameUid.Id {
|
dash.Version = existingByTitleAndFolder.Version
|
||||||
if cmd.Overwrite {
|
|
||||||
dash.Id = sameUid.Id
|
if dash.Uid == "" {
|
||||||
dash.Version = sameUid.Version
|
dash.Uid = existingByTitleAndFolder.Uid
|
||||||
} else {
|
|
||||||
return m.ErrDashboardWithSameUIDExists
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
return m.ErrDashboardWithSameNameInFolderExists
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -86,11 +75,6 @@ func SaveDashboard(cmd *m.SaveDashboardCommand) error {
|
|||||||
dash.Data.Set("uid", uid)
|
dash.Data.Set("uid", uid)
|
||||||
}
|
}
|
||||||
|
|
||||||
err := guaranteeDashboardNameIsUniqueInFolder(sess, dash)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = setHasAcl(sess, dash)
|
err = setHasAcl(sess, dash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -162,6 +146,72 @@ func SaveDashboard(cmd *m.SaveDashboardCommand) error {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getExistingDashboardForUpdate(sess *DBSession, dash *m.Dashboard, cmd *m.SaveDashboardCommand) (err error) {
|
||||||
|
dashWithIdExists := false
|
||||||
|
var existingById m.Dashboard
|
||||||
|
|
||||||
|
if dash.Id > 0 {
|
||||||
|
dashWithIdExists, err = sess.Where("id=? AND org_id=?", dash.Id, dash.OrgId).Get(&existingById)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !dashWithIdExists {
|
||||||
|
return m.ErrDashboardNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
if dash.Uid == "" {
|
||||||
|
dash.Uid = existingById.Uid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dashWithUidExists := false
|
||||||
|
var existingByUid m.Dashboard
|
||||||
|
|
||||||
|
if dash.Uid != "" {
|
||||||
|
dashWithUidExists, err = sess.Where("org_id=? AND uid=?", dash.OrgId, dash.Uid).Get(&existingByUid)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !dashWithIdExists && !dashWithUidExists {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if dashWithIdExists && dashWithUidExists && existingById.Id != existingByUid.Id {
|
||||||
|
return m.ErrDashboardWithSameUIDExists
|
||||||
|
}
|
||||||
|
|
||||||
|
existing := existingById
|
||||||
|
|
||||||
|
if !dashWithIdExists && dashWithUidExists {
|
||||||
|
dash.Id = existingByUid.Id
|
||||||
|
existing = existingByUid
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existing.IsFolder && !cmd.IsFolder) ||
|
||||||
|
(!existing.IsFolder && cmd.IsFolder) {
|
||||||
|
return m.ErrDashboardTypeMismatch
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for is someone else has written in between
|
||||||
|
if dash.Version != existing.Version {
|
||||||
|
if cmd.Overwrite {
|
||||||
|
dash.Version = existing.Version
|
||||||
|
} else {
|
||||||
|
return m.ErrDashboardVersionMismatch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// do not allow plugin dashboard updates without overwrite flag
|
||||||
|
if existing.PluginId != "" && cmd.Overwrite == false {
|
||||||
|
return m.UpdatePluginDashboardError{PluginId: existing.PluginId}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func generateNewDashboardUid(sess *DBSession, orgId int64) (string, error) {
|
func generateNewDashboardUid(sess *DBSession, orgId int64) (string, error) {
|
||||||
for i := 0; i < 3; i++ {
|
for i := 0; i < 3; i++ {
|
||||||
uid := generateNewUid()
|
uid := generateNewUid()
|
||||||
@@ -179,23 +229,6 @@ func generateNewDashboardUid(sess *DBSession, orgId int64) (string, error) {
|
|||||||
return "", m.ErrDashboardFailedGenerateUniqueUid
|
return "", m.ErrDashboardFailedGenerateUniqueUid
|
||||||
}
|
}
|
||||||
|
|
||||||
func guaranteeDashboardNameIsUniqueInFolder(sess *DBSession, dash *m.Dashboard) error {
|
|
||||||
var sameNameInFolder m.Dashboard
|
|
||||||
sameNameInFolderExist, err := sess.Where("org_id=? AND title=? AND folder_id = ? AND uid <> ?",
|
|
||||||
dash.OrgId, dash.Title, dash.FolderId, dash.Uid).
|
|
||||||
Get(&sameNameInFolder)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if sameNameInFolderExist {
|
|
||||||
return m.ErrDashboardWithSameNameInFolderExists
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func setHasAcl(sess *DBSession, dash *m.Dashboard) error {
|
func setHasAcl(sess *DBSession, dash *m.Dashboard) error {
|
||||||
// check if parent has acl
|
// check if parent has acl
|
||||||
if dash.FolderId > 0 {
|
if dash.FolderId > 0 {
|
||||||
@@ -472,9 +505,7 @@ func GetDashboardPermissionsForUser(query *m.GetDashboardPermissionsForUserQuery
|
|||||||
params = append(params, query.UserId)
|
params = append(params, query.UserId)
|
||||||
params = append(params, dialect.BooleanStr(false))
|
params = append(params, dialect.BooleanStr(false))
|
||||||
|
|
||||||
x.ShowSQL(true)
|
|
||||||
err := x.Sql(sql, params...).Find(&query.Result)
|
err := x.Sql(sql, params...).Find(&query.Result)
|
||||||
x.ShowSQL(false)
|
|
||||||
|
|
||||||
for _, p := range query.Result {
|
for _, p := range query.Result {
|
||||||
p.PermissionName = p.Permission.String()
|
p.PermissionName = p.Permission.String()
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ func TestDashboardDataAccess(t *testing.T) {
|
|||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
})
|
})
|
||||||
|
|
||||||
Convey("Should return error if no dashboard is updated", func() {
|
Convey("Should return not found error if no dashboard is found for update", func() {
|
||||||
cmd := m.SaveDashboardCommand{
|
cmd := m.SaveDashboardCommand{
|
||||||
OrgId: 1,
|
OrgId: 1,
|
||||||
Overwrite: true,
|
Overwrite: true,
|
||||||
@@ -112,7 +112,7 @@ func TestDashboardDataAccess(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
err := SaveDashboard(&cmd)
|
err := SaveDashboard(&cmd)
|
||||||
So(err, ShouldNotBeNil)
|
So(err, ShouldEqual, m.ErrDashboardNotFound)
|
||||||
})
|
})
|
||||||
|
|
||||||
Convey("Should not be able to overwrite dashboard in another org", func() {
|
Convey("Should not be able to overwrite dashboard in another org", func() {
|
||||||
@@ -130,7 +130,382 @@ func TestDashboardDataAccess(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
err := SaveDashboard(&cmd)
|
err := SaveDashboard(&cmd)
|
||||||
So(err, ShouldNotBeNil)
|
So(err, ShouldEqual, m.ErrDashboardNotFound)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Should be able to save dashboards with same name in different folders", func() {
|
||||||
|
firstSaveCmd := m.SaveDashboardCommand{
|
||||||
|
OrgId: 1,
|
||||||
|
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||||
|
"id": nil,
|
||||||
|
"title": "test dash folder and title",
|
||||||
|
"tags": []interface{}{},
|
||||||
|
"uid": "randomHash",
|
||||||
|
}),
|
||||||
|
FolderId: 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := SaveDashboard(&firstSaveCmd)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
secondSaveCmd := m.SaveDashboardCommand{
|
||||||
|
OrgId: 1,
|
||||||
|
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||||
|
"id": nil,
|
||||||
|
"title": "test dash folder and title",
|
||||||
|
"tags": []interface{}{},
|
||||||
|
"uid": "moreRandomHash",
|
||||||
|
}),
|
||||||
|
FolderId: 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = SaveDashboard(&secondSaveCmd)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(firstSaveCmd.Result.Id, ShouldNotEqual, secondSaveCmd.Result.Id)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Should be able to overwrite dashboard in same folder using title", func() {
|
||||||
|
insertTestDashboard("Dash", 1, 0, false, "prod", "webapp")
|
||||||
|
folder := insertTestDashboard("Folder", 1, 0, true, "prod", "webapp")
|
||||||
|
dashInFolder := insertTestDashboard("Dash", 1, folder.Id, false, "prod", "webapp")
|
||||||
|
|
||||||
|
cmd := m.SaveDashboardCommand{
|
||||||
|
OrgId: 1,
|
||||||
|
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||||
|
"title": "Dash",
|
||||||
|
}),
|
||||||
|
FolderId: folder.Id,
|
||||||
|
Overwrite: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := SaveDashboard(&cmd)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(cmd.Result.Id, ShouldEqual, dashInFolder.Id)
|
||||||
|
So(cmd.Result.Uid, ShouldEqual, dashInFolder.Uid)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Should be able to overwrite dashboard in General folder using title", func() {
|
||||||
|
dashInGeneral := insertTestDashboard("Dash", 1, 0, false, "prod", "webapp")
|
||||||
|
folder := insertTestDashboard("Folder", 1, 0, true, "prod", "webapp")
|
||||||
|
insertTestDashboard("Dash", 1, folder.Id, false, "prod", "webapp")
|
||||||
|
|
||||||
|
cmd := m.SaveDashboardCommand{
|
||||||
|
OrgId: 1,
|
||||||
|
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||||
|
"title": "Dash",
|
||||||
|
}),
|
||||||
|
FolderId: 0,
|
||||||
|
Overwrite: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := SaveDashboard(&cmd)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(cmd.Result.Id, ShouldEqual, dashInGeneral.Id)
|
||||||
|
So(cmd.Result.Uid, ShouldEqual, dashInGeneral.Uid)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Should not be able to overwrite folder with dashboard in general folder using title", func() {
|
||||||
|
cmd := m.SaveDashboardCommand{
|
||||||
|
OrgId: 1,
|
||||||
|
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||||
|
"title": savedFolder.Title,
|
||||||
|
}),
|
||||||
|
FolderId: 0,
|
||||||
|
IsFolder: false,
|
||||||
|
Overwrite: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := SaveDashboard(&cmd)
|
||||||
|
So(err, ShouldEqual, m.ErrDashboardWithSameNameAsFolder)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Should not be able to overwrite folder with dashboard in folder using title", func() {
|
||||||
|
cmd := m.SaveDashboardCommand{
|
||||||
|
OrgId: 1,
|
||||||
|
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||||
|
"title": savedFolder.Title,
|
||||||
|
}),
|
||||||
|
FolderId: savedFolder.Id,
|
||||||
|
IsFolder: false,
|
||||||
|
Overwrite: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := SaveDashboard(&cmd)
|
||||||
|
So(err, ShouldEqual, m.ErrDashboardWithSameNameAsFolder)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Should not be able to overwrite folder with dashboard using id", func() {
|
||||||
|
cmd := m.SaveDashboardCommand{
|
||||||
|
OrgId: 1,
|
||||||
|
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||||
|
"id": savedFolder.Id,
|
||||||
|
"title": "new title",
|
||||||
|
}),
|
||||||
|
IsFolder: false,
|
||||||
|
Overwrite: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := SaveDashboard(&cmd)
|
||||||
|
So(err, ShouldEqual, m.ErrDashboardTypeMismatch)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Should not be able to overwrite dashboard with folder using id", func() {
|
||||||
|
cmd := m.SaveDashboardCommand{
|
||||||
|
OrgId: 1,
|
||||||
|
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||||
|
"id": savedDash.Id,
|
||||||
|
"title": "new folder title",
|
||||||
|
}),
|
||||||
|
IsFolder: true,
|
||||||
|
Overwrite: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := SaveDashboard(&cmd)
|
||||||
|
So(err, ShouldEqual, m.ErrDashboardTypeMismatch)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Should not be able to overwrite folder with dashboard using uid", func() {
|
||||||
|
cmd := m.SaveDashboardCommand{
|
||||||
|
OrgId: 1,
|
||||||
|
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||||
|
"uid": savedFolder.Uid,
|
||||||
|
"title": "new title",
|
||||||
|
}),
|
||||||
|
IsFolder: false,
|
||||||
|
Overwrite: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := SaveDashboard(&cmd)
|
||||||
|
So(err, ShouldEqual, m.ErrDashboardTypeMismatch)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Should not be able to overwrite dashboard with folder using uid", func() {
|
||||||
|
cmd := m.SaveDashboardCommand{
|
||||||
|
OrgId: 1,
|
||||||
|
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||||
|
"uid": savedDash.Uid,
|
||||||
|
"title": "new folder title",
|
||||||
|
}),
|
||||||
|
IsFolder: true,
|
||||||
|
Overwrite: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := SaveDashboard(&cmd)
|
||||||
|
So(err, ShouldEqual, m.ErrDashboardTypeMismatch)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Should not be able to save dashboard with same name in the same folder without overwrite", func() {
|
||||||
|
firstSaveCmd := m.SaveDashboardCommand{
|
||||||
|
OrgId: 1,
|
||||||
|
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||||
|
"id": nil,
|
||||||
|
"title": "test dash folder and title",
|
||||||
|
"tags": []interface{}{},
|
||||||
|
"uid": "randomHash",
|
||||||
|
}),
|
||||||
|
FolderId: 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := SaveDashboard(&firstSaveCmd)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
secondSaveCmd := m.SaveDashboardCommand{
|
||||||
|
OrgId: 1,
|
||||||
|
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||||
|
"id": nil,
|
||||||
|
"title": "test dash folder and title",
|
||||||
|
"tags": []interface{}{},
|
||||||
|
"uid": "moreRandomHash",
|
||||||
|
}),
|
||||||
|
FolderId: 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = SaveDashboard(&secondSaveCmd)
|
||||||
|
So(err, ShouldEqual, m.ErrDashboardWithSameNameInFolderExists)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Should be able to save and update dashboard using same uid", func() {
|
||||||
|
cmd := m.SaveDashboardCommand{
|
||||||
|
OrgId: 1,
|
||||||
|
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||||
|
"id": nil,
|
||||||
|
"uid": "dsfalkjngailuedt",
|
||||||
|
"title": "test dash 23",
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
err := SaveDashboard(&cmd)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
err = SaveDashboard(&cmd)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Should be able to update dashboard using uid", func() {
|
||||||
|
cmd := m.SaveDashboardCommand{
|
||||||
|
OrgId: 1,
|
||||||
|
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||||
|
"uid": savedDash.Uid,
|
||||||
|
"title": "new title",
|
||||||
|
}),
|
||||||
|
FolderId: 0,
|
||||||
|
Overwrite: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := SaveDashboard(&cmd)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
Convey("Should be able to get updated dashboard by uid", func() {
|
||||||
|
query := m.GetDashboardQuery{
|
||||||
|
Uid: savedDash.Uid,
|
||||||
|
OrgId: 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := GetDashboard(&query)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
So(query.Result.Id, ShouldEqual, savedDash.Id)
|
||||||
|
So(query.Result.Title, ShouldEqual, "new title")
|
||||||
|
So(query.Result.FolderId, ShouldEqual, 0)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Should be able to update dashboard with the same title and folder id", func() {
|
||||||
|
cmd := m.SaveDashboardCommand{
|
||||||
|
OrgId: 1,
|
||||||
|
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||||
|
"uid": "randomHash",
|
||||||
|
"title": "folderId",
|
||||||
|
"style": "light",
|
||||||
|
"tags": []interface{}{},
|
||||||
|
}),
|
||||||
|
FolderId: 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := SaveDashboard(&cmd)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(cmd.Result.FolderId, ShouldEqual, 2)
|
||||||
|
|
||||||
|
cmd = m.SaveDashboardCommand{
|
||||||
|
OrgId: 1,
|
||||||
|
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||||
|
"id": cmd.Result.Id,
|
||||||
|
"uid": "randomHash",
|
||||||
|
"title": "folderId",
|
||||||
|
"style": "dark",
|
||||||
|
"version": cmd.Result.Version,
|
||||||
|
"tags": []interface{}{},
|
||||||
|
}),
|
||||||
|
FolderId: 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = SaveDashboard(&cmd)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Should be able to update using uid without id and overwrite", func() {
|
||||||
|
cmd := m.SaveDashboardCommand{
|
||||||
|
OrgId: 1,
|
||||||
|
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||||
|
"uid": savedDash.Uid,
|
||||||
|
"title": "folderId",
|
||||||
|
"version": savedDash.Version,
|
||||||
|
"tags": []interface{}{},
|
||||||
|
}),
|
||||||
|
FolderId: savedDash.FolderId,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := SaveDashboard(&cmd)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Should retry generation of uid once if it fails.", func() {
|
||||||
|
timesCalled := 0
|
||||||
|
generateNewUid = func() string {
|
||||||
|
timesCalled += 1
|
||||||
|
if timesCalled <= 2 {
|
||||||
|
return savedDash.Uid
|
||||||
|
} else {
|
||||||
|
return util.GenerateShortUid()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cmd := m.SaveDashboardCommand{
|
||||||
|
OrgId: 1,
|
||||||
|
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||||
|
"title": "new dash 12334",
|
||||||
|
"tags": []interface{}{},
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
err := SaveDashboard(&cmd)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
generateNewUid = util.GenerateShortUid
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Should be able to update dashboard by id and remove folderId", func() {
|
||||||
|
cmd := m.SaveDashboardCommand{
|
||||||
|
OrgId: 1,
|
||||||
|
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||||
|
"id": savedDash.Id,
|
||||||
|
"title": "folderId",
|
||||||
|
"tags": []interface{}{},
|
||||||
|
}),
|
||||||
|
Overwrite: true,
|
||||||
|
FolderId: 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := SaveDashboard(&cmd)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(cmd.Result.FolderId, ShouldEqual, 2)
|
||||||
|
|
||||||
|
cmd = m.SaveDashboardCommand{
|
||||||
|
OrgId: 1,
|
||||||
|
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||||
|
"id": savedDash.Id,
|
||||||
|
"title": "folderId",
|
||||||
|
"tags": []interface{}{},
|
||||||
|
}),
|
||||||
|
FolderId: 0,
|
||||||
|
Overwrite: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = SaveDashboard(&cmd)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
query := m.GetDashboardQuery{
|
||||||
|
Id: savedDash.Id,
|
||||||
|
OrgId: 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = GetDashboard(&query)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(query.Result.FolderId, ShouldEqual, 0)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Should be able to delete a dashboard folder and its children", func() {
|
||||||
|
deleteCmd := &m.DeleteDashboardCommand{Id: savedFolder.Id}
|
||||||
|
err := DeleteDashboard(deleteCmd)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
query := search.FindPersistedDashboardsQuery{
|
||||||
|
OrgId: 1,
|
||||||
|
FolderIds: []int64{savedFolder.Id},
|
||||||
|
SignedInUser: &m.SignedInUser{},
|
||||||
|
}
|
||||||
|
|
||||||
|
err = SearchDashboards(&query)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
So(len(query.Result), ShouldEqual, 0)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Should be able to get dashboard tags", func() {
|
||||||
|
query := m.GetDashboardTagsQuery{OrgId: 1}
|
||||||
|
|
||||||
|
err := GetDashboardTags(&query)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
So(len(query.Result), ShouldEqual, 2)
|
||||||
})
|
})
|
||||||
|
|
||||||
Convey("Should be able to search for dashboard folder", func() {
|
Convey("Should be able to search for dashboard folder", func() {
|
||||||
@@ -188,249 +563,6 @@ func TestDashboardDataAccess(t *testing.T) {
|
|||||||
hit2 := query.Result[1]
|
hit2 := query.Result[1]
|
||||||
So(len(hit2.Tags), ShouldEqual, 1)
|
So(len(hit2.Tags), ShouldEqual, 1)
|
||||||
})
|
})
|
||||||
|
|
||||||
Convey("DashboardIds that does not exists should not cause errors", func() {
|
|
||||||
query := search.FindPersistedDashboardsQuery{
|
|
||||||
DashboardIds: []int64{1000},
|
|
||||||
SignedInUser: &m.SignedInUser{OrgId: 1},
|
|
||||||
}
|
|
||||||
|
|
||||||
err := SearchDashboards(&query)
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
So(len(query.Result), ShouldEqual, 0)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("Should be able to save dashboards with same name in different folders", func() {
|
|
||||||
firstSaveCmd := m.SaveDashboardCommand{
|
|
||||||
OrgId: 1,
|
|
||||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
|
||||||
"id": nil,
|
|
||||||
"title": "test dash folder and title",
|
|
||||||
"tags": []interface{}{},
|
|
||||||
"uid": "randomHash",
|
|
||||||
}),
|
|
||||||
FolderId: 3,
|
|
||||||
}
|
|
||||||
|
|
||||||
err := SaveDashboard(&firstSaveCmd)
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
|
|
||||||
secondSaveCmd := m.SaveDashboardCommand{
|
|
||||||
OrgId: 1,
|
|
||||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
|
||||||
"id": nil,
|
|
||||||
"title": "test dash folder and title",
|
|
||||||
"tags": []interface{}{},
|
|
||||||
"uid": "moreRandomHash",
|
|
||||||
}),
|
|
||||||
FolderId: 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
err = SaveDashboard(&secondSaveCmd)
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("Should not be able to save dashboard with same name in the same folder", func() {
|
|
||||||
firstSaveCmd := m.SaveDashboardCommand{
|
|
||||||
OrgId: 1,
|
|
||||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
|
||||||
"id": nil,
|
|
||||||
"title": "test dash folder and title",
|
|
||||||
"tags": []interface{}{},
|
|
||||||
"uid": "randomHash",
|
|
||||||
}),
|
|
||||||
FolderId: 3,
|
|
||||||
}
|
|
||||||
|
|
||||||
err := SaveDashboard(&firstSaveCmd)
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
|
|
||||||
secondSaveCmd := m.SaveDashboardCommand{
|
|
||||||
OrgId: 1,
|
|
||||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
|
||||||
"id": nil,
|
|
||||||
"title": "test dash folder and title",
|
|
||||||
"tags": []interface{}{},
|
|
||||||
"uid": "moreRandomHash",
|
|
||||||
}),
|
|
||||||
FolderId: 3,
|
|
||||||
}
|
|
||||||
|
|
||||||
err = SaveDashboard(&secondSaveCmd)
|
|
||||||
So(err, ShouldEqual, m.ErrDashboardWithSameNameInFolderExists)
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("Should not be able to save dashboard with same uid", func() {
|
|
||||||
cmd := m.SaveDashboardCommand{
|
|
||||||
OrgId: 1,
|
|
||||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
|
||||||
"id": nil,
|
|
||||||
"title": "test dash 23",
|
|
||||||
"uid": "dsfalkjngailuedt",
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
|
|
||||||
err := SaveDashboard(&cmd)
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
err = SaveDashboard(&cmd)
|
|
||||||
So(err, ShouldNotBeNil)
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("Should be able to update dashboard with the same title and folder id", func() {
|
|
||||||
cmd := m.SaveDashboardCommand{
|
|
||||||
OrgId: 1,
|
|
||||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
|
||||||
"uid": "randomHash",
|
|
||||||
"title": "folderId",
|
|
||||||
"style": "light",
|
|
||||||
"tags": []interface{}{},
|
|
||||||
}),
|
|
||||||
FolderId: 2,
|
|
||||||
}
|
|
||||||
|
|
||||||
err := SaveDashboard(&cmd)
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
So(cmd.Result.FolderId, ShouldEqual, 2)
|
|
||||||
|
|
||||||
cmd = m.SaveDashboardCommand{
|
|
||||||
OrgId: 1,
|
|
||||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
|
||||||
"id": cmd.Result.Id,
|
|
||||||
"uid": "randomHash",
|
|
||||||
"title": "folderId",
|
|
||||||
"style": "dark",
|
|
||||||
"version": cmd.Result.Version,
|
|
||||||
"tags": []interface{}{},
|
|
||||||
}),
|
|
||||||
FolderId: 2,
|
|
||||||
}
|
|
||||||
|
|
||||||
err = SaveDashboard(&cmd)
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("Should not be able to update using just uid", func() {
|
|
||||||
cmd := m.SaveDashboardCommand{
|
|
||||||
OrgId: 1,
|
|
||||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
|
||||||
"uid": savedDash.Uid,
|
|
||||||
"title": "folderId",
|
|
||||||
"version": savedDash.Version,
|
|
||||||
"tags": []interface{}{},
|
|
||||||
}),
|
|
||||||
FolderId: savedDash.FolderId,
|
|
||||||
}
|
|
||||||
|
|
||||||
err := SaveDashboard(&cmd)
|
|
||||||
So(err, ShouldEqual, m.ErrDashboardWithSameUIDExists)
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("Should be able to update using just uid with overwrite", func() {
|
|
||||||
cmd := m.SaveDashboardCommand{
|
|
||||||
OrgId: 1,
|
|
||||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
|
||||||
"uid": savedDash.Uid,
|
|
||||||
"title": "folderId",
|
|
||||||
"version": savedDash.Version,
|
|
||||||
"tags": []interface{}{},
|
|
||||||
}),
|
|
||||||
FolderId: savedDash.FolderId,
|
|
||||||
Overwrite: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
err := SaveDashboard(&cmd)
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("Should retry generation of uid once if it fails.", func() {
|
|
||||||
timesCalled := 0
|
|
||||||
generateNewUid = func() string {
|
|
||||||
timesCalled += 1
|
|
||||||
if timesCalled <= 2 {
|
|
||||||
return savedDash.Uid
|
|
||||||
} else {
|
|
||||||
return util.GenerateShortUid()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cmd := m.SaveDashboardCommand{
|
|
||||||
OrgId: 1,
|
|
||||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
|
||||||
"title": "new dash 12334",
|
|
||||||
"tags": []interface{}{},
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
|
|
||||||
err := SaveDashboard(&cmd)
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
|
|
||||||
generateNewUid = util.GenerateShortUid
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("Should be able to update dashboard and remove folderId", func() {
|
|
||||||
cmd := m.SaveDashboardCommand{
|
|
||||||
OrgId: 1,
|
|
||||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
|
||||||
"id": 1,
|
|
||||||
"title": "folderId",
|
|
||||||
"tags": []interface{}{},
|
|
||||||
}),
|
|
||||||
Overwrite: true,
|
|
||||||
FolderId: 2,
|
|
||||||
}
|
|
||||||
|
|
||||||
err := SaveDashboard(&cmd)
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
So(cmd.Result.FolderId, ShouldEqual, 2)
|
|
||||||
|
|
||||||
cmd = m.SaveDashboardCommand{
|
|
||||||
OrgId: 1,
|
|
||||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
|
||||||
"id": 1,
|
|
||||||
"title": "folderId",
|
|
||||||
"tags": []interface{}{},
|
|
||||||
}),
|
|
||||||
FolderId: 0,
|
|
||||||
Overwrite: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
err = SaveDashboard(&cmd)
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
|
|
||||||
query := m.GetDashboardQuery{
|
|
||||||
Slug: cmd.Result.Slug,
|
|
||||||
OrgId: 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
err = GetDashboard(&query)
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
So(query.Result.FolderId, ShouldEqual, 0)
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("Should be able to delete a dashboard folder and its children", func() {
|
|
||||||
deleteCmd := &m.DeleteDashboardCommand{Id: savedFolder.Id}
|
|
||||||
err := DeleteDashboard(deleteCmd)
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
|
|
||||||
query := search.FindPersistedDashboardsQuery{
|
|
||||||
OrgId: 1,
|
|
||||||
FolderIds: []int64{savedFolder.Id},
|
|
||||||
SignedInUser: &m.SignedInUser{},
|
|
||||||
}
|
|
||||||
|
|
||||||
err = SearchDashboards(&query)
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
|
|
||||||
So(len(query.Result), ShouldEqual, 0)
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("Should be able to get dashboard tags", func() {
|
|
||||||
query := m.GetDashboardTagsQuery{OrgId: 1}
|
|
||||||
|
|
||||||
err := GetDashboardTags(&query)
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
|
|
||||||
So(len(query.Result), ShouldEqual, 2)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
Convey("Given two dashboards, one is starred dashboard by user 10, other starred by user 1", func() {
|
Convey("Given two dashboards, one is starred dashboard by user 10, other starred by user 1", func() {
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package sqlstore
|
package sqlstore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/go-xorm/xorm"
|
"github.com/go-xorm/xorm"
|
||||||
@@ -11,10 +13,33 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/services/sqlstore/sqlutil"
|
"github.com/grafana/grafana/pkg/services/sqlstore/sqlutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
dbSqlite = "sqlite"
|
||||||
|
dbMySql = "mysql"
|
||||||
|
dbPostgres = "postgres"
|
||||||
|
)
|
||||||
|
|
||||||
func InitTestDB(t *testing.T) *xorm.Engine {
|
func InitTestDB(t *testing.T) *xorm.Engine {
|
||||||
x, err := xorm.NewEngine(sqlutil.TestDB_Sqlite3.DriverName, sqlutil.TestDB_Sqlite3.ConnStr)
|
selectedDb := dbSqlite
|
||||||
//x, err := xorm.NewEngine(sqlutil.TestDB_Mysql.DriverName, sqlutil.TestDB_Mysql.ConnStr)
|
//selectedDb := dbMySql
|
||||||
//x, err := xorm.NewEngine(sqlutil.TestDB_Postgres.DriverName, sqlutil.TestDB_Postgres.ConnStr)
|
//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()
|
// x.ShowSQL()
|
||||||
|
|
||||||
|
|||||||
@@ -123,6 +123,31 @@ func TestAccountDataAccess(t *testing.T) {
|
|||||||
So(query.Result[0].Role, ShouldEqual, "Admin")
|
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() {
|
Convey("Can set using org", func() {
|
||||||
cmd := m.SetUsingOrgCommand{UserId: ac2.Id, OrgId: ac1.Id}
|
cmd := m.SetUsingOrgCommand{UserId: ac2.Id, OrgId: ac1.Id}
|
||||||
err := SetUsingOrg(&cmd)
|
err := SetUsingOrg(&cmd)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package sqlstore
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/bus"
|
"github.com/grafana/grafana/pkg/bus"
|
||||||
@@ -69,9 +70,30 @@ func UpdateOrgUser(cmd *m.UpdateOrgUserCommand) error {
|
|||||||
|
|
||||||
func GetOrgUsers(query *m.GetOrgUsersQuery) error {
|
func GetOrgUsers(query *m.GetOrgUsersQuery) error {
|
||||||
query.Result = make([]*m.OrgUserDTO, 0)
|
query.Result = make([]*m.OrgUserDTO, 0)
|
||||||
|
|
||||||
sess := x.Table("org_user")
|
sess := x.Table("org_user")
|
||||||
sess.Join("INNER", "user", fmt.Sprintf("org_user.user_id=%s.id", x.Dialect().Quote("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, "(user.email "+dialect.LikeStr()+" ? OR user.name "+dialect.LikeStr()+" ? OR user.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.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")
|
sess.Asc("user.email", "user.login")
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ class UserPicker extends Component<IProps, any> {
|
|||||||
|
|
||||||
this.debouncedSearch = debounce(this.search, 300, {
|
this.debouncedSearch = debounce(this.search, 300, {
|
||||||
leading: true,
|
leading: true,
|
||||||
trailing: false,
|
trailing: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,10 +39,10 @@ class UserPicker extends Component<IProps, any> {
|
|||||||
const { toggleLoading, backendSrv } = this.props;
|
const { toggleLoading, backendSrv } = this.props;
|
||||||
|
|
||||||
toggleLoading(true);
|
toggleLoading(true);
|
||||||
return backendSrv.get(`/api/users/search?perpage=10&page=1&query=${query}`).then(result => {
|
return backendSrv.get(`/api/org/users?query=${query}&limit=10`).then(result => {
|
||||||
const users = result.users.map(user => {
|
const users = result.map(user => {
|
||||||
return {
|
return {
|
||||||
id: user.id,
|
id: user.userId,
|
||||||
label: `${user.login} - ${user.email}`,
|
label: `${user.login} - ${user.email}`,
|
||||||
avatarUrl: user.avatarUrl,
|
avatarUrl: user.avatarUrl,
|
||||||
login: user.login,
|
login: user.login,
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export class DashboardImportCtrl {
|
|||||||
nameValidationError: any;
|
nameValidationError: any;
|
||||||
|
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
constructor(private backendSrv, private validationSrv, navModelSrv, private $location, private $scope, $routeParams) {
|
constructor(private backendSrv, private validationSrv, navModelSrv, private $location, $routeParams) {
|
||||||
this.navModel = navModelSrv.getNav('create', 'import');
|
this.navModel = navModelSrv.getNav('create', 'import');
|
||||||
|
|
||||||
this.step = 1;
|
this.step = 1;
|
||||||
@@ -124,8 +124,7 @@ export class DashboardImportCtrl {
|
|||||||
inputs: inputs,
|
inputs: inputs,
|
||||||
})
|
})
|
||||||
.then(res => {
|
.then(res => {
|
||||||
this.$location.url('dashboard/' + res.importedUri);
|
this.$location.url(res.importedUrl);
|
||||||
this.$scope.dismiss();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,10 @@ export class DashboardSrv {
|
|||||||
return this.dash;
|
return this.dash;
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSaveDashboardError(clone, err) {
|
handleSaveDashboardError(clone, options, err) {
|
||||||
|
options = options || {};
|
||||||
|
options.overwrite = true;
|
||||||
|
|
||||||
if (err.data && err.data.status === 'version-mismatch') {
|
if (err.data && err.data.status === 'version-mismatch') {
|
||||||
err.isHandled = true;
|
err.isHandled = true;
|
||||||
|
|
||||||
@@ -31,7 +34,7 @@ export class DashboardSrv {
|
|||||||
yesText: 'Save & Overwrite',
|
yesText: 'Save & Overwrite',
|
||||||
icon: 'fa-warning',
|
icon: 'fa-warning',
|
||||||
onConfirm: () => {
|
onConfirm: () => {
|
||||||
this.save(clone, { overwrite: true });
|
this.save(clone, options);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -41,12 +44,12 @@ export class DashboardSrv {
|
|||||||
|
|
||||||
this.$rootScope.appEvent('confirm-modal', {
|
this.$rootScope.appEvent('confirm-modal', {
|
||||||
title: 'Conflict',
|
title: 'Conflict',
|
||||||
text: 'Dashboard with the same name exists.',
|
text: 'A dashboard with the same name in selected folder already exists.',
|
||||||
text2: 'Would you still like to save this dashboard?',
|
text2: 'Would you still like to save this dashboard?',
|
||||||
yesText: 'Save & Overwrite',
|
yesText: 'Save & Overwrite',
|
||||||
icon: 'fa-warning',
|
icon: 'fa-warning',
|
||||||
onConfirm: () => {
|
onConfirm: () => {
|
||||||
this.save(clone, { overwrite: true });
|
this.save(clone, options);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -91,7 +94,7 @@ export class DashboardSrv {
|
|||||||
return this.backendSrv
|
return this.backendSrv
|
||||||
.saveDashboard(clone, options)
|
.saveDashboard(clone, options)
|
||||||
.then(this.postSave.bind(this, clone))
|
.then(this.postSave.bind(this, clone))
|
||||||
.catch(this.handleSaveDashboardError.bind(this, clone));
|
.catch(this.handleSaveDashboardError.bind(this, clone, options));
|
||||||
}
|
}
|
||||||
|
|
||||||
saveDashboard(options, clone) {
|
saveDashboard(options, clone) {
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ describe('DashboardImportCtrl', function() {
|
|||||||
validateNewDashboardName: jest.fn().mockReturnValue(Promise.resolve()),
|
validateNewDashboardName: jest.fn().mockReturnValue(Promise.resolve()),
|
||||||
};
|
};
|
||||||
|
|
||||||
ctx.ctrl = new DashboardImportCtrl(backendSrv, validationSrv, navModelSrv, {}, {}, {});
|
ctx.ctrl = new DashboardImportCtrl(backendSrv, validationSrv, navModelSrv, {}, {});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('when uploading json', function() {
|
describe('when uploading json', function() {
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
Old picker
|
Old picker
|
||||||
<user-picker user-picked="ctrl.userPicked($user)"></user-picker>
|
<user-picker user-picked="ctrl.userPicked($user)"></user-picker>
|
||||||
-->
|
-->
|
||||||
<select-user-picker handlePicked="ctrl.userPicked" backendSrv="ctrl.backendSrv"></select-user-picker>
|
<select-user-picker class="width-7" handlePicked="ctrl.userPicked" backendSrv="ctrl.backendSrv"></select-user-picker>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<i class="icon-gf icon-gf-dashboard"></i>
|
<i class="icon-gf icon-gf-dashboard"></i>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="dashboard/{{dash.importedUri}}" ng-show="dash.imported">
|
<a href="{{dash.importedUrl}}" ng-show="dash.imported">
|
||||||
{{dash.title}}
|
{{dash.title}}
|
||||||
</a>
|
</a>
|
||||||
<span ng-show="!dash.imported">
|
<span ng-show="!dash.imported">
|
||||||
|
|||||||
@@ -53,6 +53,6 @@ export const FolderStore = types
|
|||||||
deleteFolder: flow(function* deleteFolder() {
|
deleteFolder: flow(function* deleteFolder() {
|
||||||
const backendSrv = getEnv(self).backendSrv;
|
const backendSrv = getEnv(self).backendSrv;
|
||||||
|
|
||||||
return backendSrv.deleteDashboard(self.folder.url);
|
return backendSrv.deleteDashboard(self.folder.uid);
|
||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|||||||
Reference in New Issue
Block a user