mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
K8s/Dashboards: Fix dashboard list and add tests (#91523)
This commit is contained in:
parent
e8d5d5fbff
commit
9e116d13a5
38
pkg/registry/apis/dashboard/legacy/queries.go
Normal file
38
pkg/registry/apis/dashboard/legacy/queries.go
Normal file
@ -0,0 +1,38 @@
|
||||
package legacy
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"fmt"
|
||||
"text/template"
|
||||
|
||||
"github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate"
|
||||
)
|
||||
|
||||
// Templates setup.
|
||||
var (
|
||||
//go:embed *.sql
|
||||
sqlTemplatesFS embed.FS
|
||||
|
||||
sqlTemplates = template.Must(template.New("sql").ParseFS(sqlTemplatesFS, `*.sql`))
|
||||
)
|
||||
|
||||
func mustTemplate(filename string) *template.Template {
|
||||
if t := sqlTemplates.Lookup(filename); t != nil {
|
||||
return t
|
||||
}
|
||||
panic(fmt.Sprintf("template file not found: %s", filename))
|
||||
}
|
||||
|
||||
// Templates.
|
||||
var (
|
||||
sqlQueryDashboards = mustTemplate("query_dashboards.sql")
|
||||
)
|
||||
|
||||
type sqlQuery struct {
|
||||
*sqltemplate.SQLTemplate
|
||||
Query *DashboardQuery
|
||||
}
|
||||
|
||||
func (r sqlQuery) Validate() error {
|
||||
return nil // TODO
|
||||
}
|
167
pkg/registry/apis/dashboard/legacy/queries_test.go
Normal file
167
pkg/registry/apis/dashboard/legacy/queries_test.go
Normal file
@ -0,0 +1,167 @@
|
||||
package legacy
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"text/template"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate"
|
||||
)
|
||||
|
||||
//go:embed testdata/*
|
||||
var testdataFS embed.FS
|
||||
|
||||
func testdata(t *testing.T, filename string) []byte {
|
||||
t.Helper()
|
||||
b, err := testdataFS.ReadFile(`testdata/` + filename)
|
||||
if err != nil {
|
||||
writeTestData(filename, "<empty>")
|
||||
assert.Fail(t, "missing test file")
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func writeTestData(filename, value string) {
|
||||
_ = os.WriteFile(filepath.Join("testdata", filename), []byte(value), 0777)
|
||||
}
|
||||
|
||||
func TestQueries(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Check each dialect
|
||||
dialects := []sqltemplate.Dialect{
|
||||
sqltemplate.MySQL,
|
||||
sqltemplate.SQLite,
|
||||
sqltemplate.PostgreSQL,
|
||||
}
|
||||
|
||||
// Each template has one or more test cases, each identified with a
|
||||
// descriptive name (e.g. "happy path", "error twiddling the frobb"). Each
|
||||
// of them will test that for the same input data they must produce a result
|
||||
// that will depend on the Dialect. Expected queries should be defined in
|
||||
// separate files in the testdata directory. This improves the testing
|
||||
// experience by separating test data from test code, since mixing both
|
||||
// tends to make it more difficult to reason about what is being done,
|
||||
// especially as we want testing code to scale and make it easy to add
|
||||
// tests.
|
||||
type (
|
||||
testCase = struct {
|
||||
Name string
|
||||
|
||||
// Data should be the struct passed to the template.
|
||||
Data sqltemplate.SQLTemplateIface
|
||||
}
|
||||
)
|
||||
|
||||
// Define tests cases. Most templates are trivial and testing that they
|
||||
// generate correct code for a single Dialect is fine, since the one thing
|
||||
// that always changes is how SQL placeholder arguments are passed (most
|
||||
// Dialects use `?` while PostgreSQL uses `$1`, `$2`, etc.), and that is
|
||||
// something that should be tested in the Dialect implementation instead of
|
||||
// here. We will ask to have at least one test per SQL template, and we will
|
||||
// lean to test MySQL. Templates containing branching (conditionals, loops,
|
||||
// etc.) should be exercised at least once in each of their branches.
|
||||
//
|
||||
// NOTE: in the Data field, make sure to have pointers populated to simulate
|
||||
// data is set as it would be in a real request. The data being correctly
|
||||
// populated in each case should be tested in integration tests, where the
|
||||
// data will actually flow to and from a real database. In this tests we
|
||||
// only care about producing the correct SQL.
|
||||
testCases := map[*template.Template][]*testCase{
|
||||
sqlQueryDashboards: {
|
||||
{
|
||||
Name: "history_uid",
|
||||
Data: &sqlQuery{
|
||||
SQLTemplate: new(sqltemplate.SQLTemplate),
|
||||
Query: &DashboardQuery{
|
||||
OrgID: 2,
|
||||
UID: "UUU",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "history_uid_at_version",
|
||||
Data: &sqlQuery{
|
||||
SQLTemplate: new(sqltemplate.SQLTemplate),
|
||||
Query: &DashboardQuery{
|
||||
OrgID: 2,
|
||||
UID: "UUU",
|
||||
Version: 3,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "history_uid_second_page",
|
||||
Data: &sqlQuery{
|
||||
SQLTemplate: new(sqltemplate.SQLTemplate),
|
||||
Query: &DashboardQuery{
|
||||
OrgID: 2,
|
||||
UID: "UUU",
|
||||
LastID: 7,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "dashboard",
|
||||
Data: &sqlQuery{
|
||||
SQLTemplate: new(sqltemplate.SQLTemplate),
|
||||
Query: &DashboardQuery{
|
||||
OrgID: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "dashboard_next_page",
|
||||
Data: &sqlQuery{
|
||||
SQLTemplate: new(sqltemplate.SQLTemplate),
|
||||
Query: &DashboardQuery{
|
||||
OrgID: 2,
|
||||
LastID: 22,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Execute test cases
|
||||
for tmpl, tcs := range testCases {
|
||||
t.Run(tmpl.Name(), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.Name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, dialect := range dialects {
|
||||
filename := dialect.DialectName() + "__" + tc.Name + ".sql"
|
||||
t.Run(filename, func(t *testing.T) {
|
||||
// not parallel because we're sharing tc.Data, not
|
||||
// worth it deep cloning
|
||||
|
||||
expectedQuery := string(testdata(t, filename))
|
||||
//expectedQuery := sqltemplate.FormatSQL(rawQuery)
|
||||
|
||||
tc.Data.SetDialect(dialect)
|
||||
err := tc.Data.Validate()
|
||||
require.NoError(t, err)
|
||||
got, err := sqltemplate.Execute(tmpl, tc.Data)
|
||||
require.NoError(t, err)
|
||||
|
||||
got = sqltemplate.RemoveEmptyLines(got)
|
||||
if diff := cmp.Diff(expectedQuery, got); diff != "" {
|
||||
writeTestData(filename, got)
|
||||
t.Errorf("%s: %s", tc.Name, diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
45
pkg/registry/apis/dashboard/legacy/query_dashboards.sql
Normal file
45
pkg/registry/apis/dashboard/legacy/query_dashboards.sql
Normal file
@ -0,0 +1,45 @@
|
||||
SELECT
|
||||
dashboard.org_id, dashboard.id,
|
||||
dashboard.uid, dashboard.folder_uid,
|
||||
dashboard.deleted, plugin_id,
|
||||
dashboard_provisioning.name as origin_name,
|
||||
dashboard_provisioning.external_id as origin_path,
|
||||
dashboard_provisioning.check_sum as origin_key,
|
||||
dashboard_provisioning.updated as origin_ts,
|
||||
dashboard.created, created_user.uid as created_by, dashboard.created_by as created_by_id,
|
||||
{{ if .Query.UseHistoryTable }}
|
||||
dashboard_version.created, updated_user.uid as updated_by,updated_user.id as created_by_id,
|
||||
dashboard_version.version, dashboard_version.message, dashboard_version.data
|
||||
{{ else }}
|
||||
dashboard.updated, updated_user.uid as updated_by, dashboard.updated_by as updated_by_id,
|
||||
dashboard.version, '' as message, dashboard.data
|
||||
{{ end }}
|
||||
FROM dashboard
|
||||
{{ if .Query.UseHistoryTable }}
|
||||
LEFT OUTER JOIN dashboard_version ON dashboard.id = dashboard_version.dashboard_id
|
||||
{{ end }}
|
||||
LEFT OUTER JOIN dashboard_provisioning ON dashboard.id = dashboard_provisioning.dashboard_id
|
||||
LEFT OUTER JOIN {{ .Ident "user" }} AS created_user ON dashboard.created_by = created_user.id
|
||||
LEFT OUTER JOIN {{ .Ident "user" }} AS updated_user ON dashboard.updated_by = updated_user.id
|
||||
WHERE dashboard.is_folder = false
|
||||
AND dashboard.org_id = {{ .Arg .Query.OrgID }}
|
||||
{{ if .Query.UseHistoryTable }}
|
||||
{{ if .Query.Version }}
|
||||
AND dashboard_version.version = {{ .Arg .Query.Version }}
|
||||
{{ else if .Query.LastID }}
|
||||
AND dashboard_version.version < {{ .Arg .Query.LastID }}
|
||||
{{ end }}
|
||||
ORDER BY dashboard_version.version DESC
|
||||
{{ else }}
|
||||
{{ if .Query.UID }}
|
||||
AND dashboard.uid = {{ .Arg .Query.UID }}
|
||||
{{ else if .Query.LastID }}
|
||||
AND dashboard.id > {{ .Arg .Query.LastID }}
|
||||
{{ end }}
|
||||
{{ if .Query.GetTrash }}
|
||||
AND dashboard.deleted IS NOT NULL
|
||||
{{ else if .Query.LastID }}
|
||||
AND dashboard.deleted IS NULL
|
||||
{{ end }}
|
||||
ORDER BY dashboard.id DESC
|
||||
{{ end }}
|
@ -9,9 +9,6 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/utils/ptr"
|
||||
|
||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||
dashboardsV0 "github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1"
|
||||
@ -21,8 +18,12 @@ import (
|
||||
gapiutil "github.com/grafana/grafana/pkg/services/apiserver/utils"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/provisioning"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore/session"
|
||||
"github.com/grafana/grafana/pkg/storage/unified/resource"
|
||||
"github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/utils/ptr"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -46,10 +47,12 @@ type dashboardRow struct {
|
||||
|
||||
type dashboardSqlAccess struct {
|
||||
sql db.DB
|
||||
dialect sqltemplate.Dialect
|
||||
sess *session.SessionDB
|
||||
namespacer request.NamespaceMapper
|
||||
dashStore dashboards.Store
|
||||
provisioning provisioning.ProvisioningService
|
||||
currentRV func(ctx context.Context) (int64, error)
|
||||
|
||||
// Typically one... the server wrapper
|
||||
subscribers []chan *resource.WrittenEvent
|
||||
@ -61,131 +64,82 @@ func NewDashboardAccess(sql db.DB,
|
||||
dashStore dashboards.Store,
|
||||
provisioning provisioning.ProvisioningService,
|
||||
) DashboardAccess {
|
||||
dialect := sqltemplate.DialectForDriver(string(sql.GetDBType()))
|
||||
if dialect == nil {
|
||||
// panic?
|
||||
// fmt.Errorf("no dialect for driver %q", driverName)
|
||||
fmt.Printf("ERROR: NO DIALECT")
|
||||
}
|
||||
|
||||
sess := sql.GetSqlxSession()
|
||||
currentRV := func(ctx context.Context) (int64, error) {
|
||||
t := time.Now()
|
||||
max := ""
|
||||
err := sess.Get(ctx, &max, "SELECT MAX(updated) FROM dashboard")
|
||||
if err == nil && max != "" {
|
||||
t, _ = time.Parse(time.DateTime, max) // ignore null errors
|
||||
}
|
||||
return t.UnixMilli(), nil
|
||||
}
|
||||
if sql.GetDBType() == migrator.Postgres {
|
||||
currentRV = func(ctx context.Context) (int64, error) {
|
||||
max := time.Now()
|
||||
_ = sess.Get(ctx, &max, "SELECT MAX(updated) FROM dashboard")
|
||||
return max.UnixMilli(), nil
|
||||
}
|
||||
} else if sql.GetDBType() == migrator.MySQL {
|
||||
currentRV = func(ctx context.Context) (int64, error) {
|
||||
max := time.Now().UnixMilli()
|
||||
_ = sess.Get(ctx, &max, "SELECT UNIX_TIMESTAMP(MAX(updated)) FROM dashboard;")
|
||||
return max, nil
|
||||
}
|
||||
}
|
||||
|
||||
return &dashboardSqlAccess{
|
||||
sql: sql,
|
||||
sess: sql.GetSqlxSession(),
|
||||
sess: sess,
|
||||
dialect: dialect,
|
||||
namespacer: namespacer,
|
||||
dashStore: dashStore,
|
||||
provisioning: provisioning,
|
||||
currentRV: currentRV,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *dashboardSqlAccess) currentRV(ctx context.Context) (int64, error) {
|
||||
t := time.Now()
|
||||
max := ""
|
||||
err := a.sess.Get(ctx, &max, "SELECT MAX(updated) FROM dashboard")
|
||||
if err == nil && max != "" {
|
||||
t, err = time.Parse(time.DateTime, max)
|
||||
}
|
||||
return t.UnixMilli(), err
|
||||
}
|
||||
|
||||
const selector = `SELECT
|
||||
dashboard.org_id, dashboard.id,
|
||||
dashboard.uid, dashboard.folder_uid,
|
||||
dashboard.created,CreatedUSER.uid as created_by,
|
||||
dashboard.updated,UpdatedUSER.uid as updated_by,
|
||||
dashboard.deleted, plugin_id,
|
||||
dashboard_provisioning.name as origin_name,
|
||||
dashboard_provisioning.external_id as origin_path,
|
||||
dashboard_provisioning.check_sum as origin_key,
|
||||
dashboard_provisioning.updated as origin_ts,
|
||||
dashboard.version, '', dashboard.data
|
||||
FROM dashboard
|
||||
LEFT OUTER JOIN dashboard_provisioning ON dashboard.id = dashboard_provisioning.dashboard_id
|
||||
LEFT OUTER JOIN user AS CreatedUSER ON dashboard.created_by = CreatedUSER.id
|
||||
LEFT OUTER JOIN user AS UpdatedUSER ON dashboard.updated_by = UpdatedUSER.id
|
||||
WHERE dashboard.is_folder = false`
|
||||
|
||||
const history = `SELECT
|
||||
dashboard.org_id, dashboard.id,
|
||||
dashboard.uid, dashboard.folder_uid,
|
||||
dashboard.created,CreatedUSER.uid as created_by,
|
||||
dashboard_version.created,UpdatedUSER.uid as updated_by,
|
||||
NULL, plugin_id,
|
||||
dashboard_provisioning.name as origin_name,
|
||||
dashboard_provisioning.external_id as origin_path,
|
||||
dashboard_provisioning.check_sum as origin_key,
|
||||
dashboard_provisioning.updated as origin_ts,
|
||||
dashboard_version.version, dashboard_version.message, dashboard_version.data
|
||||
FROM dashboard
|
||||
LEFT OUTER JOIN dashboard_provisioning ON dashboard.id = dashboard_provisioning.dashboard_id
|
||||
LEFT OUTER JOIN dashboard_version ON dashboard.id = dashboard_version.dashboard_id
|
||||
LEFT OUTER JOIN user AS CreatedUSER ON dashboard.created_by = CreatedUSER.id
|
||||
LEFT OUTER JOIN user AS UpdatedUSER ON dashboard_version.created_by = UpdatedUSER.id
|
||||
WHERE dashboard.is_folder = false`
|
||||
|
||||
func (a *dashboardSqlAccess) getRows(ctx context.Context, query *DashboardQuery) (*rowsWrapper, error) {
|
||||
if len(query.Labels) > 0 {
|
||||
return nil, fmt.Errorf("labels not yet supported")
|
||||
// if query.Requirements.Folder != nil {
|
||||
// args = append(args, *query.Requirements.Folder)
|
||||
// sqlcmd = fmt.Sprintf("%s AND dashboard.folder_uid=$%d", sqlcmd, len(args))
|
||||
// sqlcmd = fmt.Sprintf("%s AND dashboard.folder_uid=?$%d", sqlcmd, len(args))
|
||||
// }
|
||||
}
|
||||
|
||||
var sqlcmd string
|
||||
args := []any{query.OrgID}
|
||||
|
||||
if query.GetHistory || query.Version > 0 {
|
||||
if query.GetTrash {
|
||||
return nil, fmt.Errorf("trash not included in history table")
|
||||
}
|
||||
|
||||
sqlcmd = fmt.Sprintf("%s AND dashboard.org_id=$%d\n ", history, len(args))
|
||||
|
||||
if query.UID == "" {
|
||||
return nil, fmt.Errorf("history query must have a UID")
|
||||
}
|
||||
|
||||
args = append(args, query.UID)
|
||||
sqlcmd = fmt.Sprintf("%s AND dashboard.uid=$%d", sqlcmd, len(args))
|
||||
|
||||
if query.Version > 0 {
|
||||
args = append(args, query.Version)
|
||||
sqlcmd = fmt.Sprintf("%s AND dashboard_version.version=$%d", sqlcmd, len(args))
|
||||
} else if query.LastID > 0 {
|
||||
args = append(args, query.LastID)
|
||||
sqlcmd = fmt.Sprintf("%s AND dashboard_version.version<$%d", sqlcmd, len(args))
|
||||
}
|
||||
|
||||
sqlcmd = fmt.Sprintf("%s\n ORDER BY dashboard_version.version desc", sqlcmd)
|
||||
} else {
|
||||
sqlcmd = fmt.Sprintf("%s AND dashboard.org_id=$%d\n ", selector, len(args))
|
||||
|
||||
if query.UID != "" {
|
||||
args = append(args, query.UID)
|
||||
sqlcmd = fmt.Sprintf("%s AND dashboard.uid=$%d", sqlcmd, len(args))
|
||||
} else if query.LastID > 0 {
|
||||
args = append(args, query.LastID)
|
||||
sqlcmd = fmt.Sprintf("%s AND dashboard.id>$%d", sqlcmd, len(args))
|
||||
}
|
||||
if query.GetTrash {
|
||||
sqlcmd = sqlcmd + " AND dashboard.deleted IS NOT NULL"
|
||||
} else {
|
||||
sqlcmd = sqlcmd + " AND dashboard.deleted IS NULL"
|
||||
}
|
||||
|
||||
sqlcmd = fmt.Sprintf("%s\n ORDER BY dashboard.id asc", sqlcmd)
|
||||
req := sqlQuery{
|
||||
SQLTemplate: sqltemplate.New(a.dialect),
|
||||
Query: query,
|
||||
}
|
||||
// fmt.Printf("%s // %v\n", sqlcmd, args)
|
||||
|
||||
rows, err := a.doQuery(ctx, sqlcmd, args...)
|
||||
tmpl := sqlQueryDashboards
|
||||
if query.UseHistoryTable() && query.GetTrash {
|
||||
return nil, fmt.Errorf("trash not included in history table")
|
||||
}
|
||||
|
||||
rawQuery, err := sqltemplate.Execute(tmpl, req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("execute template %q: %w", tmpl.Name(), err)
|
||||
}
|
||||
q := rawQuery
|
||||
// q = sqltemplate.RemoveEmptyLines(rawQuery)
|
||||
// fmt.Printf(">>%s [%+v]", q, req.GetArgs())
|
||||
|
||||
rows, err := a.sess.Query(ctx, q, req.GetArgs()...)
|
||||
if err != nil {
|
||||
if rows != nil {
|
||||
_ = rows.Close()
|
||||
}
|
||||
rows = nil
|
||||
}
|
||||
return rows, err
|
||||
}
|
||||
|
||||
func (a *dashboardSqlAccess) doQuery(ctx context.Context, query string, args ...any) (*rowsWrapper, error) {
|
||||
_, err := identity.GetRequester(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rows, err := a.sess.Query(ctx, query, args...)
|
||||
return &rowsWrapper{
|
||||
rows: rows,
|
||||
a: a,
|
||||
@ -211,6 +165,9 @@ type rowsWrapper struct {
|
||||
}
|
||||
|
||||
func (r *rowsWrapper) Close() error {
|
||||
if r.rows == nil {
|
||||
return nil
|
||||
}
|
||||
return r.rows.Close()
|
||||
}
|
||||
|
||||
@ -291,10 +248,12 @@ func (a *dashboardSqlAccess) scanRow(rows *sql.Rows) (*dashboardRow, error) {
|
||||
var folder_uid sql.NullString
|
||||
var updated time.Time
|
||||
var updatedBy sql.NullString
|
||||
var updatedByID sql.NullInt64
|
||||
var deleted sql.NullTime
|
||||
|
||||
var created time.Time
|
||||
var createdBy sql.NullString
|
||||
var createdByID sql.NullInt64
|
||||
var message sql.NullString
|
||||
|
||||
var plugin_id string
|
||||
@ -306,10 +265,10 @@ func (a *dashboardSqlAccess) scanRow(rows *sql.Rows) (*dashboardRow, error) {
|
||||
var version int64
|
||||
|
||||
err := rows.Scan(&orgId, &dashboard_id, &dash.Name, &folder_uid,
|
||||
&created, &createdBy,
|
||||
&updated, &updatedBy,
|
||||
&deleted, &plugin_id,
|
||||
&origin_name, &origin_path, &origin_hash, &origin_ts,
|
||||
&created, &createdBy, &createdByID,
|
||||
&updated, &updatedBy, &updatedByID,
|
||||
&version, &message, &data,
|
||||
)
|
||||
|
||||
@ -325,8 +284,8 @@ func (a *dashboardSqlAccess) scanRow(rows *sql.Rows) (*dashboardRow, error) {
|
||||
return nil, err
|
||||
}
|
||||
meta.SetUpdatedTimestamp(&updated)
|
||||
meta.SetCreatedBy(getUserID(createdBy))
|
||||
meta.SetUpdatedBy(getUserID(updatedBy))
|
||||
meta.SetCreatedBy(getUserID(createdBy, createdByID))
|
||||
meta.SetUpdatedBy(getUserID(updatedBy, updatedByID))
|
||||
|
||||
if deleted.Valid {
|
||||
meta.SetDeletionTimestamp(ptr.To(metav1.NewTime(deleted.Time)))
|
||||
@ -377,11 +336,14 @@ func (a *dashboardSqlAccess) scanRow(rows *sql.Rows) (*dashboardRow, error) {
|
||||
return row, err
|
||||
}
|
||||
|
||||
func getUserID(v sql.NullString) string {
|
||||
if v.String == "" {
|
||||
func getUserID(v sql.NullString, id sql.NullInt64) string {
|
||||
if v.Valid && v.String != "" {
|
||||
return identity.NewTypedIDString(identity.TypeUser, v.String).String()
|
||||
}
|
||||
if id.Valid && id.Int64 == -1 {
|
||||
return identity.NewTypedIDString(identity.TypeProvisioning, "").String()
|
||||
}
|
||||
return identity.NewTypedIDString(identity.TypeUser, v.String).String()
|
||||
return ""
|
||||
}
|
||||
|
||||
// DeleteDashboard implements DashboardAccess.
|
||||
|
@ -147,13 +147,13 @@ func (a *dashboardSqlAccess) ReadResource(ctx context.Context, req *resource.Rea
|
||||
rsp.Error = &resource.ErrorResult{
|
||||
Code: http.StatusNotFound,
|
||||
}
|
||||
} else {
|
||||
rsp.Value, err = json.Marshal(dash)
|
||||
if err != nil {
|
||||
rsp.Error = resource.AsErrorResult(err)
|
||||
}
|
||||
}
|
||||
|
||||
rsp.ResourceVersion = rv
|
||||
rsp.Value, err = json.Marshal(dash)
|
||||
if err != nil {
|
||||
rsp.Error = resource.AsErrorResult(err)
|
||||
}
|
||||
return rsp
|
||||
}
|
||||
|
||||
@ -177,11 +177,10 @@ func (a *dashboardSqlAccess) ListIterator(ctx context.Context, req *resource.Lis
|
||||
}
|
||||
|
||||
query := &DashboardQuery{
|
||||
OrgID: info.OrgID,
|
||||
Limit: int(req.Limit),
|
||||
MaxBytes: 2 * 1024 * 1024, // 2MB,
|
||||
LastID: token.id,
|
||||
Labels: req.Options.Labels,
|
||||
OrgID: info.OrgID,
|
||||
Limit: int(req.Limit),
|
||||
LastID: token.id,
|
||||
Labels: req.Options.Labels,
|
||||
}
|
||||
|
||||
listRV, err := a.currentRV(ctx)
|
||||
@ -194,7 +193,7 @@ func (a *dashboardSqlAccess) ListIterator(ctx context.Context, req *resource.Lis
|
||||
_ = rows.Close()
|
||||
}()
|
||||
}
|
||||
if err != nil {
|
||||
if err == nil {
|
||||
err = cb(rows)
|
||||
}
|
||||
return listRV, err
|
||||
@ -253,13 +252,15 @@ func (a *dashboardSqlAccess) History(ctx context.Context, req *resource.HistoryR
|
||||
if token.orgId > 0 && token.orgId != info.OrgID {
|
||||
return nil, fmt.Errorf("token and orgID mismatch")
|
||||
}
|
||||
|
||||
limit := int(req.Limit)
|
||||
if limit < 1 {
|
||||
limit = 15
|
||||
}
|
||||
query := &DashboardQuery{
|
||||
OrgID: info.OrgID,
|
||||
Limit: int(req.Limit),
|
||||
MaxBytes: 2 * 1024 * 1024, // 2MB,
|
||||
LastID: token.id,
|
||||
UID: req.Key.Name,
|
||||
OrgID: info.OrgID,
|
||||
Limit: limit + 1,
|
||||
LastID: token.id,
|
||||
UID: req.Key.Name,
|
||||
}
|
||||
if req.ShowDeleted {
|
||||
query.GetTrash = true
|
||||
@ -273,7 +274,6 @@ func (a *dashboardSqlAccess) History(ctx context.Context, req *resource.HistoryR
|
||||
}
|
||||
defer func() { _ = rows.Close() }()
|
||||
|
||||
totalSize := 0
|
||||
list := &resource.HistoryResponse{}
|
||||
for rows.Next() {
|
||||
if rows.err != nil || rows.row == nil {
|
||||
@ -291,8 +291,7 @@ func (a *dashboardSqlAccess) History(ctx context.Context, req *resource.HistoryR
|
||||
return list, err
|
||||
}
|
||||
|
||||
totalSize += len(rows.Value())
|
||||
if len(list.Items) > 0 && (totalSize > query.MaxBytes || len(list.Items) >= query.Limit) {
|
||||
if len(list.Items) >= limit {
|
||||
// if query.Requirements.Folder != nil {
|
||||
// row.token.folder = *query.Requirements.Folder
|
||||
// }
|
||||
|
18
pkg/registry/apis/dashboard/legacy/testdata/mysql__dashboard.sql
vendored
Executable file
18
pkg/registry/apis/dashboard/legacy/testdata/mysql__dashboard.sql
vendored
Executable file
@ -0,0 +1,18 @@
|
||||
SELECT
|
||||
dashboard.org_id, dashboard.id,
|
||||
dashboard.uid, dashboard.folder_uid,
|
||||
dashboard.deleted, plugin_id,
|
||||
dashboard_provisioning.name as origin_name,
|
||||
dashboard_provisioning.external_id as origin_path,
|
||||
dashboard_provisioning.check_sum as origin_key,
|
||||
dashboard_provisioning.updated as origin_ts,
|
||||
dashboard.created, created_user.uid as created_by, dashboard.created_by as created_by_id,
|
||||
dashboard.updated, updated_user.uid as updated_by, dashboard.updated_by as updated_by_id,
|
||||
dashboard.version, '' as message, dashboard.data
|
||||
FROM dashboard
|
||||
LEFT OUTER JOIN dashboard_provisioning ON dashboard.id = dashboard_provisioning.dashboard_id
|
||||
LEFT OUTER JOIN `user` AS created_user ON dashboard.created_by = created_user.id
|
||||
LEFT OUTER JOIN `user` AS updated_user ON dashboard.updated_by = updated_user.id
|
||||
WHERE dashboard.is_folder = false
|
||||
AND dashboard.org_id = ?
|
||||
ORDER BY dashboard.id DESC
|
20
pkg/registry/apis/dashboard/legacy/testdata/mysql__dashboard_next_page.sql
vendored
Executable file
20
pkg/registry/apis/dashboard/legacy/testdata/mysql__dashboard_next_page.sql
vendored
Executable file
@ -0,0 +1,20 @@
|
||||
SELECT
|
||||
dashboard.org_id, dashboard.id,
|
||||
dashboard.uid, dashboard.folder_uid,
|
||||
dashboard.deleted, plugin_id,
|
||||
dashboard_provisioning.name as origin_name,
|
||||
dashboard_provisioning.external_id as origin_path,
|
||||
dashboard_provisioning.check_sum as origin_key,
|
||||
dashboard_provisioning.updated as origin_ts,
|
||||
dashboard.created, created_user.uid as created_by, dashboard.created_by as created_by_id,
|
||||
dashboard.updated, updated_user.uid as updated_by, dashboard.updated_by as updated_by_id,
|
||||
dashboard.version, '' as message, dashboard.data
|
||||
FROM dashboard
|
||||
LEFT OUTER JOIN dashboard_provisioning ON dashboard.id = dashboard_provisioning.dashboard_id
|
||||
LEFT OUTER JOIN `user` AS created_user ON dashboard.created_by = created_user.id
|
||||
LEFT OUTER JOIN `user` AS updated_user ON dashboard.updated_by = updated_user.id
|
||||
WHERE dashboard.is_folder = false
|
||||
AND dashboard.org_id = ?
|
||||
AND dashboard.id > ?
|
||||
AND dashboard.deleted IS NULL
|
||||
ORDER BY dashboard.id DESC
|
19
pkg/registry/apis/dashboard/legacy/testdata/mysql__history_uid.sql
vendored
Executable file
19
pkg/registry/apis/dashboard/legacy/testdata/mysql__history_uid.sql
vendored
Executable file
@ -0,0 +1,19 @@
|
||||
SELECT
|
||||
dashboard.org_id, dashboard.id,
|
||||
dashboard.uid, dashboard.folder_uid,
|
||||
dashboard.deleted, plugin_id,
|
||||
dashboard_provisioning.name as origin_name,
|
||||
dashboard_provisioning.external_id as origin_path,
|
||||
dashboard_provisioning.check_sum as origin_key,
|
||||
dashboard_provisioning.updated as origin_ts,
|
||||
dashboard.created, created_user.uid as created_by, dashboard.created_by as created_by_id,
|
||||
dashboard.updated, updated_user.uid as updated_by, dashboard.updated_by as updated_by_id,
|
||||
dashboard.version, '' as message, dashboard.data
|
||||
FROM dashboard
|
||||
LEFT OUTER JOIN dashboard_provisioning ON dashboard.id = dashboard_provisioning.dashboard_id
|
||||
LEFT OUTER JOIN `user` AS created_user ON dashboard.created_by = created_user.id
|
||||
LEFT OUTER JOIN `user` AS updated_user ON dashboard.updated_by = updated_user.id
|
||||
WHERE dashboard.is_folder = false
|
||||
AND dashboard.org_id = ?
|
||||
AND dashboard.uid = ?
|
||||
ORDER BY dashboard.id DESC
|
20
pkg/registry/apis/dashboard/legacy/testdata/mysql__history_uid_at_version.sql
vendored
Executable file
20
pkg/registry/apis/dashboard/legacy/testdata/mysql__history_uid_at_version.sql
vendored
Executable file
@ -0,0 +1,20 @@
|
||||
SELECT
|
||||
dashboard.org_id, dashboard.id,
|
||||
dashboard.uid, dashboard.folder_uid,
|
||||
dashboard.deleted, plugin_id,
|
||||
dashboard_provisioning.name as origin_name,
|
||||
dashboard_provisioning.external_id as origin_path,
|
||||
dashboard_provisioning.check_sum as origin_key,
|
||||
dashboard_provisioning.updated as origin_ts,
|
||||
dashboard.created, created_user.uid as created_by, dashboard.created_by as created_by_id,
|
||||
dashboard_version.created, updated_user.uid as updated_by,updated_user.id as created_by_id,
|
||||
dashboard_version.version, dashboard_version.message, dashboard_version.data
|
||||
FROM dashboard
|
||||
LEFT OUTER JOIN dashboard_version ON dashboard.id = dashboard_version.dashboard_id
|
||||
LEFT OUTER JOIN dashboard_provisioning ON dashboard.id = dashboard_provisioning.dashboard_id
|
||||
LEFT OUTER JOIN `user` AS created_user ON dashboard.created_by = created_user.id
|
||||
LEFT OUTER JOIN `user` AS updated_user ON dashboard.updated_by = updated_user.id
|
||||
WHERE dashboard.is_folder = false
|
||||
AND dashboard.org_id = ?
|
||||
AND dashboard_version.version = ?
|
||||
ORDER BY dashboard_version.version DESC
|
20
pkg/registry/apis/dashboard/legacy/testdata/mysql__history_uid_second_page.sql
vendored
Executable file
20
pkg/registry/apis/dashboard/legacy/testdata/mysql__history_uid_second_page.sql
vendored
Executable file
@ -0,0 +1,20 @@
|
||||
SELECT
|
||||
dashboard.org_id, dashboard.id,
|
||||
dashboard.uid, dashboard.folder_uid,
|
||||
dashboard.deleted, plugin_id,
|
||||
dashboard_provisioning.name as origin_name,
|
||||
dashboard_provisioning.external_id as origin_path,
|
||||
dashboard_provisioning.check_sum as origin_key,
|
||||
dashboard_provisioning.updated as origin_ts,
|
||||
dashboard.created, created_user.uid as created_by, dashboard.created_by as created_by_id,
|
||||
dashboard.updated, updated_user.uid as updated_by, dashboard.updated_by as updated_by_id,
|
||||
dashboard.version, '' as message, dashboard.data
|
||||
FROM dashboard
|
||||
LEFT OUTER JOIN dashboard_provisioning ON dashboard.id = dashboard_provisioning.dashboard_id
|
||||
LEFT OUTER JOIN `user` AS created_user ON dashboard.created_by = created_user.id
|
||||
LEFT OUTER JOIN `user` AS updated_user ON dashboard.updated_by = updated_user.id
|
||||
WHERE dashboard.is_folder = false
|
||||
AND dashboard.org_id = ?
|
||||
AND dashboard.uid = ?
|
||||
AND dashboard.deleted IS NULL
|
||||
ORDER BY dashboard.id DESC
|
18
pkg/registry/apis/dashboard/legacy/testdata/postgres__dashboard.sql
vendored
Executable file
18
pkg/registry/apis/dashboard/legacy/testdata/postgres__dashboard.sql
vendored
Executable file
@ -0,0 +1,18 @@
|
||||
SELECT
|
||||
dashboard.org_id, dashboard.id,
|
||||
dashboard.uid, dashboard.folder_uid,
|
||||
dashboard.deleted, plugin_id,
|
||||
dashboard_provisioning.name as origin_name,
|
||||
dashboard_provisioning.external_id as origin_path,
|
||||
dashboard_provisioning.check_sum as origin_key,
|
||||
dashboard_provisioning.updated as origin_ts,
|
||||
dashboard.created, created_user.uid as created_by, dashboard.created_by as created_by_id,
|
||||
dashboard.updated, updated_user.uid as updated_by, dashboard.updated_by as updated_by_id,
|
||||
dashboard.version, '' as message, dashboard.data
|
||||
FROM dashboard
|
||||
LEFT OUTER JOIN dashboard_provisioning ON dashboard.id = dashboard_provisioning.dashboard_id
|
||||
LEFT OUTER JOIN "user" AS created_user ON dashboard.created_by = created_user.id
|
||||
LEFT OUTER JOIN "user" AS updated_user ON dashboard.updated_by = updated_user.id
|
||||
WHERE dashboard.is_folder = false
|
||||
AND dashboard.org_id = $1
|
||||
ORDER BY dashboard.id DESC
|
20
pkg/registry/apis/dashboard/legacy/testdata/postgres__dashboard_next_page.sql
vendored
Executable file
20
pkg/registry/apis/dashboard/legacy/testdata/postgres__dashboard_next_page.sql
vendored
Executable file
@ -0,0 +1,20 @@
|
||||
SELECT
|
||||
dashboard.org_id, dashboard.id,
|
||||
dashboard.uid, dashboard.folder_uid,
|
||||
dashboard.deleted, plugin_id,
|
||||
dashboard_provisioning.name as origin_name,
|
||||
dashboard_provisioning.external_id as origin_path,
|
||||
dashboard_provisioning.check_sum as origin_key,
|
||||
dashboard_provisioning.updated as origin_ts,
|
||||
dashboard.created, created_user.uid as created_by, dashboard.created_by as created_by_id,
|
||||
dashboard.updated, updated_user.uid as updated_by, dashboard.updated_by as updated_by_id,
|
||||
dashboard.version, '' as message, dashboard.data
|
||||
FROM dashboard
|
||||
LEFT OUTER JOIN dashboard_provisioning ON dashboard.id = dashboard_provisioning.dashboard_id
|
||||
LEFT OUTER JOIN "user" AS created_user ON dashboard.created_by = created_user.id
|
||||
LEFT OUTER JOIN "user" AS updated_user ON dashboard.updated_by = updated_user.id
|
||||
WHERE dashboard.is_folder = false
|
||||
AND dashboard.org_id = $1
|
||||
AND dashboard.id > $2
|
||||
AND dashboard.deleted IS NULL
|
||||
ORDER BY dashboard.id DESC
|
19
pkg/registry/apis/dashboard/legacy/testdata/postgres__history_uid.sql
vendored
Executable file
19
pkg/registry/apis/dashboard/legacy/testdata/postgres__history_uid.sql
vendored
Executable file
@ -0,0 +1,19 @@
|
||||
SELECT
|
||||
dashboard.org_id, dashboard.id,
|
||||
dashboard.uid, dashboard.folder_uid,
|
||||
dashboard.deleted, plugin_id,
|
||||
dashboard_provisioning.name as origin_name,
|
||||
dashboard_provisioning.external_id as origin_path,
|
||||
dashboard_provisioning.check_sum as origin_key,
|
||||
dashboard_provisioning.updated as origin_ts,
|
||||
dashboard.created, created_user.uid as created_by, dashboard.created_by as created_by_id,
|
||||
dashboard.updated, updated_user.uid as updated_by, dashboard.updated_by as updated_by_id,
|
||||
dashboard.version, '' as message, dashboard.data
|
||||
FROM dashboard
|
||||
LEFT OUTER JOIN dashboard_provisioning ON dashboard.id = dashboard_provisioning.dashboard_id
|
||||
LEFT OUTER JOIN "user" AS created_user ON dashboard.created_by = created_user.id
|
||||
LEFT OUTER JOIN "user" AS updated_user ON dashboard.updated_by = updated_user.id
|
||||
WHERE dashboard.is_folder = false
|
||||
AND dashboard.org_id = $1
|
||||
AND dashboard.uid = $2
|
||||
ORDER BY dashboard.id DESC
|
20
pkg/registry/apis/dashboard/legacy/testdata/postgres__history_uid_at_version.sql
vendored
Executable file
20
pkg/registry/apis/dashboard/legacy/testdata/postgres__history_uid_at_version.sql
vendored
Executable file
@ -0,0 +1,20 @@
|
||||
SELECT
|
||||
dashboard.org_id, dashboard.id,
|
||||
dashboard.uid, dashboard.folder_uid,
|
||||
dashboard.deleted, plugin_id,
|
||||
dashboard_provisioning.name as origin_name,
|
||||
dashboard_provisioning.external_id as origin_path,
|
||||
dashboard_provisioning.check_sum as origin_key,
|
||||
dashboard_provisioning.updated as origin_ts,
|
||||
dashboard.created, created_user.uid as created_by, dashboard.created_by as created_by_id,
|
||||
dashboard_version.created, updated_user.uid as updated_by,updated_user.id as created_by_id,
|
||||
dashboard_version.version, dashboard_version.message, dashboard_version.data
|
||||
FROM dashboard
|
||||
LEFT OUTER JOIN dashboard_version ON dashboard.id = dashboard_version.dashboard_id
|
||||
LEFT OUTER JOIN dashboard_provisioning ON dashboard.id = dashboard_provisioning.dashboard_id
|
||||
LEFT OUTER JOIN "user" AS created_user ON dashboard.created_by = created_user.id
|
||||
LEFT OUTER JOIN "user" AS updated_user ON dashboard.updated_by = updated_user.id
|
||||
WHERE dashboard.is_folder = false
|
||||
AND dashboard.org_id = $1
|
||||
AND dashboard_version.version = $2
|
||||
ORDER BY dashboard_version.version DESC
|
20
pkg/registry/apis/dashboard/legacy/testdata/postgres__history_uid_second_page.sql
vendored
Executable file
20
pkg/registry/apis/dashboard/legacy/testdata/postgres__history_uid_second_page.sql
vendored
Executable file
@ -0,0 +1,20 @@
|
||||
SELECT
|
||||
dashboard.org_id, dashboard.id,
|
||||
dashboard.uid, dashboard.folder_uid,
|
||||
dashboard.deleted, plugin_id,
|
||||
dashboard_provisioning.name as origin_name,
|
||||
dashboard_provisioning.external_id as origin_path,
|
||||
dashboard_provisioning.check_sum as origin_key,
|
||||
dashboard_provisioning.updated as origin_ts,
|
||||
dashboard.created, created_user.uid as created_by, dashboard.created_by as created_by_id,
|
||||
dashboard.updated, updated_user.uid as updated_by, dashboard.updated_by as updated_by_id,
|
||||
dashboard.version, '' as message, dashboard.data
|
||||
FROM dashboard
|
||||
LEFT OUTER JOIN dashboard_provisioning ON dashboard.id = dashboard_provisioning.dashboard_id
|
||||
LEFT OUTER JOIN "user" AS created_user ON dashboard.created_by = created_user.id
|
||||
LEFT OUTER JOIN "user" AS updated_user ON dashboard.updated_by = updated_user.id
|
||||
WHERE dashboard.is_folder = false
|
||||
AND dashboard.org_id = $1
|
||||
AND dashboard.uid = $2
|
||||
AND dashboard.deleted IS NULL
|
||||
ORDER BY dashboard.id DESC
|
18
pkg/registry/apis/dashboard/legacy/testdata/sqlite__dashboard.sql
vendored
Executable file
18
pkg/registry/apis/dashboard/legacy/testdata/sqlite__dashboard.sql
vendored
Executable file
@ -0,0 +1,18 @@
|
||||
SELECT
|
||||
dashboard.org_id, dashboard.id,
|
||||
dashboard.uid, dashboard.folder_uid,
|
||||
dashboard.deleted, plugin_id,
|
||||
dashboard_provisioning.name as origin_name,
|
||||
dashboard_provisioning.external_id as origin_path,
|
||||
dashboard_provisioning.check_sum as origin_key,
|
||||
dashboard_provisioning.updated as origin_ts,
|
||||
dashboard.created, created_user.uid as created_by, dashboard.created_by as created_by_id,
|
||||
dashboard.updated, updated_user.uid as updated_by, dashboard.updated_by as updated_by_id,
|
||||
dashboard.version, '' as message, dashboard.data
|
||||
FROM dashboard
|
||||
LEFT OUTER JOIN dashboard_provisioning ON dashboard.id = dashboard_provisioning.dashboard_id
|
||||
LEFT OUTER JOIN "user" AS created_user ON dashboard.created_by = created_user.id
|
||||
LEFT OUTER JOIN "user" AS updated_user ON dashboard.updated_by = updated_user.id
|
||||
WHERE dashboard.is_folder = false
|
||||
AND dashboard.org_id = ?
|
||||
ORDER BY dashboard.id DESC
|
20
pkg/registry/apis/dashboard/legacy/testdata/sqlite__dashboard_next_page.sql
vendored
Executable file
20
pkg/registry/apis/dashboard/legacy/testdata/sqlite__dashboard_next_page.sql
vendored
Executable file
@ -0,0 +1,20 @@
|
||||
SELECT
|
||||
dashboard.org_id, dashboard.id,
|
||||
dashboard.uid, dashboard.folder_uid,
|
||||
dashboard.deleted, plugin_id,
|
||||
dashboard_provisioning.name as origin_name,
|
||||
dashboard_provisioning.external_id as origin_path,
|
||||
dashboard_provisioning.check_sum as origin_key,
|
||||
dashboard_provisioning.updated as origin_ts,
|
||||
dashboard.created, created_user.uid as created_by, dashboard.created_by as created_by_id,
|
||||
dashboard.updated, updated_user.uid as updated_by, dashboard.updated_by as updated_by_id,
|
||||
dashboard.version, '' as message, dashboard.data
|
||||
FROM dashboard
|
||||
LEFT OUTER JOIN dashboard_provisioning ON dashboard.id = dashboard_provisioning.dashboard_id
|
||||
LEFT OUTER JOIN "user" AS created_user ON dashboard.created_by = created_user.id
|
||||
LEFT OUTER JOIN "user" AS updated_user ON dashboard.updated_by = updated_user.id
|
||||
WHERE dashboard.is_folder = false
|
||||
AND dashboard.org_id = ?
|
||||
AND dashboard.id > ?
|
||||
AND dashboard.deleted IS NULL
|
||||
ORDER BY dashboard.id DESC
|
19
pkg/registry/apis/dashboard/legacy/testdata/sqlite__history_uid.sql
vendored
Executable file
19
pkg/registry/apis/dashboard/legacy/testdata/sqlite__history_uid.sql
vendored
Executable file
@ -0,0 +1,19 @@
|
||||
SELECT
|
||||
dashboard.org_id, dashboard.id,
|
||||
dashboard.uid, dashboard.folder_uid,
|
||||
dashboard.deleted, plugin_id,
|
||||
dashboard_provisioning.name as origin_name,
|
||||
dashboard_provisioning.external_id as origin_path,
|
||||
dashboard_provisioning.check_sum as origin_key,
|
||||
dashboard_provisioning.updated as origin_ts,
|
||||
dashboard.created, created_user.uid as created_by, dashboard.created_by as created_by_id,
|
||||
dashboard.updated, updated_user.uid as updated_by, dashboard.updated_by as updated_by_id,
|
||||
dashboard.version, '' as message, dashboard.data
|
||||
FROM dashboard
|
||||
LEFT OUTER JOIN dashboard_provisioning ON dashboard.id = dashboard_provisioning.dashboard_id
|
||||
LEFT OUTER JOIN "user" AS created_user ON dashboard.created_by = created_user.id
|
||||
LEFT OUTER JOIN "user" AS updated_user ON dashboard.updated_by = updated_user.id
|
||||
WHERE dashboard.is_folder = false
|
||||
AND dashboard.org_id = ?
|
||||
AND dashboard.uid = ?
|
||||
ORDER BY dashboard.id DESC
|
20
pkg/registry/apis/dashboard/legacy/testdata/sqlite__history_uid_at_version.sql
vendored
Executable file
20
pkg/registry/apis/dashboard/legacy/testdata/sqlite__history_uid_at_version.sql
vendored
Executable file
@ -0,0 +1,20 @@
|
||||
SELECT
|
||||
dashboard.org_id, dashboard.id,
|
||||
dashboard.uid, dashboard.folder_uid,
|
||||
dashboard.deleted, plugin_id,
|
||||
dashboard_provisioning.name as origin_name,
|
||||
dashboard_provisioning.external_id as origin_path,
|
||||
dashboard_provisioning.check_sum as origin_key,
|
||||
dashboard_provisioning.updated as origin_ts,
|
||||
dashboard.created, created_user.uid as created_by, dashboard.created_by as created_by_id,
|
||||
dashboard_version.created, updated_user.uid as updated_by,updated_user.id as created_by_id,
|
||||
dashboard_version.version, dashboard_version.message, dashboard_version.data
|
||||
FROM dashboard
|
||||
LEFT OUTER JOIN dashboard_version ON dashboard.id = dashboard_version.dashboard_id
|
||||
LEFT OUTER JOIN dashboard_provisioning ON dashboard.id = dashboard_provisioning.dashboard_id
|
||||
LEFT OUTER JOIN "user" AS created_user ON dashboard.created_by = created_user.id
|
||||
LEFT OUTER JOIN "user" AS updated_user ON dashboard.updated_by = updated_user.id
|
||||
WHERE dashboard.is_folder = false
|
||||
AND dashboard.org_id = ?
|
||||
AND dashboard_version.version = ?
|
||||
ORDER BY dashboard_version.version DESC
|
20
pkg/registry/apis/dashboard/legacy/testdata/sqlite__history_uid_second_page.sql
vendored
Executable file
20
pkg/registry/apis/dashboard/legacy/testdata/sqlite__history_uid_second_page.sql
vendored
Executable file
@ -0,0 +1,20 @@
|
||||
SELECT
|
||||
dashboard.org_id, dashboard.id,
|
||||
dashboard.uid, dashboard.folder_uid,
|
||||
dashboard.deleted, plugin_id,
|
||||
dashboard_provisioning.name as origin_name,
|
||||
dashboard_provisioning.external_id as origin_path,
|
||||
dashboard_provisioning.check_sum as origin_key,
|
||||
dashboard_provisioning.updated as origin_ts,
|
||||
dashboard.created, created_user.uid as created_by, dashboard.created_by as created_by_id,
|
||||
dashboard.updated, updated_user.uid as updated_by, dashboard.updated_by as updated_by_id,
|
||||
dashboard.version, '' as message, dashboard.data
|
||||
FROM dashboard
|
||||
LEFT OUTER JOIN dashboard_provisioning ON dashboard.id = dashboard_provisioning.dashboard_id
|
||||
LEFT OUTER JOIN "user" AS created_user ON dashboard.created_by = created_user.id
|
||||
LEFT OUTER JOIN "user" AS updated_user ON dashboard.updated_by = updated_user.id
|
||||
WHERE dashboard.is_folder = false
|
||||
AND dashboard.org_id = ?
|
||||
AND dashboard.uid = ?
|
||||
AND dashboard.deleted IS NULL
|
||||
ORDER BY dashboard.id DESC
|
@ -10,10 +10,9 @@ import (
|
||||
// This does not check if you have permissions!
|
||||
|
||||
type DashboardQuery struct {
|
||||
OrgID int64
|
||||
UID string // to select a single dashboard
|
||||
Limit int
|
||||
MaxBytes int
|
||||
OrgID int64
|
||||
UID string // to select a single dashboard
|
||||
Limit int
|
||||
|
||||
// Included in the continue token
|
||||
// This is the ID from the last dashboard sent in the previous page
|
||||
@ -30,6 +29,10 @@ type DashboardQuery struct {
|
||||
Labels []*resource.Requirement
|
||||
}
|
||||
|
||||
func (r *DashboardQuery) UseHistoryTable() bool {
|
||||
return r.GetHistory || r.Version > 0
|
||||
}
|
||||
|
||||
type DashboardAccess interface {
|
||||
resource.StorageBackend
|
||||
resource.ResourceIndexServer
|
||||
|
@ -14,8 +14,25 @@ var MySQL = mysql{
|
||||
var _ Dialect = MySQL
|
||||
|
||||
type mysql struct {
|
||||
standardIdent
|
||||
backtickIdent
|
||||
rowLockingClauseMap
|
||||
argPlaceholderFunc
|
||||
name
|
||||
}
|
||||
|
||||
// standardIdent provides standard SQL escaping of identifiers.
|
||||
type backtickIdent struct{}
|
||||
|
||||
var standardFallback = standardIdent{}
|
||||
|
||||
func (backtickIdent) Ident(s string) (string, error) {
|
||||
switch s {
|
||||
// Internal identifiers require backticks to work properly
|
||||
case "user":
|
||||
return "`" + s + "`", nil
|
||||
case "":
|
||||
return "", ErrEmptyIdent
|
||||
}
|
||||
// standard
|
||||
return standardFallback.Ident(s)
|
||||
}
|
||||
|
@ -116,6 +116,22 @@ func FormatSQL(q string) string {
|
||||
return q
|
||||
}
|
||||
|
||||
// RemoveEmptyLines removes the empty lines from a SQL statement
|
||||
// empty lines are typical when using text template formatting
|
||||
func RemoveEmptyLines(q string) string {
|
||||
var b strings.Builder
|
||||
lines := strings.Split(strings.ReplaceAll(q, "\r\n", "\n"), "\n")
|
||||
for _, line := range lines {
|
||||
if strings.TrimSpace(line) == "" {
|
||||
continue
|
||||
}
|
||||
line = strings.ReplaceAll(line, "\t", " ")
|
||||
b.WriteString(line)
|
||||
b.WriteByte('\n')
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
type reFormatting struct {
|
||||
re *regexp.Regexp
|
||||
replacement string
|
||||
|
@ -1,16 +1,26 @@
|
||||
package dashboards
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/tests/apis"
|
||||
"github.com/grafana/grafana/pkg/tests/testinfra"
|
||||
"github.com/grafana/grafana/pkg/tests/testsuite"
|
||||
"github.com/stretchr/testify/require"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
var gvr = schema.GroupVersionResource{
|
||||
Group: "dashboard.grafana.app",
|
||||
Version: "v0alpha1",
|
||||
Resource: "dashboards",
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
testsuite.Run(m)
|
||||
}
|
||||
@ -45,6 +55,59 @@ func TestIntegrationDashboardsApp(t *testing.T) {
|
||||
_, err := helper.NewDiscoveryClient().ServerResourcesForGroupVersion("dashboard.grafana.app/v0alpha1")
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("simple crud+list", func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
client := helper.GetResourceClient(apis.ResourceClientArgs{
|
||||
User: helper.Org1.Admin,
|
||||
GVR: gvr,
|
||||
})
|
||||
rsp, err := client.Resource.List(ctx, metav1.ListOptions{})
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, rsp.Items)
|
||||
|
||||
obj := &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"spec": map[string]any{
|
||||
"title": "Test empty dashboard",
|
||||
},
|
||||
},
|
||||
}
|
||||
obj.SetGenerateName("aa")
|
||||
obj, err = client.Resource.Create(ctx, obj, metav1.CreateOptions{})
|
||||
require.NoError(t, err)
|
||||
created := obj.GetName()
|
||||
require.True(t, strings.HasPrefix(created, "aa"), "expecting prefix %s (%s)", "aa", created) // the generate name prefix
|
||||
|
||||
// The new value exists in a list
|
||||
rsp, err = client.Resource.List(ctx, metav1.ListOptions{})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, rsp.Items, 1)
|
||||
require.Equal(t, created, rsp.Items[0].GetName())
|
||||
|
||||
// Same value returned from get
|
||||
obj, err = client.Resource.Get(ctx, created, metav1.GetOptions{})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, created, obj.GetName())
|
||||
|
||||
// Commented out because the dynamic client does not like lists as sub-resource
|
||||
// // Check that it now appears in the history
|
||||
// sub, err := client.Resource.Get(ctx, created, metav1.GetOptions{}, "history")
|
||||
// require.NoError(t, err)
|
||||
// history, err := sub.ToList()
|
||||
// require.NoError(t, err)
|
||||
// require.Len(t, history.Items, 1)
|
||||
// require.Equal(t, created, history.Items[0].GetName())
|
||||
|
||||
// Delete the object
|
||||
err = client.Resource.Delete(ctx, created, metav1.DeleteOptions{})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Now it is not in the list
|
||||
rsp, err = client.Resource.List(ctx, metav1.ListOptions{})
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, rsp.Items)
|
||||
})
|
||||
|
||||
t.Run("Check discovery client", func(t *testing.T) {
|
||||
disco := helper.GetGroupVersionInfoJSON("dashboard.grafana.app")
|
||||
// fmt.Printf("%s", string(disco))
|
||||
|
@ -437,6 +437,7 @@ func (c *K8sTestHelper) CreateUser(name string, orgName string, basicRole org.Ro
|
||||
Password: user.Password(name),
|
||||
Login: fmt.Sprintf("%s-%d", name, orgId),
|
||||
OrgID: orgId,
|
||||
IsAdmin: basicRole == identity.RoleAdmin && orgId == 1, // make org1 admins grafana admins
|
||||
})
|
||||
require.NoError(c.t, err)
|
||||
require.Equal(c.t, orgId, u.OrgID)
|
||||
|
Loading…
Reference in New Issue
Block a user