influxdb/cmd/chronograf-migrator/dashboard.go

398 lines
9.8 KiB
Go

package main
import (
"context"
"errors"
"fmt"
"regexp"
"strings"
"time"
"github.com/influxdata/flux/ast"
"github.com/influxdata/influxdb"
"github.com/influxdata/influxdb/chronograf"
"github.com/influxdata/influxdb/query/influxql"
)
func convert1To2Cell(cell chronograf.DashboardCell) *influxdb.Cell {
c := &influxdb.Cell{
ID: 1,
CellProperty: influxdb.CellProperty{
X: cell.X,
Y: cell.Y,
W: cell.W,
H: cell.H,
},
}
v := influxdb.View{
ViewContents: influxdb.ViewContents{
Name: cell.Name,
},
}
switch cell.Type {
case "line":
v.Properties = influxdb.XYViewProperties{
Queries: convertQueries(cell.Queries),
Axes: convertAxes(cell.Axes),
Type: "xy",
Legend: convertLegend(cell.Legend),
Geom: "line",
ViewColors: convertColors(cell.CellColors),
Note: cell.Note,
Position: "overlaid",
}
case "line-stacked":
v.Properties = influxdb.XYViewProperties{
Queries: convertQueries(cell.Queries),
Axes: convertAxes(cell.Axes),
Type: "xy",
Legend: convertLegend(cell.Legend),
Geom: "line", // TODO(desa): maybe this needs to be stacked?
ViewColors: convertColors(cell.CellColors),
Note: cell.Note,
Position: "stacked",
}
case "line-stepplot":
v.Properties = influxdb.XYViewProperties{
Queries: convertQueries(cell.Queries),
Axes: convertAxes(cell.Axes),
Type: "xy",
Legend: convertLegend(cell.Legend),
Geom: "step",
ViewColors: convertColors(cell.CellColors),
Note: cell.Note,
Position: "overlaid",
}
case "bar":
v.Properties = influxdb.XYViewProperties{
Queries: convertQueries(cell.Queries),
Axes: convertAxes(cell.Axes),
Type: "xy",
Legend: convertLegend(cell.Legend),
Geom: "bar",
ViewColors: convertColors(cell.CellColors),
Note: cell.Note,
Position: "overlaid",
}
case "line-plus-single-stat":
v.Properties = influxdb.LinePlusSingleStatProperties{
Queries: convertQueries(cell.Queries),
Axes: convertAxes(cell.Axes),
Legend: convertLegend(cell.Legend),
ViewColors: convertColors(cell.CellColors),
Note: cell.Note,
Position: "overlaid",
}
case "single-stat":
v.Properties = influxdb.SingleStatViewProperties{
Queries: convertQueries(cell.Queries),
ViewColors: convertColors(cell.CellColors),
Note: cell.Note,
// TODO(desa): what to do about ShowNoteWhenEmpty?
}
case "gauge":
v.Properties = influxdb.GaugeViewProperties{
Queries: convertQueries(cell.Queries),
ViewColors: convertColors(cell.CellColors),
Note: cell.Note,
// TODO(desa): what to do about ShowNoteWhenEmpty?
}
case "table":
v.Properties = influxdb.TableViewProperties{
Queries: convertQueries(cell.Queries),
ViewColors: convertColors(cell.CellColors),
//TableOptions
//FieldOptions
Note: cell.Note,
// TODO(desa): what to do about ShowNoteWhenEmpty?
}
case "note":
v.Properties = influxdb.MarkdownViewProperties{
Note: cell.Note,
}
case "alerts", "news", "guide":
// TODO(desa): these do not have 2.x equivalents
v.Properties = influxdb.EmptyViewProperties{}
default:
v.Properties = influxdb.EmptyViewProperties{}
}
c.View = &v
return c
}
func convert1To2Variable(t chronograf.Template) (influxdb.Variable, error) {
v := influxdb.Variable{
Description: t.Label,
Name: t.Var[1 : len(t.Var)-1], // trims `:` from variables prefix and suffix
}
switch t.Type {
case "influxql", "databases", "fieldKeys", "tagKeys", "tagValues", "measurements":
if t.Query == nil {
return v, fmt.Errorf("expected template variable to have non-nil query")
}
}
switch t.Type {
case "influxql":
v.Arguments = &influxdb.VariableArguments{
Type: "query",
Values: influxdb.VariableQueryValues{
Query: fmt.Sprintf("// %s", t.Query.Command),
Language: "flux",
},
}
case "databases":
v.Arguments = &influxdb.VariableArguments{
Type: "query",
Values: influxdb.VariableQueryValues{
Query: fmt.Sprintf("// SHOW DATABASES %s", t.Query.DB),
Language: "flux",
},
}
case "fieldKeys":
v.Arguments = &influxdb.VariableArguments{
Type: "query",
Values: influxdb.VariableQueryValues{
Query: fmt.Sprintf("// SHOW FIELD KEYS FOR %s", t.Query.Measurement),
Language: "flux",
},
}
case "tagKeys":
v.Arguments = &influxdb.VariableArguments{
Type: "query",
Values: influxdb.VariableQueryValues{
Query: fmt.Sprintf("// SHOW TAG KEYS FOR %s", t.Query.Measurement),
Language: "flux",
},
}
case "tagValues":
v.Arguments = &influxdb.VariableArguments{
Type: "query",
Values: influxdb.VariableQueryValues{
Query: fmt.Sprintf("// SHOW TAG VALUES FOR %s", t.Query.TagKey),
Language: "flux",
},
}
case "measurements":
v.Arguments = &influxdb.VariableArguments{
Type: "query",
Values: influxdb.VariableQueryValues{
Query: fmt.Sprintf("// SHOW MEASUREMENTS ON %s", t.Query.DB),
Language: "flux",
},
}
case "csv", "constant", "text":
values := influxdb.VariableConstantValues{}
for _, val := range t.Values {
values = append(values, val.Value)
}
v.Arguments = &influxdb.VariableArguments{
Type: "constant",
Values: values,
}
case "map":
values := influxdb.VariableMapValues{}
for _, val := range t.Values {
values[val.Key] = val.Value
}
v.Arguments = &influxdb.VariableArguments{
Type: "map",
Values: values,
}
default:
return v, fmt.Errorf("unknown variable type %s", t.Type)
}
return v, nil
}
func Convert1To2Dashboard(d1 chronograf.Dashboard) (influxdb.Dashboard, []influxdb.Variable, error) {
cells := []*influxdb.Cell{}
for _, cell := range d1.Cells {
cells = append(cells, convert1To2Cell(cell))
}
d2 := influxdb.Dashboard{
Name: d1.Name,
Cells: cells,
}
vars := []influxdb.Variable{}
for _, template := range d1.Templates {
v, err := convert1To2Variable(template)
if err != nil {
return influxdb.Dashboard{}, nil, err
}
vars = append(vars, v)
}
return d2, vars, nil
}
func convertAxes(a map[string]chronograf.Axis) map[string]influxdb.Axis {
m := map[string]influxdb.Axis{}
for k, v := range a {
m[k] = influxdb.Axis{
Bounds: v.Bounds,
Label: v.Label,
Prefix: v.Prefix,
Suffix: v.Suffix,
Base: v.Base,
Scale: v.Scale,
}
}
if _, exists := m["x"]; !exists {
m["x"] = influxdb.Axis{}
}
if _, exists := m["y"]; !exists {
m["y"] = influxdb.Axis{}
}
return m
}
func convertLegend(l chronograf.Legend) influxdb.Legend {
return influxdb.Legend{
Type: l.Type,
Orientation: l.Orientation,
}
}
func convertColors(cs []chronograf.CellColor) []influxdb.ViewColor {
vs := []influxdb.ViewColor{}
hasTextColor := false
hasThresholdColor := false
for _, c := range cs {
if c.Type == "text" {
hasTextColor = true
}
if c.Type == "threshold" {
hasThresholdColor = true
}
v := influxdb.ViewColor{
ID: c.ID,
Type: c.Type,
Hex: c.Hex,
Name: c.Name,
}
vs = append(vs, v)
}
if !hasTextColor {
vs = append(vs, influxdb.ViewColor{
ID: "base",
Type: "text",
Hex: "#00C9FF",
Name: "laser",
Value: 0,
})
}
if !hasThresholdColor {
vs = append(vs, influxdb.ViewColor{
ID: "t",
Type: "threshold",
Hex: "#4591ED",
Name: "ocean",
Value: 80,
})
}
return vs
}
var influxQLVarPattern = regexp.MustCompile(`'?:(\w+):'?`)
func transpileQuery(q string) (string, error) {
now := time.Now()
t := influxql.NewTranspilerWithConfig(dbrpMapper{}, influxql.Config{
Now: now,
FallbackToDBRP: true,
})
query := q
query = strings.Replace(query, ":interval:", "8675309ns", -1)
query = strings.Replace(query, ":dashboardTime:", "now() - 15m", 1)
query = strings.Replace(query, ":upperDashboardTime:", "now()", 1)
// TODO(desa): replace all variables not using this hack
query = influxQLVarPattern.ReplaceAllString(query, "'$1'")
pkg, err := t.Transpile(context.Background(), query)
if err != nil {
return "", err
}
return ast.Format(pkg), nil
}
func convertQueries(qs []chronograf.DashboardQuery) []influxdb.DashboardQuery {
ds := []influxdb.DashboardQuery{}
for _, q := range qs {
queryText := q.Command
if q.Type == "influxql" {
// if the query is influxql, add it as a comment and attempt to
// compile it to flux
queryText = fmt.Sprintf("// %s", queryText)
tq, err := transpileQuery(q.Command)
if err != nil {
queryText = fmt.Sprintf("// Failed to transpile query: %v\n%s", err, queryText)
} else {
queryText = fmt.Sprintf("// Original Query:\n%s\n\n%s", queryText, tq)
}
}
d := influxdb.DashboardQuery{
Text: queryText,
EditMode: "advanced",
}
ds = append(ds, d)
}
if len(ds) == 0 {
d := influxdb.DashboardQuery{
Text: "// cell had no queries",
EditMode: "advanced",
BuilderConfig: influxdb.BuilderConfig{
// TODO(desa): foo
Buckets: []string{"bucket"},
},
}
ds = append(ds, d)
}
return ds
}
type dbrpMapper struct {
}
func (m dbrpMapper) FindBy(ctx context.Context, cluster string, db string, rp string) (*influxdb.DBRPMapping, error) {
return nil, errors.New("mapping not found")
}
func (m dbrpMapper) Find(ctx context.Context, filter influxdb.DBRPMappingFilter) (*influxdb.DBRPMapping, error) {
return nil, errors.New("mapping not found")
}
func (m dbrpMapper) FindMany(ctx context.Context, filter influxdb.DBRPMappingFilter, opt ...influxdb.FindOptions) ([]*influxdb.DBRPMapping, int, error) {
return nil, 0, errors.New("mapping not found")
}
func (m dbrpMapper) Create(ctx context.Context, dbrpMap *influxdb.DBRPMapping) error {
return errors.New("dbrpMapper does not support creating new mappings")
}
func (m dbrpMapper) Delete(ctx context.Context, cluster string, db string, rp string) error {
return errors.New("dbrpMapper does not support deleteing mappings")
}