2178 lines
68 KiB
Go
2178 lines
68 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/mock"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/grafana/grafana/pkg/api/dtos"
|
|
"github.com/grafana/grafana/pkg/apimachinery/errutil"
|
|
"github.com/grafana/grafana/pkg/bus"
|
|
"github.com/grafana/grafana/pkg/components/simplejson"
|
|
"github.com/grafana/grafana/pkg/infra/db"
|
|
"github.com/grafana/grafana/pkg/infra/tracing"
|
|
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
|
"github.com/grafana/grafana/pkg/services/accesscontrol/actest"
|
|
acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
|
|
"github.com/grafana/grafana/pkg/services/apiserver/client"
|
|
"github.com/grafana/grafana/pkg/services/dashboards"
|
|
dashboardsDB "github.com/grafana/grafana/pkg/services/dashboards/database"
|
|
dashsvc "github.com/grafana/grafana/pkg/services/dashboards/service"
|
|
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
|
"github.com/grafana/grafana/pkg/services/folder/folderimpl"
|
|
"github.com/grafana/grafana/pkg/services/guardian"
|
|
"github.com/grafana/grafana/pkg/services/org"
|
|
. "github.com/grafana/grafana/pkg/services/publicdashboards"
|
|
. "github.com/grafana/grafana/pkg/services/publicdashboards/models"
|
|
"github.com/grafana/grafana/pkg/services/publicdashboards/service/intervalv2"
|
|
"github.com/grafana/grafana/pkg/services/publicdashboards/validation"
|
|
"github.com/grafana/grafana/pkg/services/quota/quotatest"
|
|
"github.com/grafana/grafana/pkg/services/search/sort"
|
|
"github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest"
|
|
"github.com/grafana/grafana/pkg/services/tag/tagimpl"
|
|
"github.com/grafana/grafana/pkg/services/user"
|
|
"github.com/grafana/grafana/pkg/storage/legacysql/dualwrite"
|
|
"github.com/grafana/grafana/pkg/tests/testsuite"
|
|
"github.com/grafana/grafana/pkg/util"
|
|
)
|
|
|
|
var timeSettings = &TimeSettings{From: "now-12h", To: "now"}
|
|
var defaultPubdashTimeSettings = &TimeSettings{}
|
|
var dashboardData = simplejson.NewFromAny(map[string]any{"time": map[string]any{"from": "now-8h", "to": "now"}})
|
|
var SignedInUser = &user.SignedInUser{UserID: 1234, Login: "user@login.com"}
|
|
|
|
func TestMain(m *testing.M) {
|
|
testsuite.Run(m)
|
|
}
|
|
|
|
func TestLogPrefix(t *testing.T) {
|
|
assert.Equal(t, LogPrefix, "publicdashboards.service")
|
|
}
|
|
|
|
func TestGetPublicDashboardForView(t *testing.T) {
|
|
type storeResp struct {
|
|
pd *PublicDashboard
|
|
d *dashboards.Dashboard
|
|
err error
|
|
}
|
|
|
|
const dashboardWithRowsAndHiddenQueries = `
|
|
{
|
|
"panels": [
|
|
{
|
|
"id": 2,
|
|
"targets": [
|
|
{
|
|
"datasource": {
|
|
"type": "prometheus",
|
|
"uid": "_yxMP8Ynk"
|
|
},
|
|
"exemplar": true,
|
|
"expr": "go_goroutines{job=\"$job\"}",
|
|
"interval": "",
|
|
"legendFormat": "",
|
|
"refId": "A",
|
|
"hide": true
|
|
},
|
|
{
|
|
"datasource": {
|
|
"type": "prometheus",
|
|
"uid": "promds2"
|
|
},
|
|
"exemplar": true,
|
|
"expr": "query2",
|
|
"interval": "",
|
|
"legendFormat": "",
|
|
"refId": "B"
|
|
}
|
|
],
|
|
"title": "Panel Title",
|
|
"type": "timeseries"
|
|
},
|
|
{
|
|
"id": 3,
|
|
"collapsed": true,
|
|
"gridPos": {
|
|
"h": 1,
|
|
"w": 24,
|
|
"x": 0,
|
|
"y": 9
|
|
},
|
|
"title": "This panel is a Row",
|
|
"type": "row",
|
|
"panels": [
|
|
{
|
|
"id": 4,
|
|
"targets": [
|
|
{
|
|
"datasource": {
|
|
"type": "prometheus",
|
|
"uid": "_yxMP8Ynk"
|
|
},
|
|
"exemplar": true,
|
|
"expr": "go_goroutines{job=\"$job\"}",
|
|
"interval": "",
|
|
"legendFormat": "",
|
|
"refId": "A"
|
|
},
|
|
{
|
|
"datasource": {
|
|
"type": "prometheus",
|
|
"uid": "promds2"
|
|
},
|
|
"exemplar": true,
|
|
"expr": "query2",
|
|
"interval": "",
|
|
"legendFormat": "",
|
|
"refId": "B"
|
|
}
|
|
],
|
|
"title": "Panel inside a row",
|
|
"type": "timeseries"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"aliasColors": {
|
|
"total avg": "#6ed0e0"
|
|
},
|
|
"bars": false,
|
|
"dashLength": 10,
|
|
"dashes": false,
|
|
"datasource": {
|
|
"type": "mssql",
|
|
"uid": "P6B08AC199690F328"
|
|
},
|
|
"fieldConfig": {
|
|
"defaults": {
|
|
"links": []
|
|
},
|
|
"overrides": []
|
|
},
|
|
"fill": 2,
|
|
"fillGradient": 0,
|
|
"gridPos": {
|
|
"h": 9,
|
|
"w": 12,
|
|
"x": 0,
|
|
"y": 0
|
|
},
|
|
"hiddenSeries": false,
|
|
"id": 4,
|
|
"legend": {
|
|
"avg": false,
|
|
"current": false,
|
|
"max": false,
|
|
"min": false,
|
|
"show": true,
|
|
"total": false,
|
|
"values": false
|
|
},
|
|
"lines": true,
|
|
"linewidth": 2,
|
|
"links": [],
|
|
"nullPointMode": "null",
|
|
"options": {
|
|
"alertThreshold": true
|
|
},
|
|
"percentage": false,
|
|
"pluginVersion": "10.2.0-pre",
|
|
"pointradius": 5,
|
|
"points": false,
|
|
"renderer": "flot",
|
|
"seriesOverrides": [
|
|
{
|
|
"alias": "total avg",
|
|
"fill": 0,
|
|
"pointradius": 3,
|
|
"points": true
|
|
}
|
|
],
|
|
"spaceLength": 10,
|
|
"stack": false,
|
|
"steppedLine": false,
|
|
"targets": [
|
|
{
|
|
"alias": "",
|
|
"datasource": {
|
|
"type": "mssql",
|
|
"uid": "P6B08AC199690F328"
|
|
},
|
|
"format": "time_series",
|
|
"rawSql": "SELECT\n $__timeGroup(createdAt,'$summarize') as time,\n avg(value) as value,\n hostname as metric\nFROM \n grafana_metric\nWHERE\n $__timeFilter(createdAt) AND\n measurement = 'logins.count' AND\n hostname IN($host)\nGROUP BY $__timeGroup(createdAt,'$summarize'), hostname\nORDER BY 1",
|
|
"refId": "A"
|
|
},
|
|
{
|
|
"alias": "",
|
|
"datasource": {
|
|
"type": "mssql",
|
|
"uid": "P6B08AC199690F328"
|
|
},
|
|
"format": "time_series",
|
|
"rawSql": "SELECT\n $__timeGroup(createdAt,'$summarize') as time,\n min(value) as value,\n 'total avg' as metric\nFROM \n grafana_metric\nWHERE\n $__timeFilter(createdAt) AND\n measurement = 'logins.count'\nGROUP BY $__timeGroup(createdAt,'$summarize')\nORDER BY 1",
|
|
"refId": "B"
|
|
}
|
|
],
|
|
"thresholds": [],
|
|
"timeRegions": [],
|
|
"title": "Average logins / $summarize",
|
|
"tooltip": {
|
|
"shared": true,
|
|
"sort": 0,
|
|
"value_type": "individual"
|
|
},
|
|
"type": "graph",
|
|
"xaxis": {
|
|
"mode": "time",
|
|
"show": true,
|
|
"values": []
|
|
},
|
|
"yaxes": [
|
|
{
|
|
"format": "short",
|
|
"logBase": 1,
|
|
"show": true
|
|
},
|
|
{
|
|
"format": "short",
|
|
"logBase": 1,
|
|
"show": true
|
|
}
|
|
],
|
|
"yaxis": {
|
|
"align": false
|
|
}
|
|
},
|
|
{
|
|
"datasource": {
|
|
"type": "influxdb",
|
|
"uid": "P49A45DF074423DFB"
|
|
},
|
|
"fieldConfig": {
|
|
"defaults": {
|
|
"color": {
|
|
"mode": "thresholds"
|
|
},
|
|
"mappings": [],
|
|
"thresholds": {
|
|
"mode": "absolute",
|
|
"steps": [
|
|
{
|
|
"color": "green",
|
|
"value": null
|
|
},
|
|
{
|
|
"color": "red",
|
|
"value": 80
|
|
}
|
|
]
|
|
}
|
|
},
|
|
"overrides": []
|
|
},
|
|
"gridPos": {
|
|
"h": 9,
|
|
"w": 12,
|
|
"x": 0,
|
|
"y": 0
|
|
},
|
|
"id": 5,
|
|
"options": {
|
|
"colorMode": "value",
|
|
"graphMode": "area",
|
|
"justifyMode": "auto",
|
|
"orientation": "auto",
|
|
"reduceOptions": {
|
|
"calcs": [
|
|
"lastNotNull"
|
|
],
|
|
"fields": "/^retentionPeriod 4a2f27036bf63a3c$/",
|
|
"values": false
|
|
},
|
|
"textMode": "auto"
|
|
},
|
|
"pluginVersion": "10.2.0-pre",
|
|
"targets": [
|
|
{
|
|
"datasource": {
|
|
"type": "influxdb",
|
|
"uid": "P49A45DF074423DFB"
|
|
},
|
|
"query": "buckets()",
|
|
"refId": "A"
|
|
}
|
|
],
|
|
"title": "Panel Title",
|
|
"type": "stat"
|
|
}
|
|
],
|
|
"schemaVersion": 35,
|
|
"timepicker": {
|
|
"hidden": false
|
|
}
|
|
}`
|
|
data, _ := simplejson.NewJson([]byte(dashboardWithRowsAndHiddenQueries))
|
|
|
|
now := time.Now()
|
|
|
|
// #nosec G101 -- This is dummy/test token
|
|
accessToken := "c54b1c4dd2b143a1a7a43005264d256d"
|
|
d := &dashboards.Dashboard{UID: "mydashboard", OrgID: 0, Data: data, Slug: "dashboardSlug", Created: now, Updated: now, Version: 1, FolderUID: "myFolder"}
|
|
|
|
testCases := []struct {
|
|
Name string
|
|
AccessToken string
|
|
StoreResp *storeResp
|
|
ErrResp error
|
|
DashResp *dtos.DashboardFullWithMeta
|
|
}{
|
|
{
|
|
Name: "returns a dashboard with the time picker shown",
|
|
AccessToken: accessToken,
|
|
StoreResp: &storeResp{
|
|
pd: &PublicDashboard{AccessToken: accessToken, IsEnabled: true, TimeSelectionEnabled: true},
|
|
d: d,
|
|
err: nil,
|
|
},
|
|
ErrResp: nil,
|
|
DashResp: &dtos.DashboardFullWithMeta{
|
|
Dashboard: data,
|
|
Meta: dtos.DashboardMeta{
|
|
Slug: d.Slug,
|
|
Type: dashboards.DashTypeDB,
|
|
CanStar: false,
|
|
CanSave: false,
|
|
CanEdit: false,
|
|
CanAdmin: false,
|
|
CanDelete: false,
|
|
Created: d.Created,
|
|
Updated: d.Updated,
|
|
Version: d.Version,
|
|
IsFolder: false,
|
|
FolderUid: d.FolderUID,
|
|
PublicDashboardEnabled: true,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "returns a dashboard with the time picker hidden",
|
|
AccessToken: accessToken,
|
|
StoreResp: &storeResp{
|
|
pd: &PublicDashboard{AccessToken: accessToken, IsEnabled: true, TimeSelectionEnabled: false},
|
|
d: d,
|
|
err: nil,
|
|
},
|
|
ErrResp: nil,
|
|
DashResp: &dtos.DashboardFullWithMeta{
|
|
Dashboard: data,
|
|
Meta: dtos.DashboardMeta{
|
|
Slug: d.Slug,
|
|
Type: dashboards.DashTypeDB,
|
|
CanStar: false,
|
|
CanSave: false,
|
|
CanEdit: false,
|
|
CanAdmin: false,
|
|
CanDelete: false,
|
|
Created: d.Created,
|
|
Updated: d.Updated,
|
|
Version: d.Version,
|
|
IsFolder: false,
|
|
FolderUid: d.FolderUID,
|
|
PublicDashboardEnabled: true,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, test := range testCases {
|
|
t.Run(test.Name, func(t *testing.T) {
|
|
fakeStore := &FakePublicDashboardStore{}
|
|
fakeStore.On("FindByAccessToken", mock.Anything, mock.Anything).Return(test.StoreResp.pd, test.StoreResp.err)
|
|
fakeDashboardService := &dashboards.FakeDashboardService{}
|
|
fakeDashboardService.On("GetDashboard", mock.Anything, mock.Anything, mock.Anything).Return(test.StoreResp.d, test.StoreResp.err)
|
|
service, _, _ := newPublicDashboardServiceImpl(t, nil, nil, fakeStore, fakeDashboardService, nil)
|
|
|
|
dashboardFullWithMeta, err := service.GetPublicDashboardForView(context.Background(), test.AccessToken)
|
|
if test.ErrResp != nil {
|
|
assert.Error(t, test.ErrResp, err)
|
|
} else {
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
//assert.Equal(t, test.DashResp, dash)
|
|
|
|
if test.DashResp != nil {
|
|
assert.Equal(t, test.DashResp.Meta.Slug, dashboardFullWithMeta.Meta.Slug)
|
|
assert.Equal(t, test.DashResp.Meta.Type, dashboardFullWithMeta.Meta.Type)
|
|
assert.Equal(t, false, dashboardFullWithMeta.Meta.CanStar)
|
|
assert.Equal(t, false, dashboardFullWithMeta.Meta.CanSave)
|
|
assert.Equal(t, false, dashboardFullWithMeta.Meta.CanEdit)
|
|
assert.Equal(t, false, dashboardFullWithMeta.Meta.CanAdmin)
|
|
assert.Equal(t, false, dashboardFullWithMeta.Meta.CanDelete)
|
|
assert.Equal(t, test.DashResp.Meta.Created, dashboardFullWithMeta.Meta.Created)
|
|
assert.Equal(t, test.DashResp.Meta.Updated, dashboardFullWithMeta.Meta.Updated)
|
|
assert.Equal(t, test.DashResp.Meta.Version, dashboardFullWithMeta.Meta.Version)
|
|
assert.Equal(t, false, dashboardFullWithMeta.Meta.IsFolder)
|
|
assert.Equal(t, test.DashResp.Meta.FolderUid, dashboardFullWithMeta.Meta.FolderUid)
|
|
assert.Equal(t, test.DashResp.Meta.PublicDashboardEnabled, dashboardFullWithMeta.Meta.PublicDashboardEnabled)
|
|
|
|
// hide the timepicker if the time selection is disabled
|
|
assert.Equal(t, test.StoreResp.pd.TimeSelectionEnabled, !dashboardFullWithMeta.Dashboard.Get("timepicker").Get("hidden").MustBool())
|
|
|
|
for _, panelObj := range dashboardFullWithMeta.Dashboard.Get("panels").MustArray() {
|
|
panel := simplejson.NewFromAny(panelObj)
|
|
|
|
// if the panel is a row and it is collapsed, get the queries from the panels inside the row
|
|
if panel.Get("type").MustString() == "row" && panel.Get("collapsed").MustBool() {
|
|
// recursive call to get queries from panels inside a row
|
|
sanitizeData(panel)
|
|
continue
|
|
}
|
|
|
|
for _, targetObj := range panel.Get("targets").MustArray() {
|
|
target := simplejson.NewFromAny(targetObj)
|
|
assert.Empty(t, target.Get("expr").MustString())
|
|
assert.Empty(t, target.Get("query").MustString())
|
|
assert.Empty(t, target.Get("rawSql").MustString())
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetPublicDashboard(t *testing.T) {
|
|
type storeResp struct {
|
|
pd *PublicDashboard
|
|
d *dashboards.Dashboard
|
|
err error
|
|
}
|
|
|
|
testCases := []struct {
|
|
Name string
|
|
AccessToken string
|
|
StoreResp *storeResp
|
|
ErrResp error
|
|
DashResp *dashboards.Dashboard
|
|
}{
|
|
{
|
|
Name: "returns a dashboard",
|
|
AccessToken: "abc123",
|
|
StoreResp: &storeResp{
|
|
pd: &PublicDashboard{AccessToken: "abcdToken", IsEnabled: true},
|
|
d: &dashboards.Dashboard{UID: "mydashboard", Data: dashboardData},
|
|
err: nil,
|
|
},
|
|
ErrResp: nil,
|
|
DashResp: &dashboards.Dashboard{UID: "mydashboard", Data: dashboardData},
|
|
},
|
|
{
|
|
Name: "returns dashboard when isEnabled is false",
|
|
AccessToken: "abc123",
|
|
StoreResp: &storeResp{
|
|
pd: &PublicDashboard{AccessToken: "abcdToken", IsEnabled: false},
|
|
d: &dashboards.Dashboard{UID: "mydashboard", Data: dashboardData},
|
|
err: nil,
|
|
},
|
|
ErrResp: nil,
|
|
DashResp: &dashboards.Dashboard{UID: "mydashboard", Data: dashboardData},
|
|
},
|
|
{
|
|
Name: "returns ErrPublicDashboardNotFound if PublicDashboard missing",
|
|
AccessToken: "abc123",
|
|
StoreResp: &storeResp{pd: nil, d: nil, err: nil},
|
|
ErrResp: ErrPublicDashboardNotFound,
|
|
DashResp: nil,
|
|
},
|
|
{
|
|
Name: "returns ErrPublicDashboardNotFound if Dashboard missing",
|
|
AccessToken: "abc123",
|
|
StoreResp: &storeResp{pd: nil, d: nil, err: nil},
|
|
ErrResp: ErrPublicDashboardNotFound,
|
|
DashResp: nil,
|
|
},
|
|
}
|
|
|
|
for _, test := range testCases {
|
|
t.Run(test.Name, func(t *testing.T) {
|
|
fakeDashboardService := &dashboards.FakeDashboardService{}
|
|
fakeDashboardService.On("GetDashboard", mock.Anything, mock.Anything, mock.Anything).Return(test.StoreResp.d, test.StoreResp.err)
|
|
fakeStore := &FakePublicDashboardStore{}
|
|
fakeStore.On("FindByAccessToken", mock.Anything, mock.Anything).Return(test.StoreResp.pd, test.StoreResp.err)
|
|
service, _, _ := newPublicDashboardServiceImpl(t, nil, nil, fakeStore, fakeDashboardService, nil)
|
|
|
|
pdc, dash, err := service.FindPublicDashboardAndDashboardByAccessToken(context.Background(), test.AccessToken)
|
|
if test.ErrResp != nil {
|
|
assert.Error(t, test.ErrResp, err)
|
|
} else {
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
assert.Equal(t, test.DashResp, dash)
|
|
|
|
if test.DashResp != nil {
|
|
assert.NotNil(t, dash.CreatedBy)
|
|
assert.Equal(t, test.StoreResp.pd, pdc)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetEnabledPublicDashboard(t *testing.T) {
|
|
type storeResp struct {
|
|
pd *PublicDashboard
|
|
d *dashboards.Dashboard
|
|
err error
|
|
}
|
|
|
|
testCases := []struct {
|
|
Name string
|
|
AccessToken string
|
|
StoreResp *storeResp
|
|
ErrResp error
|
|
DashResp *dashboards.Dashboard
|
|
}{
|
|
{
|
|
Name: "returns a dashboard",
|
|
AccessToken: "abc123",
|
|
StoreResp: &storeResp{
|
|
pd: &PublicDashboard{AccessToken: "abcdToken", IsEnabled: true},
|
|
d: &dashboards.Dashboard{UID: "mydashboard", Data: dashboardData},
|
|
err: nil,
|
|
},
|
|
ErrResp: nil,
|
|
DashResp: &dashboards.Dashboard{UID: "mydashboard", Data: dashboardData},
|
|
},
|
|
{
|
|
Name: "returns ErrPublicDashboardNotFound when isEnabled is false",
|
|
AccessToken: "abc123",
|
|
StoreResp: &storeResp{
|
|
pd: &PublicDashboard{AccessToken: "abcdToken", IsEnabled: false},
|
|
d: &dashboards.Dashboard{UID: "mydashboard"},
|
|
err: nil,
|
|
},
|
|
ErrResp: ErrPublicDashboardNotFound,
|
|
DashResp: nil,
|
|
},
|
|
}
|
|
|
|
for _, test := range testCases {
|
|
t.Run(test.Name, func(t *testing.T) {
|
|
fakeStore := &FakePublicDashboardStore{}
|
|
fakeStore.On("FindByAccessToken", mock.Anything, mock.Anything).Return(test.StoreResp.pd, test.StoreResp.err)
|
|
fakeDashboardService := &dashboards.FakeDashboardService{}
|
|
fakeDashboardService.On("GetDashboard", mock.Anything, mock.Anything, mock.Anything).Return(test.StoreResp.d, test.StoreResp.err)
|
|
service, _, _ := newPublicDashboardServiceImpl(t, nil, nil, fakeStore, fakeDashboardService, nil)
|
|
|
|
pdc, dash, err := service.FindEnabledPublicDashboardAndDashboardByAccessToken(context.Background(), test.AccessToken)
|
|
if test.ErrResp != nil {
|
|
assert.Error(t, test.ErrResp, err)
|
|
} else {
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
assert.Equal(t, test.DashResp, dash)
|
|
|
|
if test.DashResp != nil {
|
|
assert.NotNil(t, dash.CreatedBy)
|
|
assert.Equal(t, test.StoreResp.pd, pdc)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// We're using sqlite here because testing all of the behaviors with mocks in
|
|
// the correct order is convoluted.
|
|
func TestCreatePublicDashboard(t *testing.T) {
|
|
t.Run("Create public dashboard", func(t *testing.T) {
|
|
fakeDashboardService := &dashboards.FakeDashboardService{}
|
|
service, sqlStore, cfg := newPublicDashboardServiceImpl(t, nil, nil, nil, fakeDashboardService, nil)
|
|
|
|
dashboardStore, err := dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore))
|
|
require.NoError(t, err)
|
|
dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, "", true, []map[string]any{}, nil)
|
|
fakeDashboardService.On("GetDashboard", mock.Anything, mock.Anything, mock.Anything).Return(dashboard, nil)
|
|
|
|
isEnabled, annotationsEnabled, timeSelectionEnabled := true, false, true
|
|
|
|
dto := &SavePublicDashboardDTO{
|
|
DashboardUid: dashboard.UID,
|
|
UserId: 7,
|
|
OrgID: dashboard.OrgID,
|
|
PublicDashboard: &PublicDashboardDTO{
|
|
IsEnabled: &isEnabled,
|
|
AnnotationsEnabled: &annotationsEnabled,
|
|
TimeSelectionEnabled: &timeSelectionEnabled,
|
|
Share: EmailShareType,
|
|
},
|
|
}
|
|
|
|
_, err = service.Create(context.Background(), SignedInUser, dto)
|
|
require.NoError(t, err)
|
|
|
|
pubdash, err := service.FindByDashboardUid(context.Background(), dashboard.OrgID, dashboard.UID)
|
|
require.NoError(t, err)
|
|
|
|
// DashboardUid/OrgId/CreatedBy set by the command, not parameters
|
|
assert.Equal(t, dashboard.UID, pubdash.DashboardUid)
|
|
assert.Equal(t, dashboard.OrgID, pubdash.OrgId)
|
|
assert.Equal(t, dto.UserId, pubdash.CreatedBy)
|
|
assert.Equal(t, *dto.PublicDashboard.AnnotationsEnabled, pubdash.AnnotationsEnabled)
|
|
assert.Equal(t, *dto.PublicDashboard.TimeSelectionEnabled, pubdash.TimeSelectionEnabled)
|
|
// ExistsEnabledByDashboardUid set by parameters
|
|
assert.Equal(t, *dto.PublicDashboard.IsEnabled, pubdash.IsEnabled)
|
|
// CreatedAt set to non-zero time
|
|
assert.NotEqual(t, &time.Time{}, pubdash.CreatedAt)
|
|
assert.Equal(t, dto.PublicDashboard.Share, pubdash.Share)
|
|
// accessToken is valid uuid
|
|
_, err = uuid.Parse(pubdash.AccessToken)
|
|
require.NoError(t, err, "expected a valid UUID, got %s", pubdash.AccessToken)
|
|
})
|
|
|
|
trueBooleanField := true
|
|
|
|
testCases := []struct {
|
|
Name string
|
|
IsEnabled *bool
|
|
TimeSelectionEnabled *bool
|
|
AnnotationsEnabled *bool
|
|
}{
|
|
{
|
|
Name: "isEnabled",
|
|
IsEnabled: nil,
|
|
TimeSelectionEnabled: &trueBooleanField,
|
|
AnnotationsEnabled: &trueBooleanField,
|
|
},
|
|
{
|
|
Name: "timeSelectionEnabled",
|
|
IsEnabled: &trueBooleanField,
|
|
TimeSelectionEnabled: nil,
|
|
AnnotationsEnabled: &trueBooleanField,
|
|
},
|
|
{
|
|
Name: "annotationsEnabled",
|
|
IsEnabled: &trueBooleanField,
|
|
TimeSelectionEnabled: &trueBooleanField,
|
|
AnnotationsEnabled: nil,
|
|
},
|
|
{
|
|
Name: "isEnabled, timeSelectionEnabled and annotationsEnabled",
|
|
IsEnabled: nil,
|
|
TimeSelectionEnabled: nil,
|
|
AnnotationsEnabled: nil,
|
|
},
|
|
}
|
|
|
|
for _, tt := range testCases {
|
|
t.Run(fmt.Sprintf("Create public dashboard with %s null boolean fields stores them as false", tt.Name), func(t *testing.T) {
|
|
fakeDashboardService := &dashboards.FakeDashboardService{}
|
|
service, sqlStore, cfg := newPublicDashboardServiceImpl(t, nil, nil, nil, fakeDashboardService, nil)
|
|
dashboardStore, err := dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore))
|
|
require.NoError(t, err)
|
|
dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, "", true, []map[string]any{}, nil)
|
|
fakeDashboardService.On("GetDashboard", mock.Anything, mock.Anything, mock.Anything).Return(dashboard, nil)
|
|
|
|
dto := &SavePublicDashboardDTO{
|
|
DashboardUid: dashboard.UID,
|
|
UserId: 7,
|
|
OrgID: dashboard.OrgID,
|
|
PublicDashboard: &PublicDashboardDTO{
|
|
IsEnabled: tt.IsEnabled,
|
|
TimeSelectionEnabled: tt.TimeSelectionEnabled,
|
|
AnnotationsEnabled: tt.AnnotationsEnabled,
|
|
Share: PublicShareType,
|
|
},
|
|
}
|
|
|
|
_, err = service.Create(context.Background(), SignedInUser, dto)
|
|
require.NoError(t, err)
|
|
pubdash, err := service.FindByDashboardUid(context.Background(), dashboard.OrgID, dashboard.UID)
|
|
require.NoError(t, err)
|
|
|
|
assertFalseIfNull(t, pubdash.IsEnabled, dto.PublicDashboard.IsEnabled)
|
|
assertFalseIfNull(t, pubdash.TimeSelectionEnabled, dto.PublicDashboard.TimeSelectionEnabled)
|
|
assertFalseIfNull(t, pubdash.AnnotationsEnabled, dto.PublicDashboard.AnnotationsEnabled)
|
|
})
|
|
}
|
|
|
|
t.Run("Validate pubdash has default time setting value", func(t *testing.T) {
|
|
fakeDashboardService := &dashboards.FakeDashboardService{}
|
|
service, sqlStore, cfg := newPublicDashboardServiceImpl(t, nil, nil, nil, fakeDashboardService, nil)
|
|
dashboardStore, err := dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore))
|
|
require.NoError(t, err)
|
|
dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, "", true, []map[string]any{}, nil)
|
|
fakeDashboardService.On("GetDashboard", mock.Anything, mock.Anything, mock.Anything).Return(dashboard, nil)
|
|
|
|
isEnabled := true
|
|
dto := &SavePublicDashboardDTO{
|
|
DashboardUid: dashboard.UID,
|
|
OrgID: dashboard.OrgID,
|
|
UserId: 7,
|
|
PublicDashboard: &PublicDashboardDTO{
|
|
IsEnabled: &isEnabled,
|
|
},
|
|
}
|
|
|
|
_, err = service.Create(context.Background(), SignedInUser, dto)
|
|
require.NoError(t, err)
|
|
|
|
pubdash, err := service.FindByDashboardUid(context.Background(), dashboard.OrgID, dashboard.UID)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, defaultPubdashTimeSettings, pubdash.TimeSettings)
|
|
})
|
|
|
|
t.Run("Creates pubdash whose dashboard has template variables successfully", func(t *testing.T) {
|
|
fakeDashboardService := &dashboards.FakeDashboardService{}
|
|
service, sqlStore, cfg := newPublicDashboardServiceImpl(t, nil, nil, nil, fakeDashboardService, nil)
|
|
dashboardStore, err := dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore))
|
|
require.NoError(t, err)
|
|
|
|
templateVars := make([]map[string]any, 1)
|
|
dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, "", true, templateVars, nil)
|
|
fakeDashboardService.On("GetDashboard", mock.Anything, mock.Anything, mock.Anything).Return(dashboard, nil)
|
|
|
|
isEnabled := true
|
|
dto := &SavePublicDashboardDTO{
|
|
DashboardUid: dashboard.UID,
|
|
OrgID: dashboard.OrgID,
|
|
UserId: 7,
|
|
PublicDashboard: &PublicDashboardDTO{
|
|
IsEnabled: &isEnabled,
|
|
},
|
|
}
|
|
|
|
_, err = service.Create(context.Background(), SignedInUser, dto)
|
|
require.NoError(t, err)
|
|
|
|
pubdash, err := service.FindByDashboardUid(context.Background(), dashboard.OrgID, dashboard.UID)
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, dashboard.UID, pubdash.DashboardUid)
|
|
assert.Equal(t, dashboard.OrgID, pubdash.OrgId)
|
|
})
|
|
|
|
t.Run("Throws an error when given pubdash uid already exists", func(t *testing.T) {
|
|
dashboard := dashboards.NewDashboard("testDashie")
|
|
pubdash := &PublicDashboard{
|
|
Uid: "ExistingUid",
|
|
IsEnabled: true,
|
|
AnnotationsEnabled: false,
|
|
DashboardUid: "NOTTHESAME",
|
|
OrgId: dashboard.OrgID,
|
|
TimeSettings: timeSettings,
|
|
}
|
|
|
|
publicDashboardStore := &FakePublicDashboardStore{}
|
|
publicDashboardStore.On("Find", mock.Anything, "ExistingUid").Return(pubdash, nil)
|
|
publicDashboardStore.On("FindByDashboardUid", mock.Anything, mock.Anything, mock.Anything).Return(nil, ErrPublicDashboardNotFound.Errorf(""))
|
|
fakeDashboardService := &dashboards.FakeDashboardService{}
|
|
fakeDashboardService.On("GetDashboard", mock.Anything, mock.Anything, mock.Anything).Return(dashboard, nil)
|
|
|
|
service, _, _ := newPublicDashboardServiceImpl(t, nil, nil, publicDashboardStore, fakeDashboardService, nil)
|
|
|
|
isEnabled := true
|
|
dto := &SavePublicDashboardDTO{
|
|
DashboardUid: "an-id",
|
|
OrgID: dashboard.OrgID,
|
|
UserId: 7,
|
|
PublicDashboard: &PublicDashboardDTO{
|
|
Uid: "ExistingUid",
|
|
IsEnabled: &isEnabled,
|
|
},
|
|
}
|
|
|
|
_, err := service.Create(context.Background(), SignedInUser, dto)
|
|
require.Error(t, err)
|
|
require.Equal(t, err, ErrPublicDashboardUidExists.Errorf("Create: public dashboard uid %s already exists", dto.PublicDashboard.Uid))
|
|
})
|
|
|
|
t.Run("Create public dashboard with given pubdash uid", func(t *testing.T) {
|
|
fakeDashboardService := &dashboards.FakeDashboardService{}
|
|
service, sqlStore, cfg := newPublicDashboardServiceImpl(t, nil, nil, nil, fakeDashboardService, nil)
|
|
dashboardStore, err := dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore))
|
|
require.NoError(t, err)
|
|
dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, "", true, []map[string]any{}, nil)
|
|
fakeDashboardService.On("GetDashboard", mock.Anything, mock.Anything, mock.Anything).Return(dashboard, nil)
|
|
|
|
isEnabled := true
|
|
|
|
dto := &SavePublicDashboardDTO{
|
|
DashboardUid: dashboard.UID,
|
|
UserId: 7,
|
|
OrgID: dashboard.OrgID,
|
|
PublicDashboard: &PublicDashboardDTO{
|
|
Uid: "GivenUid",
|
|
IsEnabled: &isEnabled,
|
|
},
|
|
}
|
|
|
|
_, err = service.Create(context.Background(), SignedInUser, dto)
|
|
require.NoError(t, err)
|
|
|
|
pubdash, err := service.FindByDashboardUid(context.Background(), dashboard.OrgID, dashboard.UID)
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, dto.PublicDashboard.Uid, pubdash.Uid)
|
|
})
|
|
|
|
t.Run("Throws an error when pubdash with given access token input already exists", func(t *testing.T) {
|
|
dashboard := dashboards.NewDashboard("testDashie")
|
|
pubdash := &PublicDashboard{
|
|
Uid: "ExistingUid",
|
|
AccessToken: "ExistingAccessToken",
|
|
IsEnabled: true,
|
|
AnnotationsEnabled: false,
|
|
DashboardUid: "NOTTHESAME",
|
|
OrgId: dashboard.OrgID,
|
|
TimeSettings: timeSettings,
|
|
}
|
|
|
|
publicDashboardStore := &FakePublicDashboardStore{}
|
|
publicDashboardStore.On("Find", mock.Anything, mock.Anything).Return(nil, nil)
|
|
publicDashboardStore.On("FindByAccessToken", mock.Anything, "ExistingAccessToken").Return(pubdash, nil)
|
|
publicDashboardStore.On("FindByDashboardUid", mock.Anything, mock.Anything, mock.Anything).Return(nil, ErrPublicDashboardNotFound.Errorf(""))
|
|
fakeDashboardService := &dashboards.FakeDashboardService{}
|
|
fakeDashboardService.On("GetDashboard", mock.Anything, mock.Anything, mock.Anything).Return(dashboard, nil)
|
|
service, _, _ := newPublicDashboardServiceImpl(t, nil, nil, publicDashboardStore, fakeDashboardService, nil)
|
|
|
|
isEnabled := true
|
|
dto := &SavePublicDashboardDTO{
|
|
DashboardUid: "an-id",
|
|
OrgID: dashboard.OrgID,
|
|
UserId: 7,
|
|
PublicDashboard: &PublicDashboardDTO{
|
|
AccessToken: "ExistingAccessToken",
|
|
IsEnabled: &isEnabled,
|
|
},
|
|
}
|
|
|
|
_, err := service.Create(context.Background(), SignedInUser, dto)
|
|
require.Error(t, err)
|
|
require.Equal(t, err, ErrPublicDashboardAccessTokenExists.Errorf("Create: public dashboard access token %s already exists", dto.PublicDashboard.AccessToken))
|
|
})
|
|
|
|
t.Run("Create public dashboard with given pubdash access token", func(t *testing.T) {
|
|
fakeDashboardService := &dashboards.FakeDashboardService{}
|
|
service, sqlStore, cfg := newPublicDashboardServiceImpl(t, nil, nil, nil, fakeDashboardService, nil)
|
|
dashboardStore, err := dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore))
|
|
require.NoError(t, err)
|
|
dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, "", true, []map[string]interface{}{}, nil)
|
|
fakeDashboardService.On("GetDashboard", mock.Anything, mock.Anything, mock.Anything).Return(dashboard, nil)
|
|
|
|
isEnabled := true
|
|
|
|
dto := &SavePublicDashboardDTO{
|
|
DashboardUid: dashboard.UID,
|
|
UserId: 7,
|
|
OrgID: dashboard.OrgID,
|
|
PublicDashboard: &PublicDashboardDTO{
|
|
AccessToken: "GivenAccessToken",
|
|
IsEnabled: &isEnabled,
|
|
},
|
|
}
|
|
|
|
_, err = service.Create(context.Background(), SignedInUser, dto)
|
|
require.NoError(t, err)
|
|
|
|
pubdash, err := service.FindByDashboardUid(context.Background(), dashboard.OrgID, dashboard.UID)
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, dto.PublicDashboard.AccessToken, pubdash.AccessToken)
|
|
})
|
|
|
|
t.Run("Throws an error when pubdash with generated access token already exists", func(t *testing.T) {
|
|
dashboard := dashboards.NewDashboard("testDashie")
|
|
pubdash := &PublicDashboard{
|
|
IsEnabled: true,
|
|
AnnotationsEnabled: false,
|
|
DashboardUid: "NOTTHESAME",
|
|
OrgId: dashboard.OrgID,
|
|
TimeSettings: timeSettings,
|
|
}
|
|
|
|
publicDashboardStore := &FakePublicDashboardStore{}
|
|
publicDashboardStore.On("FindByAccessToken", mock.Anything, mock.Anything).Return(pubdash, nil)
|
|
service, _, _ := newPublicDashboardServiceImpl(t, nil, nil, publicDashboardStore, nil, nil)
|
|
|
|
_, err := service.NewPublicDashboardAccessToken(context.Background())
|
|
require.Error(t, err)
|
|
require.Equal(t, err, ErrInternalServerError.Errorf("failed to generate a unique accessToken for public dashboard"))
|
|
})
|
|
|
|
t.Run("Returns error if public dashboard exists", func(t *testing.T) {
|
|
publicdashboardStore := &FakePublicDashboardStore{}
|
|
publicdashboardStore.On("FindByDashboardUid", mock.Anything, mock.Anything, mock.Anything).Return(&PublicDashboard{Uid: "newPubdashUid"}, nil)
|
|
publicdashboardStore.On("Find", mock.Anything, mock.Anything).Return(nil, nil)
|
|
fakeDashboardService := &dashboards.FakeDashboardService{}
|
|
service, sqlStore, cfg := newPublicDashboardServiceImpl(t, nil, nil, publicdashboardStore, fakeDashboardService, nil)
|
|
|
|
dashboardStore, err := dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore))
|
|
require.NoError(t, err)
|
|
dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, "", true, []map[string]any{}, nil)
|
|
fakeDashboardService.On("GetDashboard", mock.Anything, mock.Anything, mock.Anything).Return(dashboard, nil)
|
|
|
|
isEnabled, annotationsEnabled := true, false
|
|
dto := &SavePublicDashboardDTO{
|
|
DashboardUid: dashboard.UID,
|
|
UserId: 7,
|
|
PublicDashboard: &PublicDashboardDTO{
|
|
AnnotationsEnabled: &annotationsEnabled,
|
|
IsEnabled: &isEnabled,
|
|
},
|
|
}
|
|
|
|
savedPubdash, err := service.Create(context.Background(), SignedInUser, dto)
|
|
assert.Error(t, err)
|
|
assert.Nil(t, savedPubdash)
|
|
assert.True(t, ErrDashboardIsPublic.Is(err))
|
|
})
|
|
|
|
t.Run("Validate pubdash has default share value", func(t *testing.T) {
|
|
fakeDashboardService := &dashboards.FakeDashboardService{}
|
|
service, sqlStore, cfg := newPublicDashboardServiceImpl(t, nil, nil, nil, fakeDashboardService, nil)
|
|
|
|
dashboardStore, err := dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore))
|
|
require.NoError(t, err)
|
|
dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, "", true, []map[string]any{}, nil)
|
|
fakeDashboardService.On("GetDashboard", mock.Anything, mock.Anything, mock.Anything).Return(dashboard, nil)
|
|
|
|
isEnabled := true
|
|
dto := &SavePublicDashboardDTO{
|
|
DashboardUid: dashboard.UID,
|
|
OrgID: dashboard.OrgID,
|
|
UserId: 7,
|
|
PublicDashboard: &PublicDashboardDTO{
|
|
IsEnabled: &isEnabled,
|
|
},
|
|
}
|
|
|
|
_, err = service.Create(context.Background(), SignedInUser, dto)
|
|
require.NoError(t, err)
|
|
|
|
pubdash, err := service.FindByDashboardUid(context.Background(), dashboard.OrgID, dashboard.UID)
|
|
require.NoError(t, err)
|
|
// if share type is empty should be populated with public by default
|
|
assert.Equal(t, PublicShareType, pubdash.Share)
|
|
})
|
|
}
|
|
|
|
func assertFalseIfNull(t *testing.T, expectedValue bool, nullableValue *bool) {
|
|
if nullableValue == nil {
|
|
assert.Equal(t, expectedValue, false)
|
|
} else {
|
|
assert.Equal(t, expectedValue, *nullableValue)
|
|
}
|
|
}
|
|
|
|
func TestUpdatePublicDashboard(t *testing.T) {
|
|
fakeDashboardService := &dashboards.FakeDashboardService{}
|
|
service, sqlStore, cfg := newPublicDashboardServiceImpl(t, nil, nil, nil, fakeDashboardService, nil)
|
|
|
|
dashboardStore, err := dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore))
|
|
require.NoError(t, err)
|
|
dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, "", true, []map[string]any{}, nil)
|
|
dashboard2 := insertTestDashboard(t, dashboardStore, "testDashie2", 1, 0, "", true, []map[string]any{}, nil)
|
|
fakeDashboardService.On("GetDashboard", mock.Anything, mock.Anything, mock.Anything).Return(dashboard, nil)
|
|
|
|
t.Run("Updating public dashboard", func(t *testing.T) {
|
|
isEnabled, annotationsEnabled, timeSelectionEnabled := true, false, false
|
|
dto := &SavePublicDashboardDTO{
|
|
DashboardUid: dashboard.UID,
|
|
UserId: 7,
|
|
PublicDashboard: &PublicDashboardDTO{
|
|
IsEnabled: &isEnabled,
|
|
AnnotationsEnabled: &annotationsEnabled,
|
|
TimeSelectionEnabled: &timeSelectionEnabled,
|
|
},
|
|
}
|
|
|
|
// insert initial pubdash
|
|
savedPubdash, err := service.Create(context.Background(), SignedInUser, dto)
|
|
require.NoError(t, err)
|
|
|
|
isEnabled, annotationsEnabled, timeSelectionEnabled = true, true, true
|
|
|
|
dto = &SavePublicDashboardDTO{
|
|
Uid: savedPubdash.Uid,
|
|
DashboardUid: dashboard.UID,
|
|
OrgID: 9,
|
|
UserId: 8,
|
|
PublicDashboard: &PublicDashboardDTO{
|
|
IsEnabled: &isEnabled,
|
|
AnnotationsEnabled: &annotationsEnabled,
|
|
TimeSelectionEnabled: &timeSelectionEnabled,
|
|
},
|
|
}
|
|
|
|
updatedPubdash, err := service.Update(context.Background(), SignedInUser, dto)
|
|
require.NoError(t, err)
|
|
|
|
// don't get updated
|
|
assert.Equal(t, savedPubdash.DashboardUid, updatedPubdash.DashboardUid)
|
|
assert.Equal(t, savedPubdash.OrgId, updatedPubdash.OrgId)
|
|
assert.Equal(t, savedPubdash.CreatedAt, updatedPubdash.CreatedAt)
|
|
assert.Equal(t, savedPubdash.CreatedBy, updatedPubdash.CreatedBy)
|
|
assert.Equal(t, savedPubdash.AccessToken, updatedPubdash.AccessToken)
|
|
|
|
// gets updated
|
|
assert.Equal(t, *dto.PublicDashboard.IsEnabled, updatedPubdash.IsEnabled)
|
|
assert.Equal(t, *dto.PublicDashboard.AnnotationsEnabled, updatedPubdash.AnnotationsEnabled)
|
|
assert.Equal(t, *dto.PublicDashboard.TimeSelectionEnabled, updatedPubdash.TimeSelectionEnabled)
|
|
assert.Equal(t, dto.UserId, updatedPubdash.UpdatedBy)
|
|
assert.NotEqual(t, &time.Time{}, updatedPubdash.UpdatedAt)
|
|
})
|
|
|
|
t.Run("Updating set empty time settings", func(t *testing.T) {
|
|
isEnabled := true
|
|
|
|
dto := &SavePublicDashboardDTO{
|
|
DashboardUid: dashboard.UID,
|
|
UserId: 7,
|
|
PublicDashboard: &PublicDashboardDTO{
|
|
IsEnabled: &isEnabled,
|
|
},
|
|
}
|
|
|
|
savedPubdash, err := service.Create(context.Background(), SignedInUser, dto)
|
|
require.NoError(t, err)
|
|
|
|
dto = &SavePublicDashboardDTO{
|
|
Uid: savedPubdash.Uid,
|
|
DashboardUid: dashboard.UID,
|
|
OrgID: 9,
|
|
UserId: 8,
|
|
PublicDashboard: &PublicDashboardDTO{
|
|
IsEnabled: &isEnabled,
|
|
},
|
|
}
|
|
|
|
updatedPubdash, err := service.Update(context.Background(), SignedInUser, dto)
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, &TimeSettings{}, updatedPubdash.TimeSettings)
|
|
})
|
|
|
|
t.Run("Should fail when public dashboard uid does not match dashboard uid", func(t *testing.T) {
|
|
isEnabled := true
|
|
|
|
dto := &SavePublicDashboardDTO{
|
|
DashboardUid: dashboard.UID,
|
|
UserId: 7,
|
|
PublicDashboard: &PublicDashboardDTO{
|
|
IsEnabled: &isEnabled,
|
|
},
|
|
}
|
|
|
|
// insert initial pubdash
|
|
savedPubdash, err := service.Create(context.Background(), SignedInUser, dto)
|
|
require.NoError(t, err)
|
|
|
|
dto = &SavePublicDashboardDTO{
|
|
Uid: savedPubdash.Uid,
|
|
DashboardUid: dashboard2.UID,
|
|
OrgID: 9,
|
|
UserId: 8,
|
|
PublicDashboard: &PublicDashboardDTO{
|
|
IsEnabled: &isEnabled,
|
|
},
|
|
}
|
|
_, err = service.Update(context.Background(), SignedInUser, dto)
|
|
assert.Error(t, err)
|
|
})
|
|
|
|
t.Run("Updating not existent dashboard", func(t *testing.T) {
|
|
dto := &SavePublicDashboardDTO{
|
|
DashboardUid: "NOTEXISTENTDASHBOARD",
|
|
UserId: 7,
|
|
PublicDashboard: &PublicDashboardDTO{},
|
|
}
|
|
fds := &dashboards.FakeDashboardService{}
|
|
fds.On("GetDashboard", mock.Anything, mock.Anything, mock.Anything).Return(nil, dashboards.ErrDashboardNotFound)
|
|
service.dashboardService = fds
|
|
updatedPubdash, err := service.Update(context.Background(), SignedInUser, dto)
|
|
assert.Error(t, err)
|
|
|
|
var grafanaErr errutil.Error
|
|
ok := errors.As(err, &grafanaErr)
|
|
assert.True(t, ok)
|
|
assert.Equal(t, "publicdashboards.dashboardNotFound", grafanaErr.MessageID)
|
|
assert.Empty(t, updatedPubdash)
|
|
})
|
|
|
|
trueBooleanField := true
|
|
timeSettings := &TimeSettings{From: "now-8", To: "now"}
|
|
shareType := EmailShareType
|
|
|
|
testCases := []struct {
|
|
Name string
|
|
IsEnabled *bool
|
|
TimeSelectionEnabled *bool
|
|
AnnotationsEnabled *bool
|
|
TimeSettings *TimeSettings
|
|
ShareType ShareType
|
|
}{
|
|
{
|
|
Name: "isEnabled",
|
|
IsEnabled: nil,
|
|
TimeSelectionEnabled: &trueBooleanField,
|
|
AnnotationsEnabled: &trueBooleanField,
|
|
TimeSettings: timeSettings,
|
|
ShareType: shareType,
|
|
},
|
|
{
|
|
Name: "timeSelectionEnabled",
|
|
IsEnabled: &trueBooleanField,
|
|
TimeSelectionEnabled: nil,
|
|
AnnotationsEnabled: &trueBooleanField,
|
|
TimeSettings: timeSettings,
|
|
ShareType: shareType,
|
|
},
|
|
{
|
|
Name: "annotationsEnabled",
|
|
IsEnabled: &trueBooleanField,
|
|
TimeSelectionEnabled: &trueBooleanField,
|
|
AnnotationsEnabled: nil,
|
|
TimeSettings: timeSettings,
|
|
ShareType: shareType,
|
|
},
|
|
{
|
|
Name: "isEnabled, timeSelectionEnabled and annotationsEnabled",
|
|
IsEnabled: nil,
|
|
TimeSelectionEnabled: nil,
|
|
AnnotationsEnabled: nil,
|
|
TimeSettings: nil,
|
|
ShareType: "",
|
|
},
|
|
}
|
|
|
|
for _, tt := range testCases {
|
|
t.Run(fmt.Sprintf("Update public dashboard with %s null boolean fields let those fields with old persisted value", tt.Name), func(t *testing.T) {
|
|
fakeDashboardService := &dashboards.FakeDashboardService{}
|
|
service, sqlStore, cfg := newPublicDashboardServiceImpl(t, nil, nil, nil, fakeDashboardService, nil)
|
|
|
|
dashboardStore, err := dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore))
|
|
require.NoError(t, err)
|
|
dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, "", true, []map[string]any{}, nil)
|
|
fakeDashboardService.On("GetDashboard", mock.Anything, mock.Anything, mock.Anything).Return(dashboard, nil)
|
|
|
|
isEnabled, annotationsEnabled, timeSelectionEnabled := true, true, false
|
|
|
|
dto := &SavePublicDashboardDTO{
|
|
DashboardUid: dashboard.UID,
|
|
UserId: 7,
|
|
PublicDashboard: &PublicDashboardDTO{
|
|
IsEnabled: &isEnabled,
|
|
AnnotationsEnabled: &annotationsEnabled,
|
|
TimeSelectionEnabled: &timeSelectionEnabled,
|
|
Share: PublicShareType,
|
|
},
|
|
}
|
|
|
|
// insert initial pubdash
|
|
savedPubdash, err := service.Create(context.Background(), SignedInUser, dto)
|
|
require.NoError(t, err)
|
|
|
|
dto = &SavePublicDashboardDTO{
|
|
Uid: savedPubdash.Uid,
|
|
DashboardUid: dashboard.UID,
|
|
OrgID: 9,
|
|
UserId: 8,
|
|
PublicDashboard: &PublicDashboardDTO{
|
|
IsEnabled: tt.IsEnabled,
|
|
AnnotationsEnabled: tt.AnnotationsEnabled,
|
|
TimeSelectionEnabled: tt.TimeSelectionEnabled,
|
|
Share: tt.ShareType,
|
|
},
|
|
}
|
|
updatedPubdash, err := service.Update(context.Background(), SignedInUser, dto)
|
|
require.NoError(t, err)
|
|
|
|
assertOldValueIfNull(t, updatedPubdash.IsEnabled, savedPubdash.IsEnabled, dto.PublicDashboard.IsEnabled)
|
|
assertOldValueIfNull(t, updatedPubdash.AnnotationsEnabled, savedPubdash.AnnotationsEnabled, dto.PublicDashboard.AnnotationsEnabled)
|
|
assertOldValueIfNull(t, updatedPubdash.TimeSelectionEnabled, savedPubdash.TimeSelectionEnabled, dto.PublicDashboard.TimeSelectionEnabled)
|
|
|
|
if dto.PublicDashboard.Share == "" {
|
|
assert.Equal(t, updatedPubdash.Share, savedPubdash.Share)
|
|
} else {
|
|
assert.Equal(t, updatedPubdash.Share, dto.PublicDashboard.Share)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func assertOldValueIfNull(t *testing.T, expectedValue bool, oldValue bool, nullableValue *bool) {
|
|
if nullableValue == nil {
|
|
assert.Equal(t, expectedValue, oldValue)
|
|
} else {
|
|
assert.Equal(t, expectedValue, *nullableValue)
|
|
}
|
|
}
|
|
|
|
func TestDeletePublicDashboard(t *testing.T) {
|
|
pubdash := &PublicDashboard{Uid: "2", OrgId: 1, DashboardUid: "uid"}
|
|
|
|
type mockFindResponse struct {
|
|
PublicDashboard *PublicDashboard
|
|
Err error
|
|
}
|
|
|
|
type mockDeleteResponse struct {
|
|
AffectedRowsResp int64
|
|
StoreRespErr error
|
|
}
|
|
|
|
testCases := []struct {
|
|
Name string
|
|
ExpectedErrResp error
|
|
mockFindStore *mockFindResponse
|
|
mockDeleteStore *mockDeleteResponse
|
|
}{
|
|
{
|
|
Name: "Successfully deletes a public dashboard",
|
|
ExpectedErrResp: nil,
|
|
mockFindStore: &mockFindResponse{pubdash, nil},
|
|
mockDeleteStore: &mockDeleteResponse{1, nil},
|
|
},
|
|
{
|
|
Name: "Public dashboard not found",
|
|
ExpectedErrResp: ErrInternalServerError.Errorf("Delete: failed to find public dashboard by uid: pubdashUID: error"),
|
|
mockFindStore: &mockFindResponse{pubdash, errors.New("error")},
|
|
mockDeleteStore: &mockDeleteResponse{0, nil},
|
|
},
|
|
{
|
|
Name: "Public dashboard not found by UID",
|
|
ExpectedErrResp: ErrPublicDashboardNotFound.Errorf("Delete: public dashboard not found by uid: pubdashUID"),
|
|
mockFindStore: &mockFindResponse{nil, nil},
|
|
mockDeleteStore: &mockDeleteResponse{0, nil},
|
|
},
|
|
{
|
|
Name: "Public dashboard UID does not belong to the dashboard",
|
|
ExpectedErrResp: ErrInvalidUid.Errorf("Delete: the public dashboard does not belong to the dashboard"),
|
|
mockFindStore: &mockFindResponse{&PublicDashboard{Uid: "2", OrgId: 1, DashboardUid: "wrong"}, nil},
|
|
mockDeleteStore: &mockDeleteResponse{0, nil},
|
|
},
|
|
|
|
{
|
|
Name: "Failed to delete - Database error",
|
|
ExpectedErrResp: ErrInternalServerError.Errorf("Delete: failed to delete a public dashboard by Uid: pubdashUID db error!"),
|
|
mockFindStore: &mockFindResponse{pubdash, nil},
|
|
mockDeleteStore: &mockDeleteResponse{1, errors.New("db error!")},
|
|
},
|
|
}
|
|
|
|
for _, tt := range testCases {
|
|
t.Run(tt.Name, func(t *testing.T) {
|
|
store := NewFakePublicDashboardStore(t)
|
|
store.On("Find", mock.Anything, mock.Anything).Return(tt.mockFindStore.PublicDashboard, tt.mockFindStore.Err)
|
|
if tt.ExpectedErrResp == nil || tt.mockDeleteStore.StoreRespErr != nil {
|
|
store.On("Delete", mock.Anything, mock.Anything).Return(tt.mockDeleteStore.AffectedRowsResp, tt.mockDeleteStore.StoreRespErr)
|
|
}
|
|
service, _, _ := newPublicDashboardServiceImpl(t, nil, nil, store, nil, nil)
|
|
|
|
err := service.Delete(context.Background(), "pubdashUID", "uid")
|
|
if tt.ExpectedErrResp != nil {
|
|
assert.Equal(t, tt.ExpectedErrResp.Error(), err.Error())
|
|
} else {
|
|
assert.NoError(t, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPublicDashboardServiceImpl_getSafeIntervalAndMaxDataPoints(t *testing.T) {
|
|
type args struct {
|
|
reqDTO PublicDashboardQueryDTO
|
|
ts TimeSettings
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
wantSafeInterval int64
|
|
wantSafeMaxDataPoints int64
|
|
}{
|
|
{
|
|
name: "return original interval",
|
|
args: args{
|
|
reqDTO: PublicDashboardQueryDTO{
|
|
IntervalMs: 10000,
|
|
MaxDataPoints: 300,
|
|
},
|
|
ts: TimeSettings{
|
|
From: "now-3h",
|
|
To: "now",
|
|
},
|
|
},
|
|
wantSafeInterval: 10000,
|
|
wantSafeMaxDataPoints: 300,
|
|
},
|
|
{
|
|
name: "return safe interval because of a small interval",
|
|
args: args{
|
|
reqDTO: PublicDashboardQueryDTO{
|
|
IntervalMs: 1000,
|
|
MaxDataPoints: 300,
|
|
},
|
|
ts: TimeSettings{
|
|
From: "now-6h",
|
|
To: "now",
|
|
},
|
|
},
|
|
wantSafeInterval: 2000,
|
|
wantSafeMaxDataPoints: 11000,
|
|
},
|
|
{
|
|
name: "return safe interval for long time range",
|
|
args: args{
|
|
reqDTO: PublicDashboardQueryDTO{
|
|
IntervalMs: 100,
|
|
MaxDataPoints: 300,
|
|
},
|
|
ts: TimeSettings{
|
|
From: "now-90d",
|
|
To: "now",
|
|
},
|
|
},
|
|
wantSafeInterval: 600000,
|
|
wantSafeMaxDataPoints: 11000,
|
|
},
|
|
{
|
|
name: "return safe interval when reqDTO is empty",
|
|
args: args{
|
|
reqDTO: PublicDashboardQueryDTO{},
|
|
ts: TimeSettings{
|
|
From: "now-90d",
|
|
To: "now",
|
|
},
|
|
},
|
|
wantSafeInterval: 600000,
|
|
wantSafeMaxDataPoints: 11000,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
pd := &PublicDashboardServiceImpl{
|
|
intervalCalculator: intervalv2.NewCalculator(),
|
|
}
|
|
got, got1 := pd.getSafeIntervalAndMaxDataPoints(tt.args.reqDTO, tt.args.ts)
|
|
assert.Equalf(t, tt.wantSafeInterval, got, "getSafeIntervalAndMaxDataPoints(%v, %v)", tt.args.reqDTO, tt.args.ts)
|
|
assert.Equalf(t, tt.wantSafeMaxDataPoints, got1, "getSafeIntervalAndMaxDataPoints(%v, %v)", tt.args.reqDTO, tt.args.ts)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDashboardEnabledChanged(t *testing.T) {
|
|
t.Run("created isEnabled: false", func(t *testing.T) {
|
|
assert.False(t, publicDashboardIsEnabledChanged(nil, &PublicDashboard{IsEnabled: false}))
|
|
})
|
|
|
|
t.Run("created isEnabled: true", func(t *testing.T) {
|
|
assert.True(t, publicDashboardIsEnabledChanged(nil, &PublicDashboard{IsEnabled: true}))
|
|
})
|
|
|
|
t.Run("updated isEnabled same", func(t *testing.T) {
|
|
assert.False(t, publicDashboardIsEnabledChanged(&PublicDashboard{IsEnabled: true}, &PublicDashboard{IsEnabled: true}))
|
|
})
|
|
|
|
t.Run("updated isEnabled changed", func(t *testing.T) {
|
|
assert.True(t, publicDashboardIsEnabledChanged(&PublicDashboard{IsEnabled: false}, &PublicDashboard{IsEnabled: true}))
|
|
})
|
|
}
|
|
|
|
func TestPublicDashboardServiceImpl_ListPublicDashboards(t *testing.T) {
|
|
features := featuremgmt.WithFeatures()
|
|
testDB, cfg := db.InitTestDBWithCfg(t)
|
|
dashStore, err := dashboardsDB.ProvideDashboardStore(testDB, cfg, features, tagimpl.ProvideService(testDB))
|
|
require.NoError(t, err)
|
|
ac := acmock.New()
|
|
|
|
fStore := folderimpl.ProvideStore(testDB)
|
|
folderPermissions := acmock.NewMockedPermissionsService()
|
|
folderStore := folderimpl.ProvideDashboardFolderStore(testDB)
|
|
folderSvc := folderimpl.ProvideService(
|
|
fStore, ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashStore, folderStore,
|
|
nil, testDB, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService(), sort.ProvideService())
|
|
|
|
dashboardService, err := dashsvc.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, featuremgmt.WithFeatures(), folderPermissions, ac, folderSvc, fStore, nil, client.MockTestRestConfig{}, nil, quotatest.New(false, nil), nil, nil, nil, dualwrite.ProvideTestService(), sort.ProvideService())
|
|
require.NoError(t, err)
|
|
dashboardService.RegisterDashboardPermissions(&actest.FakePermissionsService{})
|
|
fakeGuardian := &guardian.FakeDashboardGuardian{
|
|
CanSaveValue: true,
|
|
CanEditUIDs: []string{},
|
|
CanViewUIDs: []string{},
|
|
}
|
|
guardian.MockDashboardGuardian(fakeGuardian)
|
|
|
|
// insert in test data so we can check that permissions are working properly through the dashboard service
|
|
// this will create 4 dashboards and 3 users
|
|
// user1 has access to all dashboards ("*")
|
|
// user2 has access to solely one dashboard
|
|
// user3 has access to all created dashboards through specific permissions
|
|
creatingUser := &user.SignedInUser{
|
|
UserID: 1,
|
|
OrgID: 1,
|
|
OrgRole: org.RoleAdmin,
|
|
}
|
|
dashboardsToSave := []dashboards.SaveDashboardDTO{
|
|
{
|
|
OrgID: 1,
|
|
User: creatingUser,
|
|
Dashboard: &dashboards.Dashboard{
|
|
OrgID: 1,
|
|
UID: "9S6TmO67z",
|
|
Title: "test",
|
|
Slug: "test",
|
|
Data: simplejson.New(),
|
|
},
|
|
},
|
|
{
|
|
OrgID: 1,
|
|
User: creatingUser,
|
|
Dashboard: &dashboards.Dashboard{
|
|
OrgID: 1,
|
|
UID: "1S6TmO67z",
|
|
Title: "my first dashboard",
|
|
Slug: "my-first-dashboard",
|
|
Data: simplejson.New(),
|
|
},
|
|
},
|
|
{
|
|
OrgID: 1,
|
|
User: creatingUser,
|
|
Dashboard: &dashboards.Dashboard{
|
|
OrgID: 1,
|
|
UID: "2S6TmO67z",
|
|
Title: "my second dashboard",
|
|
Slug: "my-second-dashboard",
|
|
Data: simplejson.New(),
|
|
},
|
|
},
|
|
{
|
|
OrgID: 1,
|
|
User: creatingUser,
|
|
Dashboard: &dashboards.Dashboard{
|
|
OrgID: 1,
|
|
UID: "0S6TmO67z",
|
|
Title: "my zero dashboard",
|
|
Slug: "my-zero-dashboard",
|
|
Data: simplejson.New(),
|
|
},
|
|
},
|
|
}
|
|
for _, dash := range dashboardsToSave {
|
|
_, err = dashboardService.SaveDashboard(context.Background(), &dash, true)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
users := []user.User{
|
|
{
|
|
ID: 1,
|
|
UID: "user1",
|
|
Email: "test1@gmail.com",
|
|
Login: "user1",
|
|
Created: time.Now(),
|
|
Updated: time.Now(),
|
|
},
|
|
{
|
|
ID: 2,
|
|
UID: "user2",
|
|
Login: "user2",
|
|
Email: "test2@gmail.com",
|
|
Created: time.Now(),
|
|
Updated: time.Now(),
|
|
},
|
|
{
|
|
ID: 3,
|
|
UID: "user3",
|
|
Login: "user3",
|
|
Email: "test3@gmail.com",
|
|
Created: time.Now(),
|
|
Updated: time.Now(),
|
|
},
|
|
}
|
|
roles := []accesscontrol.Role{
|
|
{
|
|
ID: 1,
|
|
UID: "role1",
|
|
Name: "forUser1",
|
|
Created: time.Now(),
|
|
Updated: time.Now(),
|
|
},
|
|
{
|
|
ID: 2,
|
|
UID: "role2",
|
|
Name: "forUser2",
|
|
Created: time.Now(),
|
|
Updated: time.Now(),
|
|
},
|
|
{
|
|
ID: 3,
|
|
UID: "role3",
|
|
Name: "forUser3",
|
|
Created: time.Now(),
|
|
Updated: time.Now(),
|
|
},
|
|
}
|
|
|
|
userRoles := []accesscontrol.UserRole{
|
|
{
|
|
ID: 1,
|
|
OrgID: 1,
|
|
UserID: 1,
|
|
RoleID: 1,
|
|
Created: time.Now(),
|
|
},
|
|
{
|
|
ID: 2,
|
|
OrgID: 1,
|
|
UserID: 2,
|
|
RoleID: 2,
|
|
Created: time.Now(),
|
|
},
|
|
{
|
|
ID: 3,
|
|
OrgID: 1,
|
|
UserID: 3,
|
|
RoleID: 3,
|
|
Created: time.Now(),
|
|
},
|
|
}
|
|
|
|
permissions := []accesscontrol.Permission{
|
|
{
|
|
ID: 1,
|
|
RoleID: 1,
|
|
Action: dashboards.ActionDashboardsRead,
|
|
Scope: "*",
|
|
Kind: "dashboards",
|
|
Created: time.Now(),
|
|
Updated: time.Now(),
|
|
},
|
|
{
|
|
ID: 2,
|
|
RoleID: 2,
|
|
Action: dashboards.ActionDashboardsRead,
|
|
Scope: "dashboards:uid:1S6TmO67z",
|
|
Attribute: "uid",
|
|
Identifier: "1S6TmO67z",
|
|
Kind: "dashboards",
|
|
Created: time.Now(),
|
|
Updated: time.Now(),
|
|
},
|
|
{
|
|
ID: 3,
|
|
RoleID: 3,
|
|
Action: dashboards.ActionDashboardsRead,
|
|
Scope: "dashboards:uid:0S6TmO67z",
|
|
Identifier: "0S6TmO67z",
|
|
Attribute: "uid",
|
|
Kind: "dashboards",
|
|
Created: time.Now(),
|
|
Updated: time.Now(),
|
|
},
|
|
{
|
|
ID: 4,
|
|
RoleID: 3,
|
|
Action: dashboards.ActionDashboardsRead,
|
|
Scope: "dashboards:uid:1S6TmO67z",
|
|
Identifier: "1S6TmO67z",
|
|
Kind: "dashboards",
|
|
Attribute: "uid",
|
|
Created: time.Now(),
|
|
Updated: time.Now(),
|
|
},
|
|
{
|
|
ID: 5,
|
|
RoleID: 3,
|
|
Action: dashboards.ActionDashboardsRead,
|
|
Scope: "dashboards:uid:2S6TmO67z",
|
|
Identifier: "2S6TmO67z",
|
|
Kind: "dashboards",
|
|
Attribute: "uid",
|
|
Created: time.Now(),
|
|
Updated: time.Now(),
|
|
},
|
|
{
|
|
ID: 6,
|
|
RoleID: 3,
|
|
Action: dashboards.ActionDashboardsRead,
|
|
Scope: "dashboards:uid:9S6TmO67z",
|
|
Identifier: "9S6TmO67z",
|
|
Kind: "dashboards",
|
|
Attribute: "uid",
|
|
Created: time.Now(),
|
|
Updated: time.Now(),
|
|
},
|
|
{
|
|
ID: 7,
|
|
RoleID: 1,
|
|
Action: dashboards.ActionFoldersRead,
|
|
Scope: "*",
|
|
Kind: "folders",
|
|
Created: time.Now(),
|
|
Updated: time.Now(),
|
|
},
|
|
{
|
|
ID: 8,
|
|
RoleID: 2,
|
|
Action: dashboards.ActionFoldersRead,
|
|
Scope: "*",
|
|
Kind: "folders",
|
|
Created: time.Now(),
|
|
Updated: time.Now(),
|
|
},
|
|
{
|
|
ID: 9,
|
|
RoleID: 3,
|
|
Action: dashboards.ActionFoldersRead,
|
|
Scope: "*",
|
|
Kind: "folders",
|
|
Created: time.Now(),
|
|
Updated: time.Now(),
|
|
},
|
|
}
|
|
|
|
err = testDB.WithDbSession(context.Background(), func(sess *db.Session) error {
|
|
if _, err := sess.Insert(users); err != nil {
|
|
return err
|
|
}
|
|
if _, err := sess.Insert(roles); err != nil {
|
|
return err
|
|
}
|
|
|
|
if _, err := sess.Insert(userRoles); err != nil {
|
|
return err
|
|
}
|
|
_, err := sess.Insert(permissions)
|
|
return err
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
type args struct {
|
|
ctx context.Context
|
|
query *PublicDashboardListQuery
|
|
}
|
|
type mockResponse struct {
|
|
PublicDashboardListResponseWithPagination *PublicDashboardListResponseWithPagination
|
|
Err error
|
|
DashboardResponse []dashboards.DashboardSearchProjection
|
|
DashboardErr error
|
|
}
|
|
|
|
expectedFinalResponse := []*PublicDashboardListResponse{
|
|
{
|
|
Uid: "1GwW7mgVk",
|
|
AccessToken: "1b458cb7fe7f42c68712078bcacee6e3",
|
|
DashboardUid: "1S6TmO67z",
|
|
Title: "my first dashboard",
|
|
Slug: "my-first-dashboard",
|
|
IsEnabled: true,
|
|
},
|
|
{
|
|
Uid: "2GwW7mgVk",
|
|
AccessToken: "2b458cb7fe7f42c68712078bcacee6e3",
|
|
DashboardUid: "2S6TmO67z",
|
|
Title: "my second dashboard",
|
|
Slug: "my-second-dashboard",
|
|
IsEnabled: false,
|
|
},
|
|
{
|
|
Uid: "0GwW7mgVk",
|
|
AccessToken: "0b458cb7fe7f42c68712078bcacee6e3",
|
|
DashboardUid: "0S6TmO67z",
|
|
Title: "my zero dashboard",
|
|
Slug: "my-zero-dashboard",
|
|
IsEnabled: true,
|
|
},
|
|
{
|
|
Uid: "9GwW7mgVk",
|
|
AccessToken: "deletedashboardaccesstoken",
|
|
DashboardUid: "9S6TmO67z",
|
|
Title: "test",
|
|
Slug: "test",
|
|
IsEnabled: true,
|
|
},
|
|
}
|
|
mockedStoreResponse := []*PublicDashboardListResponse{
|
|
{
|
|
Uid: "0GwW7mgVk",
|
|
AccessToken: "0b458cb7fe7f42c68712078bcacee6e3",
|
|
DashboardUid: "0S6TmO67z",
|
|
IsEnabled: true,
|
|
},
|
|
{
|
|
Uid: "1GwW7mgVk",
|
|
AccessToken: "1b458cb7fe7f42c68712078bcacee6e3",
|
|
DashboardUid: "1S6TmO67z",
|
|
IsEnabled: true,
|
|
},
|
|
{
|
|
Uid: "2GwW7mgVk",
|
|
AccessToken: "2b458cb7fe7f42c68712078bcacee6e3",
|
|
DashboardUid: "2S6TmO67z",
|
|
IsEnabled: false,
|
|
},
|
|
{
|
|
Uid: "9GwW7mgVk",
|
|
AccessToken: "deletedashboardaccesstoken",
|
|
DashboardUid: "9S6TmO67z",
|
|
IsEnabled: true,
|
|
},
|
|
}
|
|
|
|
testCases := []struct {
|
|
name string
|
|
args args
|
|
want *PublicDashboardListResponseWithPagination
|
|
mockResponse *mockResponse
|
|
wantErr assert.ErrorAssertionFunc
|
|
}{
|
|
{
|
|
name: "should return full response when user has access to all dashboards",
|
|
args: args{
|
|
ctx: context.Background(),
|
|
query: &PublicDashboardListQuery{
|
|
User: &user.SignedInUser{OrgID: 1, UserID: 1, Permissions: map[int64]map[string][]string{1: {"dashboards:read": {"*"}, "folders:read": {"*"}}}},
|
|
OrgID: 1,
|
|
Page: 1,
|
|
Limit: 50,
|
|
},
|
|
},
|
|
mockResponse: &mockResponse{
|
|
PublicDashboardListResponseWithPagination: &PublicDashboardListResponseWithPagination{
|
|
TotalCount: int64(len(mockedStoreResponse)),
|
|
PublicDashboards: mockedStoreResponse,
|
|
},
|
|
Err: nil,
|
|
},
|
|
want: &PublicDashboardListResponseWithPagination{
|
|
Page: 1,
|
|
PerPage: 50,
|
|
TotalCount: int64(len(expectedFinalResponse)),
|
|
PublicDashboards: expectedFinalResponse,
|
|
},
|
|
wantErr: assert.NoError,
|
|
},
|
|
{
|
|
name: "should only return the one dashboard user 2 has access to",
|
|
args: args{
|
|
ctx: context.Background(),
|
|
query: &PublicDashboardListQuery{
|
|
User: &user.SignedInUser{OrgID: 1, UserID: 2, Permissions: map[int64]map[string][]string{1: {"dashboards:read": {"dashboards:uid:1S6TmO67z"}, "folders:read": {"*"}}}},
|
|
OrgID: 1,
|
|
Page: 1,
|
|
Limit: 50,
|
|
},
|
|
},
|
|
mockResponse: &mockResponse{
|
|
PublicDashboardListResponseWithPagination: &PublicDashboardListResponseWithPagination{
|
|
TotalCount: int64(len(mockedStoreResponse)),
|
|
PublicDashboards: mockedStoreResponse,
|
|
},
|
|
Err: nil,
|
|
},
|
|
want: &PublicDashboardListResponseWithPagination{
|
|
Page: 1,
|
|
PerPage: 50,
|
|
TotalCount: 1,
|
|
PublicDashboards: []*PublicDashboardListResponse{expectedFinalResponse[0]},
|
|
},
|
|
wantErr: assert.NoError,
|
|
},
|
|
{
|
|
name: "should return full response when user 3 has specific access to all dashboards",
|
|
args: args{
|
|
ctx: context.Background(),
|
|
query: &PublicDashboardListQuery{
|
|
User: &user.SignedInUser{OrgID: 1, UserID: 3, Permissions: map[int64]map[string][]string{1: {"dashboards:read": {"dashboards:uid:0S6TmO67z", "dashboards:uid:1S6TmO67z", "dashboards:uid:2S6TmO67z", "dashboards:uid:9S6TmO67z"}, "folders:read": {"*"}}}},
|
|
OrgID: 1,
|
|
Page: 1,
|
|
Limit: 50,
|
|
},
|
|
},
|
|
mockResponse: &mockResponse{
|
|
PublicDashboardListResponseWithPagination: &PublicDashboardListResponseWithPagination{
|
|
TotalCount: int64(len(mockedStoreResponse)),
|
|
PublicDashboards: mockedStoreResponse,
|
|
},
|
|
Err: nil,
|
|
},
|
|
want: &PublicDashboardListResponseWithPagination{
|
|
Page: 1,
|
|
PerPage: 50,
|
|
TotalCount: int64(len(expectedFinalResponse)),
|
|
PublicDashboards: expectedFinalResponse,
|
|
},
|
|
wantErr: assert.NoError,
|
|
},
|
|
{
|
|
name: "should an empty response for a user with no access",
|
|
args: args{
|
|
ctx: context.Background(),
|
|
query: &PublicDashboardListQuery{
|
|
User: &user.SignedInUser{OrgID: 1, UserID: 4, Permissions: map[int64]map[string][]string{}},
|
|
OrgID: 1,
|
|
Page: 1,
|
|
Limit: 50,
|
|
},
|
|
},
|
|
mockResponse: &mockResponse{
|
|
PublicDashboardListResponseWithPagination: &PublicDashboardListResponseWithPagination{
|
|
TotalCount: int64(len(mockedStoreResponse)),
|
|
PublicDashboards: mockedStoreResponse,
|
|
},
|
|
Err: nil,
|
|
},
|
|
want: &PublicDashboardListResponseWithPagination{
|
|
Page: 1,
|
|
PerPage: 50,
|
|
TotalCount: 0,
|
|
PublicDashboards: []*PublicDashboardListResponse{},
|
|
},
|
|
wantErr: assert.NoError,
|
|
},
|
|
{
|
|
name: "should return correct pagination response if limited",
|
|
args: args{
|
|
ctx: context.Background(),
|
|
query: &PublicDashboardListQuery{
|
|
User: &user.SignedInUser{OrgID: 1, UserID: 1, Permissions: map[int64]map[string][]string{1: {"dashboards:read": {"*"}, "folders:read": {"*"}}}},
|
|
OrgID: 1,
|
|
Page: 1,
|
|
Limit: 2,
|
|
},
|
|
},
|
|
mockResponse: &mockResponse{
|
|
PublicDashboardListResponseWithPagination: &PublicDashboardListResponseWithPagination{
|
|
TotalCount: int64(len(mockedStoreResponse)),
|
|
PublicDashboards: mockedStoreResponse,
|
|
},
|
|
Err: nil,
|
|
},
|
|
want: &PublicDashboardListResponseWithPagination{
|
|
Page: 1,
|
|
PerPage: 2,
|
|
TotalCount: 4,
|
|
PublicDashboards: expectedFinalResponse[:2],
|
|
},
|
|
wantErr: assert.NoError,
|
|
},
|
|
{
|
|
name: "should return correct page",
|
|
args: args{
|
|
ctx: context.Background(),
|
|
query: &PublicDashboardListQuery{
|
|
User: &user.SignedInUser{OrgID: 1, UserID: 1, Permissions: map[int64]map[string][]string{1: {"dashboards:read": {"*"}, "folders:read": {"*"}}}},
|
|
OrgID: 1,
|
|
Page: 2,
|
|
Limit: 2,
|
|
},
|
|
},
|
|
mockResponse: &mockResponse{
|
|
PublicDashboardListResponseWithPagination: &PublicDashboardListResponseWithPagination{
|
|
TotalCount: int64(len(mockedStoreResponse)),
|
|
PublicDashboards: mockedStoreResponse,
|
|
},
|
|
Err: nil,
|
|
},
|
|
want: &PublicDashboardListResponseWithPagination{
|
|
Page: 2,
|
|
PerPage: 2,
|
|
TotalCount: 4,
|
|
PublicDashboards: expectedFinalResponse[2:],
|
|
},
|
|
wantErr: assert.NoError,
|
|
},
|
|
{
|
|
name: "should return error when store returns error",
|
|
args: args{
|
|
ctx: context.Background(),
|
|
query: &PublicDashboardListQuery{
|
|
User: &user.SignedInUser{OrgID: 1, Permissions: map[int64]map[string][]string{
|
|
1: {"dashboards:read": {"dashboards:uid:0S6TmO67z"}}},
|
|
},
|
|
OrgID: 1,
|
|
Page: 1,
|
|
Limit: 50,
|
|
},
|
|
},
|
|
mockResponse: &mockResponse{
|
|
PublicDashboardListResponseWithPagination: nil,
|
|
Err: errors.New("an err"),
|
|
},
|
|
want: nil,
|
|
wantErr: assert.Error,
|
|
},
|
|
}
|
|
|
|
for _, tt := range testCases {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
store := NewFakePublicDashboardStore(t)
|
|
store.On("FindAll", mock.Anything, mock.Anything).
|
|
Return(tt.mockResponse.PublicDashboardListResponseWithPagination, tt.mockResponse.Err)
|
|
pd, _, _ := newPublicDashboardServiceImpl(t, testDB, cfg, store, dashboardService, nil)
|
|
pd.ac = ac
|
|
|
|
got, err := pd.FindAllWithPagination(tt.args.ctx, tt.args.query)
|
|
if !tt.wantErr(t, err, fmt.Sprintf("FindAllWithPagination(%v, %v)", tt.args.ctx, tt.args.query)) {
|
|
return
|
|
}
|
|
assert.Equalf(t, tt.want, got, "FindAllWithPagination(%v, %v)", tt.args.ctx, tt.args.query)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPublicDashboardServiceImpl_NewPublicDashboardUid(t *testing.T) {
|
|
mockedDashboard := &PublicDashboard{
|
|
IsEnabled: true,
|
|
AnnotationsEnabled: false,
|
|
DashboardUid: "NOTTHESAME",
|
|
OrgId: 9999999,
|
|
TimeSettings: timeSettings,
|
|
}
|
|
|
|
type args struct {
|
|
ctx context.Context
|
|
}
|
|
|
|
type mockResponse struct {
|
|
PublicDashboard *PublicDashboard
|
|
Err error
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
mockStore *mockResponse
|
|
want string
|
|
wantErr assert.ErrorAssertionFunc
|
|
}{
|
|
{
|
|
name: "should return a new uid",
|
|
args: args{ctx: context.Background()},
|
|
mockStore: &mockResponse{nil, nil},
|
|
want: "NOTTHESAME",
|
|
wantErr: assert.NoError,
|
|
},
|
|
{
|
|
name: "should return an error if the generated uid exists 3 times",
|
|
args: args{ctx: context.Background()},
|
|
mockStore: &mockResponse{mockedDashboard, nil},
|
|
want: "",
|
|
wantErr: assert.Error,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
store := NewFakePublicDashboardStore(t)
|
|
store.On("Find", mock.Anything, mock.Anything).
|
|
Return(tt.mockStore.PublicDashboard, tt.mockStore.Err)
|
|
|
|
pd := &PublicDashboardServiceImpl{store: store}
|
|
|
|
got, err := pd.NewPublicDashboardUid(tt.args.ctx)
|
|
if !tt.wantErr(t, err, fmt.Sprintf("NewPublicDashboardUid(%v)", tt.args.ctx)) {
|
|
return
|
|
}
|
|
|
|
if err == nil {
|
|
assert.NotEqual(t, got, tt.want, "NewPublicDashboardUid(%v)", tt.args.ctx)
|
|
assert.True(t, util.IsValidShortUID(got), "NewPublicDashboardUid(%v)", tt.args.ctx)
|
|
store.AssertNumberOfCalls(t, "Find", 1)
|
|
} else {
|
|
store.AssertNumberOfCalls(t, "Find", 3)
|
|
assert.True(t, ErrInternalServerError.Is(err))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPublicDashboardServiceImpl_NewPublicDashboardAccessToken(t *testing.T) {
|
|
mockedDashboard := &PublicDashboard{
|
|
IsEnabled: true,
|
|
AnnotationsEnabled: false,
|
|
DashboardUid: "NOTTHESAME",
|
|
OrgId: 9999999,
|
|
TimeSettings: timeSettings,
|
|
}
|
|
|
|
type args struct {
|
|
ctx context.Context
|
|
}
|
|
|
|
type mockResponse struct {
|
|
PublicDashboard *PublicDashboard
|
|
Err error
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
mockStore *mockResponse
|
|
want string
|
|
wantErr assert.ErrorAssertionFunc
|
|
}{
|
|
{
|
|
name: "should return a new access token",
|
|
args: args{ctx: context.Background()},
|
|
mockStore: &mockResponse{nil, nil},
|
|
want: "6522e152530f4ee76522e152530f4ee7",
|
|
wantErr: assert.NoError,
|
|
},
|
|
{
|
|
name: "should return an error if the generated access token exists 3 times",
|
|
args: args{ctx: context.Background()},
|
|
mockStore: &mockResponse{mockedDashboard, nil},
|
|
want: "",
|
|
wantErr: assert.Error,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
store := NewFakePublicDashboardStore(t)
|
|
store.On("FindByAccessToken", mock.Anything, mock.Anything).
|
|
Return(tt.mockStore.PublicDashboard, tt.mockStore.Err)
|
|
|
|
pd := &PublicDashboardServiceImpl{store: store}
|
|
|
|
got, err := pd.NewPublicDashboardAccessToken(tt.args.ctx)
|
|
if !tt.wantErr(t, err, fmt.Sprintf("NewPublicDashboardAccessToken(%v)", tt.args.ctx)) {
|
|
return
|
|
}
|
|
|
|
if err == nil {
|
|
assert.NotEqual(t, got, tt.want, "NewPublicDashboardAccessToken(%v)", tt.args.ctx)
|
|
assert.True(t, validation.IsValidAccessToken(got), "NewPublicDashboardAccessToken(%v)", tt.args.ctx)
|
|
store.AssertNumberOfCalls(t, "FindByAccessToken", 1)
|
|
} else {
|
|
store.AssertNumberOfCalls(t, "FindByAccessToken", 3)
|
|
assert.True(t, ErrInternalServerError.Is(err))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGenerateAccessToken(t *testing.T) {
|
|
accessToken, err := GenerateAccessToken()
|
|
|
|
t.Run("length", func(t *testing.T) {
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 32, len(accessToken))
|
|
})
|
|
|
|
t.Run("no - ", func(t *testing.T) {
|
|
assert.False(t, strings.Contains("-", accessToken))
|
|
})
|
|
}
|
|
|
|
func CreateDatasource(dsType string, uid string) struct {
|
|
Type *string `json:"type,omitempty"`
|
|
Uid *string `json:"uid,omitempty"`
|
|
} {
|
|
return struct {
|
|
Type *string `json:"type,omitempty"`
|
|
Uid *string `json:"uid,omitempty"`
|
|
}{
|
|
Type: &dsType,
|
|
Uid: &uid,
|
|
}
|
|
}
|
|
|
|
func AddAnnotationsToDashboard(t *testing.T, dash *dashboards.Dashboard, annotations []DashAnnotation) *dashboards.Dashboard {
|
|
type annotationsDto struct {
|
|
List []DashAnnotation `json:"list"`
|
|
}
|
|
annos := annotationsDto{}
|
|
annos.List = annotations
|
|
annoJSON, err := json.Marshal(annos)
|
|
require.NoError(t, err)
|
|
|
|
dashAnnos, err := simplejson.NewJson(annoJSON)
|
|
require.NoError(t, err)
|
|
|
|
dash.Data.Set("annotations", dashAnnos)
|
|
|
|
return dash
|
|
}
|
|
|
|
func insertTestDashboard(t *testing.T, dashboardStore dashboards.Store, title string, orgId int64,
|
|
folderId int64, folderUID string, isFolder bool, templateVars []map[string]any, customPanels []any, tags ...any) *dashboards.Dashboard {
|
|
t.Helper()
|
|
|
|
var dashboardPanels []any
|
|
if customPanels != nil {
|
|
dashboardPanels = customPanels
|
|
} else {
|
|
dashboardPanels = []any{
|
|
map[string]any{
|
|
"id": 1,
|
|
"datasource": map[string]any{
|
|
"uid": "ds1",
|
|
},
|
|
"targets": []any{
|
|
map[string]any{
|
|
"datasource": map[string]any{
|
|
"type": "mysql",
|
|
"uid": "ds1",
|
|
},
|
|
"refId": "A",
|
|
},
|
|
map[string]any{
|
|
"datasource": map[string]any{
|
|
"type": "prometheus",
|
|
"uid": "ds2",
|
|
},
|
|
"refId": "B",
|
|
},
|
|
},
|
|
},
|
|
map[string]any{
|
|
"id": 2,
|
|
"datasource": map[string]any{
|
|
"uid": "ds3",
|
|
},
|
|
"targets": []any{
|
|
map[string]any{
|
|
"datasource": map[string]any{
|
|
"type": "mysql",
|
|
"uid": "ds3",
|
|
},
|
|
"refId": "C",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
cmd := dashboards.SaveDashboardCommand{
|
|
OrgID: orgId,
|
|
FolderUID: folderUID,
|
|
IsFolder: isFolder,
|
|
Dashboard: simplejson.NewFromAny(map[string]any{
|
|
"id": nil,
|
|
"title": title,
|
|
"tags": tags,
|
|
"panels": dashboardPanels,
|
|
"templating": map[string]any{
|
|
"list": templateVars,
|
|
},
|
|
"time": map[string]any{
|
|
"from": "2022-09-01T00:00:00.000Z",
|
|
"to": "2022-09-01T12:00:00.000Z",
|
|
},
|
|
}),
|
|
}
|
|
dash, err := dashboardStore.SaveDashboard(context.Background(), cmd)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, dash)
|
|
dash.Data.Set("id", dash.ID)
|
|
dash.Data.Set("uid", dash.UID)
|
|
return dash
|
|
}
|