mirror of https://github.com/grafana/grafana
schema: Generate Go and Typescript from Thema coremodels (#49193)
* Add go code generator for coremodels * Just generate the entire coremodel for now Maybe we'll need more flexibility as more coremodels are added, but for now this is fine. * Add note on type comment about stability, grodkit * Remove local replace directive for thema * Generate typescript from coremodel * Update pkg/coremodel/dashboard/addenda.go Co-authored-by: Ryan McKinley <ryantxu@gmail.com> * Update cuetsy to new release * Update thema to latest * Fix enum generation for FieldColorModeId * Put main generated object at the end of the file * Tweaks to generated Go output * Retweak back to var * Add generated coremodel test * Remove local replace statement again * Add Make target and call into cuetsy cmd from gen * Rename and comment linsrc for readability * Move key codegen bits into reusable package * Move body of cuetsifier into codegen pkg Also genericize the diffing output into reusable WriteDiffer. * Refactor coremodel generator to use WriteDiffer * Add gen-cue step to CI * Whip all the codegen automation into shape * Add simplistic coremodel canonicality controls * Remove erroneously committed test * Bump thema version * Remove dead code * Improve wording of non-canonicality comment Co-authored-by: Ryan McKinley <ryantxu@gmail.com>pull/49751/head
parent
a641949a05
commit
be06d37a20
@ -0,0 +1,222 @@ |
|||||||
|
// This file is autogenerated. DO NOT EDIT.
|
||||||
|
//
|
||||||
|
// To regenerate, run "make gen-cue" from repository root.
|
||||||
|
//
|
||||||
|
// Derived from the Thema lineage at pkg/coremodel/dashboard
|
||||||
|
|
||||||
|
|
||||||
|
// This model is a WIP and not yet canonical. Consequently, its members are
|
||||||
|
// not exported to exclude it from grafana-schema's public API surface.
|
||||||
|
|
||||||
|
interface AnnotationQuery { |
||||||
|
builtIn: number; |
||||||
|
datasource: {}; |
||||||
|
enable: boolean; |
||||||
|
hide?: boolean; |
||||||
|
iconColor?: string; |
||||||
|
name?: string; |
||||||
|
rawQuery?: string; |
||||||
|
showIn: number; |
||||||
|
target?: {}; |
||||||
|
type: string; |
||||||
|
} |
||||||
|
|
||||||
|
const defaultAnnotationQuery: Partial<AnnotationQuery> = { |
||||||
|
builtIn: 0, |
||||||
|
enable: true, |
||||||
|
hide: false, |
||||||
|
showIn: 0, |
||||||
|
type: 'dashboard', |
||||||
|
}; |
||||||
|
|
||||||
|
interface VariableModel { |
||||||
|
label?: string; |
||||||
|
name: string; |
||||||
|
type: VariableType; |
||||||
|
} |
||||||
|
|
||||||
|
interface DashboardLink { |
||||||
|
asDropdown: boolean; |
||||||
|
icon?: string; |
||||||
|
includeVars: boolean; |
||||||
|
keepTime: boolean; |
||||||
|
tags: string[]; |
||||||
|
targetBlank: boolean; |
||||||
|
title: string; |
||||||
|
tooltip?: string; |
||||||
|
type: DashboardLinkType; |
||||||
|
url?: string; |
||||||
|
} |
||||||
|
|
||||||
|
const defaultDashboardLink: Partial<DashboardLink> = { |
||||||
|
asDropdown: false, |
||||||
|
includeVars: false, |
||||||
|
keepTime: false, |
||||||
|
tags: [], |
||||||
|
targetBlank: false, |
||||||
|
}; |
||||||
|
|
||||||
|
type DashboardLinkType = 'link' | 'dashboards'; |
||||||
|
|
||||||
|
type VariableType = 'query' | 'adhoc' | 'constant' | 'datasource' | 'interval' | 'textbox' | 'custom' | 'system'; |
||||||
|
|
||||||
|
enum FieldColorModeId { |
||||||
|
ContinuousGrYlRd = 'continuous-GrYlRd', |
||||||
|
Fixed = 'fixed', |
||||||
|
PaletteClassic = 'palette-classic', |
||||||
|
PaletteSaturated = 'palette-saturated', |
||||||
|
Thresholds = 'thresholds', |
||||||
|
} |
||||||
|
|
||||||
|
type FieldColorSeriesByMode = 'min' | 'max' | 'last'; |
||||||
|
|
||||||
|
interface FieldColor { |
||||||
|
fixedColor?: string; |
||||||
|
mode: FieldColorModeId | string; |
||||||
|
seriesBy?: FieldColorSeriesByMode; |
||||||
|
} |
||||||
|
|
||||||
|
interface Threshold { |
||||||
|
color: string; |
||||||
|
state?: string; |
||||||
|
value?: number; |
||||||
|
} |
||||||
|
|
||||||
|
enum ThresholdsMode { |
||||||
|
Absolute = 'absolute', |
||||||
|
Percentage = 'percentage', |
||||||
|
} |
||||||
|
|
||||||
|
interface ThresholdsConfig { |
||||||
|
mode: ThresholdsMode; |
||||||
|
steps: Threshold[]; |
||||||
|
} |
||||||
|
|
||||||
|
const defaultThresholdsConfig: Partial<ThresholdsConfig> = { |
||||||
|
steps: [], |
||||||
|
}; |
||||||
|
|
||||||
|
interface Transformation { |
||||||
|
id: string; |
||||||
|
options: {}; |
||||||
|
} |
||||||
|
|
||||||
|
enum DashboardCursorSync { |
||||||
|
Crosshair = 1, |
||||||
|
Off = 0, |
||||||
|
Tooltip = 2, |
||||||
|
} |
||||||
|
|
||||||
|
const defaultDashboardCursorSync: DashboardCursorSync = DashboardCursorSync.Off; |
||||||
|
|
||||||
|
interface Panel { |
||||||
|
datasource?: {}; |
||||||
|
description?: string; |
||||||
|
fieldConfig: { |
||||||
|
defaults: {}; |
||||||
|
overrides: { |
||||||
|
matcher: { |
||||||
|
id: string; |
||||||
|
}; |
||||||
|
properties: { |
||||||
|
id: string; |
||||||
|
}[]; |
||||||
|
}[]; |
||||||
|
}; |
||||||
|
gridPos?: { |
||||||
|
h: number; |
||||||
|
w: number; |
||||||
|
x: number; |
||||||
|
y: number; |
||||||
|
}; |
||||||
|
id?: number; |
||||||
|
interval?: string; |
||||||
|
links?: DashboardLink[]; |
||||||
|
maxDataPoints?: number; |
||||||
|
options: {}; |
||||||
|
pluginVersion?: string; |
||||||
|
repeat?: string; |
||||||
|
repeatDirection: 'h' | 'v'; |
||||||
|
tags?: string[]; |
||||||
|
targets?: {}[]; |
||||||
|
thresholds?: any[]; |
||||||
|
timeFrom?: string; |
||||||
|
timeRegions?: any[]; |
||||||
|
timeShift?: string; |
||||||
|
title?: string; |
||||||
|
transformations: Transformation[]; |
||||||
|
transparent: boolean; |
||||||
|
type: string; |
||||||
|
} |
||||||
|
|
||||||
|
const defaultPanel: Partial<Panel> = { |
||||||
|
links: [], |
||||||
|
repeatDirection: 'h', |
||||||
|
tags: [], |
||||||
|
targets: [], |
||||||
|
thresholds: [], |
||||||
|
timeRegions: [], |
||||||
|
transformations: [], |
||||||
|
transparent: false, |
||||||
|
}; |
||||||
|
|
||||||
|
interface Dashboard { |
||||||
|
annotations?: { |
||||||
|
list: AnnotationQuery[]; |
||||||
|
}; |
||||||
|
description?: string; |
||||||
|
editable: boolean; |
||||||
|
fiscalYearStartMonth?: number; |
||||||
|
gnetId?: string; |
||||||
|
graphTooltip: DashboardCursorSync; |
||||||
|
id?: number; |
||||||
|
links?: DashboardLink[]; |
||||||
|
liveNow?: boolean; |
||||||
|
panels?: Panel | { |
||||||
|
type: 'graph'; |
||||||
|
} | { |
||||||
|
type: 'heatmap'; |
||||||
|
} | { |
||||||
|
type: 'row'; |
||||||
|
collapsed: boolean; |
||||||
|
id: number; |
||||||
|
panels: Panel | { |
||||||
|
type: 'graph'; |
||||||
|
} | { |
||||||
|
type: 'heatmap'; |
||||||
|
}[]; |
||||||
|
}[]; |
||||||
|
refresh?: string | false; |
||||||
|
schemaVersion: number; |
||||||
|
style: 'light' | 'dark'; |
||||||
|
tags?: string[]; |
||||||
|
templating?: { |
||||||
|
list: VariableModel[]; |
||||||
|
}; |
||||||
|
time?: { |
||||||
|
from: string; |
||||||
|
to: string; |
||||||
|
}; |
||||||
|
timepicker?: { |
||||||
|
collapse: boolean; |
||||||
|
enable: boolean; |
||||||
|
hidden: boolean; |
||||||
|
refresh_intervals: string[]; |
||||||
|
}; |
||||||
|
timezone?: 'browser' | 'utc' | ''; |
||||||
|
title?: string; |
||||||
|
uid?: string; |
||||||
|
version?: number; |
||||||
|
weekStart?: string; |
||||||
|
} |
||||||
|
|
||||||
|
const defaultDashboard: Partial<Dashboard> = { |
||||||
|
editable: true, |
||||||
|
graphTooltip: DashboardCursorSync.Off, |
||||||
|
links: [], |
||||||
|
panels: [], |
||||||
|
schemaVersion: 36, |
||||||
|
style: 'dark', |
||||||
|
tags: [], |
||||||
|
timezone: 'browser', |
||||||
|
}; |
@ -1,89 +0,0 @@ |
|||||||
package schema |
|
||||||
|
|
||||||
AxisPlacement: "auto" | "top" | "right" | "bottom" | "left" | "hidden" @cuetsy(kind="enum") |
|
||||||
VisibilityMode: "auto" | "never" | "always" @cuetsy(kind="enum") |
|
||||||
GraphDrawStyle: "line" | "bars" | "points" @cuetsy(kind="enum") |
|
||||||
LineInterpolation: "linear" | "smooth" | "stepBefore" | "stepAfter" @cuetsy(kind="enum") |
|
||||||
ScaleDistribution: "linear" | "log" | "ordinal" @cuetsy(kind="enum") |
|
||||||
GraphGradientMode: "none" | "opacity" | "hue" | "scheme" @cuetsy(kind="enum") |
|
||||||
StackingMode: "none" | "normal" | "percent" @cuetsy(kind="enum") |
|
||||||
GraphTransform: "constant" | "negative-Y" @cuetsy(kind="enum") |
|
||||||
BarAlignment: -1 | 0 | 1 @cuetsy(kind="enum",memberNames="Before|Center|After") |
|
||||||
ScaleOrientation: 0 | 1 @cuetsy(kind="enum",memberNames="Horizontal|Vertical") |
|
||||||
ScaleDirection: 1 | 1 | -1 | -1 @cuetsy(kind="enum",memberNames="Up|Right|Down|Left") |
|
||||||
LineStyle: { |
|
||||||
fill?: "solid" | "dash" | "dot" | "square" |
|
||||||
dash?: [...number] |
|
||||||
} @cuetsy(kind="interface") |
|
||||||
LineConfig: { |
|
||||||
lineColor?: string |
|
||||||
lineWidth?: number |
|
||||||
lineInterpolation?: LineInterpolation |
|
||||||
lineStyle?: LineStyle |
|
||||||
|
|
||||||
// Indicate if null values should be treated as gaps or connected. |
|
||||||
// When the value is a number, it represents the maximum delta in the |
|
||||||
// X axis that should be considered connected. For timeseries, this is milliseconds |
|
||||||
spanNulls?: bool | number |
|
||||||
} @cuetsy(kind="interface") |
|
||||||
BarConfig: { |
|
||||||
barAlignment?: BarAlignment |
|
||||||
barWidthFactor?: number |
|
||||||
barMaxWidth?: number |
|
||||||
} @cuetsy(kind="interface") |
|
||||||
FillConfig: { |
|
||||||
fillColor?: string |
|
||||||
fillOpacity?: number |
|
||||||
fillBelowTo?: string |
|
||||||
} @cuetsy(kind="interface") |
|
||||||
PointsConfig: { |
|
||||||
showPoints?: VisibilityMode |
|
||||||
pointSize?: number |
|
||||||
pointColor?: string |
|
||||||
pointSymbol?: string |
|
||||||
} @cuetsy(kind="interface") |
|
||||||
ScaleDistributionConfig: { |
|
||||||
type: ScaleDistribution |
|
||||||
log?: number |
|
||||||
} @cuetsy(kind="interface") |
|
||||||
AxisConfig: { |
|
||||||
axisPlacement?: AxisPlacement |
|
||||||
axisLabel?: string |
|
||||||
axisWidth?: number |
|
||||||
axisSoftMin?: number |
|
||||||
axisSoftMax?: number |
|
||||||
axisGridShow?: bool |
|
||||||
scaleDistribution?: ScaleDistributionConfig |
|
||||||
} @cuetsy(kind="interface") |
|
||||||
HideSeriesConfig: { |
|
||||||
tooltip: bool |
|
||||||
legend: bool |
|
||||||
viz: bool |
|
||||||
} @cuetsy(kind="interface") |
|
||||||
StackingConfig: { |
|
||||||
mode?: StackingMode |
|
||||||
group?: string |
|
||||||
} @cuetsy(kind="interface") |
|
||||||
StackableFieldConfig: { |
|
||||||
stacking?: StackingConfig |
|
||||||
} @cuetsy(kind="interface") |
|
||||||
HideableFieldConfig: { |
|
||||||
hideFrom?: HideSeriesConfig |
|
||||||
} @cuetsy(kind="interface") |
|
||||||
GraphTresholdsStyleMode: "off" | "line" | "area" | "line+area" | "series" @cuetsy(kind="enum",memberNames="Off|Line|Area|LineAndArea|Series") |
|
||||||
GraphThresholdsStyleConfig: { |
|
||||||
mode: GraphTresholdsStyleMode |
|
||||||
} @cuetsy(kind="interface") |
|
||||||
GraphFieldConfig: { |
|
||||||
LineConfig |
|
||||||
FillConfig |
|
||||||
PointsConfig |
|
||||||
AxisConfig |
|
||||||
BarConfig |
|
||||||
StackableFieldConfig |
|
||||||
HideableFieldConfig |
|
||||||
drawStyle?: GraphDrawStyle |
|
||||||
gradientMode?: GraphGradientMode |
|
||||||
thresholdsStyle?: GraphThresholdsStyleConfig |
|
||||||
transform?: GraphTransform |
|
||||||
} @cuetsy(kind="interface") |
|
@ -1,15 +0,0 @@ |
|||||||
package schema |
|
||||||
|
|
||||||
LegendPlacement: "bottom" | "right" @cuetsy(kind="type") |
|
||||||
|
|
||||||
LegendDisplayMode: "list" | "table" | "hidden" @cuetsy(kind="enum") |
|
||||||
|
|
||||||
VizLegendOptions: { |
|
||||||
displayMode: LegendDisplayMode |
|
||||||
placement: LegendPlacement |
|
||||||
asTable?: bool |
|
||||||
isVisible?: bool |
|
||||||
sortBy?: string |
|
||||||
sortDesc?: bool |
|
||||||
calcs: [...string] |
|
||||||
} @cuetsy(kind="interface") |
|
@ -1,15 +0,0 @@ |
|||||||
package schema |
|
||||||
|
|
||||||
// TODO -- should not be table specific! |
|
||||||
FieldTextAlignment: "auto" | "left" | "right" | "center" @cuetsy(kind="type") |
|
||||||
|
|
||||||
TableCellDisplayMode: "auto" | "color-text" | "color-background" | "color-background-solid" | "gradient-gauge" | "lcd-gauge" | "json-view" | "basic" | "image" @cuetsy(kind="enum",memberNames="Auto|ColorText|ColorBackground|ColorBackgroundSolid|GradientGauge|LcdGauge|JSONView|BasicGauge|Image") |
|
||||||
|
|
||||||
TableFieldOptions: { |
|
||||||
width?: number |
|
||||||
minWidth?: number |
|
||||||
align: FieldTextAlignment | *"auto" |
|
||||||
displayMode: TableCellDisplayMode | *"auto" |
|
||||||
hidden?: bool // ?? default is missing or false ?? |
|
||||||
filterable?: bool |
|
||||||
} @cuetsy(kind="interface") |
|
@ -1,8 +0,0 @@ |
|||||||
package schema |
|
||||||
|
|
||||||
VizTextDisplayOptions: { |
|
||||||
// Explicit title text size |
|
||||||
titleSize?: number |
|
||||||
// Explicit value text size |
|
||||||
valueSize?: number |
|
||||||
} @cuetsy(kind="interface") |
|
@ -1,9 +0,0 @@ |
|||||||
package schema |
|
||||||
|
|
||||||
TooltipDisplayMode: "single" | "multi" | "none" @cuetsy(kind="enum") |
|
||||||
SortOrder: "asc" | "desc" | "none" @cuetsy(kind="enum") |
|
||||||
|
|
||||||
VizTooltipOptions: { |
|
||||||
mode: TooltipDisplayMode |
|
||||||
sort: SortOrder |
|
||||||
} @cuetsy(kind="interface") |
|
@ -0,0 +1,399 @@ |
|||||||
|
package codegen |
||||||
|
|
||||||
|
import ( |
||||||
|
"bytes" |
||||||
|
"errors" |
||||||
|
"fmt" |
||||||
|
"go/ast" |
||||||
|
"go/format" |
||||||
|
"go/parser" |
||||||
|
"go/token" |
||||||
|
"io/ioutil" |
||||||
|
"os" |
||||||
|
"path/filepath" |
||||||
|
"regexp" |
||||||
|
"strings" |
||||||
|
"testing/fstest" |
||||||
|
"text/template" |
||||||
|
|
||||||
|
"cuelang.org/go/pkg/encoding/yaml" |
||||||
|
"github.com/deepmap/oapi-codegen/pkg/codegen" |
||||||
|
"github.com/getkin/kin-openapi/openapi3" |
||||||
|
"github.com/grafana/cuetsy" |
||||||
|
"github.com/grafana/grafana/pkg/cuectx" |
||||||
|
"github.com/grafana/thema" |
||||||
|
"github.com/grafana/thema/encoding/openapi" |
||||||
|
"golang.org/x/tools/imports" |
||||||
|
) |
||||||
|
|
||||||
|
// ExtractedLineage contains the results of statically analyzing a Grafana
|
||||||
|
// directory for a Thema lineage.
|
||||||
|
type ExtractedLineage struct { |
||||||
|
Lineage thema.Lineage |
||||||
|
// Absolute path to the coremodel's lineage.cue file.
|
||||||
|
LineagePath string |
||||||
|
// Path to the coremodel's lineage.cue file relative to repo root.
|
||||||
|
RelativePath string |
||||||
|
// Indicates whether the coremodel is considered canonical or not. Generated
|
||||||
|
// code from not-yet-canonical coremodels should include appropriate caveats in
|
||||||
|
// documentation and possibly be hidden from external public API surface areas.
|
||||||
|
IsCanonical bool |
||||||
|
} |
||||||
|
|
||||||
|
// ExtractLineage loads a Grafana Thema lineage from the filesystem.
|
||||||
|
//
|
||||||
|
// The provided path must be the absolute path to the file containing the
|
||||||
|
// lineage to be loaded.
|
||||||
|
//
|
||||||
|
// This loading approach is intended primarily for use with code generators, or
|
||||||
|
// other use cases external to grafana-server backend. For code within
|
||||||
|
// grafana-server, prefer lineage loaders provided in e.g. pkg/coremodel/*.
|
||||||
|
func ExtractLineage(path string, lib thema.Library) (*ExtractedLineage, error) { |
||||||
|
if !filepath.IsAbs(path) { |
||||||
|
return nil, fmt.Errorf("must provide an absolute path, got %q", path) |
||||||
|
} |
||||||
|
|
||||||
|
ec := &ExtractedLineage{ |
||||||
|
LineagePath: path, |
||||||
|
} |
||||||
|
|
||||||
|
var find func(path string) (string, error) |
||||||
|
find = func(path string) (string, error) { |
||||||
|
parent := filepath.Dir(path) |
||||||
|
if parent == path { |
||||||
|
return "", errors.New("grafana root directory could not be found") |
||||||
|
} |
||||||
|
fp := filepath.Join(path, "go.mod") |
||||||
|
if _, err := os.Stat(fp); err == nil { |
||||||
|
return path, nil |
||||||
|
} |
||||||
|
return find(parent) |
||||||
|
} |
||||||
|
groot, err := find(path) |
||||||
|
if err != nil { |
||||||
|
return ec, err |
||||||
|
} |
||||||
|
|
||||||
|
f, err := os.Open(ec.LineagePath) |
||||||
|
if err != nil { |
||||||
|
return nil, fmt.Errorf("could not open lineage file at %s: %w", path, err) |
||||||
|
} |
||||||
|
|
||||||
|
byt, err := ioutil.ReadAll(f) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
fs := fstest.MapFS{ |
||||||
|
"lineage.cue": &fstest.MapFile{ |
||||||
|
Data: byt, |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
ec.RelativePath, err = filepath.Rel(groot, filepath.Dir(path)) |
||||||
|
if err != nil { |
||||||
|
// should be unreachable, since we rootclimbed to find groot above
|
||||||
|
panic(err) |
||||||
|
} |
||||||
|
ec.Lineage, err = cuectx.LoadGrafanaInstancesWithThema(ec.RelativePath, fs, lib) |
||||||
|
if err != nil { |
||||||
|
return ec, err |
||||||
|
} |
||||||
|
ec.IsCanonical = isCanonical(ec.Lineage.Name()) |
||||||
|
return ec, nil |
||||||
|
} |
||||||
|
|
||||||
|
func isCanonical(name string) bool { |
||||||
|
return canonicalCoremodels[name] |
||||||
|
} |
||||||
|
|
||||||
|
// FIXME specificying coremodel canonicality DOES NOT belong here - it should be part of the coremodel declaration.
|
||||||
|
var canonicalCoremodels = map[string]bool{ |
||||||
|
"dashboard": false, |
||||||
|
} |
||||||
|
|
||||||
|
// GenerateGoCoremodel generates a standard Go coremodel from a Thema lineage.
|
||||||
|
//
|
||||||
|
// The provided path must be a directory. Generated code files will be written
|
||||||
|
// to that path. The final element of the path must match the Lineage.Name().
|
||||||
|
func (ls *ExtractedLineage) GenerateGoCoremodel(path string) (WriteDiffer, error) { |
||||||
|
lin, lib := ls.Lineage, ls.Lineage.Library() |
||||||
|
_, name := filepath.Split(path) |
||||||
|
if name != lin.Name() { |
||||||
|
return nil, fmt.Errorf("lineage name %q must match final element of path, got %q", lin.Name(), path) |
||||||
|
} |
||||||
|
|
||||||
|
sch := thema.SchemaP(lin, thema.LatestVersion(lin)) |
||||||
|
f, err := openapi.GenerateSchema(sch, nil) |
||||||
|
if err != nil { |
||||||
|
return nil, fmt.Errorf("thema openapi generation failed: %w", err) |
||||||
|
} |
||||||
|
|
||||||
|
str, err := yaml.Marshal(lib.Context().BuildFile(f)) |
||||||
|
if err != nil { |
||||||
|
return nil, fmt.Errorf("cue-yaml marshaling failed: %w", err) |
||||||
|
} |
||||||
|
|
||||||
|
loader := openapi3.NewLoader() |
||||||
|
oT, err := loader.LoadFromData([]byte(str)) |
||||||
|
if err != nil { |
||||||
|
return nil, fmt.Errorf("loading generated openapi failed; %w", err) |
||||||
|
} |
||||||
|
|
||||||
|
gostr, err := codegen.Generate(oT, lin.Name(), codegen.Options{ |
||||||
|
GenerateTypes: true, |
||||||
|
SkipPrune: true, |
||||||
|
SkipFmt: true, |
||||||
|
UserTemplates: map[string]string{ |
||||||
|
"imports.tmpl": fmt.Sprintf(tmplImports, ls.RelativePath), |
||||||
|
"typedef.tmpl": tmplTypedef, |
||||||
|
}, |
||||||
|
}) |
||||||
|
if err != nil { |
||||||
|
return nil, fmt.Errorf("openapi generation failed: %w", err) |
||||||
|
} |
||||||
|
|
||||||
|
vars := goPkg{ |
||||||
|
Name: lin.Name(), |
||||||
|
LineagePath: ls.RelativePath, |
||||||
|
LatestSeqv: sch.Version()[0], |
||||||
|
LatestSchv: sch.Version()[1], |
||||||
|
} |
||||||
|
var buuf bytes.Buffer |
||||||
|
err = tmplAddenda.Execute(&buuf, vars) |
||||||
|
if err != nil { |
||||||
|
panic(err) |
||||||
|
} |
||||||
|
|
||||||
|
fset := token.NewFileSet() |
||||||
|
gf, err := parser.ParseFile(fset, "coremodel_gen.go", gostr+buuf.String(), parser.ParseComments) |
||||||
|
if err != nil { |
||||||
|
return nil, fmt.Errorf("generated go file parsing failed: %w", err) |
||||||
|
} |
||||||
|
m := makeReplacer(lin.Name()) |
||||||
|
ast.Walk(m, gf) |
||||||
|
|
||||||
|
var buf bytes.Buffer |
||||||
|
err = format.Node(&buf, fset, gf) |
||||||
|
if err != nil { |
||||||
|
return nil, fmt.Errorf("ast printing failed: %w", err) |
||||||
|
} |
||||||
|
|
||||||
|
byt, err := imports.Process("coremodel_gen.go", buf.Bytes(), nil) |
||||||
|
if err != nil { |
||||||
|
return nil, fmt.Errorf("goimports processing failed: %w", err) |
||||||
|
} |
||||||
|
|
||||||
|
// Generate the assignability test. TODO do this in a framework test instead
|
||||||
|
var buf3 bytes.Buffer |
||||||
|
err = tmplAssignableTest.Execute(&buf3, vars) |
||||||
|
if err != nil { |
||||||
|
return nil, fmt.Errorf("failed generating assignability test file: %w", err) |
||||||
|
} |
||||||
|
|
||||||
|
wd := NewWriteDiffer() |
||||||
|
wd[filepath.Join(path, "coremodel_gen.go")] = byt |
||||||
|
wd[filepath.Join(path, "coremodel_gen_test.go")] = buf3.Bytes() |
||||||
|
|
||||||
|
return wd, nil |
||||||
|
} |
||||||
|
|
||||||
|
type goPkg struct { |
||||||
|
Name string |
||||||
|
LineagePath string |
||||||
|
LatestSeqv, LatestSchv uint |
||||||
|
IsComposed bool |
||||||
|
} |
||||||
|
|
||||||
|
func (ls *ExtractedLineage) GenerateTypescriptCoremodel(path string) (WriteDiffer, error) { |
||||||
|
_, name := filepath.Split(path) |
||||||
|
if name != ls.Lineage.Name() { |
||||||
|
return nil, fmt.Errorf("lineage name %q must match final element of path, got %q", ls.Lineage.Name(), path) |
||||||
|
} |
||||||
|
|
||||||
|
schv := thema.SchemaP(ls.Lineage, thema.LatestVersion(ls.Lineage)).UnwrapCUE() |
||||||
|
|
||||||
|
parts, err := cuetsy.GenerateAST(schv, cuetsy.Config{}) |
||||||
|
if err != nil { |
||||||
|
return nil, fmt.Errorf("cuetsy parts gen failed: %w", err) |
||||||
|
} |
||||||
|
|
||||||
|
top, err := cuetsy.GenerateSingleAST(string(makeReplacer(ls.Lineage.Name())), schv, cuetsy.TypeInterface) |
||||||
|
if err != nil { |
||||||
|
return nil, fmt.Errorf("cuetsy top gen failed: %w", err) |
||||||
|
} |
||||||
|
|
||||||
|
// TODO until cuetsy can toposort its outputs, put the top/parent type at the bottom of the file.
|
||||||
|
parts.Nodes = append(parts.Nodes, top.T, top.D) |
||||||
|
// parts.Nodes = append([]ts.Decl{top.T, top.D}, parts.Nodes...)
|
||||||
|
|
||||||
|
var strb strings.Builder |
||||||
|
var str string |
||||||
|
fpath := ls.Lineage.Name() + ".gen.ts" |
||||||
|
strb.WriteString(fmt.Sprintf(genHeader, ls.RelativePath)) |
||||||
|
|
||||||
|
if !ls.IsCanonical { |
||||||
|
fpath = fmt.Sprintf("%s_experimental.gen.ts", ls.Lineage.Name()) |
||||||
|
strb.WriteString(` |
||||||
|
// This model is a WIP and not yet canonical. Consequently, its members are
|
||||||
|
// not exported to exclude it from grafana-schema's public API surface.
|
||||||
|
|
||||||
|
`) |
||||||
|
strb.WriteString(fmt.Sprint(parts)) |
||||||
|
// TODO replace this regexp with cuetsy config for whether members are exported
|
||||||
|
re := regexp.MustCompile(`(?m)^export `) |
||||||
|
str = re.ReplaceAllLiteralString(strb.String(), "") |
||||||
|
} else { |
||||||
|
strb.WriteString(fmt.Sprint(parts)) |
||||||
|
str = strb.String() |
||||||
|
} |
||||||
|
|
||||||
|
wd := NewWriteDiffer() |
||||||
|
wd[filepath.Join(path, fpath)] = []byte(str) |
||||||
|
return wd, nil |
||||||
|
} |
||||||
|
|
||||||
|
type modelReplacer string |
||||||
|
|
||||||
|
func makeReplacer(name string) modelReplacer { |
||||||
|
return modelReplacer(fmt.Sprintf("%s%s", string(strings.ToUpper(name)[0]), name[1:])) |
||||||
|
} |
||||||
|
|
||||||
|
func (m modelReplacer) Visit(n ast.Node) ast.Visitor { |
||||||
|
switch x := n.(type) { |
||||||
|
case *ast.Ident: |
||||||
|
x.Name = m.replacePrefix(x.Name) |
||||||
|
} |
||||||
|
return m |
||||||
|
} |
||||||
|
|
||||||
|
func (m modelReplacer) replacePrefix(str string) string { |
||||||
|
if len(str) >= len(m) && str[:len(m)] == string(m) { |
||||||
|
return strings.Replace(str, string(m), "Model", 1) |
||||||
|
} |
||||||
|
return str |
||||||
|
} |
||||||
|
|
||||||
|
var genHeader = `// This file is autogenerated. DO NOT EDIT.
|
||||||
|
//
|
||||||
|
// To regenerate, run "make gen-cue" from repository root.
|
||||||
|
//
|
||||||
|
// Derived from the Thema lineage at %s
|
||||||
|
|
||||||
|
` |
||||||
|
|
||||||
|
var tmplImports = genHeader + `package {{ .PackageName }} |
||||||
|
|
||||||
|
import ( |
||||||
|
"embed" |
||||||
|
"bytes" |
||||||
|
"compress/gzip" |
||||||
|
"context" |
||||||
|
"encoding/base64" |
||||||
|
"encoding/json" |
||||||
|
"encoding/xml" |
||||||
|
"errors" |
||||||
|
"fmt" |
||||||
|
"io" |
||||||
|
"io/ioutil" |
||||||
|
"net/http" |
||||||
|
"net/url" |
||||||
|
"path" |
||||||
|
"path/filepath" |
||||||
|
"strings" |
||||||
|
"time" |
||||||
|
|
||||||
|
"github.com/deepmap/oapi-codegen/pkg/runtime" |
||||||
|
openapi_types "github.com/deepmap/oapi-codegen/pkg/types" |
||||||
|
"github.com/getkin/kin-openapi/openapi3" |
||||||
|
"github.com/grafana/thema" |
||||||
|
"github.com/grafana/grafana/pkg/cuectx" |
||||||
|
) |
||||||
|
` |
||||||
|
|
||||||
|
var tmplAddenda = template.Must(template.New("addenda").Parse(` |
||||||
|
//go:embed lineage.cue
|
||||||
|
var cueFS embed.FS |
||||||
|
|
||||||
|
// codegen ensures that this is always the latest Thema schema version
|
||||||
|
var currentVersion = thema.SV({{ .LatestSeqv }}, {{ .LatestSchv }}) |
||||||
|
|
||||||
|
// Lineage returns the Thema lineage representing a Grafana {{ .Name }}.
|
||||||
|
//
|
||||||
|
// The lineage is the canonical specification of the current {{ .Name }} schema,
|
||||||
|
// all prior schema versions, and the mappings that allow migration between
|
||||||
|
// schema versions.
|
||||||
|
{{- if .IsComposed }}//
|
||||||
|
// This is the base variant of the schema. It does not include any composed
|
||||||
|
// plugin schemas.{{ end }}
|
||||||
|
func Lineage(lib thema.Library, opts ...thema.BindOption) (thema.Lineage, error) { |
||||||
|
return cuectx.LoadGrafanaInstancesWithThema(filepath.Join("pkg", "coremodel", "dashboard"), cueFS, lib, opts...) |
||||||
|
} |
||||||
|
|
||||||
|
var _ thema.LineageFactory = Lineage |
||||||
|
|
||||||
|
// Coremodel contains the foundational schema declaration for {{ .Name }}s.
|
||||||
|
type Coremodel struct { |
||||||
|
lin thema.Lineage |
||||||
|
} |
||||||
|
|
||||||
|
// Lineage returns the canonical dashboard Lineage.
|
||||||
|
func (c *Coremodel) Lineage() thema.Lineage { |
||||||
|
return c.lin |
||||||
|
} |
||||||
|
|
||||||
|
// CurrentSchema returns the current (latest) {{ .Name }} Thema schema.
|
||||||
|
func (c *Coremodel) CurrentSchema() thema.Schema { |
||||||
|
return thema.SchemaP(c.lin, currentVersion) |
||||||
|
} |
||||||
|
|
||||||
|
// GoType returns a pointer to an empty Go struct that corresponds to
|
||||||
|
// the current Thema schema.
|
||||||
|
func (c *Coremodel) GoType() interface{} { |
||||||
|
return &Model{} |
||||||
|
} |
||||||
|
|
||||||
|
func ProvideCoremodel(lib thema.Library) (*Coremodel, error) { |
||||||
|
lin, err := Lineage(lib) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
return &Coremodel{ |
||||||
|
lin: lin, |
||||||
|
}, nil |
||||||
|
} |
||||||
|
`)) |
||||||
|
|
||||||
|
var tmplAssignableTest = template.Must(template.New("addenda").Parse(fmt.Sprintf(genHeader, "{{ .LineagePath }}") + `package {{ .Name }} |
||||||
|
|
||||||
|
import ( |
||||||
|
"testing" |
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/cuectx" |
||||||
|
"github.com/grafana/thema" |
||||||
|
) |
||||||
|
|
||||||
|
func TestSchemaAssignability(t *testing.T) { |
||||||
|
lin, err := Lineage(cuectx.ProvideThemaLibrary()) |
||||||
|
if err != nil { |
||||||
|
t.Fatal(err) |
||||||
|
} |
||||||
|
|
||||||
|
sch := thema.SchemaP(lin, currentVersion) |
||||||
|
|
||||||
|
err = thema.AssignableTo(sch, &Model{}) |
||||||
|
if err != nil { |
||||||
|
t.Fatal(err) |
||||||
|
} |
||||||
|
} |
||||||
|
`)) |
||||||
|
|
||||||
|
var tmplTypedef = `{{range .Types}} |
||||||
|
{{ with .Schema.Description }}{{ . }}{{ else }}// {{.TypeName}} defines model for {{.JsonName}}.{{ end }}
|
||||||
|
//
|
||||||
|
// THIS TYPE IS INTENDED FOR INTERNAL USE BY THE GRAFANA BACKEND, AND IS SUBJECT TO BREAKING CHANGES.
|
||||||
|
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
|
||||||
|
type {{.TypeName}} {{if and (opts.AliasTypes) (.CanAlias)}}={{end}} {{.Schema.TypeDecl}} |
||||||
|
{{end}} |
||||||
|
` |
@ -0,0 +1,134 @@ |
|||||||
|
package codegen |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
"errors" |
||||||
|
"fmt" |
||||||
|
"io" |
||||||
|
"os" |
||||||
|
"path/filepath" |
||||||
|
"sort" |
||||||
|
|
||||||
|
"github.com/google/go-cmp/cmp" |
||||||
|
"github.com/hashicorp/go-multierror" |
||||||
|
"golang.org/x/sync/errgroup" |
||||||
|
) |
||||||
|
|
||||||
|
// WriteDiffer is a pseudo-filesystem that supports batch-writing its contents
|
||||||
|
// to the real filesystem, or batch-comparing its contents to the real
|
||||||
|
// filesystem. Its intended use is for idiomatic `go generate`-style code
|
||||||
|
// generators, where it is expected that the results of codegen are committed to
|
||||||
|
// version control.
|
||||||
|
//
|
||||||
|
// In such cases, the normal behavior of a generator is to write files to disk,
|
||||||
|
// but in CI, that behavior should change to verify that what is already on disk
|
||||||
|
// is identical to the results of code generation. This allows CI to ensure that
|
||||||
|
// the results of code generation are always up to date. WriteDiffer supports
|
||||||
|
// these related behaviors through its Write() and Verify() methods, respectively.
|
||||||
|
//
|
||||||
|
// Note that the statelessness of WriteDiffer means that, if a particular input
|
||||||
|
// to the code generator goes away, it will not notice generated files left
|
||||||
|
// behind if their inputs are removed.
|
||||||
|
// TODO introduce a search/match system
|
||||||
|
type WriteDiffer map[string][]byte |
||||||
|
|
||||||
|
func NewWriteDiffer() WriteDiffer { |
||||||
|
return WriteDiffer(make(map[string][]byte)) |
||||||
|
} |
||||||
|
|
||||||
|
type writeSlice []struct { |
||||||
|
path string |
||||||
|
contents []byte |
||||||
|
} |
||||||
|
|
||||||
|
// Verify checks the contents of each file against the filesystem. It emits an error
|
||||||
|
// if any of its contained files differ.
|
||||||
|
func (wd WriteDiffer) Verify() error { |
||||||
|
var result error |
||||||
|
|
||||||
|
for _, item := range wd.toSlice() { |
||||||
|
if _, err := os.Stat(item.path); err != nil { |
||||||
|
if errors.Is(err, os.ErrNotExist) { |
||||||
|
result = multierror.Append(result, fmt.Errorf("%s: generated file should exist, but does not", item.path)) |
||||||
|
} else { |
||||||
|
result = multierror.Append(result, fmt.Errorf("%s: could not stat generated file: %w", item.path, err)) |
||||||
|
} |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
f, err := os.Open(filepath.Clean(item.path)) |
||||||
|
if err != nil { |
||||||
|
result = multierror.Append(result, fmt.Errorf("%s: %w", item.path, err)) |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
ob, err := io.ReadAll(f) |
||||||
|
if err != nil { |
||||||
|
result = multierror.Append(result, fmt.Errorf("%s: %w", item.path, err)) |
||||||
|
continue |
||||||
|
} |
||||||
|
dstr := cmp.Diff(string(ob), string(item.contents)) |
||||||
|
if dstr != "" { |
||||||
|
result = multierror.Append(result, fmt.Errorf("%s would have changed:\n\n%s", item.path, dstr)) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return result |
||||||
|
} |
||||||
|
|
||||||
|
// Write writes all of the files to their indicated paths.
|
||||||
|
func (wd WriteDiffer) Write() error { |
||||||
|
g, _ := errgroup.WithContext(context.TODO()) |
||||||
|
g.SetLimit(12) |
||||||
|
|
||||||
|
for _, item := range wd.toSlice() { |
||||||
|
it := item |
||||||
|
g.Go(func() error { |
||||||
|
err := os.MkdirAll(filepath.Dir(it.path), os.ModePerm) |
||||||
|
if err != nil { |
||||||
|
return fmt.Errorf("%s: failed to ensure parent directory exists: %w", it.path, err) |
||||||
|
} |
||||||
|
|
||||||
|
if err := os.WriteFile(it.path, it.contents, 0644); err != nil { |
||||||
|
return fmt.Errorf("%s: error while writing file: %w", it.path, err) |
||||||
|
} |
||||||
|
return nil |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
return g.Wait() |
||||||
|
} |
||||||
|
|
||||||
|
func (wd WriteDiffer) toSlice() writeSlice { |
||||||
|
sl := make(writeSlice, 0, len(wd)) |
||||||
|
type ws struct { |
||||||
|
path string |
||||||
|
contents []byte |
||||||
|
} |
||||||
|
|
||||||
|
for k, v := range wd { |
||||||
|
sl = append(sl, ws{ |
||||||
|
path: k, |
||||||
|
contents: v, |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
sort.Slice(sl, func(i, j int) bool { |
||||||
|
return sl[i].path < sl[j].path |
||||||
|
}) |
||||||
|
|
||||||
|
return sl |
||||||
|
} |
||||||
|
|
||||||
|
// Merge combines all the entries from the provided WriteDiffer into the callee
|
||||||
|
// WriteDiffer. Duplicate paths result in an error.
|
||||||
|
func (wd WriteDiffer) Merge(wd2 WriteDiffer) error { |
||||||
|
for k, v := range wd2 { |
||||||
|
if _, has := wd[k]; has { |
||||||
|
return fmt.Errorf("path %s already exists in write differ", k) |
||||||
|
} |
||||||
|
wd[k] = v |
||||||
|
} |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
@ -0,0 +1,343 @@ |
|||||||
|
package codegen |
||||||
|
|
||||||
|
import ( |
||||||
|
"bytes" |
||||||
|
gerrors "errors" |
||||||
|
"fmt" |
||||||
|
"io" |
||||||
|
"io/fs" |
||||||
|
"os" |
||||||
|
"path/filepath" |
||||||
|
"strings" |
||||||
|
"testing/fstest" |
||||||
|
"text/template" |
||||||
|
|
||||||
|
"cuelang.org/go/cue" |
||||||
|
"cuelang.org/go/cue/ast" |
||||||
|
"cuelang.org/go/cue/errors" |
||||||
|
cload "cuelang.org/go/cue/load" |
||||||
|
"cuelang.org/go/cue/parser" |
||||||
|
"github.com/grafana/cuetsy" |
||||||
|
"github.com/grafana/grafana/pkg/schema/load" |
||||||
|
) |
||||||
|
|
||||||
|
// The only import statement we currently allow in any models.cue file
|
||||||
|
const allowedImport = "github.com/grafana/grafana/packages/grafana-schema/src/schema" |
||||||
|
|
||||||
|
var importMap = map[string]string{ |
||||||
|
allowedImport: "@grafana/schema", |
||||||
|
} |
||||||
|
|
||||||
|
// Hard-coded list of paths to skip. Remove a particular file as we're ready
|
||||||
|
// to rely on the TypeScript auto-generated by cuetsy for that particular file.
|
||||||
|
var skipPaths = []string{ |
||||||
|
"public/app/plugins/panel/barchart/models.cue", |
||||||
|
"public/app/plugins/panel/canvas/models.cue", |
||||||
|
"public/app/plugins/panel/histogram/models.cue", |
||||||
|
"public/app/plugins/panel/heatmap-new/models.cue", |
||||||
|
"public/app/plugins/panel/candlestick/models.cue", |
||||||
|
"public/app/plugins/panel/state-timeline/models.cue", |
||||||
|
"public/app/plugins/panel/status-history/models.cue", |
||||||
|
"public/app/plugins/panel/table/models.cue", |
||||||
|
"public/app/plugins/panel/timeseries/models.cue", |
||||||
|
} |
||||||
|
|
||||||
|
const prefix = "/" |
||||||
|
|
||||||
|
var paths = load.GetDefaultLoadPaths() |
||||||
|
|
||||||
|
// CuetsifyPlugins runs cuetsy against plugins' models.cue files.
|
||||||
|
func CuetsifyPlugins(ctx *cue.Context, root string) (WriteDiffer, error) { |
||||||
|
// TODO this whole func has a lot of old, crufty behavior from the scuemata era; needs TLC
|
||||||
|
var fspaths load.BaseLoadPaths |
||||||
|
var err error |
||||||
|
|
||||||
|
fspaths.BaseCueFS, err = populateMapFSFromRoot(paths.BaseCueFS, root, "") |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
fspaths.DistPluginCueFS, err = populateMapFSFromRoot(paths.DistPluginCueFS, root, "") |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
overlay, err := defaultOverlay(fspaths) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
// Prep the cue load config
|
||||||
|
clcfg := &cload.Config{ |
||||||
|
Overlay: overlay, |
||||||
|
// FIXME these module paths won't work for things not under our cue.mod - AKA third-party plugins
|
||||||
|
ModuleRoot: prefix, |
||||||
|
Module: "github.com/grafana/grafana", |
||||||
|
} |
||||||
|
|
||||||
|
// FIXME hardcoding paths to exclude is not the way to handle this
|
||||||
|
excl := map[string]bool{ |
||||||
|
"cue.mod": true, |
||||||
|
"cue/scuemata": true, |
||||||
|
"packages/grafana-schema/src/scuemata/dashboard": true, |
||||||
|
"packages/grafana-schema/src/scuemata/dashboard/dist": true, |
||||||
|
} |
||||||
|
|
||||||
|
exclude := func(path string) bool { |
||||||
|
dir := filepath.Dir(path) |
||||||
|
if excl[dir] { |
||||||
|
return true |
||||||
|
} |
||||||
|
for _, p := range skipPaths { |
||||||
|
if path == p { |
||||||
|
return true |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
outfiles := NewWriteDiffer() |
||||||
|
|
||||||
|
cuetsify := func(in fs.FS) error { |
||||||
|
seen := make(map[string]bool) |
||||||
|
return fs.WalkDir(in, ".", func(path string, d fs.DirEntry, err error) error { |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
dir := filepath.Dir(path) |
||||||
|
|
||||||
|
if d.IsDir() || filepath.Ext(d.Name()) != ".cue" || seen[dir] || exclude(path) { |
||||||
|
return nil |
||||||
|
} |
||||||
|
seen[dir] = true |
||||||
|
clcfg.Dir = filepath.Join(root, dir) |
||||||
|
// FIXME Horrible hack to figure out the identifier used for
|
||||||
|
// imported packages - intercept the parser called by the loader to
|
||||||
|
// look at the ast.Files on their way in to building.
|
||||||
|
// Much better if we could work backwards from the cue.Value,
|
||||||
|
// maybe even directly in cuetsy itself, and figure out when a
|
||||||
|
// referenced object is "out of bounds".
|
||||||
|
// var imports sync.Map
|
||||||
|
var imports []*ast.ImportSpec |
||||||
|
clcfg.ParseFile = func(name string, src interface{}) (*ast.File, error) { |
||||||
|
f, err := parser.ParseFile(name, src, parser.ParseComments) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
imports = append(imports, f.Imports...) |
||||||
|
return f, nil |
||||||
|
} |
||||||
|
if strings.Contains(path, "public/app/plugins") { |
||||||
|
clcfg.Package = "grafanaschema" |
||||||
|
} else { |
||||||
|
clcfg.Package = "" |
||||||
|
} |
||||||
|
|
||||||
|
// FIXME loading in this way causes all files in a dir to be loaded
|
||||||
|
// as a single cue.Instance or cue.Value, which makes it quite
|
||||||
|
// difficult to map them _back_ onto the original file and generate
|
||||||
|
// discrete .gen.ts files for each .cue input. However, going one
|
||||||
|
// .cue file at a time and passing it as the first arg to
|
||||||
|
// load.Instances() means that the other files are ignored
|
||||||
|
// completely, causing references between these files to be
|
||||||
|
// unresolved, and thus encounter a different kind of error.
|
||||||
|
insts := cload.Instances(nil, clcfg) |
||||||
|
if len(insts) > 1 { |
||||||
|
panic("extra instances") |
||||||
|
} |
||||||
|
bi := insts[0] |
||||||
|
|
||||||
|
v := ctx.BuildInstance(bi) |
||||||
|
if v.Err() != nil { |
||||||
|
return v.Err() |
||||||
|
} |
||||||
|
|
||||||
|
var b []byte |
||||||
|
f := &tsFile{} |
||||||
|
seen := make(map[string]bool) |
||||||
|
// FIXME explicitly mapping path patterns to conversion patterns
|
||||||
|
// is exactly what we want to avoid
|
||||||
|
switch { |
||||||
|
// panel plugin models.cue files
|
||||||
|
case strings.Contains(path, "public/app/plugins"): |
||||||
|
for _, im := range imports { |
||||||
|
ip := strings.Trim(im.Path.Value, "\"") |
||||||
|
if ip != allowedImport { |
||||||
|
// TODO make a specific error type for this
|
||||||
|
return errors.Newf(im.Pos(), "import %q not allowed, panel plugins may only import from %q", ip, allowedImport) |
||||||
|
} |
||||||
|
// TODO this approach will silently swallow the unfixable
|
||||||
|
// error case where multiple files in the same dir import
|
||||||
|
// the same package to a different ident
|
||||||
|
if !seen[ip] { |
||||||
|
seen[ip] = true |
||||||
|
f.Imports = append(f.Imports, convertImport(im)) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Extract the latest schema and its version number. (All of this goes away with Thema, whew)
|
||||||
|
f.V = &tsModver{} |
||||||
|
lins := v.LookupPath(cue.ParsePath("Panel.lineages")) |
||||||
|
f.V.Lin, _ = lins.Len().Int64() |
||||||
|
f.V.Lin = f.V.Lin - 1 |
||||||
|
schs := lins.LookupPath(cue.MakePath(cue.Index(int(f.V.Lin)))) |
||||||
|
f.V.Sch, _ = schs.Len().Int64() |
||||||
|
f.V.Sch = f.V.Sch - 1 |
||||||
|
latest := schs.LookupPath(cue.MakePath(cue.Index(int(f.V.Sch)))) |
||||||
|
|
||||||
|
b, err = cuetsy.Generate(latest, cuetsy.Config{}) |
||||||
|
default: |
||||||
|
b, err = cuetsy.Generate(v, cuetsy.Config{}) |
||||||
|
} |
||||||
|
|
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
f.Body = string(b) |
||||||
|
|
||||||
|
var buf bytes.Buffer |
||||||
|
err = tsTemplate.Execute(&buf, f) |
||||||
|
outfiles[filepath.Join(root, strings.Replace(path, ".cue", ".gen.ts", -1))] = buf.Bytes() |
||||||
|
return err |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
err = cuetsify(fspaths.BaseCueFS) |
||||||
|
if err != nil { |
||||||
|
return nil, gerrors.New(errors.Details(err, nil)) |
||||||
|
} |
||||||
|
err = cuetsify(fspaths.DistPluginCueFS) |
||||||
|
if err != nil { |
||||||
|
return nil, gerrors.New(errors.Details(err, nil)) |
||||||
|
} |
||||||
|
|
||||||
|
return outfiles, nil |
||||||
|
} |
||||||
|
|
||||||
|
func convertImport(im *ast.ImportSpec) *tsImport { |
||||||
|
tsim := &tsImport{ |
||||||
|
Pkg: importMap[allowedImport], |
||||||
|
} |
||||||
|
if im.Name != nil && im.Name.String() != "" { |
||||||
|
tsim.Ident = im.Name.String() |
||||||
|
} else { |
||||||
|
sl := strings.Split(im.Path.Value, "/") |
||||||
|
final := sl[len(sl)-1] |
||||||
|
if idx := strings.Index(final, ":"); idx != -1 { |
||||||
|
tsim.Pkg = final[idx:] |
||||||
|
} else { |
||||||
|
tsim.Pkg = final |
||||||
|
} |
||||||
|
} |
||||||
|
return tsim |
||||||
|
} |
||||||
|
|
||||||
|
func defaultOverlay(p load.BaseLoadPaths) (map[string]cload.Source, error) { |
||||||
|
overlay := make(map[string]cload.Source) |
||||||
|
|
||||||
|
if err := toOverlay(prefix, p.BaseCueFS, overlay); err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
if err := toOverlay(prefix, p.DistPluginCueFS, overlay); err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
return overlay, nil |
||||||
|
} |
||||||
|
|
||||||
|
func toOverlay(prefix string, vfs fs.FS, overlay map[string]cload.Source) error { |
||||||
|
if !filepath.IsAbs(prefix) { |
||||||
|
return fmt.Errorf("must provide absolute path prefix when generating cue overlay, got %q", prefix) |
||||||
|
} |
||||||
|
err := fs.WalkDir(vfs, ".", func(path string, d fs.DirEntry, err error) error { |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
if d.IsDir() { |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
f, err := vfs.Open(path) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
defer func(f fs.File) { |
||||||
|
err := f.Close() |
||||||
|
if err != nil { |
||||||
|
return |
||||||
|
} |
||||||
|
}(f) |
||||||
|
|
||||||
|
b, err := io.ReadAll(f) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
overlay[filepath.Join(prefix, path)] = cload.FromBytes(b) |
||||||
|
return nil |
||||||
|
}) |
||||||
|
|
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// Helper function that populates an fs.FS by walking over a virtual filesystem,
|
||||||
|
// and reading files from disk corresponding to each file encountered.
|
||||||
|
func populateMapFSFromRoot(in fs.FS, root, join string) (fs.FS, error) { |
||||||
|
out := make(fstest.MapFS) |
||||||
|
err := fs.WalkDir(in, ".", func(path string, d fs.DirEntry, err error) error { |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
if d.IsDir() { |
||||||
|
return nil |
||||||
|
} |
||||||
|
// Ignore gosec warning G304. The input set here is necessarily
|
||||||
|
// constrained to files specified in embed.go
|
||||||
|
// nolint:gosec
|
||||||
|
b, err := os.Open(filepath.Join(root, join, path)) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
byt, err := io.ReadAll(b) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
out[path] = &fstest.MapFile{Data: byt} |
||||||
|
return nil |
||||||
|
}) |
||||||
|
return out, err |
||||||
|
} |
||||||
|
|
||||||
|
type tsFile struct { |
||||||
|
V *tsModver |
||||||
|
Imports []*tsImport |
||||||
|
Body string |
||||||
|
} |
||||||
|
|
||||||
|
type tsModver struct { |
||||||
|
Lin, Sch int64 |
||||||
|
} |
||||||
|
|
||||||
|
type tsImport struct { |
||||||
|
Ident string |
||||||
|
Pkg string |
||||||
|
} |
||||||
|
|
||||||
|
var tsTemplate = template.Must(template.New("cuetsygen").Parse(`//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
// This file is autogenerated. DO NOT EDIT.
|
||||||
|
//
|
||||||
|
// To regenerate, run "make gen-cue" from the repository root.
|
||||||
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
{{range .Imports}} |
||||||
|
import * as {{.Ident}} from '{{.Pkg}}';{{end}} |
||||||
|
{{if .V}} |
||||||
|
export const modelVersion = Object.freeze([{{ .V.Lin }}, {{ .V.Sch }}]); |
||||||
|
{{end}} |
||||||
|
{{.Body}}`)) |
@ -0,0 +1,10 @@ |
|||||||
|
package dashboard |
||||||
|
|
||||||
|
// HandoffSchemaVersion is the minimum schemaVersion for dashboards at which the
|
||||||
|
// Thema-based dashboard schema is possibly valid
|
||||||
|
//
|
||||||
|
// schemaVersion is the original version numbering system for dashboards. If a
|
||||||
|
// dashboard is below this schemaVersion, it is necessary for the frontend
|
||||||
|
// typescript dashboard migration logic to first run and get it past this
|
||||||
|
// number, after which Thema can take over.
|
||||||
|
const HandoffSchemaVersion = 36 |
@ -1,40 +0,0 @@ |
|||||||
package dashboard |
|
||||||
|
|
||||||
import ( |
|
||||||
"github.com/grafana/thema" |
|
||||||
) |
|
||||||
|
|
||||||
// Coremodel contains the foundational schema declaration for dashboards.
|
|
||||||
type Coremodel struct { |
|
||||||
lin thema.Lineage |
|
||||||
} |
|
||||||
|
|
||||||
// Lineage returns the canonical dashboard Lineage.
|
|
||||||
func (c *Coremodel) Lineage() thema.Lineage { |
|
||||||
return c.lin |
|
||||||
} |
|
||||||
|
|
||||||
func (c *Coremodel) CurrentSchema() thema.Schema { |
|
||||||
sch, err := c.lin.Schema(currentVersion) |
|
||||||
if err != nil { |
|
||||||
// Only reachable if our own schema currentVersion does not exist, which
|
|
||||||
// can really only happen transitionally during development
|
|
||||||
panic(err) |
|
||||||
} |
|
||||||
return sch |
|
||||||
} |
|
||||||
|
|
||||||
func (c *Coremodel) GoType() interface{} { |
|
||||||
return &model{} |
|
||||||
} |
|
||||||
|
|
||||||
func ProvideCoremodel(lib thema.Library) (*Coremodel, error) { |
|
||||||
lin, err := Lineage(lib) |
|
||||||
if err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
|
|
||||||
return &Coremodel{ |
|
||||||
lin: lin, |
|
||||||
}, nil |
|
||||||
} |
|
@ -0,0 +1,751 @@ |
|||||||
|
// This file is autogenerated. DO NOT EDIT.
|
||||||
|
//
|
||||||
|
// To regenerate, run "make gen-cue" from repository root.
|
||||||
|
//
|
||||||
|
// Derived from the Thema lineage at pkg/coremodel/dashboard
|
||||||
|
|
||||||
|
package dashboard |
||||||
|
|
||||||
|
import ( |
||||||
|
"embed" |
||||||
|
"path/filepath" |
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/cuectx" |
||||||
|
"github.com/grafana/thema" |
||||||
|
) |
||||||
|
|
||||||
|
// Defines values for DashboardGraphTooltip.
|
||||||
|
const ( |
||||||
|
ModelGraphTooltipN0 ModelGraphTooltip = 0 |
||||||
|
|
||||||
|
ModelGraphTooltipN1 ModelGraphTooltip = 1 |
||||||
|
|
||||||
|
ModelGraphTooltipN2 ModelGraphTooltip = 2 |
||||||
|
) |
||||||
|
|
||||||
|
// Defines values for DashboardStyle.
|
||||||
|
const ( |
||||||
|
ModelStyleDark ModelStyle = "dark" |
||||||
|
|
||||||
|
ModelStyleLight ModelStyle = "light" |
||||||
|
) |
||||||
|
|
||||||
|
// Defines values for DashboardTimezone.
|
||||||
|
const ( |
||||||
|
ModelTimezoneBrowser ModelTimezone = "browser" |
||||||
|
|
||||||
|
ModelTimezoneEmpty ModelTimezone = "" |
||||||
|
|
||||||
|
ModelTimezoneUtc ModelTimezone = "utc" |
||||||
|
) |
||||||
|
|
||||||
|
// Defines values for DashboardDashboardCursorSync.
|
||||||
|
const ( |
||||||
|
ModelDashboardCursorSyncN0 ModelDashboardCursorSync = 0 |
||||||
|
|
||||||
|
ModelDashboardCursorSyncN1 ModelDashboardCursorSync = 1 |
||||||
|
|
||||||
|
ModelDashboardCursorSyncN2 ModelDashboardCursorSync = 2 |
||||||
|
) |
||||||
|
|
||||||
|
// Defines values for DashboardDashboardLinkType.
|
||||||
|
const ( |
||||||
|
ModelDashboardLinkTypeDashboards ModelDashboardLinkType = "dashboards" |
||||||
|
|
||||||
|
ModelDashboardLinkTypeLink ModelDashboardLinkType = "link" |
||||||
|
) |
||||||
|
|
||||||
|
// Defines values for DashboardFieldColorModeId.
|
||||||
|
const ( |
||||||
|
ModelFieldColorModeIdContinuousGrYlRd ModelFieldColorModeId = "continuous-GrYlRd" |
||||||
|
|
||||||
|
ModelFieldColorModeIdFixed ModelFieldColorModeId = "fixed" |
||||||
|
|
||||||
|
ModelFieldColorModeIdPaletteClassic ModelFieldColorModeId = "palette-classic" |
||||||
|
|
||||||
|
ModelFieldColorModeIdPaletteSaturated ModelFieldColorModeId = "palette-saturated" |
||||||
|
|
||||||
|
ModelFieldColorModeIdThresholds ModelFieldColorModeId = "thresholds" |
||||||
|
) |
||||||
|
|
||||||
|
// Defines values for DashboardFieldColorSeriesByMode.
|
||||||
|
const ( |
||||||
|
ModelFieldColorSeriesByModeLast ModelFieldColorSeriesByMode = "last" |
||||||
|
|
||||||
|
ModelFieldColorSeriesByModeMax ModelFieldColorSeriesByMode = "max" |
||||||
|
|
||||||
|
ModelFieldColorSeriesByModeMin ModelFieldColorSeriesByMode = "min" |
||||||
|
) |
||||||
|
|
||||||
|
// Defines values for DashboardGraphPanelType.
|
||||||
|
const ( |
||||||
|
ModelGraphPanelTypeGraph ModelGraphPanelType = "graph" |
||||||
|
) |
||||||
|
|
||||||
|
// Defines values for DashboardHeatmapPanelType.
|
||||||
|
const ( |
||||||
|
ModelHeatmapPanelTypeHeatmap ModelHeatmapPanelType = "heatmap" |
||||||
|
) |
||||||
|
|
||||||
|
// Defines values for DashboardPanelRepeatDirection.
|
||||||
|
const ( |
||||||
|
ModelPanelRepeatDirectionH ModelPanelRepeatDirection = "h" |
||||||
|
|
||||||
|
ModelPanelRepeatDirectionV ModelPanelRepeatDirection = "v" |
||||||
|
) |
||||||
|
|
||||||
|
// Defines values for DashboardRowPanelType.
|
||||||
|
const ( |
||||||
|
ModelRowPanelTypeRow ModelRowPanelType = "row" |
||||||
|
) |
||||||
|
|
||||||
|
// Defines values for DashboardThresholdsConfigMode.
|
||||||
|
const ( |
||||||
|
ModelThresholdsConfigModeAbsolute ModelThresholdsConfigMode = "absolute" |
||||||
|
|
||||||
|
ModelThresholdsConfigModePercentage ModelThresholdsConfigMode = "percentage" |
||||||
|
) |
||||||
|
|
||||||
|
// Defines values for DashboardThresholdsMode.
|
||||||
|
const ( |
||||||
|
ModelThresholdsModeAbsolute ModelThresholdsMode = "absolute" |
||||||
|
|
||||||
|
ModelThresholdsModePercentage ModelThresholdsMode = "percentage" |
||||||
|
) |
||||||
|
|
||||||
|
// Defines values for DashboardVariableModelType.
|
||||||
|
const ( |
||||||
|
ModelVariableModelTypeAdhoc ModelVariableModelType = "adhoc" |
||||||
|
|
||||||
|
ModelVariableModelTypeConstant ModelVariableModelType = "constant" |
||||||
|
|
||||||
|
ModelVariableModelTypeCustom ModelVariableModelType = "custom" |
||||||
|
|
||||||
|
ModelVariableModelTypeDatasource ModelVariableModelType = "datasource" |
||||||
|
|
||||||
|
ModelVariableModelTypeInterval ModelVariableModelType = "interval" |
||||||
|
|
||||||
|
ModelVariableModelTypeQuery ModelVariableModelType = "query" |
||||||
|
|
||||||
|
ModelVariableModelTypeSystem ModelVariableModelType = "system" |
||||||
|
|
||||||
|
ModelVariableModelTypeTextbox ModelVariableModelType = "textbox" |
||||||
|
) |
||||||
|
|
||||||
|
// Defines values for DashboardVariableType.
|
||||||
|
const ( |
||||||
|
ModelVariableTypeAdhoc ModelVariableType = "adhoc" |
||||||
|
|
||||||
|
ModelVariableTypeConstant ModelVariableType = "constant" |
||||||
|
|
||||||
|
ModelVariableTypeCustom ModelVariableType = "custom" |
||||||
|
|
||||||
|
ModelVariableTypeDatasource ModelVariableType = "datasource" |
||||||
|
|
||||||
|
ModelVariableTypeInterval ModelVariableType = "interval" |
||||||
|
|
||||||
|
ModelVariableTypeQuery ModelVariableType = "query" |
||||||
|
|
||||||
|
ModelVariableTypeSystem ModelVariableType = "system" |
||||||
|
|
||||||
|
ModelVariableTypeTextbox ModelVariableType = "textbox" |
||||||
|
) |
||||||
|
|
||||||
|
// Dashboard defines model for dashboard.
|
||||||
|
//
|
||||||
|
// THIS TYPE IS INTENDED FOR INTERNAL USE BY THE GRAFANA BACKEND, AND IS SUBJECT TO BREAKING CHANGES.
|
||||||
|
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
|
||||||
|
type Model struct { |
||||||
|
Annotations *struct { |
||||||
|
List []ModelAnnotationQuery `json:"list"` |
||||||
|
} `json:"annotations,omitempty"` |
||||||
|
|
||||||
|
// Description of dashboard.
|
||||||
|
Description *string `json:"description,omitempty"` |
||||||
|
|
||||||
|
// Whether a dashboard is editable or not.
|
||||||
|
Editable bool `json:"editable"` |
||||||
|
|
||||||
|
// TODO docs
|
||||||
|
FiscalYearStartMonth *int `json:"fiscalYearStartMonth,omitempty"` |
||||||
|
GnetId *string `json:"gnetId,omitempty"` |
||||||
|
GraphTooltip ModelGraphTooltip `json:"graphTooltip"` |
||||||
|
|
||||||
|
// Unique numeric identifier for the dashboard.
|
||||||
|
// TODO must isolate or remove identifiers local to a Grafana instance...?
|
||||||
|
Id *int64 `json:"id,omitempty"` |
||||||
|
|
||||||
|
// TODO docs
|
||||||
|
Links *[]ModelDashboardLink `json:"links,omitempty"` |
||||||
|
|
||||||
|
// TODO docs
|
||||||
|
LiveNow *bool `json:"liveNow,omitempty"` |
||||||
|
Panels *[]interface{} `json:"panels,omitempty"` |
||||||
|
|
||||||
|
// TODO docs
|
||||||
|
Refresh *interface{} `json:"refresh,omitempty"` |
||||||
|
|
||||||
|
// Version of the JSON schema, incremented each time a Grafana update brings
|
||||||
|
// changes to said schema.
|
||||||
|
// TODO this is the existing schema numbering system. It will be replaced by Thema's themaVersion
|
||||||
|
SchemaVersion int `json:"schemaVersion"` |
||||||
|
|
||||||
|
// Theme of dashboard.
|
||||||
|
Style ModelStyle `json:"style"` |
||||||
|
|
||||||
|
// Tags associated with dashboard.
|
||||||
|
Tags *[]string `json:"tags,omitempty"` |
||||||
|
Templating *struct { |
||||||
|
List []ModelVariableModel `json:"list"` |
||||||
|
} `json:"templating,omitempty"` |
||||||
|
|
||||||
|
// Time range for dashboard, e.g. last 6 hours, last 7 days, etc
|
||||||
|
Time *struct { |
||||||
|
From string `json:"from"` |
||||||
|
To string `json:"to"` |
||||||
|
} `json:"time,omitempty"` |
||||||
|
|
||||||
|
// TODO docs
|
||||||
|
// TODO this appears to be spread all over in the frontend. Concepts will likely need tidying in tandem with schema changes
|
||||||
|
Timepicker *struct { |
||||||
|
// Whether timepicker is collapsed or not.
|
||||||
|
Collapse bool `json:"collapse"` |
||||||
|
|
||||||
|
// Whether timepicker is enabled or not.
|
||||||
|
Enable bool `json:"enable"` |
||||||
|
|
||||||
|
// Whether timepicker is visible or not.
|
||||||
|
Hidden bool `json:"hidden"` |
||||||
|
|
||||||
|
// Selectable intervals for auto-refresh.
|
||||||
|
RefreshIntervals []string `json:"refresh_intervals"` |
||||||
|
} `json:"timepicker,omitempty"` |
||||||
|
|
||||||
|
// Timezone of dashboard,
|
||||||
|
Timezone *ModelTimezone `json:"timezone,omitempty"` |
||||||
|
|
||||||
|
// Title of dashboard.
|
||||||
|
Title *string `json:"title,omitempty"` |
||||||
|
|
||||||
|
// Unique dashboard identifier that can be generated by anyone. string (8-40)
|
||||||
|
Uid *string `json:"uid,omitempty"` |
||||||
|
|
||||||
|
// Version of the dashboard, incremented each time the dashboard is updated.
|
||||||
|
Version *int `json:"version,omitempty"` |
||||||
|
|
||||||
|
// TODO docs
|
||||||
|
WeekStart *string `json:"weekStart,omitempty"` |
||||||
|
} |
||||||
|
|
||||||
|
// DashboardGraphTooltip defines model for Dashboard.GraphTooltip.
|
||||||
|
//
|
||||||
|
// THIS TYPE IS INTENDED FOR INTERNAL USE BY THE GRAFANA BACKEND, AND IS SUBJECT TO BREAKING CHANGES.
|
||||||
|
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
|
||||||
|
type ModelGraphTooltip int |
||||||
|
|
||||||
|
// Theme of dashboard.
|
||||||
|
//
|
||||||
|
// THIS TYPE IS INTENDED FOR INTERNAL USE BY THE GRAFANA BACKEND, AND IS SUBJECT TO BREAKING CHANGES.
|
||||||
|
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
|
||||||
|
type ModelStyle string |
||||||
|
|
||||||
|
// Timezone of dashboard,
|
||||||
|
//
|
||||||
|
// THIS TYPE IS INTENDED FOR INTERNAL USE BY THE GRAFANA BACKEND, AND IS SUBJECT TO BREAKING CHANGES.
|
||||||
|
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
|
||||||
|
type ModelTimezone string |
||||||
|
|
||||||
|
// TODO docs
|
||||||
|
// FROM: AnnotationQuery in grafana-data/src/types/annotations.ts
|
||||||
|
//
|
||||||
|
// THIS TYPE IS INTENDED FOR INTERNAL USE BY THE GRAFANA BACKEND, AND IS SUBJECT TO BREAKING CHANGES.
|
||||||
|
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
|
||||||
|
type ModelAnnotationQuery struct { |
||||||
|
BuiltIn int `json:"builtIn"` |
||||||
|
|
||||||
|
// Datasource to use for annotation.
|
||||||
|
Datasource struct { |
||||||
|
Type *string `json:"type,omitempty"` |
||||||
|
Uid *string `json:"uid,omitempty"` |
||||||
|
} `json:"datasource"` |
||||||
|
|
||||||
|
// Whether annotation is enabled.
|
||||||
|
Enable bool `json:"enable"` |
||||||
|
|
||||||
|
// Whether to hide annotation.
|
||||||
|
Hide *bool `json:"hide,omitempty"` |
||||||
|
|
||||||
|
// Annotation icon color.
|
||||||
|
IconColor *string `json:"iconColor,omitempty"` |
||||||
|
|
||||||
|
// Name of annotation.
|
||||||
|
Name *string `json:"name,omitempty"` |
||||||
|
|
||||||
|
// Query for annotation data.
|
||||||
|
RawQuery *string `json:"rawQuery,omitempty"` |
||||||
|
ShowIn int `json:"showIn"` |
||||||
|
|
||||||
|
// Schema for panel targets is specified by datasource
|
||||||
|
// plugins. We use a placeholder definition, which the Go
|
||||||
|
// schema loader either left open/as-is with the Base
|
||||||
|
// variant of the Dashboard and Panel families, or filled
|
||||||
|
// with types derived from plugins in the Instance variant.
|
||||||
|
// When working directly from CUE, importers can extend this
|
||||||
|
// type directly to achieve the same effect.
|
||||||
|
Target *ModelTarget `json:"target,omitempty"` |
||||||
|
Type string `json:"type"` |
||||||
|
} |
||||||
|
|
||||||
|
// 0 for no shared crosshair or tooltip (default).
|
||||||
|
// 1 for shared crosshair.
|
||||||
|
// 2 for shared crosshair AND shared tooltip.
|
||||||
|
//
|
||||||
|
// THIS TYPE IS INTENDED FOR INTERNAL USE BY THE GRAFANA BACKEND, AND IS SUBJECT TO BREAKING CHANGES.
|
||||||
|
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
|
||||||
|
type ModelDashboardCursorSync int |
||||||
|
|
||||||
|
// FROM public/app/features/dashboard/state/DashboardModels.ts - ish
|
||||||
|
// TODO docs
|
||||||
|
//
|
||||||
|
// THIS TYPE IS INTENDED FOR INTERNAL USE BY THE GRAFANA BACKEND, AND IS SUBJECT TO BREAKING CHANGES.
|
||||||
|
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
|
||||||
|
type ModelDashboardLink struct { |
||||||
|
AsDropdown bool `json:"asDropdown"` |
||||||
|
Icon *string `json:"icon,omitempty"` |
||||||
|
IncludeVars bool `json:"includeVars"` |
||||||
|
KeepTime bool `json:"keepTime"` |
||||||
|
Tags []string `json:"tags"` |
||||||
|
TargetBlank bool `json:"targetBlank"` |
||||||
|
Title string `json:"title"` |
||||||
|
Tooltip *string `json:"tooltip,omitempty"` |
||||||
|
Type ModelDashboardLinkType `json:"type"` |
||||||
|
Url *string `json:"url,omitempty"` |
||||||
|
} |
||||||
|
|
||||||
|
// DashboardDashboardLinkType defines model for DashboardDashboardLink.Type.
|
||||||
|
//
|
||||||
|
// THIS TYPE IS INTENDED FOR INTERNAL USE BY THE GRAFANA BACKEND, AND IS SUBJECT TO BREAKING CHANGES.
|
||||||
|
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
|
||||||
|
type ModelDashboardLinkType string |
||||||
|
|
||||||
|
// TODO docs
|
||||||
|
//
|
||||||
|
// THIS TYPE IS INTENDED FOR INTERNAL USE BY THE GRAFANA BACKEND, AND IS SUBJECT TO BREAKING CHANGES.
|
||||||
|
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
|
||||||
|
type ModelFieldColor struct { |
||||||
|
// Stores the fixed color value if mode is fixed
|
||||||
|
FixedColor *string `json:"fixedColor,omitempty"` |
||||||
|
|
||||||
|
// The main color scheme mode
|
||||||
|
Mode interface{} `json:"mode"` |
||||||
|
|
||||||
|
// TODO docs
|
||||||
|
SeriesBy *ModelFieldColorSeriesByMode `json:"seriesBy,omitempty"` |
||||||
|
} |
||||||
|
|
||||||
|
// TODO docs
|
||||||
|
//
|
||||||
|
// THIS TYPE IS INTENDED FOR INTERNAL USE BY THE GRAFANA BACKEND, AND IS SUBJECT TO BREAKING CHANGES.
|
||||||
|
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
|
||||||
|
type ModelFieldColorModeId string |
||||||
|
|
||||||
|
// TODO docs
|
||||||
|
//
|
||||||
|
// THIS TYPE IS INTENDED FOR INTERNAL USE BY THE GRAFANA BACKEND, AND IS SUBJECT TO BREAKING CHANGES.
|
||||||
|
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
|
||||||
|
type ModelFieldColorSeriesByMode string |
||||||
|
|
||||||
|
// DashboardGraphPanel defines model for dashboard.GraphPanel.
|
||||||
|
//
|
||||||
|
// THIS TYPE IS INTENDED FOR INTERNAL USE BY THE GRAFANA BACKEND, AND IS SUBJECT TO BREAKING CHANGES.
|
||||||
|
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
|
||||||
|
type ModelGraphPanel struct { |
||||||
|
// Support for legacy graph and heatmap panels.
|
||||||
|
Type ModelGraphPanelType `json:"type"` |
||||||
|
} |
||||||
|
|
||||||
|
// Support for legacy graph and heatmap panels.
|
||||||
|
//
|
||||||
|
// THIS TYPE IS INTENDED FOR INTERNAL USE BY THE GRAFANA BACKEND, AND IS SUBJECT TO BREAKING CHANGES.
|
||||||
|
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
|
||||||
|
type ModelGraphPanelType string |
||||||
|
|
||||||
|
// DashboardHeatmapPanel defines model for dashboard.HeatmapPanel.
|
||||||
|
//
|
||||||
|
// THIS TYPE IS INTENDED FOR INTERNAL USE BY THE GRAFANA BACKEND, AND IS SUBJECT TO BREAKING CHANGES.
|
||||||
|
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
|
||||||
|
type ModelHeatmapPanel struct { |
||||||
|
Type ModelHeatmapPanelType `json:"type"` |
||||||
|
} |
||||||
|
|
||||||
|
// DashboardHeatmapPanelType defines model for DashboardHeatmapPanel.Type.
|
||||||
|
//
|
||||||
|
// THIS TYPE IS INTENDED FOR INTERNAL USE BY THE GRAFANA BACKEND, AND IS SUBJECT TO BREAKING CHANGES.
|
||||||
|
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
|
||||||
|
type ModelHeatmapPanelType string |
||||||
|
|
||||||
|
// Dashboard panels. Panels are canonically defined inline
|
||||||
|
// because they share a version timeline with the dashboard
|
||||||
|
// schema; they do not evolve independently.
|
||||||
|
//
|
||||||
|
// THIS TYPE IS INTENDED FOR INTERNAL USE BY THE GRAFANA BACKEND, AND IS SUBJECT TO BREAKING CHANGES.
|
||||||
|
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
|
||||||
|
type ModelPanel struct { |
||||||
|
// The datasource used in all targets.
|
||||||
|
Datasource *struct { |
||||||
|
Type *string `json:"type,omitempty"` |
||||||
|
Uid *string `json:"uid,omitempty"` |
||||||
|
} `json:"datasource,omitempty"` |
||||||
|
|
||||||
|
// Description.
|
||||||
|
Description *string `json:"description,omitempty"` |
||||||
|
FieldConfig struct { |
||||||
|
Defaults struct { |
||||||
|
// TODO docs
|
||||||
|
Color *ModelFieldColor `json:"color,omitempty"` |
||||||
|
|
||||||
|
// custom is specified by the PanelFieldConfig field
|
||||||
|
// in panel plugin schemas.
|
||||||
|
Custom *map[string]interface{} `json:"custom,omitempty"` |
||||||
|
|
||||||
|
// Significant digits (for display)
|
||||||
|
Decimals *float32 `json:"decimals,omitempty"` |
||||||
|
|
||||||
|
// Human readable field metadata
|
||||||
|
Description *string `json:"description,omitempty"` |
||||||
|
|
||||||
|
// The display value for this field. This supports template variables blank is auto
|
||||||
|
DisplayName *string `json:"displayName,omitempty"` |
||||||
|
|
||||||
|
// This can be used by data sources that return and explicit naming structure for values and labels
|
||||||
|
// When this property is configured, this value is used rather than the default naming strategy.
|
||||||
|
DisplayNameFromDS *string `json:"displayNameFromDS,omitempty"` |
||||||
|
|
||||||
|
// True if data source field supports ad-hoc filters
|
||||||
|
Filterable *bool `json:"filterable,omitempty"` |
||||||
|
|
||||||
|
// // The behavior when clicking on a result
|
||||||
|
Links *[]interface{} `json:"links,omitempty"` |
||||||
|
|
||||||
|
// Convert input values into a display string
|
||||||
|
//
|
||||||
|
// TODO this one corresponds to a complex type with
|
||||||
|
// generics on the typescript side. Ouch. Will
|
||||||
|
// either need special care, or we'll just need to
|
||||||
|
// accept a very loosely specified schema. It's very
|
||||||
|
// unlikely we'll be able to translate cue to
|
||||||
|
// typescript generics in the general case, though
|
||||||
|
// this particular one *may* be able to work.
|
||||||
|
Mappings *[]map[string]interface{} `json:"mappings,omitempty"` |
||||||
|
Max *float32 `json:"max,omitempty"` |
||||||
|
Min *float32 `json:"min,omitempty"` |
||||||
|
|
||||||
|
// Alternative to empty string
|
||||||
|
NoValue *string `json:"noValue,omitempty"` |
||||||
|
|
||||||
|
// An explict path to the field in the datasource. When the frame meta includes a path,
|
||||||
|
// This will default to `${frame.meta.path}/${field.name}
|
||||||
|
//
|
||||||
|
// When defined, this value can be used as an identifier within the datasource scope, and
|
||||||
|
// may be used to update the results
|
||||||
|
Path *string `json:"path,omitempty"` |
||||||
|
Thresholds *ModelThresholdsConfig `json:"thresholds,omitempty"` |
||||||
|
|
||||||
|
// Numeric Options
|
||||||
|
Unit *string `json:"unit,omitempty"` |
||||||
|
|
||||||
|
// True if data source can write a value to the path. Auth/authz are supported separately
|
||||||
|
Writeable *bool `json:"writeable,omitempty"` |
||||||
|
} `json:"defaults"` |
||||||
|
Overrides []struct { |
||||||
|
Matcher struct { |
||||||
|
Id string `json:"id"` |
||||||
|
Options *interface{} `json:"options,omitempty"` |
||||||
|
} `json:"matcher"` |
||||||
|
Properties []struct { |
||||||
|
Id string `json:"id"` |
||||||
|
Value *interface{} `json:"value,omitempty"` |
||||||
|
} `json:"properties"` |
||||||
|
} `json:"overrides"` |
||||||
|
} `json:"fieldConfig"` |
||||||
|
|
||||||
|
// Grid position.
|
||||||
|
GridPos *struct { |
||||||
|
// Panel
|
||||||
|
H int `json:"h"` |
||||||
|
|
||||||
|
// true if fixed
|
||||||
|
Static *bool `json:"static,omitempty"` |
||||||
|
|
||||||
|
// Panel
|
||||||
|
W int `json:"w"` |
||||||
|
|
||||||
|
// Panel x
|
||||||
|
X int `json:"x"` |
||||||
|
|
||||||
|
// Panel y
|
||||||
|
Y int `json:"y"` |
||||||
|
} `json:"gridPos,omitempty"` |
||||||
|
|
||||||
|
// TODO docs
|
||||||
|
Id *int `json:"id,omitempty"` |
||||||
|
|
||||||
|
// TODO docs
|
||||||
|
// TODO tighter constraint
|
||||||
|
Interval *string `json:"interval,omitempty"` |
||||||
|
|
||||||
|
// Panel links.
|
||||||
|
// TODO fill this out - seems there are a couple variants?
|
||||||
|
Links *[]ModelDashboardLink `json:"links,omitempty"` |
||||||
|
|
||||||
|
// TODO docs
|
||||||
|
MaxDataPoints *float32 `json:"maxDataPoints,omitempty"` |
||||||
|
|
||||||
|
// options is specified by the PanelOptions field in panel
|
||||||
|
// plugin schemas.
|
||||||
|
Options map[string]interface{} `json:"options"` |
||||||
|
|
||||||
|
// FIXME this almost certainly has to be changed in favor of scuemata versions
|
||||||
|
PluginVersion *string `json:"pluginVersion,omitempty"` |
||||||
|
|
||||||
|
// Name of template variable to repeat for.
|
||||||
|
Repeat *string `json:"repeat,omitempty"` |
||||||
|
|
||||||
|
// Direction to repeat in if 'repeat' is set.
|
||||||
|
// "h" for horizontal, "v" for vertical.
|
||||||
|
RepeatDirection ModelPanelRepeatDirection `json:"repeatDirection"` |
||||||
|
|
||||||
|
// TODO docs
|
||||||
|
Tags *[]string `json:"tags,omitempty"` |
||||||
|
|
||||||
|
// TODO docs
|
||||||
|
Targets *[]ModelTarget `json:"targets,omitempty"` |
||||||
|
|
||||||
|
// TODO docs
|
||||||
|
Thresholds *[]interface{} `json:"thresholds,omitempty"` |
||||||
|
|
||||||
|
// TODO docs
|
||||||
|
// TODO tighter constraint
|
||||||
|
TimeFrom *string `json:"timeFrom,omitempty"` |
||||||
|
|
||||||
|
// TODO docs
|
||||||
|
TimeRegions *[]interface{} `json:"timeRegions,omitempty"` |
||||||
|
|
||||||
|
// TODO docs
|
||||||
|
// TODO tighter constraint
|
||||||
|
TimeShift *string `json:"timeShift,omitempty"` |
||||||
|
|
||||||
|
// Panel title.
|
||||||
|
Title *string `json:"title,omitempty"` |
||||||
|
Transformations []struct { |
||||||
|
Id string `json:"id"` |
||||||
|
Options map[string]interface{} `json:"options"` |
||||||
|
} `json:"transformations"` |
||||||
|
|
||||||
|
// Whether to display the panel without a background.
|
||||||
|
Transparent bool `json:"transparent"` |
||||||
|
|
||||||
|
// The panel plugin type id. May not be empty.
|
||||||
|
Type string `json:"type"` |
||||||
|
} |
||||||
|
|
||||||
|
// Direction to repeat in if 'repeat' is set.
|
||||||
|
// "h" for horizontal, "v" for vertical.
|
||||||
|
//
|
||||||
|
// THIS TYPE IS INTENDED FOR INTERNAL USE BY THE GRAFANA BACKEND, AND IS SUBJECT TO BREAKING CHANGES.
|
||||||
|
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
|
||||||
|
type ModelPanelRepeatDirection string |
||||||
|
|
||||||
|
// Row panel
|
||||||
|
//
|
||||||
|
// THIS TYPE IS INTENDED FOR INTERNAL USE BY THE GRAFANA BACKEND, AND IS SUBJECT TO BREAKING CHANGES.
|
||||||
|
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
|
||||||
|
type ModelRowPanel struct { |
||||||
|
Collapsed bool `json:"collapsed"` |
||||||
|
|
||||||
|
// Name of default datasource.
|
||||||
|
Datasource *struct { |
||||||
|
Type *string `json:"type,omitempty"` |
||||||
|
Uid *string `json:"uid,omitempty"` |
||||||
|
} `json:"datasource,omitempty"` |
||||||
|
GridPos *struct { |
||||||
|
// Panel
|
||||||
|
H int `json:"h"` |
||||||
|
|
||||||
|
// true if fixed
|
||||||
|
Static *bool `json:"static,omitempty"` |
||||||
|
|
||||||
|
// Panel
|
||||||
|
W int `json:"w"` |
||||||
|
|
||||||
|
// Panel x
|
||||||
|
X int `json:"x"` |
||||||
|
|
||||||
|
// Panel y
|
||||||
|
Y int `json:"y"` |
||||||
|
} `json:"gridPos,omitempty"` |
||||||
|
Id int `json:"id"` |
||||||
|
Panels []interface{} `json:"panels"` |
||||||
|
|
||||||
|
// Name of template variable to repeat for.
|
||||||
|
Repeat *string `json:"repeat,omitempty"` |
||||||
|
Title *string `json:"title,omitempty"` |
||||||
|
Type ModelRowPanelType `json:"type"` |
||||||
|
} |
||||||
|
|
||||||
|
// DashboardRowPanelType defines model for DashboardRowPanel.Type.
|
||||||
|
//
|
||||||
|
// THIS TYPE IS INTENDED FOR INTERNAL USE BY THE GRAFANA BACKEND, AND IS SUBJECT TO BREAKING CHANGES.
|
||||||
|
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
|
||||||
|
type ModelRowPanelType string |
||||||
|
|
||||||
|
// Schema for panel targets is specified by datasource
|
||||||
|
// plugins. We use a placeholder definition, which the Go
|
||||||
|
// schema loader either left open/as-is with the Base
|
||||||
|
// variant of the Dashboard and Panel families, or filled
|
||||||
|
// with types derived from plugins in the Instance variant.
|
||||||
|
// When working directly from CUE, importers can extend this
|
||||||
|
// type directly to achieve the same effect.
|
||||||
|
//
|
||||||
|
// THIS TYPE IS INTENDED FOR INTERNAL USE BY THE GRAFANA BACKEND, AND IS SUBJECT TO BREAKING CHANGES.
|
||||||
|
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
|
||||||
|
type ModelTarget map[string]interface{} |
||||||
|
|
||||||
|
// TODO docs
|
||||||
|
//
|
||||||
|
// THIS TYPE IS INTENDED FOR INTERNAL USE BY THE GRAFANA BACKEND, AND IS SUBJECT TO BREAKING CHANGES.
|
||||||
|
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
|
||||||
|
type ModelThreshold struct { |
||||||
|
// TODO docs
|
||||||
|
Color string `json:"color"` |
||||||
|
|
||||||
|
// TODO docs
|
||||||
|
// TODO are the values here enumerable into a disjunction?
|
||||||
|
// Some seem to be listed in typescript comment
|
||||||
|
State *string `json:"state,omitempty"` |
||||||
|
|
||||||
|
// TODO docs
|
||||||
|
// FIXME the corresponding typescript field is required/non-optional, but nulls currently appear here when serializing -Infinity to JSON
|
||||||
|
Value *float32 `json:"value,omitempty"` |
||||||
|
} |
||||||
|
|
||||||
|
// DashboardThresholdsConfig defines model for dashboard.ThresholdsConfig.
|
||||||
|
//
|
||||||
|
// THIS TYPE IS INTENDED FOR INTERNAL USE BY THE GRAFANA BACKEND, AND IS SUBJECT TO BREAKING CHANGES.
|
||||||
|
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
|
||||||
|
type ModelThresholdsConfig struct { |
||||||
|
Mode ModelThresholdsConfigMode `json:"mode"` |
||||||
|
|
||||||
|
// Must be sorted by 'value', first value is always -Infinity
|
||||||
|
Steps []struct { |
||||||
|
// TODO docs
|
||||||
|
Color string `json:"color"` |
||||||
|
|
||||||
|
// TODO docs
|
||||||
|
// TODO are the values here enumerable into a disjunction?
|
||||||
|
// Some seem to be listed in typescript comment
|
||||||
|
State *string `json:"state,omitempty"` |
||||||
|
|
||||||
|
// TODO docs
|
||||||
|
// FIXME the corresponding typescript field is required/non-optional, but nulls currently appear here when serializing -Infinity to JSON
|
||||||
|
Value *float32 `json:"value,omitempty"` |
||||||
|
} `json:"steps"` |
||||||
|
} |
||||||
|
|
||||||
|
// DashboardThresholdsConfigMode defines model for DashboardThresholdsConfig.Mode.
|
||||||
|
//
|
||||||
|
// THIS TYPE IS INTENDED FOR INTERNAL USE BY THE GRAFANA BACKEND, AND IS SUBJECT TO BREAKING CHANGES.
|
||||||
|
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
|
||||||
|
type ModelThresholdsConfigMode string |
||||||
|
|
||||||
|
// DashboardThresholdsMode defines model for dashboard.ThresholdsMode.
|
||||||
|
//
|
||||||
|
// THIS TYPE IS INTENDED FOR INTERNAL USE BY THE GRAFANA BACKEND, AND IS SUBJECT TO BREAKING CHANGES.
|
||||||
|
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
|
||||||
|
type ModelThresholdsMode string |
||||||
|
|
||||||
|
// TODO docs
|
||||||
|
// FIXME this is extremely underspecfied; wasn't obvious which typescript types corresponded to it
|
||||||
|
//
|
||||||
|
// THIS TYPE IS INTENDED FOR INTERNAL USE BY THE GRAFANA BACKEND, AND IS SUBJECT TO BREAKING CHANGES.
|
||||||
|
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
|
||||||
|
type ModelTransformation struct { |
||||||
|
Id string `json:"id"` |
||||||
|
Options map[string]interface{} `json:"options"` |
||||||
|
} |
||||||
|
|
||||||
|
// FROM: packages/grafana-data/src/types/templateVars.ts
|
||||||
|
// TODO docs
|
||||||
|
// TODO what about what's in public/app/features/types.ts?
|
||||||
|
// TODO there appear to be a lot of different kinds of [template] vars here? if so need a disjunction
|
||||||
|
//
|
||||||
|
// THIS TYPE IS INTENDED FOR INTERNAL USE BY THE GRAFANA BACKEND, AND IS SUBJECT TO BREAKING CHANGES.
|
||||||
|
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
|
||||||
|
type ModelVariableModel struct { |
||||||
|
Label *string `json:"label,omitempty"` |
||||||
|
Name string `json:"name"` |
||||||
|
Type ModelVariableModelType `json:"type"` |
||||||
|
} |
||||||
|
|
||||||
|
// DashboardVariableModelType defines model for DashboardVariableModel.Type.
|
||||||
|
//
|
||||||
|
// THIS TYPE IS INTENDED FOR INTERNAL USE BY THE GRAFANA BACKEND, AND IS SUBJECT TO BREAKING CHANGES.
|
||||||
|
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
|
||||||
|
type ModelVariableModelType string |
||||||
|
|
||||||
|
// FROM: packages/grafana-data/src/types/templateVars.ts
|
||||||
|
// TODO docs
|
||||||
|
// TODO this implies some wider pattern/discriminated union, probably?
|
||||||
|
//
|
||||||
|
// THIS TYPE IS INTENDED FOR INTERNAL USE BY THE GRAFANA BACKEND, AND IS SUBJECT TO BREAKING CHANGES.
|
||||||
|
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
|
||||||
|
type ModelVariableType string |
||||||
|
|
||||||
|
//go:embed lineage.cue
|
||||||
|
var cueFS embed.FS |
||||||
|
|
||||||
|
// codegen ensures that this is always the latest Thema schema version
|
||||||
|
var currentVersion = thema.SV(0, 0) |
||||||
|
|
||||||
|
// Lineage returns the Thema lineage representing a Grafana dashboard.
|
||||||
|
//
|
||||||
|
// The lineage is the canonical specification of the current dashboard schema,
|
||||||
|
// all prior schema versions, and the mappings that allow migration between
|
||||||
|
// schema versions.
|
||||||
|
func Lineage(lib thema.Library, opts ...thema.BindOption) (thema.Lineage, error) { |
||||||
|
return cuectx.LoadGrafanaInstancesWithThema(filepath.Join("pkg", "coremodel", "dashboard"), cueFS, lib, opts...) |
||||||
|
} |
||||||
|
|
||||||
|
var _ thema.LineageFactory = Lineage |
||||||
|
|
||||||
|
// Coremodel contains the foundational schema declaration for dashboards.
|
||||||
|
type Coremodel struct { |
||||||
|
lin thema.Lineage |
||||||
|
} |
||||||
|
|
||||||
|
// Lineage returns the canonical dashboard Lineage.
|
||||||
|
func (c *Coremodel) Lineage() thema.Lineage { |
||||||
|
return c.lin |
||||||
|
} |
||||||
|
|
||||||
|
// CurrentSchema returns the current (latest) dashboard Thema schema.
|
||||||
|
func (c *Coremodel) CurrentSchema() thema.Schema { |
||||||
|
return thema.SchemaP(c.lin, currentVersion) |
||||||
|
} |
||||||
|
|
||||||
|
// GoType returns a pointer to an empty Go struct that corresponds to
|
||||||
|
// the current Thema schema.
|
||||||
|
func (c *Coremodel) GoType() interface{} { |
||||||
|
return &Model{} |
||||||
|
} |
||||||
|
|
||||||
|
func ProvideCoremodel(lib thema.Library) (*Coremodel, error) { |
||||||
|
lin, err := Lineage(lib) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
return &Coremodel{ |
||||||
|
lin: lin, |
||||||
|
}, nil |
||||||
|
} |
@ -0,0 +1,28 @@ |
|||||||
|
// This file is autogenerated. DO NOT EDIT.
|
||||||
|
//
|
||||||
|
// To regenerate, run "make gen-cue" from repository root.
|
||||||
|
//
|
||||||
|
// Derived from the Thema lineage at pkg/coremodel/dashboard
|
||||||
|
|
||||||
|
package dashboard |
||||||
|
|
||||||
|
import ( |
||||||
|
"testing" |
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/cuectx" |
||||||
|
"github.com/grafana/thema" |
||||||
|
) |
||||||
|
|
||||||
|
func TestSchemaAssignability(t *testing.T) { |
||||||
|
lin, err := Lineage(cuectx.ProvideThemaLibrary()) |
||||||
|
if err != nil { |
||||||
|
t.Fatal(err) |
||||||
|
} |
||||||
|
|
||||||
|
sch := thema.SchemaP(lin, currentVersion) |
||||||
|
|
||||||
|
err = thema.AssignableTo(sch, &Model{}) |
||||||
|
if err != nil { |
||||||
|
t.Fatal(err) |
||||||
|
} |
||||||
|
} |
@ -1,117 +0,0 @@ |
|||||||
package dashboard |
|
||||||
|
|
||||||
import ( |
|
||||||
"embed" |
|
||||||
"path/filepath" |
|
||||||
|
|
||||||
"github.com/grafana/thema" |
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/cuectx" |
|
||||||
) |
|
||||||
|
|
||||||
var ( |
|
||||||
//go:embed lineage.cue
|
|
||||||
cueFS embed.FS |
|
||||||
|
|
||||||
// TODO: this should be generated by Thema.
|
|
||||||
currentVersion = thema.SV(0, 0) |
|
||||||
) |
|
||||||
|
|
||||||
// HandoffSchemaVersion is the minimum schemaVersion for dashboards at which the
|
|
||||||
// Thema-based dashboard schema is known to be valid.
|
|
||||||
//
|
|
||||||
// schemaVersion is the original version numbering system for dashboards. If a
|
|
||||||
// dashboard is below this schemaVersion, it is necessary for the frontend
|
|
||||||
// typescript dashboard migration logic to first run and get it past this
|
|
||||||
// number, after which Thema can take over.
|
|
||||||
const HandoffSchemaVersion = 36 |
|
||||||
|
|
||||||
// Lineage returns the Thema lineage representing Grafana dashboards. The
|
|
||||||
// lineage is the canonical specification of the current datasource schema, all
|
|
||||||
// prior schema versions, and the mappings that allow migration between schema
|
|
||||||
// versions.
|
|
||||||
//
|
|
||||||
// This is the base variant of the schema, which does not include any composed
|
|
||||||
// plugin schemas.
|
|
||||||
func Lineage(lib thema.Library, opts ...thema.BindOption) (thema.Lineage, error) { |
|
||||||
return cuectx.LoadGrafanaInstancesWithThema(filepath.Join("pkg", "coremodel", "dashboard"), cueFS, lib, opts...) |
|
||||||
} |
|
||||||
|
|
||||||
// Model is a dummy struct stand-in for dashboards.
|
|
||||||
//
|
|
||||||
// It exists solely to trick compgen into accepting the dashboard coremodel as valid.
|
|
||||||
type Model struct{} |
|
||||||
|
|
||||||
// model is a hacky Go struct representing a dashboard.
|
|
||||||
//
|
|
||||||
// This exists solely because the coremodel framework enforces that there is a Go struct to which
|
|
||||||
// all valid Thema schema instances can be assigned, per Thema's assignability checker. See
|
|
||||||
// https://github.com/grafana/thema/blob/main/docs/invariants.md#go-assignability for rules.
|
|
||||||
//
|
|
||||||
// DO NOT RELY ON THIS FOR ANYTHING REAL. It is unclear whether we will ever attempt to have a correct, complete
|
|
||||||
// Go struct representation of dashboards, let alone compress it into a single struct.
|
|
||||||
type model struct { |
|
||||||
Title string `json:"title"` |
|
||||||
Description string `json:"description"` |
|
||||||
GnetId string `json:"gnetId"` |
|
||||||
Tags []string `json:"tags"` |
|
||||||
Style string `json:"style"` |
|
||||||
Timezone string `json:"timezone"` |
|
||||||
Editable bool `json:"editable"` |
|
||||||
GraphTooltip uint8 `json:"graphTooltip"` |
|
||||||
Time struct { |
|
||||||
From string `json:"from"` |
|
||||||
To string `json:"to"` |
|
||||||
} `json:"time"` |
|
||||||
Timepicker struct { |
|
||||||
Collapse bool `json:"collapse"` |
|
||||||
Enable bool `json:"enable"` |
|
||||||
Hidden bool `json:"hidden"` |
|
||||||
RefreshIntervals []string `json:"refresh_intervals"` |
|
||||||
} `json:"timepicker"` |
|
||||||
Templating struct { |
|
||||||
List []interface{} `json:"list"` |
|
||||||
} `json:"templating"` |
|
||||||
Annotations struct { |
|
||||||
List []struct { |
|
||||||
Name string `json:"name"` |
|
||||||
Type string `json:"type"` |
|
||||||
BuiltIn uint8 `json:"builtIn"` |
|
||||||
Datasource struct { |
|
||||||
Type string `json:"type"` |
|
||||||
Uid string `json:"uid"` |
|
||||||
} `json:"datasource"` |
|
||||||
Enable bool `json:"enable"` |
|
||||||
Hide bool `json:"hide,omitempty"` |
|
||||||
IconColor string `json:"iconColor"` |
|
||||||
RawQuery string `json:"rawQuery,omitempty"` |
|
||||||
ShowIn int `json:"showIn"` |
|
||||||
Target interface{} `json:"target"` |
|
||||||
} `json:"list"` |
|
||||||
} `json:"annotations"` |
|
||||||
Refresh interface{} `json:"refresh"` // (bool|string)
|
|
||||||
SchemaVersion int `json:"schemaVersion"` |
|
||||||
Links []struct { |
|
||||||
Title string `json:"title"` |
|
||||||
Type string `json:"type"` |
|
||||||
Icon string `json:"icon,omitempty"` |
|
||||||
Tooltip string `json:"tooltip,omitempty"` |
|
||||||
Url string `json:"url,omitempty"` |
|
||||||
Tags []string `json:"tags"` |
|
||||||
AsDropdown bool `json:"asDropdown"` |
|
||||||
TargetBlank bool `json:"targetBlank"` |
|
||||||
IncludeVars bool `json:"includeVars"` |
|
||||||
KeepTime bool `json:"keepTime"` |
|
||||||
} `json:"links"` |
|
||||||
Panels []interface{} `json:"panels"` |
|
||||||
FiscalYearStartMonth uint8 `json:"fiscalYearStartMonth"` |
|
||||||
LiveNow bool `json:"liveNow"` |
|
||||||
WeekStart string `json:"weekStart"` |
|
||||||
|
|
||||||
// //
|
|
||||||
|
|
||||||
Uid string `json:"uid"` |
|
||||||
// OrgId int64 `json:"orgId"`
|
|
||||||
Id int64 `json:"id,omitempty"` |
|
||||||
Version int `json:"version"` |
|
||||||
} |
|
@ -0,0 +1,93 @@ |
|||||||
|
// go:build ignore
|
||||||
|
//go:build ignore
|
||||||
|
// +build ignore
|
||||||
|
|
||||||
|
package main |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
"io/ioutil" |
||||||
|
"os" |
||||||
|
"path/filepath" |
||||||
|
"strings" |
||||||
|
|
||||||
|
"cuelang.org/go/cue/cuecontext" |
||||||
|
gcgen "github.com/grafana/grafana/pkg/codegen" |
||||||
|
"github.com/grafana/thema" |
||||||
|
) |
||||||
|
|
||||||
|
var lib = thema.NewLibrary(cuecontext.New()) |
||||||
|
|
||||||
|
const sep = string(filepath.Separator) |
||||||
|
|
||||||
|
// Generate Go and Typescript implementations for all coremodels, and populate the
|
||||||
|
// coremodel static registry.
|
||||||
|
func main() { |
||||||
|
if len(os.Args) > 1 { |
||||||
|
fmt.Fprintf(os.Stderr, "coremodel code generator does not currently accept any arguments\n, got %q", os.Args) |
||||||
|
os.Exit(1) |
||||||
|
} |
||||||
|
|
||||||
|
cwd, err := os.Getwd() |
||||||
|
if err != nil { |
||||||
|
fmt.Fprintf(os.Stderr, "could not get working directory: %s", err) |
||||||
|
os.Exit(1) |
||||||
|
} |
||||||
|
|
||||||
|
// TODO this binds us to only having coremodels in a single directory. If we need more, compgen is the way
|
||||||
|
grootp := strings.Split(cwd, sep) |
||||||
|
groot := filepath.Join(sep, filepath.Join(grootp[:len(grootp)-3]...)) |
||||||
|
|
||||||
|
cmroot := filepath.Join(groot, "pkg", "coremodel") |
||||||
|
tsroot := filepath.Join(groot, "packages", "grafana-schema", "src", "schema") |
||||||
|
|
||||||
|
items, err := ioutil.ReadDir(cmroot) |
||||||
|
if err != nil { |
||||||
|
fmt.Fprintf(os.Stderr, "could not read coremodels parent dir %s: %s\n", cmroot, err) |
||||||
|
os.Exit(1) |
||||||
|
} |
||||||
|
|
||||||
|
var lins []*gcgen.ExtractedLineage |
||||||
|
for _, item := range items { |
||||||
|
if item.IsDir() { |
||||||
|
lin, err := gcgen.ExtractLineage(filepath.Join(cmroot, item.Name(), "lineage.cue"), lib) |
||||||
|
if err != nil { |
||||||
|
fmt.Fprintf(os.Stderr, "could not process coremodel dir %s: %s\n", cmroot, err) |
||||||
|
os.Exit(1) |
||||||
|
} |
||||||
|
|
||||||
|
lins = append(lins, lin) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
wd := gcgen.NewWriteDiffer() |
||||||
|
for _, ls := range lins { |
||||||
|
wdg, err := ls.GenerateGoCoremodel(filepath.Join(cmroot, ls.Lineage.Name())) |
||||||
|
if err != nil { |
||||||
|
fmt.Fprintf(os.Stderr, "failed to generate Go for %s: %s\n", ls.Lineage.Name(), err) |
||||||
|
os.Exit(1) |
||||||
|
} |
||||||
|
wd.Merge(wdg) |
||||||
|
|
||||||
|
wdt, err := ls.GenerateTypescriptCoremodel(filepath.Join(tsroot, ls.Lineage.Name())) |
||||||
|
if err != nil { |
||||||
|
fmt.Fprintf(os.Stderr, "failed to generate TypeScript for %s: %s\n", ls.Lineage.Name(), err) |
||||||
|
os.Exit(1) |
||||||
|
} |
||||||
|
wd.Merge(wdt) |
||||||
|
} |
||||||
|
|
||||||
|
if _, set := os.LookupEnv("CODEGEN_VERIFY"); set { |
||||||
|
err = wd.Verify() |
||||||
|
if err != nil { |
||||||
|
fmt.Fprintf(os.Stderr, "generated code is not up to date:\n%s\nrun `make gen-cue` to regenerate\n\n", err) |
||||||
|
os.Exit(1) |
||||||
|
} |
||||||
|
} else { |
||||||
|
err = wd.Write() |
||||||
|
if err != nil { |
||||||
|
fmt.Fprintf(os.Stderr, "error while writing generated code to disk:\n%s\n", err) |
||||||
|
os.Exit(1) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,63 @@ |
|||||||
|
// go:build ignore
|
||||||
|
//go:build ignore
|
||||||
|
// +build ignore
|
||||||
|
|
||||||
|
package main |
||||||
|
|
||||||
|
import ( |
||||||
|
"errors" |
||||||
|
"fmt" |
||||||
|
"os" |
||||||
|
"path/filepath" |
||||||
|
|
||||||
|
"cuelang.org/go/cue/cuecontext" |
||||||
|
"github.com/grafana/grafana/pkg/codegen" |
||||||
|
) |
||||||
|
|
||||||
|
// Generate TypeScript for all plugin models.cue
|
||||||
|
func main() { |
||||||
|
if len(os.Args) > 1 { |
||||||
|
fmt.Fprintf(os.Stderr, "plugin thema code generator does not currently accept any arguments\n, got %q", os.Args) |
||||||
|
os.Exit(1) |
||||||
|
} |
||||||
|
|
||||||
|
cwd, err := os.Getwd() |
||||||
|
if err != nil { |
||||||
|
fmt.Fprintf(os.Stderr, "could not get working directory: %s", err) |
||||||
|
os.Exit(1) |
||||||
|
} |
||||||
|
|
||||||
|
var find func(path string) (string, error) |
||||||
|
find = func(path string) (string, error) { |
||||||
|
parent := filepath.Dir(path) |
||||||
|
if parent == path { |
||||||
|
return "", errors.New("grafana root directory could not be found") |
||||||
|
} |
||||||
|
fp := filepath.Join(path, "go.mod") |
||||||
|
if _, err := os.Stat(fp); err == nil { |
||||||
|
return path, nil |
||||||
|
} |
||||||
|
return find(parent) |
||||||
|
} |
||||||
|
groot, err := find(cwd) |
||||||
|
if err != nil { |
||||||
|
fmt.Fprint(os.Stderr, err) |
||||||
|
os.Exit(1) |
||||||
|
} |
||||||
|
|
||||||
|
wd, err := codegen.CuetsifyPlugins(cuecontext.New(), groot) |
||||||
|
|
||||||
|
if _, set := os.LookupEnv("CODEGEN_VERIFY"); set { |
||||||
|
err = wd.Verify() |
||||||
|
if err != nil { |
||||||
|
fmt.Fprintf(os.Stderr, "generated code is out of sync with inputs:\n%s\nrun `make gen-cue` to regenerate\n\n", err) |
||||||
|
os.Exit(1) |
||||||
|
} |
||||||
|
} else { |
||||||
|
err = wd.Write() |
||||||
|
if err != nil { |
||||||
|
fmt.Fprintf(os.Stderr, "error while writing generated code to disk:\n%s\n", err) |
||||||
|
os.Exit(1) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,3 @@ |
|||||||
|
package plugins |
||||||
|
|
||||||
|
//go:generate go run gen.go
|
Loading…
Reference in new issue