LibraryElements: Creates usage stats for panels and variables (#34476)

* LibraryPanels: Adds usage collection

* Refactor: renames Panel and Variable consts

* Chore: initialize stats

* Refactor: moves library element migrations to migration namespace
This commit is contained in:
Hugo Häggmark 2021-05-24 06:11:01 +02:00 committed by GitHub
parent abe5c06d69
commit 7204a64717
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 174 additions and 126 deletions

View File

@ -185,6 +185,12 @@ var (
grafanaBuildVersion *prometheus.GaugeVec
grafanaPluginBuildInfoDesc *prometheus.GaugeVec
// StatsTotalLibraryPanels is a metric of total number of library panels stored in Grafana.
StatsTotalLibraryPanels prometheus.Gauge
// StatsTotalLibraryVariables is a metric of total number of library variables stored in Grafana.
StatsTotalLibraryVariables prometheus.Gauge
)
func init() {
@ -547,6 +553,18 @@ func init() {
Help: "number of evaluation calls",
Namespace: ExporterName,
})
StatsTotalLibraryPanels = prometheus.NewGauge(prometheus.GaugeOpts{
Name: "stat_totals_library_panels",
Help: "total amount of library panels in the database",
Namespace: ExporterName,
})
StatsTotalLibraryVariables = prometheus.NewGauge(prometheus.GaugeOpts{
Name: "stat_totals_library_variables",
Help: "total amount of library variables in the database",
Namespace: ExporterName,
})
}
// SetBuildInformation sets the build information for this binary
@ -640,6 +658,8 @@ func initMetricVars() {
StatsTotalDashboardVersions,
StatsTotalAnnotations,
MAccessEvaluationCount,
StatsTotalLibraryPanels,
StatsTotalLibraryVariables,
)
}

View File

