Skip to content

Commit 9e116d1

Browse files
authored
K8s/Dashboards: Fix dashboard list and add tests (grafana#91523)
1 parent e8d5d5f commit 9e116d1

25 files changed

+738
-136
lines changed

Diff for: pkg/registry/apis/dashboard/legacy/queries.go

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package legacy
2+
3+
import (
4+
"embed"
5+
"fmt"
6+
"text/template"
7+
8+
"github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate"
9+
)
10+
11+
// Templates setup.
12+
var (
13+
//go:embed *.sql
14+
sqlTemplatesFS embed.FS
15+
16+
sqlTemplates = template.Must(template.New("sql").ParseFS(sqlTemplatesFS, `*.sql`))
17+
)
18+
19+
func mustTemplate(filename string) *template.Template {
20+
if t := sqlTemplates.Lookup(filename); t != nil {
21+
return t
22+
}
23+
panic(fmt.Sprintf("template file not found: %s", filename))
24+
}
25+
26+
// Templates.
27+
var (
28+
sqlQueryDashboards = mustTemplate("query_dashboards.sql")
29+
)
30+
31+
type sqlQuery struct {
32+
*sqltemplate.SQLTemplate
33+
Query *DashboardQuery
34+
}
35+
36+
func (r sqlQuery) Validate() error {
37+
return nil // TODO
38+
}

Diff for: pkg/registry/apis/dashboard/legacy/queries_test.go

+167
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
package legacy
2+
3+
import (
4+
"embed"
5+
"os"
6+
"path/filepath"
7+
"testing"
8+
"text/template"
9+
10+
"github.com/google/go-cmp/cmp"
11+
"github.com/stretchr/testify/assert"
12+
"github.com/stretchr/testify/require"
13+
14+
"github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate"
15+
)
16+
17+
//go:embed testdata/*
18+
var testdataFS embed.FS
19+
20+
func testdata(t *testing.T, filename string) []byte {
21+
t.Helper()
22+
b, err := testdataFS.ReadFile(`testdata/` + filename)
23+
if err != nil {
24+
writeTestData(filename, "<empty>")
25+
assert.Fail(t, "missing test file")
26+
}
27+
return b
28+
}
29+
30+
func writeTestData(filename, value string) {
31+
_ = os.WriteFile(filepath.Join("testdata", filename), []byte(value), 0777)
32+
}
33+
34+
func TestQueries(t *testing.T) {
35+
t.Parallel()
36+
37+
// Check each dialect
38+
dialects := []sqltemplate.Dialect{
39+
sqltemplate.MySQL,
40+
sqltemplate.SQLite,
41+
sqltemplate.PostgreSQL,
42+
}
43+
44+
// Each template has one or more test cases, each identified with a
45+
// descriptive name (e.g. "happy path", "error twiddling the frobb"). Each
46+
// of them will test that for the same input data they must produce a result
47+
// that will depend on the Dialect. Expected queries should be defined in
48+
// separate files in the testdata directory. This improves the testing
49+
// experience by separating test data from test code, since mixing both
50+
// tends to make it more difficult to reason about what is being done,
51+
// especially as we want testing code to scale and make it easy to add
52+
// tests.
53+
type (
54+
testCase = struct {
55+
Name string
56+
57+
// Data should be the struct passed to the template.
58+
Data sqltemplate.SQLTemplateIface
59+
}
60+
)
61+
62+
// Define tests cases. Most templates are trivial and testing that they
63+
// generate correct code for a single Dialect is fine, since the one thing
64+
// that always changes is how SQL placeholder arguments are passed (most
65+
// Dialects use `?` while PostgreSQL uses `$1`, `$2`, etc.), and that is
66+
// something that should be tested in the Dialect implementation instead of
67+
// here. We will ask to have at least one test per SQL template, and we will
68+
// lean to test MySQL. Templates containing branching (conditionals, loops,
69+
// etc.) should be exercised at least once in each of their branches.
70+
//
71+
// NOTE: in the Data field, make sure to have pointers populated to simulate
72+
// data is set as it would be in a real request. The data being correctly
73+
// populated in each case should be tested in integration tests, where the
74+
// data will actually flow to and from a real database. In this tests we
75+
// only care about producing the correct SQL.
76+
testCases := map[*template.Template][]*testCase{
77+
sqlQueryDashboards: {
78+
{
79+
Name: "history_uid",
80+
Data: &sqlQuery{
81+
SQLTemplate: new(sqltemplate.SQLTemplate),
82+
Query: &DashboardQuery{
83+
OrgID: 2,
84+
UID: "UUU",
85+
},
86+
},
87+
},
88+
{
89+
Name: "history_uid_at_version",
90+
Data: &sqlQuery{
91+
SQLTemplate: new(sqltemplate.SQLTemplate),
92+
Query: &DashboardQuery{
93+
OrgID: 2,
94+
UID: "UUU",
95+
Version: 3,
96+
},
97+
},
98+
},
99+
{
100+
Name: "history_uid_second_page",
101+
Data: &sqlQuery{
102+
SQLTemplate: new(sqltemplate.SQLTemplate),
103+
Query: &DashboardQuery{
104+
OrgID: 2,
105+
UID: "UUU",
106+
LastID: 7,
107+
},
108+
},
109+
},
110+
{
111+
Name: "dashboard",
112+
Data: &sqlQuery{
113+
SQLTemplate: new(sqltemplate.SQLTemplate),
114+
Query: &DashboardQuery{
115+
OrgID: 2,
116+
},
117+
},
118+
},
119+
{
120+
Name: "dashboard_next_page",
121+
Data: &sqlQuery{
122+
SQLTemplate: new(sqltemplate.SQLTemplate),
123+
Query: &DashboardQuery{
124+
OrgID: 2,
125+
LastID: 22,
126+
},
127+
},
128+
},
129+
},
130+
}
131+
132+
// Execute test cases
133+
for tmpl, tcs := range testCases {
134+
t.Run(tmpl.Name(), func(t *testing.T) {
135+
t.Parallel()
136+
137+
for _, tc := range tcs {
138+
t.Run(tc.Name, func(t *testing.T) {
139+
t.Parallel()
140+
141+
for _, dialect := range dialects {
142+
filename := dialect.DialectName() + "__" + tc.Name + ".sql"
143+
t.Run(filename, func(t *testing.T) {
144+
// not parallel because we're sharing tc.Data, not
145+
// worth it deep cloning
146+
147+
expectedQuery := string(testdata(t, filename))
148+
//expectedQuery := sqltemplate.FormatSQL(rawQuery)
149+
150+
tc.Data.SetDialect(dialect)
151+
err := tc.Data.Validate()
152+
require.NoError(t, err)
153+
got, err := sqltemplate.Execute(tmpl, tc.Data)
154+
require.NoError(t, err)
155+
156+
got = sqltemplate.RemoveEmptyLines(got)
157+
if diff := cmp.Diff(expectedQuery, got); diff != "" {
158+
writeTestData(filename, got)
159+
t.Errorf("%s: %s", tc.Name, diff)
160+
}
161+
})
162+
}
163+
})
164+
}
165+
})
166+
}
167+
}
+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
SELECT
2+
dashboard.org_id, dashboard.id,
3+
dashboard.uid, dashboard.folder_uid,
4+
dashboard.deleted, plugin_id,
5+
dashboard_provisioning.name as origin_name,
6+
dashboard_provisioning.external_id as origin_path,
7+
dashboard_provisioning.check_sum as origin_key,
8+
dashboard_provisioning.updated as origin_ts,
9+
dashboard.created, created_user.uid as created_by, dashboard.created_by as created_by_id,
10+
{{ if .Query.UseHistoryTable }}
11+
dashboard_version.created, updated_user.uid as updated_by,updated_user.id as created_by_id,
12+
dashboard_version.version, dashboard_version.message, dashboard_version.data
13+
{{ else }}
14+
dashboard.updated, updated_user.uid as updated_by, dashboard.updated_by as updated_by_id,
15+
dashboard.version, '' as message, dashboard.data
16+
{{ end }}
17+
FROM dashboard
18+
{{ if .Query.UseHistoryTable }}
19+
LEFT OUTER JOIN dashboard_version ON dashboard.id = dashboard_version.dashboard_id
20+
{{ end }}
21+
LEFT OUTER JOIN dashboard_provisioning ON dashboard.id = dashboard_provisioning.dashboard_id
22+
LEFT OUTER JOIN {{ .Ident "user" }} AS created_user ON dashboard.created_by = created_user.id
23+
LEFT OUTER JOIN {{ .Ident "user" }} AS updated_user ON dashboard.updated_by = updated_user.id
24+
WHERE dashboard.is_folder = false
25+
AND dashboard.org_id = {{ .Arg .Query.OrgID }}
26+
{{ if .Query.UseHistoryTable }}
27+
{{ if .Query.Version }}
28+
AND dashboard_version.version = {{ .Arg .Query.Version }}
29+
{{ else if .Query.LastID }}
30+
AND dashboard_version.version < {{ .Arg .Query.LastID }}
31+
{{ end }}
32+
ORDER BY dashboard_version.version DESC
33+
{{ else }}
34+
{{ if .Query.UID }}
35+
AND dashboard.uid = {{ .Arg .Query.UID }}
36+
{{ else if .Query.LastID }}
37+
AND dashboard.id > {{ .Arg .Query.LastID }}
38+
{{ end }}
39+
{{ if .Query.GetTrash }}
40+
AND dashboard.deleted IS NOT NULL
41+
{{ else if .Query.LastID }}
42+
AND dashboard.deleted IS NULL
43+
{{ end }}
44+
ORDER BY dashboard.id DESC
45+
{{ end }}

0 commit comments

Comments
 (0)