mirror of
https://github.com/grafana/grafana.git
synced 2024-12-01 21:19:28 -06:00
451 lines
11 KiB
Go
451 lines
11 KiB
Go
package dashboard
|
|
|
|
import (
|
|
"io"
|
|
"strconv"
|
|
"strings"
|
|
|
|
jsoniter "github.com/json-iterator/go"
|
|
)
|
|
|
|
func logf(format string, a ...interface{}) {
|
|
//fmt.Printf(format, a...)
|
|
}
|
|
|
|
type templateVariable struct {
|
|
current struct {
|
|
value interface{}
|
|
}
|
|
name string
|
|
query interface{}
|
|
variableType string
|
|
}
|
|
|
|
type datasourceVariableLookup struct {
|
|
variableNameToRefs map[string][]DataSourceRef
|
|
dsLookup DatasourceLookup
|
|
}
|
|
|
|
func (d *datasourceVariableLookup) getDsRefsByTemplateVariableValue(value string, datasourceType string) []DataSourceRef {
|
|
switch value {
|
|
case "default":
|
|
// can be the default DS, or a DS with UID="default"
|
|
candidateDs := d.dsLookup.ByRef(&DataSourceRef{UID: value})
|
|
if candidateDs == nil {
|
|
// get the actual default DS
|
|
candidateDs = d.dsLookup.ByRef(nil)
|
|
}
|
|
|
|
if candidateDs != nil {
|
|
return []DataSourceRef{*candidateDs}
|
|
}
|
|
return []DataSourceRef{}
|
|
case "$__all":
|
|
// TODO: filter datasources by template variable's regex
|
|
return d.dsLookup.ByType(datasourceType)
|
|
case "":
|
|
return []DataSourceRef{}
|
|
case "No data sources found":
|
|
return []DataSourceRef{}
|
|
default:
|
|
// some variables use `ds.name` rather `ds.uid`
|
|
if ref := d.dsLookup.ByRef(&DataSourceRef{
|
|
UID: value,
|
|
}); ref != nil {
|
|
return []DataSourceRef{*ref}
|
|
}
|
|
|
|
// discard variable
|
|
return []DataSourceRef{}
|
|
}
|
|
}
|
|
|
|
func (d *datasourceVariableLookup) add(templateVariable templateVariable) {
|
|
var refs []DataSourceRef
|
|
|
|
datasourceType, isDataSourceTypeValid := templateVariable.query.(string)
|
|
if !isDataSourceTypeValid {
|
|
d.variableNameToRefs[templateVariable.name] = refs
|
|
return
|
|
}
|
|
|
|
if values, multiValueVariable := templateVariable.current.value.([]interface{}); multiValueVariable {
|
|
for _, value := range values {
|
|
if valueAsString, ok := value.(string); ok {
|
|
refs = append(refs, d.getDsRefsByTemplateVariableValue(valueAsString, datasourceType)...)
|
|
}
|
|
}
|
|
}
|
|
|
|
if value, stringValue := templateVariable.current.value.(string); stringValue {
|
|
refs = append(refs, d.getDsRefsByTemplateVariableValue(value, datasourceType)...)
|
|
}
|
|
|
|
d.variableNameToRefs[templateVariable.name] = unique(refs)
|
|
}
|
|
|
|
func unique(refs []DataSourceRef) []DataSourceRef {
|
|
var uniqueRefs []DataSourceRef
|
|
uidPresence := make(map[string]bool)
|
|
for _, ref := range refs {
|
|
if !uidPresence[ref.UID] {
|
|
uidPresence[ref.UID] = true
|
|
uniqueRefs = append(uniqueRefs, ref)
|
|
}
|
|
}
|
|
return uniqueRefs
|
|
}
|
|
|
|
func (d *datasourceVariableLookup) getDatasourceRefs(name string) []DataSourceRef {
|
|
refs, ok := d.variableNameToRefs[name]
|
|
if ok {
|
|
return refs
|
|
}
|
|
|
|
return []DataSourceRef{}
|
|
}
|
|
|
|
func newDatasourceVariableLookup(dsLookup DatasourceLookup) *datasourceVariableLookup {
|
|
return &datasourceVariableLookup{
|
|
variableNameToRefs: make(map[string][]DataSourceRef),
|
|
dsLookup: dsLookup,
|
|
}
|
|
}
|
|
|
|
// nolint:gocyclo
|
|
// ReadDashboard will take a byte stream and return dashboard info
|
|
func readDashboard(stream io.Reader, lookup DatasourceLookup) (*dashboardInfo, error) {
|
|
dash := &dashboardInfo{}
|
|
|
|
iter := jsoniter.Parse(jsoniter.ConfigDefault, stream, 1024)
|
|
|
|
datasourceVariablesLookup := newDatasourceVariableLookup(lookup)
|
|
|
|
for l1Field := iter.ReadObject(); l1Field != ""; l1Field = iter.ReadObject() {
|
|
// Skip null values so we don't need special int handling
|
|
if iter.WhatIsNext() == jsoniter.NilValue {
|
|
iter.Skip()
|
|
continue
|
|
}
|
|
|
|
switch l1Field {
|
|
case "id":
|
|
dash.ID = iter.ReadInt64()
|
|
|
|
case "uid":
|
|
iter.ReadString()
|
|
|
|
case "title":
|
|
dash.Title = iter.ReadString()
|
|
|
|
case "description":
|
|
dash.Description = iter.ReadString()
|
|
|
|
case "schemaVersion":
|
|
switch iter.WhatIsNext() {
|
|
case jsoniter.NumberValue:
|
|
dash.SchemaVersion = iter.ReadInt64()
|
|
case jsoniter.StringValue:
|
|
val := iter.ReadString()
|
|
if v, err := strconv.ParseInt(val, 10, 64); err == nil {
|
|
dash.SchemaVersion = v
|
|
}
|
|
default:
|
|
iter.Skip()
|
|
}
|
|
case "timezone":
|
|
dash.TimeZone = iter.ReadString()
|
|
|
|
case "editable":
|
|
dash.ReadOnly = !iter.ReadBool()
|
|
|
|
case "refresh":
|
|
nxt := iter.WhatIsNext()
|
|
if nxt == jsoniter.StringValue {
|
|
dash.Refresh = iter.ReadString()
|
|
} else {
|
|
iter.Skip()
|
|
}
|
|
|
|
case "tags":
|
|
for iter.ReadArray() {
|
|
dash.Tags = append(dash.Tags, iter.ReadString())
|
|
}
|
|
|
|
case "links":
|
|
for iter.ReadArray() {
|
|
iter.Skip()
|
|
dash.LinkCount++
|
|
}
|
|
|
|
case "time":
|
|
obj, ok := iter.Read().(map[string]interface{})
|
|
if ok {
|
|
if timeFrom, ok := obj["from"].(string); ok {
|
|
dash.TimeFrom = timeFrom
|
|
}
|
|
if timeTo, ok := obj["to"].(string); ok {
|
|
dash.TimeTo = timeTo
|
|
}
|
|
}
|
|
case "panels":
|
|
for iter.ReadArray() {
|
|
dash.Panels = append(dash.Panels, readpanelInfo(iter, lookup))
|
|
}
|
|
|
|
case "rows":
|
|
for iter.ReadArray() {
|
|
v := iter.Read()
|
|
logf("[DASHBOARD.ROW???] id=%s // %v\n", dash.ID, v)
|
|
}
|
|
|
|
case "annotations":
|
|
for sub := iter.ReadObject(); sub != ""; sub = iter.ReadObject() {
|
|
if sub == "list" {
|
|
for iter.ReadArray() {
|
|
v := iter.Read()
|
|
logf("[dash.anno] %v\n", v)
|
|
}
|
|
} else {
|
|
iter.Skip()
|
|
}
|
|
}
|
|
|
|
case "templating":
|
|
for sub := iter.ReadObject(); sub != ""; sub = iter.ReadObject() {
|
|
if sub == "list" {
|
|
for iter.ReadArray() {
|
|
templateVariable := templateVariable{}
|
|
|
|
for k := iter.ReadObject(); k != ""; k = iter.ReadObject() {
|
|
switch k {
|
|
case "name":
|
|
name := iter.ReadString()
|
|
dash.TemplateVars = append(dash.TemplateVars, name)
|
|
templateVariable.name = name
|
|
case "type":
|
|
templateVariable.variableType = iter.ReadString()
|
|
case "query":
|
|
templateVariable.query = iter.Read()
|
|
case "current":
|
|
for c := iter.ReadObject(); c != ""; c = iter.ReadObject() {
|
|
if c == "value" {
|
|
templateVariable.current.value = iter.Read()
|
|
} else {
|
|
iter.Skip()
|
|
}
|
|
}
|
|
default:
|
|
iter.Skip()
|
|
}
|
|
}
|
|
|
|
if templateVariable.variableType == "datasource" {
|
|
datasourceVariablesLookup.add(templateVariable)
|
|
}
|
|
}
|
|
} else {
|
|
iter.Skip()
|
|
}
|
|
}
|
|
|
|
// Ignore these properties
|
|
case "timepicker":
|
|
fallthrough
|
|
case "version":
|
|
fallthrough
|
|
case "iteration":
|
|
iter.Skip()
|
|
|
|
default:
|
|
v := iter.Read()
|
|
logf("[DASHBOARD] support key: %s / %v\n", l1Field, v)
|
|
}
|
|
}
|
|
|
|
replaceDatasourceVariables(dash, datasourceVariablesLookup)
|
|
fillDefaultDatasources(dash, lookup)
|
|
filterOutSpecialDatasources(dash)
|
|
|
|
targets := newTargetInfo(lookup)
|
|
for _, panel := range dash.Panels {
|
|
targets.addPanel(panel)
|
|
}
|
|
dash.Datasource = targets.GetDatasourceInfo()
|
|
|
|
return dash, iter.Error
|
|
}
|
|
|
|
func panelRequiresDatasource(panel panelInfo) bool {
|
|
return panel.Type != "row"
|
|
}
|
|
|
|
func fillDefaultDatasources(dash *dashboardInfo, lookup DatasourceLookup) {
|
|
for i, panel := range dash.Panels {
|
|
if len(panel.Datasource) != 0 || !panelRequiresDatasource(panel) {
|
|
continue
|
|
}
|
|
|
|
defaultDs := lookup.ByRef(nil)
|
|
if defaultDs != nil {
|
|
dash.Panels[i].Datasource = []DataSourceRef{*defaultDs}
|
|
}
|
|
}
|
|
}
|
|
|
|
func filterOutSpecialDatasources(dash *dashboardInfo) {
|
|
for i, panel := range dash.Panels {
|
|
var dsRefs []DataSourceRef
|
|
|
|
// partition into actual datasource references and variables
|
|
for _, ds := range panel.Datasource {
|
|
switch ds.UID {
|
|
case "-- Mixed --":
|
|
// The actual datasources used as targets will remain
|
|
continue
|
|
case "-- Dashboard --":
|
|
// The `Dashboard` datasource refers to the results of the query used in another panel
|
|
continue
|
|
default:
|
|
dsRefs = append(dsRefs, ds)
|
|
}
|
|
}
|
|
|
|
dash.Panels[i].Datasource = dsRefs
|
|
}
|
|
}
|
|
|
|
func replaceDatasourceVariables(dash *dashboardInfo, datasourceVariablesLookup *datasourceVariableLookup) {
|
|
for i, panel := range dash.Panels {
|
|
var dsVariableRefs []DataSourceRef
|
|
var dsRefs []DataSourceRef
|
|
|
|
// partition into actual datasource references and variables
|
|
for i := range panel.Datasource {
|
|
uid := panel.Datasource[i].UID
|
|
if isVariableRef(uid) {
|
|
dsVariableRefs = append(dsVariableRefs, panel.Datasource[i])
|
|
} else {
|
|
dsRefs = append(dsRefs, panel.Datasource[i])
|
|
}
|
|
}
|
|
|
|
variables := findDatasourceRefsForVariables(dsVariableRefs, datasourceVariablesLookup)
|
|
dash.Panels[i].Datasource = append(dsRefs, variables...)
|
|
}
|
|
}
|
|
|
|
func isSpecialDatasource(uid string) bool {
|
|
return uid == "-- Mixed --" || uid == "-- Dashboard --"
|
|
}
|
|
|
|
func isVariableRef(uid string) bool {
|
|
return strings.HasPrefix(uid, "$")
|
|
}
|
|
|
|
func getDataSourceVariableName(dsVariableRef DataSourceRef) string {
|
|
if strings.HasPrefix(dsVariableRef.UID, "${") {
|
|
return strings.TrimPrefix(strings.TrimSuffix(dsVariableRef.UID, "}"), "${")
|
|
}
|
|
|
|
return strings.TrimPrefix(dsVariableRef.UID, "$")
|
|
}
|
|
|
|
func findDatasourceRefsForVariables(dsVariableRefs []DataSourceRef, datasourceVariablesLookup *datasourceVariableLookup) []DataSourceRef {
|
|
var referencedDs []DataSourceRef
|
|
for _, dsVariableRef := range dsVariableRefs {
|
|
variableName := getDataSourceVariableName(dsVariableRef)
|
|
refs := datasourceVariablesLookup.getDatasourceRefs(variableName)
|
|
referencedDs = append(referencedDs, refs...)
|
|
}
|
|
return referencedDs
|
|
}
|
|
|
|
// will always return strings for now
|
|
func readpanelInfo(iter *jsoniter.Iterator, lookup DatasourceLookup) panelInfo {
|
|
panel := panelInfo{}
|
|
|
|
targets := newTargetInfo(lookup)
|
|
|
|
for l1Field := iter.ReadObject(); l1Field != ""; l1Field = iter.ReadObject() {
|
|
if iter.WhatIsNext() == jsoniter.NilValue {
|
|
if l1Field == "datasource" {
|
|
targets.addDatasource(iter)
|
|
continue
|
|
}
|
|
|
|
// Skip null values so we don't need special int handling
|
|
iter.Skip()
|
|
continue
|
|
}
|
|
|
|
switch l1Field {
|
|
case "id":
|
|
panel.ID = iter.ReadInt64()
|
|
|
|
case "type":
|
|
panel.Type = iter.ReadString()
|
|
|
|
case "title":
|
|
panel.Title = iter.ReadString()
|
|
|
|
case "description":
|
|
panel.Description = iter.ReadString()
|
|
|
|
case "pluginVersion":
|
|
panel.PluginVersion = iter.ReadString() // since 7x (the saved version for the plugin model)
|
|
|
|
case "datasource":
|
|
targets.addDatasource(iter)
|
|
|
|
case "targets":
|
|
switch iter.WhatIsNext() {
|
|
case jsoniter.ArrayValue:
|
|
for iter.ReadArray() {
|
|
targets.addTarget(iter)
|
|
}
|
|
case jsoniter.ObjectValue:
|
|
for f := iter.ReadObject(); f != ""; f = iter.ReadObject() {
|
|
targets.addTarget(iter)
|
|
}
|
|
default:
|
|
iter.Skip()
|
|
}
|
|
|
|
case "transformations":
|
|
for iter.ReadArray() {
|
|
for sub := iter.ReadObject(); sub != ""; sub = iter.ReadObject() {
|
|
if sub == "id" {
|
|
panel.Transformer = append(panel.Transformer, iter.ReadString())
|
|
} else {
|
|
iter.Skip()
|
|
}
|
|
}
|
|
}
|
|
|
|
// Rows have nested panels
|
|
case "panels":
|
|
for iter.ReadArray() {
|
|
panel.Collapsed = append(panel.Collapsed, readpanelInfo(iter, lookup))
|
|
}
|
|
|
|
case "options":
|
|
fallthrough
|
|
|
|
case "gridPos":
|
|
fallthrough
|
|
|
|
case "fieldConfig":
|
|
iter.Skip()
|
|
|
|
default:
|
|
v := iter.Read()
|
|
logf("[PANEL] support key: %s / %v\n", l1Field, v)
|
|
}
|
|
}
|
|
|
|
panel.Datasource = targets.GetDatasourceInfo()
|
|
|
|
return panel
|
|
}
|