@ -72,6 +72,8 @@ func (uss *UsageStatsService) GetUsageReport(ctx context.Context) (UsageReport,
metrics["stats.dashboard_versions.count"] = statsQuery.Result.DashboardVersions
metrics["stats.annotations.count"] = statsQuery.Result.Annotations
metrics["stats.alert_rules.count"] = statsQuery.Result.AlertRules
metrics["stats.library_panels.count"] = statsQuery.Result.LibraryPanels
metrics["stats.library_variables.count"] = statsQuery.Result.LibraryVariables
validLicCount := 0
if uss.License.HasValidLicense() {
validLicCount = 1
@ -317,6 +319,8 @@ func (uss *UsageStatsService) updateTotalStats() {
metrics.StatsTotalDashboardVersions.Set(float64(statsQuery.Result.DashboardVersions))
metrics.StatsTotalAnnotations.Set(float64(statsQuery.Result.Annotations))
metrics.StatsTotalAlertRules.Set(float64(statsQuery.Result.AlertRules))
metrics.StatsTotalLibraryPanels.Set(float64(statsQuery.Result.LibraryPanels))
metrics.StatsTotalLibraryVariables.Set(float64(statsQuery.Result.LibraryVariables))
dsStats := models.GetDataSourceStatsQuery{}
if err := uss.Bus.Dispatch(&dsStats); err != nil {

View File

@ -63,6 +63,8 @@ func TestMetrics(t *testing.T) {
DashboardVersions: 16,
Annotations: 17,
AlertRules: 18,
LibraryPanels: 19,
LibraryVariables: 20,
}
getSystemStatsQuery = query
return nil
@ -313,6 +315,8 @@ func TestMetrics(t *testing.T) {
assert.Equal(t, 16, metrics.Get("stats.dashboard_versions.count").MustInt())
assert.Equal(t, 17, metrics.Get("stats.annotations.count").MustInt())
assert.Equal(t, 18, metrics.Get("stats.alert_rules.count").MustInt())
assert.Equal(t, 19, metrics.Get("stats.library_panels.count").MustInt())
assert.Equal(t, 20, metrics.Get("stats.library_variables.count").MustInt())
assert.Equal(t, 9, metrics.Get("stats.ds."+models.DS_ES+".count").MustInt())
assert.Equal(t, 10, metrics.Get("stats.ds."+models.DS_PROMETHEUS+".count").MustInt())

View File

@ -0,0 +1,13 @@
package models
// LibraryElementKind is used for the kind of library element
type LibraryElementKind int
const (
// PanelElement is used for library elements that are of the Panel kind
PanelElement LibraryElementKind = iota + 1
// VariableElement is used for library elements that are of the Variable kind
VariableElement
)
const LibraryElementConnectionTableName = "library_element_connection"

View File

@ -19,6 +19,8 @@ type SystemStats struct {
DashboardVersions int64
Annotations int64
AlertRules int64
LibraryPanels int64
LibraryVariables int64
Admins int
Editors int

View File

@ -22,7 +22,7 @@ SELECT DISTINCT
, u1.email AS created_by_email
, u2.login AS updated_by_name
, u2.email AS updated_by_email
, (SELECT COUNT(connection_id) FROM ` + connectionTableName + ` WHERE element_id = le.id AND kind=1) AS connected_dashboards`
, (SELECT COUNT(connection_id) FROM ` + models.LibraryElementConnectionTableName + ` WHERE element_id = le.id AND kind=1) AS connected_dashboards`
)
func getFromLibraryElementDTOWithMeta(dialect migrator.Dialect) string {
@ -41,9 +41,9 @@ func syncFieldsWithModel(libraryElement *LibraryElement) error {
return err
}
if LibraryElementKind(libraryElement.Kind) == Panel {
if models.LibraryElementKind(libraryElement.Kind) == models.PanelElement {
model["title"] = libraryElement.Name
} else if LibraryElementKind(libraryElement.Kind) == Variable {
} else if models.LibraryElementKind(libraryElement.Kind) == models.VariableElement {
model["name"] = libraryElement.Name
}
if model["type"] != nil {
@ -520,7 +520,7 @@ func (l *LibraryElementService) getConnections(c *models.ReqContext, uid string)
var libraryElementConnections []libraryElementConnectionWithMeta
builder := sqlstore.SQLBuilder{}
builder.Write("SELECT lec.*, u1.login AS created_by_name, u1.email AS created_by_email")
builder.Write(" FROM " + connectionTableName + " AS lec")
builder.Write(" FROM " + models.LibraryElementConnectionTableName + " AS lec")
builder.Write(" LEFT JOIN " + l.SQLStore.Dialect.Quote("user") + " AS u1 ON lec.created_by = u1.id")
builder.Write(" INNER JOIN dashboard AS dashboard on lec.connection_id = dashboard.id")
builder.Write(` WHERE lec.element_id=?`, element.ID)
@ -562,7 +562,7 @@ func (l *LibraryElementService) getElementsForDashboardID(c *models.ReqContext,
", coalesce(dashboard.uid, '') AS folder_uid" +
getFromLibraryElementDTOWithMeta(l.SQLStore.Dialect) +
" LEFT JOIN dashboard AS dashboard ON dashboard.id = le.folder_id" +
" INNER JOIN " + connectionTableName + " AS lce ON lce.element_id = le.id AND lce.kind=1 AND lce.connection_id=?"
" INNER JOIN " + models.LibraryElementConnectionTableName + " AS lce ON lce.element_id = le.id AND lce.kind=1 AND lce.connection_id=?"
sess := session.SQL(sql, dashboardID)
err := sess.Find(&libraryElements)
if err != nil {
@ -610,7 +610,7 @@ func (l *LibraryElementService) getElementsForDashboardID(c *models.ReqContext,
// connectElementsToDashboardID adds connections for all elements Library Elements in a Dashboard.
func (l *LibraryElementService) connectElementsToDashboardID(c *models.ReqContext, elementUIDs []string, dashboardID int64) error {
err := l.SQLStore.WithTransactionalDbSession(c.Context.Req.Context(), func(session *sqlstore.DBSession) error {
_, err := session.Exec("DELETE FROM "+connectionTableName+" WHERE kind=1 AND connection_id=?", dashboardID)
_, err := session.Exec("DELETE FROM "+models.LibraryElementConnectionTableName+" WHERE kind=1 AND connection_id=?", dashboardID)
if err != nil {
return err
}
@ -646,7 +646,7 @@ func (l *LibraryElementService) connectElementsToDashboardID(c *models.ReqContex
// disconnectElementsFromDashboardID deletes connections for all Library Elements in a Dashboard.
func (l *LibraryElementService) disconnectElementsFromDashboardID(c *models.ReqContext, dashboardID int64) error {
return l.SQLStore.WithTransactionalDbSession(c.Context.Req.Context(), func(session *sqlstore.DBSession) error {
_, err := session.Exec("DELETE FROM "+connectionTableName+" WHERE kind=1 AND connection_id=?", dashboardID)
_, err := session.Exec("DELETE FROM "+models.LibraryElementConnectionTableName+" WHERE kind=1 AND connection_id=?", dashboardID)
if err != nil {
return err
}
@ -676,7 +676,7 @@ func (l *LibraryElementService) deleteLibraryElementsInFolderUID(c *models.ReqCo
ConnectionID int64 `xorm:"connection_id"`
}
sql := "SELECT lec.connection_id FROM library_element AS le"
sql += " INNER JOIN " + connectionTableName + " AS lec on le.id = lec.element_id"
sql += " INNER JOIN " + models.LibraryElementConnectionTableName + " AS lec on le.id = lec.element_id"
sql += " WHERE le.folder_id=? AND le.org_id=?"
err = session.SQL(sql, folderID, c.SignedInUser.OrgId).Find(&connectionIDs)
if err != nil {
@ -694,7 +694,7 @@ func (l *LibraryElementService) deleteLibraryElementsInFolderUID(c *models.ReqCo
return err
}
for _, elementID := range elementIDs {
_, err := session.Exec("DELETE FROM "+connectionTableName+" WHERE element_id=?", elementID.ID)
_, err := session.Exec("DELETE FROM "+models.LibraryElementConnectionTableName+" WHERE element_id=?", elementID.ID)
if err != nil {
return err
}

View File

@ -11,11 +11,11 @@ func isGeneralFolder(folderID int64) bool {
}
func (l *LibraryElementService) requireSupportedElementKind(kindAsInt int64) error {
kind := LibraryElementKind(kindAsInt)
kind := models.LibraryElementKind(kindAsInt)
switch kind {
case Panel:
case models.PanelElement:
return nil
case Variable:
case models.VariableElement:
return nil
default:
return errLibraryElementUnSupportedElementKind

View File

@ -6,7 +6,6 @@ import (
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/registry"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
"github.com/grafana/grafana/pkg/setting"
)
@ -27,8 +26,6 @@ type LibraryElementService struct {
log log.Logger
}
const connectionTableName = "library_element_connection"
func init() {
registry.RegisterService(&LibraryElementService{})
}
@ -66,51 +63,3 @@ func (l *LibraryElementService) DisconnectElementsFromDashboard(c *models.ReqCon
func (l *LibraryElementService) DeleteLibraryElementsInFolder(c *models.ReqContext, folderUID string) error {
return l.deleteLibraryElementsInFolderUID(c, folderUID)
}
// AddMigration defines database migrations.
// If Panel Library is not enabled does nothing.
func (l *LibraryElementService) AddMigration(mg *migrator.Migrator) {
libraryElementsV1 := migrator.Table{
Name: "library_element",
Columns: []*migrator.Column{
{Name: "id", Type: migrator.DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
{Name: "org_id", Type: migrator.DB_BigInt, Nullable: false},
{Name: "folder_id", Type: migrator.DB_BigInt, Nullable: false},
{Name: "uid", Type: migrator.DB_NVarchar, Length: 40, Nullable: false},
{Name: "name", Type: migrator.DB_NVarchar, Length: 150, Nullable: false},
{Name: "kind", Type: migrator.DB_BigInt, Nullable: false},
{Name: "type", Type: migrator.DB_NVarchar, Length: 40, Nullable: false},
{Name: "description", Type: migrator.DB_NVarchar, Length: 255, Nullable: false},
{Name: "model", Type: migrator.DB_Text, Nullable: false},
{Name: "created", Type: migrator.DB_DateTime, Nullable: false},
{Name: "created_by", Type: migrator.DB_BigInt, Nullable: false},
{Name: "updated", Type: migrator.DB_DateTime, Nullable: false},
{Name: "updated_by", Type: migrator.DB_BigInt, Nullable: false},
{Name: "version", Type: migrator.DB_BigInt, Nullable: false},
},
Indices: []*migrator.Index{
{Cols: []string{"org_id", "folder_id", "name", "kind"}, Type: migrator.UniqueIndex},
},
}
mg.AddMigration("create library_element table v1", migrator.NewAddTableMigration(libraryElementsV1))
mg.AddMigration("add index library_element org_id-folder_id-name-kind", migrator.NewAddIndexMigration(libraryElementsV1, libraryElementsV1.Indices[0]))
libraryElementConnectionV1 := migrator.Table{
Name: connectionTableName,
Columns: []*migrator.Column{
{Name: "id", Type: migrator.DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
{Name: "element_id", Type: migrator.DB_BigInt, Nullable: false},
{Name: "kind", Type: migrator.DB_BigInt, Nullable: false},
{Name: "connection_id", Type: migrator.DB_BigInt, Nullable: false},
{Name: "created", Type: migrator.DB_DateTime, Nullable: false},
{Name: "created_by", Type: migrator.DB_BigInt, Nullable: false},
},
Indices: []*migrator.Index{
{Cols: []string{"element_id", "kind", "connection_id"}, Type: migrator.UniqueIndex},
},
}
mg.AddMigration("create "+connectionTableName+" table v1", migrator.NewAddTableMigration(libraryElementConnectionV1))
mg.AddMigration("add index "+connectionTableName+" element_id-kind-connection_id", migrator.NewAddIndexMigration(libraryElementConnectionV1, libraryElementConnectionV1.Indices[0]))
}

View File

@ -5,6 +5,8 @@ import (
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/models"
)
func TestCreateLibraryElement(t *testing.T) {
@ -24,7 +26,7 @@ func TestCreateLibraryElement(t *testing.T) {
FolderID: 1,
UID: sc.initialResult.Result.UID,
Name: "Text - Library Panel",
Kind: int64(Panel),
Kind: int64(models.PanelElement),
Type: "text",
Description: "A description",
Model: map[string]interface{}{
@ -69,7 +71,7 @@ func TestCreateLibraryElement(t *testing.T) {
FolderID: 1,
UID: result.Result.UID,
Name: "Library Panel Name",
Kind: int64(Panel),
Kind: int64(models.PanelElement),
Type: "text",
Description: "A description",
Model: map[string]interface{}{

View File

@ -42,7 +42,7 @@ func TestGetAllLibraryElements(t *testing.T) {
err := sc.reqContext.Req.ParseForm()
require.NoError(t, err)
sc.reqContext.Req.Form.Add("kind", strconv.FormatInt(int64(Panel), 10))
sc.reqContext.Req.Form.Add("kind", strconv.FormatInt(int64(models.PanelElement), 10))
resp = sc.service.getAllHandler(sc.reqContext)
require.Equal(t, 200, resp.Status())
@ -62,7 +62,7 @@ func TestGetAllLibraryElements(t *testing.T) {
FolderID: 1,
UID: result.Result.Elements[0].UID,
Name: "Text - Library Panel",
Kind: int64(Panel),
Kind: int64(models.PanelElement),
Type: "text",
Description: "A description",
Model: map[string]interface{}{
@ -107,7 +107,7 @@ func TestGetAllLibraryElements(t *testing.T) {
err := sc.reqContext.Req.ParseForm()
require.NoError(t, err)
sc.reqContext.Req.Form.Add("kind", strconv.FormatInt(int64(Variable), 10))
sc.reqContext.Req.Form.Add("kind", strconv.FormatInt(int64(models.VariableElement), 10))
resp = sc.service.getAllHandler(sc.reqContext)
require.Equal(t, 200, resp.Status())
@ -127,7 +127,7 @@ func TestGetAllLibraryElements(t *testing.T) {
FolderID: 1,
UID: result.Result.Elements[0].UID,
Name: "query0",
Kind: int64(Variable),
Kind: int64(models.VariableElement),
Type: "query",
Description: "A description",
Model: map[string]interface{}{
@ -187,7 +187,7 @@ func TestGetAllLibraryElements(t *testing.T) {
FolderID: 1,
UID: result.Result.Elements[0].UID,
Name: "Text - Library Panel",
Kind: int64(Panel),
Kind: int64(models.PanelElement),
Type: "text",
Description: "A description",
Model: map[string]interface{}{
@ -222,7 +222,7 @@ func TestGetAllLibraryElements(t *testing.T) {
FolderID: 1,
UID: result.Result.Elements[1].UID,
Name: "Text - Library Panel2",
Kind: int64(Panel),
Kind: int64(models.PanelElement),
Type: "text",
Description: "A description",
Model: map[string]interface{}{
@ -286,7 +286,7 @@ func TestGetAllLibraryElements(t *testing.T) {
FolderID: 1,
UID: result.Result.Elements[0].UID,
Name: "Text - Library Panel2",
Kind: int64(Panel),
Kind: int64(models.PanelElement),
Type: "text",
Description: "A description",
Model: map[string]interface{}{
@ -321,7 +321,7 @@ func TestGetAllLibraryElements(t *testing.T) {
FolderID: 1,
UID: result.Result.Elements[1].UID,
Name: "Text - Library Panel",
Kind: int64(Panel),
Kind: int64(models.PanelElement),
Type: "text",
Description: "A description",
Model: map[string]interface{}{
@ -360,7 +360,7 @@ func TestGetAllLibraryElements(t *testing.T) {
scenarioWithPanel(t, "When an admin tries to get all library panels and two exist and typeFilter is set to existing types, it should succeed and the result should be correct",
func(t *testing.T, sc scenarioContext) {
command := getCreateCommandWithModel(sc.folder.Id, "Gauge - Library Panel", Panel, []byte(`
command := getCreateCommandWithModel(sc.folder.Id, "Gauge - Library Panel", models.PanelElement, []byte(`
{
"datasource": "${DS_GDEV-TESTDATA}",
"id": 1,
@ -372,7 +372,7 @@ func TestGetAllLibraryElements(t *testing.T) {
resp := sc.service.createHandler(sc.reqContext, command)
require.Equal(t, 200, resp.Status())
command = getCreateCommandWithModel(sc.folder.Id, "BarGauge - Library Panel", Panel, []byte(`
command = getCreateCommandWithModel(sc.folder.Id, "BarGauge - Library Panel", models.PanelElement, []byte(`
{
"datasource": "${DS_GDEV-TESTDATA}",
"id": 1,
@ -405,7 +405,7 @@ func TestGetAllLibraryElements(t *testing.T) {
FolderID: 1,
UID: result.Result.Elements[0].UID,
Name: "BarGauge - Library Panel",
Kind: int64(Panel),
Kind: int64(models.PanelElement),
Type: "bargauge",
Description: "BarGauge description",
Model: map[string]interface{}{
@ -440,7 +440,7 @@ func TestGetAllLibraryElements(t *testing.T) {
FolderID: 1,
UID: result.Result.Elements[1].UID,
Name: "Gauge - Library Panel",
Kind: int64(Panel),
Kind: int64(models.PanelElement),
Type: "gauge",
Description: "Gauge description",
Model: map[string]interface{}{
@ -479,7 +479,7 @@ func TestGetAllLibraryElements(t *testing.T) {
scenarioWithPanel(t, "When an admin tries to get all library panels and two exist and typeFilter is set to a nonexistent type, it should succeed and the result should be correct",
func(t *testing.T, sc scenarioContext) {
command := getCreateCommandWithModel(sc.folder.Id, "Gauge - Library Panel", Panel, []byte(`
command := getCreateCommandWithModel(sc.folder.Id, "Gauge - Library Panel", models.PanelElement, []byte(`
{
"datasource": "${DS_GDEV-TESTDATA}",
"id": 1,
@ -542,7 +542,7 @@ func TestGetAllLibraryElements(t *testing.T) {
FolderID: newFolder.Id,
UID: result.Result.Elements[0].UID,
Name: "Text - Library Panel2",
Kind: int64(Panel),
Kind: int64(models.PanelElement),
Type: "text",
Description: "A description",
Model: map[string]interface{}{
@ -637,7 +637,7 @@ func TestGetAllLibraryElements(t *testing.T) {
FolderID: 1,
UID: result.Result.Elements[0].UID,
Name: "Text - Library Panel",
Kind: int64(Panel),
Kind: int64(models.PanelElement),
Type: "text",
Description: "A description",
Model: map[string]interface{}{
@ -672,7 +672,7 @@ func TestGetAllLibraryElements(t *testing.T) {
FolderID: 1,
UID: result.Result.Elements[1].UID,
Name: "Text - Library Panel2",
Kind: int64(Panel),
Kind: int64(models.PanelElement),
Type: "text",
Description: "A description",
Model: map[string]interface{}{
@ -736,7 +736,7 @@ func TestGetAllLibraryElements(t *testing.T) {
FolderID: 1,
UID: result.Result.Elements[0].UID,
Name: "Text - Library Panel2",
Kind: int64(Panel),
Kind: int64(models.PanelElement),
Type: "text",
Description: "A description",
Model: map[string]interface{}{
@ -800,7 +800,7 @@ func TestGetAllLibraryElements(t *testing.T) {
FolderID: 1,
UID: result.Result.Elements[0].UID,
Name: "Text - Library Panel",
Kind: int64(Panel),
Kind: int64(models.PanelElement),
Type: "text",
Description: "A description",
Model: map[string]interface{}{
@ -865,7 +865,7 @@ func TestGetAllLibraryElements(t *testing.T) {
FolderID: 1,
UID: result.Result.Elements[0].UID,
Name: "Text - Library Panel2",
Kind: int64(Panel),
Kind: int64(models.PanelElement),
Type: "text",
Description: "A description",
Model: map[string]interface{}{
@ -904,7 +904,7 @@ func TestGetAllLibraryElements(t *testing.T) {
scenarioWithPanel(t, "When an admin tries to get all library panels and two exist and searchString exists in the description, it should succeed and the result should be correct",
func(t *testing.T, sc scenarioContext) {
command := getCreateCommandWithModel(sc.folder.Id, "Text - Library Panel2", Panel, []byte(`
command := getCreateCommandWithModel(sc.folder.Id, "Text - Library Panel2", models.PanelElement, []byte(`
{
"datasource": "${DS_GDEV-TESTDATA}",
"id": 1,
@ -939,7 +939,7 @@ func TestGetAllLibraryElements(t *testing.T) {
FolderID: 1,
UID: result.Result.Elements[0].UID,
Name: "Text - Library Panel",
Kind: int64(Panel),
Kind: int64(models.PanelElement),
Type: "text",
Description: "A description",
Model: map[string]interface{}{
@ -978,7 +978,7 @@ func TestGetAllLibraryElements(t *testing.T) {
scenarioWithPanel(t, "When an admin tries to get all library panels and two exist and searchString exists in both name and description, it should succeed and the result should be correct",
func(t *testing.T, sc scenarioContext) {
command := getCreateCommandWithModel(sc.folder.Id, "Some Other", Panel, []byte(`
command := getCreateCommandWithModel(sc.folder.Id, "Some Other", models.PanelElement, []byte(`
{
"datasource": "${DS_GDEV-TESTDATA}",
"id": 1,
@ -1011,7 +1011,7 @@ func TestGetAllLibraryElements(t *testing.T) {
FolderID: 1,
UID: result.Result.Elements[0].UID,
Name: "Some Other",
Kind: int64(Panel),
Kind: int64(models.PanelElement),
Type: "text",
Description: "A Library Panel",
Model: map[string]interface{}{
@ -1046,7 +1046,7 @@ func TestGetAllLibraryElements(t *testing.T) {
FolderID: 1,
UID: result.Result.Elements[1].UID,
Name: "Text - Library Panel",
Kind: int64(Panel),
Kind: int64(models.PanelElement),
Type: "text",
Description: "A description",
Model: map[string]interface{}{
@ -1112,7 +1112,7 @@ func TestGetAllLibraryElements(t *testing.T) {
FolderID: 1,
UID: result.Result.Elements[0].UID,
Name: "Text - Library Panel2",
Kind: int64(Panel),
Kind: int64(models.PanelElement),
Type: "text",
Description: "A description",
Model: map[string]interface{}{

View File

@ -35,7 +35,7 @@ func TestGetLibraryElement(t *testing.T) {
FolderID: 1,
UID: res.Result.UID,
Name: "Text - Library Panel",
Kind: int64(Panel),
Kind: int64(models.PanelElement),
Type: "text",
Description: "A description",
Model: map[string]interface{}{
@ -130,7 +130,7 @@ func TestGetLibraryElement(t *testing.T) {
FolderID: 1,
UID: res.Result.UID,
Name: "Text - Library Panel",
Kind: int64(Panel),
Kind: int64(models.PanelElement),
Type: "text",
Description: "A description",
Model: map[string]interface{}{

View File

@ -5,12 +5,14 @@ import (
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/models"
)
func TestPatchLibraryElement(t *testing.T) {
scenarioWithPanel(t, "When an admin tries to patch a library panel that does not exist, it should fail",
func(t *testing.T, sc scenarioContext) {
cmd := patchLibraryElementCommand{Kind: int64(Panel)}
cmd := patchLibraryElementCommand{Kind: int64(models.PanelElement)}
sc.reqContext.ReplaceAllParams(map[string]string{":uid": "unknown"})
resp := sc.service.patchHandler(sc.reqContext, cmd)
require.Equal(t, 404, resp.Status())
@ -31,7 +33,7 @@ func TestPatchLibraryElement(t *testing.T) {
"type": "graph"
}
`),
Kind: int64(Panel),
Kind: int64(models.PanelElement),
Version: 1,
}
sc.reqContext.ReplaceAllParams(map[string]string{":uid": sc.initialResult.Result.UID})
@ -45,7 +47,7 @@ func TestPatchLibraryElement(t *testing.T) {
FolderID: newFolder.Id,
UID: sc.initialResult.Result.UID,
Name: "Panel - New name",
Kind: int64(Panel),
Kind: int64(models.PanelElement),
Type: "graph",
Description: "An updated description",
Model: map[string]interface{}{
@ -83,7 +85,7 @@ func TestPatchLibraryElement(t *testing.T) {
newFolder := createFolderWithACL(t, sc.sqlStore, "NewFolder", sc.user, []folderACLItem{})
cmd := patchLibraryElementCommand{
FolderID: newFolder.Id,
Kind: int64(Panel),
Kind: int64(models.PanelElement),
Version: 1,
}
sc.reqContext.ReplaceAllParams(map[string]string{":uid": sc.initialResult.Result.UID})
@ -104,7 +106,7 @@ func TestPatchLibraryElement(t *testing.T) {
cmd := patchLibraryElementCommand{
FolderID: -1,
Name: "New Name",
Kind: int64(Panel),
Kind: int64(models.PanelElement),
Version: 1,
}
sc.reqContext.ReplaceAllParams(map[string]string{":uid": sc.initialResult.Result.UID})
@ -125,7 +127,7 @@ func TestPatchLibraryElement(t *testing.T) {
cmd := patchLibraryElementCommand{
FolderID: -1,
Model: []byte(`{ "title": "New Model Title", "name": "New Model Name", "type":"graph", "description": "New description" }`),
Kind: int64(Panel),
Kind: int64(models.PanelElement),
Version: 1,
}
sc.reqContext.ReplaceAllParams(map[string]string{":uid": sc.initialResult.Result.UID})
@ -152,7 +154,7 @@ func TestPatchLibraryElement(t *testing.T) {
cmd := patchLibraryElementCommand{
FolderID: -1,
Model: []byte(`{ "description": "New description" }`),
Kind: int64(Panel),
Kind: int64(models.PanelElement),
Version: 1,
}
sc.reqContext.ReplaceAllParams(map[string]string{":uid": sc.initialResult.Result.UID})
@ -178,7 +180,7 @@ func TestPatchLibraryElement(t *testing.T) {
cmd := patchLibraryElementCommand{
FolderID: -1,
Model: []byte(`{ "type": "graph" }`),
Kind: int64(Panel),
Kind: int64(models.PanelElement),
Version: 1,
}
sc.reqContext.ReplaceAllParams(map[string]string{":uid": sc.initialResult.Result.UID})
@ -201,7 +203,7 @@ func TestPatchLibraryElement(t *testing.T) {
scenarioWithPanel(t, "When another admin tries to patch a library panel, it should change UpdatedBy successfully and return correct result",
func(t *testing.T, sc scenarioContext) {
cmd := patchLibraryElementCommand{FolderID: -1, Version: 1, Kind: int64(Panel)}
cmd := patchLibraryElementCommand{FolderID: -1, Version: 1, Kind: int64(models.PanelElement)}
sc.reqContext.UserId = 2
sc.reqContext.ReplaceAllParams(map[string]string{":uid": sc.initialResult.Result.UID})
resp := sc.service.patchHandler(sc.reqContext, cmd)
@ -223,7 +225,7 @@ func TestPatchLibraryElement(t *testing.T) {
cmd := patchLibraryElementCommand{
Name: "Text - Library Panel",
Version: 1,
Kind: int64(Panel),
Kind: int64(models.PanelElement),
}
sc.reqContext.ReplaceAllParams(map[string]string{":uid": result.Result.UID})
resp = sc.service.patchHandler(sc.reqContext, cmd)
@ -239,7 +241,7 @@ func TestPatchLibraryElement(t *testing.T) {
cmd := patchLibraryElementCommand{
FolderID: 1,
Version: 1,
Kind: int64(Panel),
Kind: int64(models.PanelElement),
}
sc.reqContext.ReplaceAllParams(map[string]string{":uid": result.Result.UID})
resp = sc.service.patchHandler(sc.reqContext, cmd)
@ -251,7 +253,7 @@ func TestPatchLibraryElement(t *testing.T) {
cmd := patchLibraryElementCommand{
FolderID: sc.folder.Id,
Version: 1,
Kind: int64(Panel),
Kind: int64(models.PanelElement),
}
sc.reqContext.OrgId = 2
sc.reqContext.ReplaceAllParams(map[string]string{":uid": sc.initialResult.Result.UID})
@ -264,7 +266,7 @@ func TestPatchLibraryElement(t *testing.T) {
cmd := patchLibraryElementCommand{
FolderID: sc.folder.Id,
Version: 1,
Kind: int64(Panel),
Kind: int64(models.PanelElement),
}
sc.reqContext.ReplaceAllParams(map[string]string{":uid": sc.initialResult.Result.UID})
resp := sc.service.patchHandler(sc.reqContext, cmd)
@ -278,14 +280,14 @@ func TestPatchLibraryElement(t *testing.T) {
cmd := patchLibraryElementCommand{
FolderID: sc.folder.Id,
Version: 1,
Kind: int64(Variable),
Kind: int64(models.VariableElement),
}
sc.reqContext.ReplaceAllParams(map[string]string{":uid": sc.initialResult.Result.UID})
resp := sc.service.patchHandler(sc.reqContext, cmd)
require.Equal(t, 200, resp.Status())
var result = validateAndUnMarshalResponse(t, resp)
sc.initialResult.Result.Type = "text"
sc.initialResult.Result.Kind = int64(Panel)
sc.initialResult.Result.Kind = int64(models.PanelElement)
sc.initialResult.Result.Description = "A description"
sc.initialResult.Result.Model = map[string]interface{}{
"datasource": "${DS_GDEV-TESTDATA}",

View File

@ -84,7 +84,7 @@ func TestLibraryElementPermissions(t *testing.T) {
toFolder := createFolderWithACL(t, sc.sqlStore, "Folder", sc.user, testCase.items)
sc.reqContext.SignedInUser.OrgRole = testCase.role
cmd := patchLibraryElementCommand{FolderID: toFolder.Id, Version: 1, Kind: int64(Panel)}
cmd := patchLibraryElementCommand{FolderID: toFolder.Id, Version: 1, Kind: int64(models.PanelElement)}
sc.reqContext.ReplaceAllParams(map[string]string{":uid": result.Result.UID})
resp = sc.service.patchHandler(sc.reqContext, cmd)
require.Equal(t, testCase.status, resp.Status())
@ -99,7 +99,7 @@ func TestLibraryElementPermissions(t *testing.T) {
toFolder := createFolderWithACL(t, sc.sqlStore, "Folder", sc.user, everyonePermissions)
sc.reqContext.SignedInUser.OrgRole = testCase.role
cmd := patchLibraryElementCommand{FolderID: toFolder.Id, Version: 1, Kind: int64(Panel)}
cmd := patchLibraryElementCommand{FolderID: toFolder.Id, Version: 1, Kind: int64(models.PanelElement)}
sc.reqContext.ReplaceAllParams(map[string]string{":uid": result.Result.UID})
resp = sc.service.patchHandler(sc.reqContext, cmd)
require.Equal(t, testCase.status, resp.Status())
@ -146,7 +146,7 @@ func TestLibraryElementPermissions(t *testing.T) {
result := validateAndUnMarshalResponse(t, resp)
sc.reqContext.SignedInUser.OrgRole = testCase.role
cmd := patchLibraryElementCommand{FolderID: 0, Version: 1, Kind: int64(Panel)}
cmd := patchLibraryElementCommand{FolderID: 0, Version: 1, Kind: int64(models.PanelElement)}
sc.reqContext.ReplaceAllParams(map[string]string{":uid": result.Result.UID})
resp = sc.service.patchHandler(sc.reqContext, cmd)
require.Equal(t, testCase.status, resp.Status())
@ -160,7 +160,7 @@ func TestLibraryElementPermissions(t *testing.T) {
result := validateAndUnMarshalResponse(t, resp)
sc.reqContext.SignedInUser.OrgRole = testCase.role
cmd := patchLibraryElementCommand{FolderID: folder.Id, Version: 1, Kind: int64(Panel)}
cmd := patchLibraryElementCommand{FolderID: folder.Id, Version: 1, Kind: int64(models.PanelElement)}
sc.reqContext.ReplaceAllParams(map[string]string{":uid": result.Result.UID})
resp = sc.service.patchHandler(sc.reqContext, cmd)
require.Equal(t, testCase.status, resp.Status())
@ -205,7 +205,7 @@ func TestLibraryElementPermissions(t *testing.T) {
result := validateAndUnMarshalResponse(t, resp)
sc.reqContext.SignedInUser.OrgRole = testCase.role
cmd := patchLibraryElementCommand{FolderID: -100, Version: 1, Kind: int64(Panel)}
cmd := patchLibraryElementCommand{FolderID: -100, Version: 1, Kind: int64(models.PanelElement)}
sc.reqContext.ReplaceAllParams(map[string]string{":uid": result.Result.UID})
resp = sc.service.patchHandler(sc.reqContext, cmd)
require.Equal(t, 404, resp.Status())

View File

@ -126,7 +126,7 @@ type libraryElementsSearchResult struct {
}
func getCreatePanelCommand(folderID int64, name string) CreateLibraryElementCommand {
command := getCreateCommandWithModel(folderID, name, Panel, []byte(`
command := getCreateCommandWithModel(folderID, name, models.PanelElement, []byte(`
{
"datasource": "${DS_GDEV-TESTDATA}",
"id": 1,
@ -140,7 +140,7 @@ func getCreatePanelCommand(folderID int64, name string) CreateLibraryElementComm
}
func getCreateVariableCommand(folderID int64, name string) CreateLibraryElementCommand {
command := getCreateCommandWithModel(folderID, name, Variable, []byte(`
command := getCreateCommandWithModel(folderID, name, models.VariableElement, []byte(`
{
"datasource": "${DS_GDEV-TESTDATA}",
"name": "query0",
@ -152,7 +152,7 @@ func getCreateVariableCommand(folderID int64, name string) CreateLibraryElementC
return command
}
func getCreateCommandWithModel(folderID int64, name string, kind LibraryElementKind, model []byte) CreateLibraryElementCommand {
func getCreateCommandWithModel(folderID int64, name string, kind models.LibraryElementKind, model []byte) CreateLibraryElementCommand {
command := CreateLibraryElementCommand{
FolderID: folderID,
Name: name,

View File

@ -6,13 +6,6 @@ import (
"time"
)
type LibraryElementKind int
const (
Panel LibraryElementKind = iota + 1
Variable
)
type LibraryConnectionKind int
const (

View File

@ -5,6 +5,7 @@ import (
"strconv"
"strings"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/sqlstore"
)
@ -38,7 +39,7 @@ func writePerPageSQL(query searchLibraryElementsQuery, sqlStore *sqlstore.SQLSto
}
func writeKindSQL(query searchLibraryElementsQuery, builder *sqlstore.SQLBuilder) {
if LibraryElementKind(query.kind) == Panel || LibraryElementKind(query.kind) == Variable {
if models.LibraryElementKind(query.kind) == models.PanelElement || models.LibraryElementKind(query.kind) == models.VariableElement {
builder.Write(" AND le.kind = ?", query.kind)
}
}

View File

@ -76,7 +76,7 @@ func (lps *LibraryPanelService) LoadLibraryPanelsForDashboard(c *models.ReqConte
continue
}
if libraryelements.LibraryElementKind(elementInDB.Kind) != libraryelements.Panel {
if models.LibraryElementKind(elementInDB.Kind) != models.PanelElement {
continue
}

View File

@ -493,7 +493,7 @@ func TestConnectLibraryPanelsForDashboard(t *testing.T) {
"description": "Unused description"
}
`),
Kind: int64(libraryelements.Panel),
Kind: int64(models.PanelElement),
})
require.NoError(t, err)
dashJSON := map[string]interface{}{
@ -783,7 +783,7 @@ func scenarioWithLibraryPanel(t *testing.T, desc string, fn func(t *testing.T, s
"description": "A description"
}
`),
Kind: int64(libraryelements.Panel),
Kind: int64(models.PanelElement),
}
resp, err := sc.elementService.CreateElement(sc.reqContext, command)
require.NoError(t, err)

View File

@ -0,0 +1,53 @@
package migrations
import (
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
)
// addLibraryElementsMigrations defines database migrations for library elements.
func addLibraryElementsMigrations(mg *migrator.Migrator) {
libraryElementsV1 := migrator.Table{
Name: "library_element",
Columns: []*migrator.Column{
{Name: "id", Type: migrator.DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
{Name: "org_id", Type: migrator.DB_BigInt, Nullable: false},
{Name: "folder_id", Type: migrator.DB_BigInt, Nullable: false},
{Name: "uid", Type: migrator.DB_NVarchar, Length: 40, Nullable: false},
{Name: "name", Type: migrator.DB_NVarchar, Length: 150, Nullable: false},
{Name: "kind", Type: migrator.DB_BigInt, Nullable: false},
{Name: "type", Type: migrator.DB_NVarchar, Length: 40, Nullable: false},
{Name: "description", Type: migrator.DB_NVarchar, Length: 255, Nullable: false},
{Name: "model", Type: migrator.DB_Text, Nullable: false},
{Name: "created", Type: migrator.DB_DateTime, Nullable: false},
{Name: "created_by", Type: migrator.DB_BigInt, Nullable: false},
{Name: "updated", Type: migrator.DB_DateTime, Nullable: false},
{Name: "updated_by", Type: migrator.DB_BigInt, Nullable: false},
{Name: "version", Type: migrator.DB_BigInt, Nullable: false},
},
Indices: []*migrator.Index{
{Cols: []string{"org_id", "folder_id", "name", "kind"}, Type: migrator.UniqueIndex},
},
}
mg.AddMigration("create library_element table v1", migrator.NewAddTableMigration(libraryElementsV1))
mg.AddMigration("add index library_element org_id-folder_id-name-kind", migrator.NewAddIndexMigration(libraryElementsV1, libraryElementsV1.Indices[0]))
libraryElementConnectionV1 := migrator.Table{
Name: models.LibraryElementConnectionTableName,
Columns: []*migrator.Column{
{Name: "id", Type: migrator.DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
{Name: "element_id", Type: migrator.DB_BigInt, Nullable: false},
{Name: "kind", Type: migrator.DB_BigInt, Nullable: false},
{Name: "connection_id", Type: migrator.DB_BigInt, Nullable: false},
{Name: "created", Type: migrator.DB_DateTime, Nullable: false},
{Name: "created_by", Type: migrator.DB_BigInt, Nullable: false},
},
Indices: []*migrator.Index{
{Cols: []string{"element_id", "kind", "connection_id"}, Type: migrator.UniqueIndex},
},
}
mg.AddMigration("create "+models.LibraryElementConnectionTableName+" table v1", migrator.NewAddTableMigration(libraryElementConnectionV1))
mg.AddMigration("add index "+models.LibraryElementConnectionTableName+" element_id-kind-connection_id", migrator.NewAddIndexMigration(libraryElementConnectionV1, libraryElementConnectionV1.Indices[0]))
}

View File

@ -40,6 +40,7 @@ func AddMigrations(mg *Migrator) {
addShortURLMigrations(mg)
ualert.AddTablesMigrations(mg)
ualert.AddDashAlertMigration(mg)
addLibraryElementsMigrations(mg)
}
func addMigrationLogMigrations(mg *Migrator) {

View File

@ -80,6 +80,8 @@ func GetSystemStats(query *models.GetSystemStatsQuery) error {
sb.Write(`(SELECT COUNT(id) FROM ` + dialect.Quote("team") + `) AS teams,`)
sb.Write(`(SELECT COUNT(id) FROM ` + dialect.Quote("user_auth_token") + `) AS auth_tokens,`)
sb.Write(`(SELECT COUNT(id) FROM ` + dialect.Quote("alert_rule") + `) AS alert_rules,`)
sb.Write(`(SELECT COUNT(id) FROM `+dialect.Quote("library_element")+` WHERE kind = ?) AS library_panels,`, models.PanelElement)
sb.Write(`(SELECT COUNT(id) FROM `+dialect.Quote("library_element")+` WHERE kind = ?) AS library_variables,`, models.VariableElement)
sb.Write(roleCounterSQL())

View File

@ -24,6 +24,8 @@ func TestStatsDataAccess(t *testing.T) {
assert.Equal(t, 0, query.Result.Editors)
assert.Equal(t, 0, query.Result.Viewers)
assert.Equal(t, 3, query.Result.Admins)
assert.Equal(t, int64(0), query.Result.LibraryPanels)
assert.Equal(t, int64(0), query.Result.LibraryVariables)
})
t.Run("Get system user count stats should not results in error", func(t *testing.T) {