mirror of https://github.com/grafana/grafana
Introduce "scuemata" system for CUE-based specification of Grafana objects (#32527)
parent
7351645d63
commit
bba4d9bd7f
@ -0,0 +1 @@ |
|||||||
|
module: "github.com/grafana/grafana" |
@ -0,0 +1,213 @@ |
|||||||
|
package grafanaschema |
||||||
|
|
||||||
|
import "github.com/grafana/grafana/cue/scuemata" |
||||||
|
|
||||||
|
Family: scuemata.#Family & { |
||||||
|
lineages: [ |
||||||
|
[ |
||||||
|
{ // 0.0 |
||||||
|
// Unique numeric identifier for the dashboard. |
||||||
|
// TODO must isolate or remove identifiers local to a Grafana instance...? |
||||||
|
id?: number |
||||||
|
// Unique dashboard identifier that can be generated by anyone. string (8-40) |
||||||
|
uid: string |
||||||
|
// Title of dashboard. |
||||||
|
title?: string |
||||||
|
// Description of dashboard. |
||||||
|
description?: string |
||||||
|
|
||||||
|
gnetId?: string |
||||||
|
// Tags associated with dashboard. |
||||||
|
tags?: [...string] |
||||||
|
// Theme of dashboard. |
||||||
|
style: *"light" | "dark" |
||||||
|
// Timezone of dashboard, |
||||||
|
timezone?: *"browser" | "utc" |
||||||
|
// Whether a dashboard is editable or not. |
||||||
|
editable: bool | *true |
||||||
|
// 0 for no shared crosshair or tooltip (default). |
||||||
|
// 1 for shared crosshair. |
||||||
|
// 2 for shared crosshair AND shared tooltip. |
||||||
|
graphTooltip: >=0 & <=2 | *0 |
||||||
|
// Time range for dashboard, e.g. last 6 hours, last 7 days, etc |
||||||
|
time?: { |
||||||
|
from: string | *"now-6h" |
||||||
|
to: string | *"now" |
||||||
|
} |
||||||
|
// Timepicker metadata. |
||||||
|
timepicker?: { |
||||||
|
// Whether timepicker is collapsed or not. |
||||||
|
collapse: bool | *false |
||||||
|
// Whether timepicker is enabled or not. |
||||||
|
enable: bool | *true |
||||||
|
// Whether timepicker is visible or not. |
||||||
|
hidden: bool | *false |
||||||
|
// Selectable intervals for auto-refresh. |
||||||
|
refresh_intervals: [...string] | *["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"] |
||||||
|
} |
||||||
|
// Templating. |
||||||
|
templating?: list: [...{...}] |
||||||
|
// Annotations. |
||||||
|
annotations?: list: [...{ |
||||||
|
builtIn: number | *0 |
||||||
|
// Datasource to use for annotation. |
||||||
|
datasource: string |
||||||
|
// Whether annotation is enabled. |
||||||
|
enable?: bool | *true |
||||||
|
// Whether to hide annotation. |
||||||
|
hide?: bool | *false |
||||||
|
// Annotation icon color. |
||||||
|
iconColor?: string |
||||||
|
// Name of annotation. |
||||||
|
name?: string |
||||||
|
type: string | *"dashboard" |
||||||
|
// Query for annotation data. |
||||||
|
rawQuery?: string |
||||||
|
showIn: number | *0 |
||||||
|
}] |
||||||
|
// Auto-refresh interval. |
||||||
|
refresh?: string |
||||||
|
// Version of the JSON schema, incremented each time a Grafana update brings |
||||||
|
// changes to said schema. |
||||||
|
schemaVersion: number | *25 |
||||||
|
// Version of the dashboard, incremented each time the dashboard is updated. |
||||||
|
version?: number |
||||||
|
panels?: [...#Panel] |
||||||
|
|
||||||
|
// Dashboard panels. Panels are canonically defined inline |
||||||
|
// because they share a version timeline with the dashboard |
||||||
|
// schema; they do not vary independently. We create a separate, |
||||||
|
// synthetic Family to represent them in Go, for ease of generating |
||||||
|
// e.g. JSON Schema. |
||||||
|
#Panel: { |
||||||
|
// The panel plugin type id. |
||||||
|
type: !="" |
||||||
|
|
||||||
|
// Internal - the exact major and minor versions of the panel plugin |
||||||
|
// schema. Hidden and therefore not a part of the data model, but |
||||||
|
// expected to be filled with panel plugin schema versions so that it's |
||||||
|
// possible to figure out which schema version matched on a successful |
||||||
|
// unification. |
||||||
|
// _pv: { maj: int, min: int } |
||||||
|
// The major and minor versions of the panel plugin for this schema. |
||||||
|
// TODO 2-tuple list instead of struct? |
||||||
|
panelSchema: { maj: number, min: number } |
||||||
|
|
||||||
|
// Panel title. |
||||||
|
title?: string |
||||||
|
// Description. |
||||||
|
description?: string |
||||||
|
// Whether to display the panel without a background. |
||||||
|
transparent: bool | *false |
||||||
|
// Name of default datasource. |
||||||
|
datasource?: string |
||||||
|
// Grid position. |
||||||
|
gridPos?: { |
||||||
|
// Panel |
||||||
|
h: number & >0 | *9 |
||||||
|
// Panel |
||||||
|
w: number & >0 & <=24 | *12 |
||||||
|
// Panel x |
||||||
|
x: number & >=0 & <24 | *0 |
||||||
|
// Panel y |
||||||
|
y: number & >=0 | *0 |
||||||
|
// true if fixed |
||||||
|
static?: bool |
||||||
|
} |
||||||
|
// Panel links. |
||||||
|
// links?: [..._panelLink] |
||||||
|
// Name of template variable to repeat for. |
||||||
|
repeat?: string |
||||||
|
// Direction to repeat in if 'repeat' is set. |
||||||
|
// "h" for horizontal, "v" for vertical. |
||||||
|
repeatDirection: *"h" | "v" |
||||||
|
// 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. |
||||||
|
targets?: [...{}] |
||||||
|
|
||||||
|
// The values depend on panel type |
||||||
|
options: {...} |
||||||
|
|
||||||
|
fieldConfig: { |
||||||
|
defaults: { |
||||||
|
// The display value for this field. This supports template variables blank is auto |
||||||
|
displayName?: string |
||||||
|
|
||||||
|
// 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 |
||||||
|
|
||||||
|
// Human readable field metadata |
||||||
|
description?: string |
||||||
|
|
||||||
|
// 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 |
||||||
|
|
||||||
|
// True if data source can write a value to the path. Auth/authz are supported separately |
||||||
|
writeable?: bool |
||||||
|
|
||||||
|
// True if data source field supports ad-hoc filters |
||||||
|
filterable?: bool |
||||||
|
|
||||||
|
// Numeric Options |
||||||
|
unit?: string |
||||||
|
|
||||||
|
// Significant digits (for display) |
||||||
|
decimals?: number |
||||||
|
|
||||||
|
min?: number |
||||||
|
max?: number |
||||||
|
|
||||||
|
// // Convert input values into a display string |
||||||
|
// mappings?: ValueMapping[]; |
||||||
|
|
||||||
|
// // Map numeric values to states |
||||||
|
// thresholds?: ThresholdsConfig; |
||||||
|
|
||||||
|
// // Map values to a display color |
||||||
|
// color?: FieldColor; |
||||||
|
|
||||||
|
// // Used when reducing field values |
||||||
|
// nullValueMode?: NullValueMode; |
||||||
|
|
||||||
|
// // The behavior when clicking on a result |
||||||
|
// links?: DataLink[]; |
||||||
|
|
||||||
|
// Alternative to empty string |
||||||
|
noValue?: string |
||||||
|
|
||||||
|
// Can always exist. Valid fields within this are |
||||||
|
// defined by the panel plugin - that's the |
||||||
|
// PanelFieldConfig that comes from the plugin. |
||||||
|
custom?: {...} |
||||||
|
} |
||||||
|
overrides: [...{ |
||||||
|
matcher: { |
||||||
|
id: string | *"" |
||||||
|
options?: _ |
||||||
|
} |
||||||
|
properties: [...{ |
||||||
|
id: string | *"" |
||||||
|
value?: _ |
||||||
|
}] |
||||||
|
}] |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
] |
||||||
|
] |
||||||
|
} |
||||||
|
|
||||||
|
#Latest: { |
||||||
|
#Dashboard: Family.latest |
||||||
|
#Panel: Family.latest._Panel |
||||||
|
} |
@ -0,0 +1,21 @@ |
|||||||
|
package scuemata |
||||||
|
|
||||||
|
// Definition of the shape of a panel plugin's schema declarations in its |
||||||
|
// schema.cue file. |
||||||
|
// |
||||||
|
// Note that these keys do not appear directly in any real JSON artifact; |
||||||
|
// rather, they are composed into panel structures as they are defined within |
||||||
|
// the larger Dashboard schema. |
||||||
|
#PanelSchema: { |
||||||
|
PanelOptions: {...} |
||||||
|
PanelFieldConfig: {...} |
||||||
|
} |
||||||
|
|
||||||
|
// A lineage of panel schema |
||||||
|
#PanelLineage: [#PanelSchema, ...#PanelSchema] |
||||||
|
|
||||||
|
// Panel plugin-specific Family |
||||||
|
#PanelFamily: { |
||||||
|
lineages: [#PanelLineage, ...#PanelLineage] |
||||||
|
migrations: [...#Migration] |
||||||
|
} |
@ -0,0 +1,60 @@ |
|||||||
|
package scuemata |
||||||
|
|
||||||
|
// A family is a collection of schemas that specify a single kind of object, |
||||||
|
// allowing evolution of the canonical schema for that kind of object over time. |
||||||
|
// |
||||||
|
// The schemas are organized into a list of Lineages, which are themselves ordered |
||||||
|
// lists of schemas where each schema with its predecessor in the lineage. |
||||||
|
// |
||||||
|
// If it is desired to define a schema with a breaking schema relative to its |
||||||
|
// predecessors, a new Lineage must be created, as well as a Migration that defines |
||||||
|
// a mapping to the new schema from the latest schema in prior Lineage. |
||||||
|
// |
||||||
|
// The version number of a schema is not controlled by the schema itself, but by |
||||||
|
// its position in the list of lineages - e.g., 0.0 corresponds to the first |
||||||
|
// schema in the first lineage. |
||||||
|
#Family: { |
||||||
|
lineages: [#Lineage, ...#Lineage] |
||||||
|
migrations: [...#Migration] |
||||||
|
let lseq = lineages[len(lineages)-1] |
||||||
|
latest: #LastSchema & {_p: lseq} |
||||||
|
} |
||||||
|
|
||||||
|
// A Lineage is a non-empty list containing an ordered series of schemas that |
||||||
|
// all describe a single kind of object, where each schema is backwards |
||||||
|
// compatible with its predecessor. |
||||||
|
#Lineage: [{...}, ...{...}] |
||||||
|
|
||||||
|
#LastSchema: { |
||||||
|
_p: #Lineage |
||||||
|
_p[len(_p)-1] |
||||||
|
} |
||||||
|
|
||||||
|
// A Migration defines a relation between two schemas, "_from" and "_to". The |
||||||
|
// relation expresses any complex mappings that must be performed to |
||||||
|
// transform an input artifact valid with respect to the _from schema, into |
||||||
|
// an artifact valid with respect to the _to schema. This is accomplished |
||||||
|
// in two stages: |
||||||
|
// 1. A Migration is initially defined by passing in schemas for _from and _to, |
||||||
|
// and mappings that translate _from to _to are defined in _rel. |
||||||
|
// 2. A concrete object may then be unified with _to, resulting in its values |
||||||
|
// being mapped onto "result" by way of _rel. |
||||||
|
// |
||||||
|
// This is the absolute simplest possible definition of a Migration. It's |
||||||
|
// incumbent on the implementor to manually ensure the correctness and |
||||||
|
// completeness of the mapping. The primary value in defining such a generic |
||||||
|
// structure is to allow comparably generic logic for migrating concrete |
||||||
|
// artifacts through schema changes. |
||||||
|
// |
||||||
|
// If _to isn't backwards compatible (accretion-only) with _from, then _rel must |
||||||
|
// explicitly enumerate every field in _from and map it to a field in _to, even |
||||||
|
// if they're identical. This is laborious for anything outside trivially tiny |
||||||
|
// schema. We'll want to eventually add helpers for whitelisting or blacklisting |
||||||
|
// of paths in _from, so that migrations of larger schema can focus narrowly on |
||||||
|
// the points of actual change. |
||||||
|
#Migration: { |
||||||
|
from: {...} |
||||||
|
to: {...} |
||||||
|
rel: {...} |
||||||
|
result: to & rel |
||||||
|
} |
@ -0,0 +1,93 @@ |
|||||||
|
package grafanaschema |
||||||
|
|
||||||
|
TableCellDisplayMode: { |
||||||
|
Auto: "auto", |
||||||
|
ColorText: "color-text", |
||||||
|
ColorBackground: "color-background", |
||||||
|
GradientGauge: "gradient-gauge", |
||||||
|
LcdGauge: "lcd-gauge", |
||||||
|
JSONView: "json-view", |
||||||
|
BasicGauge: "basic", |
||||||
|
Image: "image", |
||||||
|
} @cuetsy(targetType="enum") |
||||||
|
|
||||||
|
TableFieldOptions: { |
||||||
|
width?: number |
||||||
|
align: FieldTextAlignment | *"auto" |
||||||
|
displayMode: TableCellDisplayMode | *"auto" |
||||||
|
hidden?: bool // ?? default is missing or false ?? |
||||||
|
} @cuetsy(targetType="interface") |
||||||
|
|
||||||
|
TableSortByFieldState: { |
||||||
|
displayName: string |
||||||
|
desc?: bool |
||||||
|
} @cuetsy(targetType="interface") |
||||||
|
|
||||||
|
TooltipMode: "single" | "multi" | "none" @cuetsy(targetType="type") |
||||||
|
FieldTextAlignment: "auto" | "left" | "right" | "center" @cuetsy(targetType="type") |
||||||
|
AxisPlacement: "auto" | "top" | "right" | "bottom" | "left" | "hidden" @cuetsy(targetType="enum") |
||||||
|
PointVisibility: "auto" | "never" | "always" @cuetsy(targetType="enum") |
||||||
|
DrawStyle: "line" | "bars" | "points" @cuetsy(targetType="enum") |
||||||
|
LineInterpolation: "linear" | "smooth" | "stepBefore" | "stepAfter" @cuetsy(targetType="enum") |
||||||
|
ScaleDistribution: "linear" | "log" @cuetsy(targetType="enum") |
||||||
|
GraphGradientMode: "none" | "opacity" | "hue" | "scheme" @cuetsy(targetType="enum") |
||||||
|
LineStyle: { |
||||||
|
fill?: "solid" | "dash" | "dot" | "square" |
||||||
|
dash?: [number] |
||||||
|
} @cuetsy(targetType="interface") |
||||||
|
LineConfig: { |
||||||
|
lineColor?: string |
||||||
|
lineWidth?: number |
||||||
|
lineInterpolation?: LineInterpolation |
||||||
|
lineStyle?: LineStyle |
||||||
|
spanNulls?: bool |
||||||
|
} @cuetsy(targetType="interface") |
||||||
|
FillConfig: { |
||||||
|
fillColor?: string |
||||||
|
fillOpacity?: number |
||||||
|
fillBelowTo?: string |
||||||
|
} @cuetsy(targetType="interface") |
||||||
|
PointsConfig: { |
||||||
|
showPoints?: PointVisibility |
||||||
|
pointSize?: number |
||||||
|
pointColor?: string |
||||||
|
pointSymbol?: string |
||||||
|
} @cuetsy(targetType="interface") |
||||||
|
ScaleDistributionConfig: { |
||||||
|
type: ScaleDistribution |
||||||
|
log?: number |
||||||
|
} @cuetsy(targetType="interface") |
||||||
|
AxisConfig: { |
||||||
|
axisPlacement?: AxisPlacement |
||||||
|
axisLabel?: string |
||||||
|
axisWidth?: number |
||||||
|
axisSoftMin?: number |
||||||
|
axisSoftMax?: number |
||||||
|
scaleDistribution?: ScaleDistributionConfig |
||||||
|
} @cuetsy(targetType="interface") |
||||||
|
HideSeriesConfig: { |
||||||
|
tooltip: bool |
||||||
|
legend: bool |
||||||
|
graph: bool |
||||||
|
} @cuetsy(targetType="interface") |
||||||
|
LegendPlacement: "bottom" | "right" @cuetsy(targetType="type") |
||||||
|
LegendDisplayMode: "list" | "table" | "hidden" @cuetsy(targetType="enum") |
||||||
|
GraphTooltipOptions: { |
||||||
|
mode: TooltipMode |
||||||
|
} @cuetsy(targetType="interface") |
||||||
|
TableFieldOptions: { |
||||||
|
width?: number |
||||||
|
align: FieldTextAlignment | *"auto" |
||||||
|
displayMode: TableCellDisplayMode | *"auto" |
||||||
|
hidden?: bool |
||||||
|
} @cuetsy(targetType="interface") |
||||||
|
GraphFieldConfig: LineConfig & FillConfig & PointsConfig & AxisConfig & { |
||||||
|
drawStyle?: DrawStyle |
||||||
|
gradientMode?: GraphGradientMode |
||||||
|
hideFrom?: HideSeriesConfig |
||||||
|
} @cuetsy(targetType="interface") |
||||||
|
VizLegendOptions: { |
||||||
|
displayMode: LegendDisplayMode |
||||||
|
placement: LegendPlacement |
||||||
|
calcs: [string] |
||||||
|
} @cuetsy(targetType="interface") |
@ -1,68 +0,0 @@ |
|||||||
package main |
|
||||||
|
|
||||||
#Dashboard: { |
|
||||||
// Unique numeric identifier for the dashboard. (generated by the db) |
|
||||||
id: int |
|
||||||
// Unique dashboard identifier that can be generated by anyone. string (8-40) |
|
||||||
uid: string |
|
||||||
// Title of dashboard. |
|
||||||
title?: string |
|
||||||
// Description of dashboard. |
|
||||||
description?: string |
|
||||||
// Tags associated with dashboard. |
|
||||||
tags?: [...string] |
|
||||||
// Theme of dashboard. |
|
||||||
style: *"light" | "dark" |
|
||||||
// Timezone of dashboard, |
|
||||||
timezone?: *"browser" | "utc" |
|
||||||
// Whether a dashboard is editable or not. |
|
||||||
editable: bool | *true |
|
||||||
// 0 for no shared crosshair or tooltip (default). |
|
||||||
// 1 for shared crosshair. |
|
||||||
// 2 for shared crosshair AND shared tooltip. |
|
||||||
graphTooltip: int >= 0 <= 2 | *0 |
|
||||||
// Time range for dashboard, e.g. last 6 hours, last 7 days, etc |
|
||||||
time?: { |
|
||||||
from: string | *"now-6h" |
|
||||||
to: string | *"now" |
|
||||||
} |
|
||||||
// Timepicker metadata. |
|
||||||
timepicker?: { |
|
||||||
// Whether timepicker is collapsed or not. |
|
||||||
collapse: bool | *false |
|
||||||
// Whether timepicker is enabled or not. |
|
||||||
enable: bool | *true |
|
||||||
// Whether timepicker is visible or not. |
|
||||||
hidden: bool | *false |
|
||||||
// Selectable intervals for auto-refresh. |
|
||||||
refresh_intervals: [...string] | *["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"] |
|
||||||
} |
|
||||||
// Templating. |
|
||||||
templating?: list: [...{}] |
|
||||||
// Annotations. |
|
||||||
annotations?: list: [...{ |
|
||||||
builtIn: int | *0 |
|
||||||
// Datasource to use for annotation. |
|
||||||
datasource: string |
|
||||||
// Whether annotation is enabled. |
|
||||||
enable?: bool | *true |
|
||||||
// Whether to hide annotation. |
|
||||||
hide?: bool | *false |
|
||||||
// Annotation icon color. |
|
||||||
iconColor?: string |
|
||||||
// Name of annotation. |
|
||||||
name?: string |
|
||||||
// Query for annotation data. |
|
||||||
rawQuery: string |
|
||||||
showIn: int | *0 |
|
||||||
}] | *[] |
|
||||||
// Auto-refresh interval. |
|
||||||
refresh: string |
|
||||||
// Version of the JSON schema, incremented each time a Grafana update brings |
|
||||||
// changes to said schema. |
|
||||||
schemaVersion: int | *25 |
|
||||||
// Version of the dashboard, incremented each time the dashboard is updated. |
|
||||||
version: string |
|
||||||
// Dashboard panels. |
|
||||||
panels?: [...{}] |
|
||||||
} |
|
@ -1,86 +0,0 @@ |
|||||||
# Dashboard Schemas |
|
||||||
|
|
||||||
Schema description documents for [Grafana Dashboard |
|
||||||
JSON](https://grafana.com/docs/grafana/latest/reference/dashboard/) and core |
|
||||||
panels. |
|
||||||
|
|
||||||
> **Note:** This directory is experimental. The schemas are not currently |
|
||||||
> implemented or enforced in Grafana. |
|
||||||
|
|
||||||
Schemas are defined in [Cue](https://cuelang.org/). Cue was chosen because it |
|
||||||
strongly facilitates our primary use cases - [schema |
|
||||||
definition](https://cuelang.org/docs/usecases/datadef/), [data |
|
||||||
validation](https://cuelang.org/docs/usecases/validation/), and [code |
|
||||||
generation/extraction](https://cuelang.org/docs/usecases/generate/). |
|
||||||
|
|
||||||
## Schema Organization |
|
||||||
|
|
||||||
Each schema describes part of a dashboard. `Dashboard.cue` is the main dashboard |
|
||||||
schema object. All other schemas describe nested objects within a dashboard. |
|
||||||
They are grouped in the following directories: |
|
||||||
|
|
||||||
* `panels` - schemas for |
|
||||||
[panels](https://grafana.com/docs/grafana/latest/panels/panels-overview/). |
|
||||||
* `targets` - targets represent |
|
||||||
[queries](https://grafana.com/docs/grafana/latest/panels/queries/). Each [data |
|
||||||
source](https://grafana.com/docs/grafana/latest/datasources/) type has a |
|
||||||
unique target schema. |
|
||||||
* `variables` - schemas for |
|
||||||
[variables](https://grafana.com/docs/grafana/latest/variables/variable-types/). |
|
||||||
* `transformations` - schemas for |
|
||||||
[transformations](https://grafana.com/docs/grafana/latest/panels/transformations/types-options/). |
|
||||||
|
|
||||||
The following somewhat conveys how they fit together when constructing a |
|
||||||
dashboard: |
|
||||||
|
|
||||||
``` |
|
||||||
+-----------+ +-----------+ |
|
||||||
| Dashboard +------> Variables | |
|
||||||
+---------+-+ +-----------+ |
|
||||||
| +--------+ +---------+ |
|
||||||
+----> Panels +----> Targets | |
|
||||||
+------+-+ +---------+ |
|
||||||
| +-----------------+ |
|
||||||
+------> Transformations | |
|
||||||
+-----------------+ |
|
||||||
``` |
|
||||||
|
|
||||||
## Definitions |
|
||||||
|
|
||||||
All schemas are [Cue |
|
||||||
definitions](https://cuelang.org/docs/references/spec/#definitions-and-hidden-fields). |
|
||||||
Schemas intended to be exported must begin with a capital letter. For example, |
|
||||||
[Gauge](./panels/Gauge.cue). Definitions beginning with a lowercase letter will |
|
||||||
not be exported. These are reusable components for constructing the exported |
|
||||||
definitions. For example, [`#panel`](./panels/panel.cue) is intended to |
|
||||||
be a base schema for panels. `#Gauge` extends `#panel` with the following: |
|
||||||
|
|
||||||
``` |
|
||||||
#Gauge: panel & { |
|
||||||
... |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
## Exporting OpenAPI |
|
||||||
|
|
||||||
[OpenAPI](https://www.openapis.org/) schemas can be exported from these CUE |
|
||||||
sources. |
|
||||||
|
|
||||||
### Command Line |
|
||||||
|
|
||||||
While you can use `cue export` to output OpenAPI documents, it does not expand |
|
||||||
references which makes the output unusable. |
|
||||||
|
|
||||||
``` |
|
||||||
cue export --out openapi -o - ./... |
|
||||||
``` |
|
||||||
|
|
||||||
### Using Go |
|
||||||
|
|
||||||
You need to use Go to generate useable OpenAPI schemas. This directory contains |
|
||||||
a Go program that will output just the OpenAPI schemas for one or many Cue |
|
||||||
packages. |
|
||||||
|
|
||||||
``` |
|
||||||
go run . <entrypoint> ... |
|
||||||
``` |
|
@ -1 +0,0 @@ |
|||||||
module: "github.com/grafana/grafana/dashboard-schemas" |
|
@ -1,5 +0,0 @@ |
|||||||
module github.com/grafana/grafana/dashboard-schemas |
|
||||||
|
|
||||||
go 1.15 |
|
||||||
|
|
||||||
require cuelang.org/go v0.2.2 |
|
@ -1,185 +0,0 @@ |
|||||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= |
|
||||||
cuelang.org/go v0.2.2 h1:i/wFo48WDibGHKQTRZ08nB8PqmGpVpQ2sRflZPj73nQ= |
|
||||||
cuelang.org/go v0.2.2/go.mod h1:Dyjk8Y/B3CfFT1jQKJU0g5PpCeMiDe0yMOhk57oXwqo= |
|
||||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= |
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= |
|
||||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= |
|
||||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= |
|
||||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= |
|
||||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= |
|
||||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= |
|
||||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= |
|
||||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= |
|
||||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= |
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= |
|
||||||
github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= |
|
||||||
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= |
|
||||||
github.com/cockroachdb/apd/v2 v2.0.1 h1:y1Rh3tEU89D+7Tgbw+lp52T6p/GJLpDmNvr10UWqLTE= |
|
||||||
github.com/cockroachdb/apd/v2 v2.0.1/go.mod h1:DDxRlzC2lo3/vSlmSoS7JkqbbrARPuFOGr0B9pvN3Gw= |
|
||||||
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= |
|
||||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= |
|
||||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= |
|
||||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= |
|
||||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= |
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= |
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= |
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= |
|
||||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= |
|
||||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= |
|
||||||
github.com/emicklei/proto v1.6.15 h1:XbpwxmuOPrdES97FrSfpyy67SSCV/wBIKXqgJzh6hNw= |
|
||||||
github.com/emicklei/proto v1.6.15/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A= |
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= |
|
||||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= |
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= |
|
||||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= |
|
||||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= |
|
||||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= |
|
||||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= |
|
||||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= |
|
||||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= |
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= |
|
||||||
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= |
|
||||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= |
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= |
|
||||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= |
|
||||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= |
|
||||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= |
|
||||||
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= |
|
||||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= |
|
||||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= |
|
||||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= |
|
||||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= |
|
||||||
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= |
|
||||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= |
|
||||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= |
|
||||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= |
|
||||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= |
|
||||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= |
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= |
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= |
|
||||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= |
|
||||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= |
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= |
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= |
|
||||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= |
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= |
|
||||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= |
|
||||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= |
|
||||||
github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A= |
|
||||||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= |
|
||||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= |
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= |
|
||||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= |
|
||||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= |
|
||||||
github.com/mpvl/unique v0.0.0-20150818121801-cbe035fff7de h1:D5x39vF5KCwKQaw+OC9ZPiLVHXz3UFw2+psEX+gYcto= |
|
||||||
github.com/mpvl/unique v0.0.0-20150818121801-cbe035fff7de/go.mod h1:kJun4WP5gFuHZgRjZUWWuH1DTxCtxbHDOIJsudS8jzY= |
|
||||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= |
|
||||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= |
|
||||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= |
|
||||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= |
|
||||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= |
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= |
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= |
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= |
|
||||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= |
|
||||||
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= |
|
||||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= |
|
||||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= |
|
||||||
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= |
|
||||||
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= |
|
||||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= |
|
||||||
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= |
|
||||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= |
|
||||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= |
|
||||||
github.com/rogpeppe/go-internal v1.6.0/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= |
|
||||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= |
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= |
|
||||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= |
|
||||||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= |
|
||||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= |
|
||||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= |
|
||||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= |
|
||||||
github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= |
|
||||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= |
|
||||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= |
|
||||||
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= |
|
||||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= |
|
||||||
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= |
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= |
|
||||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= |
|
||||||
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= |
|
||||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= |
|
||||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= |
|
||||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= |
|
||||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= |
|
||||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= |
|
||||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= |
|
||||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= |
|
||||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= |
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= |
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= |
|
||||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= |
|
||||||
golang.org/x/exp v0.0.0-20200513190911-00229845015e/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= |
|
||||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= |
|
||||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= |
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= |
|
||||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= |
|
||||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= |
|
||||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= |
|
||||||
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= |
|
||||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= |
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= |
|
||||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= |
|
||||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= |
|
||||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= |
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= |
|
||||||
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= |
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= |
|
||||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZVDP2S5ou6y0gSgXHu8= |
|
||||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= |
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= |
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= |
|
||||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= |
|
||||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= |
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= |
|
||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= |
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= |
|
||||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= |
|
||||||
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= |
|
||||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= |
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= |
|
||||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
|
||||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= |
|
||||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= |
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= |
|
||||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= |
|
||||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= |
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= |
|
||||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= |
|
||||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= |
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= |
|
||||||
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= |
|
||||||
golang.org/x/tools v0.0.0-20200612220849-54c614fe050c h1:g6oFfz6Cmw68izP3xsdud3Oxu145IPkeFzyRg58AKHM= |
|
||||||
golang.org/x/tools v0.0.0-20200612220849-54c614fe050c/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= |
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= |
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= |
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= |
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= |
|
||||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= |
|
||||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= |
|
||||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= |
|
||||||
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= |
|
||||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= |
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= |
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= |
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= |
|
||||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= |
|
||||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= |
|
||||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= |
|
||||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= |
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= |
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200121175148-a6ecf24a6d71 h1:Xe2gvTZUJpsvOWUnvmL/tmhVBZUmHSvLbMjRj6NUUKo= |
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200121175148-a6ecf24a6d71/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= |
|
||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= |
|
@ -1,58 +0,0 @@ |
|||||||
package main |
|
||||||
|
|
||||||
import ( |
|
||||||
"fmt" |
|
||||||
"log" |
|
||||||
"os" |
|
||||||
|
|
||||||
"cuelang.org/go/cue" |
|
||||||
"cuelang.org/go/cue/load" |
|
||||||
"cuelang.org/go/encoding/openapi" |
|
||||||
) |
|
||||||
|
|
||||||
func main() { |
|
||||||
b, err := openAPISchemas(os.Args[1:]) |
|
||||||
if err != nil { |
|
||||||
log.Fatal(err) |
|
||||||
} |
|
||||||
fmt.Println(string(b)) |
|
||||||
} |
|
||||||
|
|
||||||
// openAPISchemas returns OpenAPI schema JSON of the Cue entrypoints passed to
|
|
||||||
// it. It is not a valid OpenAPI document - just the schemas.
|
|
||||||
func openAPISchemas(entrypoints []string) ([]byte, error) { |
|
||||||
|
|
||||||
var r cue.Runtime |
|
||||||
cfg := openapi.Config{ |
|
||||||
ExpandReferences: true, |
|
||||||
} |
|
||||||
bis := load.Instances(entrypoints, nil) |
|
||||||
|
|
||||||
// collect all schemas
|
|
||||||
var pairs []openapi.KeyValue |
|
||||||
for _, bi := range bis { |
|
||||||
if bi.Err != nil { |
|
||||||
return nil, bi.Err |
|
||||||
} |
|
||||||
inst, err := r.Build(bi) |
|
||||||
if err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
om, err := cfg.Schemas(inst) |
|
||||||
if err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
pairs = append(pairs, om.Pairs()...) |
|
||||||
} |
|
||||||
|
|
||||||
// add all schemas to new ordered map
|
|
||||||
om := openapi.OrderedMap{} |
|
||||||
om.SetAll(pairs) |
|
||||||
|
|
||||||
j, err := om.MarshalJSON() |
|
||||||
if err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
|
|
||||||
return j, nil |
|
||||||
} |
|
@ -1,47 +0,0 @@ |
|||||||
package main |
|
||||||
|
|
||||||
import ( |
|
||||||
"encoding/json" |
|
||||||
"testing" |
|
||||||
) |
|
||||||
|
|
||||||
func TestOpenAPISchemas(t *testing.T) { |
|
||||||
|
|
||||||
tests := map[string]struct { |
|
||||||
entrypoints []string |
|
||||||
}{ |
|
||||||
"All packages": { |
|
||||||
entrypoints: []string{"./..."}, |
|
||||||
}, |
|
||||||
"One package": { |
|
||||||
entrypoints: []string{"./panels"}, |
|
||||||
}, |
|
||||||
"Many packags": { |
|
||||||
entrypoints: []string{ |
|
||||||
"./panels", |
|
||||||
"./targets", |
|
||||||
"./transformations", |
|
||||||
"./variables", |
|
||||||
}, |
|
||||||
}, |
|
||||||
} |
|
||||||
|
|
||||||
for testName, test := range tests { |
|
||||||
|
|
||||||
t.Logf("Running test case %s...", testName) |
|
||||||
|
|
||||||
j, err := openAPISchemas(test.entrypoints) |
|
||||||
if err != nil { |
|
||||||
t.Fatal(err) |
|
||||||
} |
|
||||||
|
|
||||||
// We don't want to validate the JSON content since it's expected to change
|
|
||||||
// often. Only that it is valid JSON by unmarshalling it.
|
|
||||||
|
|
||||||
var iface interface{} |
|
||||||
err = json.Unmarshal(j, &iface) |
|
||||||
if err != nil { |
|
||||||
t.Fatal(err) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -1,74 +0,0 @@ |
|||||||
package panels |
|
||||||
|
|
||||||
// Gauge is a single value panel that can repeat a gauge for every series, |
|
||||||
// column or row. |
|
||||||
#Gauge: _panel & { |
|
||||||
// Field config. |
|
||||||
fieldConfig: { |
|
||||||
// Defaults. |
|
||||||
defaults: { |
|
||||||
// Custom. |
|
||||||
custom: {} |
|
||||||
// Unit. |
|
||||||
unit: string |
|
||||||
// Min. |
|
||||||
min: int |
|
||||||
// Max. |
|
||||||
max: int |
|
||||||
// Decimals. |
|
||||||
decimals: int |
|
||||||
// Change the field or series name. |
|
||||||
displayName: string |
|
||||||
// What to show when there is no value. |
|
||||||
noValue: string |
|
||||||
// Threshold config. |
|
||||||
thresholds: _thresholds |
|
||||||
// Mappings. |
|
||||||
mappings: [..._mapping] |
|
||||||
// Data Links. |
|
||||||
links: [..._dataLink] |
|
||||||
} |
|
||||||
// Overrides. |
|
||||||
overrides: [..._override] |
|
||||||
} |
|
||||||
// Options. |
|
||||||
options: { |
|
||||||
// Reduce options. |
|
||||||
reduceOptions: { |
|
||||||
// * `true` - Show a calculated value based on all rows. |
|
||||||
// * `false` - Show a separate stat for every row. |
|
||||||
values: bool | *false |
|
||||||
// If values is false, sets max number of rows to |
|
||||||
// display. |
|
||||||
limit: int |
|
||||||
// Reducer function/calculation. |
|
||||||
calcs: [ |
|
||||||
"allIsZero", |
|
||||||
"allIsNull", |
|
||||||
"changeCount", |
|
||||||
"count", |
|
||||||
"delta", |
|
||||||
"diff", |
|
||||||
"distinctCount", |
|
||||||
"first", |
|
||||||
"firstNotNull", |
|
||||||
"lastNotNull", |
|
||||||
"last", |
|
||||||
"logmin", |
|
||||||
"max", |
|
||||||
"min", |
|
||||||
"range", |
|
||||||
"step", |
|
||||||
"sum", |
|
||||||
] | *["mean"] |
|
||||||
// Fields that should be included in the panel. |
|
||||||
fields: string | *"" |
|
||||||
} |
|
||||||
// Render the threshold values around the gauge bar. |
|
||||||
showThresholdLabels: bool | *false |
|
||||||
// Render the thresholds as an outer bar. |
|
||||||
showThresholdMarkers: bool | *true |
|
||||||
} |
|
||||||
// Panel type. |
|
||||||
type: string | *"gauge" |
|
||||||
} |
|
@ -1,192 +0,0 @@ |
|||||||
package panels |
|
||||||
|
|
||||||
#Graph: _panel & { |
|
||||||
// Display values as a bar chart. |
|
||||||
bars: bool | *false |
|
||||||
// Dashed line length. |
|
||||||
dashLength: int | *10 |
|
||||||
// Show line with dashes. |
|
||||||
dashes: bool | *false |
|
||||||
// Dashed line spacing when `dashes` is true. |
|
||||||
spaceLength: int | *10 |
|
||||||
// Controls how many decimals are displayed for legend values and graph hover |
|
||||||
// tooltips. |
|
||||||
decimals: int |
|
||||||
// Field config. |
|
||||||
fieldConfig: { |
|
||||||
// Defaults. |
|
||||||
defaults: custom: {} |
|
||||||
// Overrides. |
|
||||||
overrides: [..._override] |
|
||||||
} |
|
||||||
// Amount of color fill for a series. Expects a value between 0 and 1. |
|
||||||
fill: number >= 0 <= 1 | *1 |
|
||||||
// Degree of gradient on the area fill. 0 is no gradient, 10 is a steep |
|
||||||
// gradient. |
|
||||||
fillGradient: int >= 0 <= 10 | *0 |
|
||||||
// Hide the series. |
|
||||||
hiddenSeries: bool | *false |
|
||||||
// Lengend options. |
|
||||||
legend: { |
|
||||||
// Whether to display legend in table. |
|
||||||
alignAsTable: bool | *false |
|
||||||
// Average of all values returned from the metric query. |
|
||||||
avg: bool | *false |
|
||||||
// Last value returned from the metric query. |
|
||||||
current: bool | *false |
|
||||||
// Maximum of all values returned from the metric query. |
|
||||||
max: bool | *false |
|
||||||
// Minimum of all values returned from the metric query. |
|
||||||
min: bool | *false |
|
||||||
// Display legend to the right. |
|
||||||
rightSide: bool | *false |
|
||||||
// Show or hide the legend. |
|
||||||
show: bool | *true |
|
||||||
// Available when `rightSide` is true. The minimum width for the legend in |
|
||||||
// pixels. |
|
||||||
sideWidth?: int |
|
||||||
// Sum of all values returned from the metric query. |
|
||||||
total: bool | *false |
|
||||||
// Values. |
|
||||||
values: bool | *true |
|
||||||
} |
|
||||||
// Display values as a line graph. |
|
||||||
lines: bool | *true |
|
||||||
// The width of the line for a series. |
|
||||||
linewidth: int | *1 |
|
||||||
// How null values are displayed. |
|
||||||
// * 'null' - If there is a gap in the series, meaning a null value, then the |
|
||||||
// line in the graph will be broken and show the gap. |
|
||||||
// * 'null as zero' - If there is a gap in the series, meaning a null value, |
|
||||||
// then it will be displayed as a zero value in the graph panel. |
|
||||||
// * 'connected' - If there is a gap in the series, meaning a null value or |
|
||||||
// values, then the line will skip the gap and connect to the next non-null |
|
||||||
// value. |
|
||||||
nullPointMode: string | *"null" |
|
||||||
// Options. |
|
||||||
options: { |
|
||||||
// Data links. |
|
||||||
dataLinks: [..._dataLink] |
|
||||||
} |
|
||||||
// Available when `stack` is true. Each series is drawn as a percentage of the |
|
||||||
// total of all series. |
|
||||||
percentage: bool | *false |
|
||||||
// Controls how large the points are. |
|
||||||
pointradius: int |
|
||||||
// Display points for values. |
|
||||||
points: bool | *true |
|
||||||
// Renderer. |
|
||||||
renderer: string | *"flot" |
|
||||||
// Series overrides allow a series in a graph panel to be rendered |
|
||||||
// differently from the others. You can customize display options on a |
|
||||||
// per-series bases or by using regex rules. For example, one series can have |
|
||||||
// a thicker line width to make it stand out or be moved to the right Y-axis. |
|
||||||
seriesOverrides: [...{ |
|
||||||
// Alias or regex matching the series you'd like to target. |
|
||||||
alias?: string |
|
||||||
bars?: bool |
|
||||||
lines?: bool |
|
||||||
fill?: int |
|
||||||
fillGradient?: int |
|
||||||
linewidth?: int |
|
||||||
nullPointMode?: string |
|
||||||
fillBelowTo?: string |
|
||||||
steppedLine?: bool |
|
||||||
dashes?: bool |
|
||||||
hiddenSeries?: bool |
|
||||||
dashLength?: int |
|
||||||
spaceLength?: int |
|
||||||
points?: bool |
|
||||||
pointradius?: int |
|
||||||
stack?: int |
|
||||||
color?: string |
|
||||||
yaxis?: int |
|
||||||
zindex?: int |
|
||||||
transform?: string |
|
||||||
legend?: bool |
|
||||||
hideTooltip?: bool |
|
||||||
}] |
|
||||||
// Each series is stacked on top of another. |
|
||||||
stack: bool | *false |
|
||||||
// Draws adjacent points as staircase. |
|
||||||
steppedLine: bool | *false |
|
||||||
// Threshold config. |
|
||||||
thresholds: _thresholds |
|
||||||
// Time from. |
|
||||||
timeFrom: string |
|
||||||
// Time regions. |
|
||||||
timeRegions: [...string] |
|
||||||
// Time shift |
|
||||||
timeShift: string |
|
||||||
// Tooltip settings. |
|
||||||
tooltip: { |
|
||||||
// * true - The hover tooltip shows all series in the graph. Grafana |
|
||||||
// highlights the series that you are hovering over in bold in the series |
|
||||||
// list in the tooltip. |
|
||||||
// * false - The hover tooltip shows only a single series, the one that you |
|
||||||
// are hovering over on the graph. |
|
||||||
shared: bool | *true |
|
||||||
// * 0 (none) - The order of the series in the tooltip is determined by the |
|
||||||
// sort order in your query. For example, they could be alphabetically |
|
||||||
// sorted by series name. |
|
||||||
// * 1 (increasing) - The series in the hover tooltip are sorted by value |
|
||||||
// and in increasing order, with the lowest value at the top of the list. |
|
||||||
// * 2 (decreasing) - The series in the hover tooltip are sorted by value |
|
||||||
// and in decreasing order, with the highest value at the top of the list. |
|
||||||
sort: int >= 0 <= 2 | *2 |
|
||||||
// Value type. |
|
||||||
value_type: string | *"individual" |
|
||||||
} |
|
||||||
// Panel type. |
|
||||||
type: string | *"graph" |
|
||||||
xaxis: { |
|
||||||
// Buckets. |
|
||||||
buckets: string |
|
||||||
// The display mode completely changes the visualization of the graph |
|
||||||
// panel. It’s like three panels in one. The main mode is the time series |
|
||||||
// mode with time on the X-axis. The other two modes are a basic bar chart |
|
||||||
// mode with series on the X-axis instead of time and a histogram mode. |
|
||||||
// * 'time' - The X-axis represents time and that the data is grouped by |
|
||||||
// time (for example, by hour, or by minute). |
|
||||||
// * 'series' - The data is grouped by series and not by time. The Y-axis |
|
||||||
// still represents the value. |
|
||||||
// * 'histogram' - Converts the graph into a histogram. A histogram is a |
|
||||||
// kind of bar chart that groups numbers into ranges, often called buckets |
|
||||||
// or bins. Taller bars show that more data falls in that range. |
|
||||||
mode: string | *"time" |
|
||||||
// Name. |
|
||||||
name: string |
|
||||||
// Show or hide the axis. |
|
||||||
show: bool | *true |
|
||||||
// Values |
|
||||||
values: [...number] |
|
||||||
} |
|
||||||
yaxes: [...{ |
|
||||||
// Defines how many decimals are displayed for Y value. |
|
||||||
decimals: int |
|
||||||
// The display unit for the Y value. |
|
||||||
format: string | *"short" |
|
||||||
// The Y axis label. |
|
||||||
label: string |
|
||||||
// The scale to use for the Y value - linear, or logarithmic. |
|
||||||
// * 1 - linear |
|
||||||
// * 2 - log (base 2) |
|
||||||
// * 10 - log (base 10) |
|
||||||
// * 32 - log (base 32) |
|
||||||
// * 1024 - log (base 1024) |
|
||||||
logBase: int | *1 |
|
||||||
// The maximum Y value. |
|
||||||
max?: int |
|
||||||
// The minimum Y value. |
|
||||||
min?: int |
|
||||||
// Show or hide the axis. |
|
||||||
show: bool | *true |
|
||||||
}] |
|
||||||
yaxis: { |
|
||||||
// Align left and right Y-axes by value. |
|
||||||
align: bool | *false |
|
||||||
// Available when align is true. Value to use for alignment of left and |
|
||||||
// right Y-axes, starting from Y=0. |
|
||||||
alignLevel: int | *0 |
|
||||||
} |
|
||||||
} |
|
@ -1,24 +0,0 @@ |
|||||||
package panels |
|
||||||
|
|
||||||
// A row is a logical divider within a dashboard. It is used |
|
||||||
// to group panels together. |
|
||||||
#Row: { |
|
||||||
// Whether the row is collapsed or not. |
|
||||||
collapsed: bool | *true |
|
||||||
// Name of default data source. |
|
||||||
datasource?: string |
|
||||||
// Grid position. |
|
||||||
gridPos?: _gridPos |
|
||||||
// Dashboard panels. |
|
||||||
panels?: [...{}] |
|
||||||
// Name of template variable to repeat for. |
|
||||||
repeat?: string |
|
||||||
// Whether to display the title. |
|
||||||
showTitle: bool | *true |
|
||||||
// Title. |
|
||||||
title?: string |
|
||||||
// Size of title. |
|
||||||
titleSize: string | *"h6" |
|
||||||
// Panel type. |
|
||||||
type: string | *"row" |
|
||||||
} |
|
@ -1,12 +0,0 @@ |
|||||||
package panels |
|
||||||
|
|
||||||
_gridPos: { |
|
||||||
// Panel height. |
|
||||||
h?: int > 0 | *9 |
|
||||||
// Panel width. |
|
||||||
w?: int > 0 <= 24 | *12 |
|
||||||
// Panel x position. |
|
||||||
x?: int >= 0 < 24 | *0 |
|
||||||
// Panel y position. |
|
||||||
y?: int >= 0 | *0 |
|
||||||
} |
|
@ -1,14 +0,0 @@ |
|||||||
package panels |
|
||||||
|
|
||||||
_link: { |
|
||||||
// Link title. |
|
||||||
title?: string |
|
||||||
// Whether to open link in new browser tab. |
|
||||||
targetBlank: bool | *true |
|
||||||
// URL of link. |
|
||||||
url: string |
|
||||||
} |
|
||||||
|
|
||||||
_panelLink: _link |
|
||||||
|
|
||||||
_dataLink: _link |
|
@ -1,11 +0,0 @@ |
|||||||
package panels |
|
||||||
|
|
||||||
_mapping: { |
|
||||||
id: int |
|
||||||
from: string |
|
||||||
operator: string |
|
||||||
to: string |
|
||||||
text: string |
|
||||||
type: int |
|
||||||
value: string |
|
||||||
} |
|
@ -1,12 +0,0 @@ |
|||||||
package panels |
|
||||||
|
|
||||||
_override: { |
|
||||||
matcher: { |
|
||||||
id: string |
|
||||||
options: string |
|
||||||
} |
|
||||||
properties: [...{ |
|
||||||
id: string |
|
||||||
value: int |
|
||||||
}] |
|
||||||
} |
|
@ -1,24 +0,0 @@ |
|||||||
package panels |
|
||||||
|
|
||||||
_panel: { |
|
||||||
// Panel title. |
|
||||||
title?: string |
|
||||||
// Description. |
|
||||||
description?: string |
|
||||||
// Whether to display the panel without a background. |
|
||||||
transparent: bool | *false |
|
||||||
// Name of default datasource. |
|
||||||
datasource?: string |
|
||||||
// Grid position. |
|
||||||
gridPos?: _gridPos |
|
||||||
// Panel links. |
|
||||||
links?: [..._panelLink] |
|
||||||
// Name of template variable to repeat for. |
|
||||||
repeat?: string |
|
||||||
// Direction to repeat in if 'repeat' is set. |
|
||||||
// "h" for horizontal, "v" for vertical. |
|
||||||
repeatDirection: *"h" | "v" |
|
||||||
// Panel targets - datasource and query configurations to use as |
|
||||||
// a basis for vizualization. |
|
||||||
targets?: [...{}] |
|
||||||
} |
|
@ -1,11 +0,0 @@ |
|||||||
package panels |
|
||||||
|
|
||||||
_thresholds: { |
|
||||||
// Threshold mode. |
|
||||||
mode: string | *"absolute" |
|
||||||
// Threshold steps. |
|
||||||
steps: [...{ |
|
||||||
color: string |
|
||||||
value: number |
|
||||||
}] |
|
||||||
} |
|
@ -1,19 +0,0 @@ |
|||||||
package targets |
|
||||||
|
|
||||||
#Prometheus: { |
|
||||||
// Query expression. |
|
||||||
expr: string |
|
||||||
// Controls the name of the time series, using name or pattern. |
|
||||||
legendFormat?: string |
|
||||||
// Interval. |
|
||||||
interval?: int | *1 |
|
||||||
// Target reference ID. |
|
||||||
refId: string |
|
||||||
// Perform an “instant” query, to return only the latest value that |
|
||||||
// Prometheus has scraped for the requested time series. |
|
||||||
instant: bool | *false |
|
||||||
// Resolution. |
|
||||||
intervalFactor?: int |
|
||||||
// Format. |
|
||||||
format: *"time_series" | "table" | "heat_map" |
|
||||||
} |
|
@ -1,39 +0,0 @@ |
|||||||
package transformations |
|
||||||
|
|
||||||
// Add field from calculation. |
|
||||||
#CalculateField: { |
|
||||||
// Transformation ID. |
|
||||||
id: string | *"calculateField" |
|
||||||
// Configuration options. |
|
||||||
options: { |
|
||||||
// The name of your new field. If you leave this blank, then the field will |
|
||||||
// be named to match the calculation. |
|
||||||
alias: string |
|
||||||
// Binary options. |
|
||||||
binary: { |
|
||||||
// Field or number for left side of equation. |
|
||||||
left: string |
|
||||||
// Field or number for right side of equation. |
|
||||||
right: string |
|
||||||
// Operator. |
|
||||||
operator: string | *"+" |
|
||||||
// Calculation to use. |
|
||||||
reducer: string | *"sum" |
|
||||||
} |
|
||||||
// 'reduceRow' - apply selected calculation on each row of selected fields |
|
||||||
// independently. |
|
||||||
// 'binary' - apply basic math operation(sum, multiply, etc) on values in a |
|
||||||
// single row from two selected fields. |
|
||||||
mode: *"reduceRow" | "binary" |
|
||||||
// Reduce options. |
|
||||||
reduce: { |
|
||||||
// Calculation to use. |
|
||||||
reducer: string |
|
||||||
// Fields to include in calculation. |
|
||||||
include: [...string] |
|
||||||
} |
|
||||||
// Hide all other fields and display only your calculated field in the |
|
||||||
// visualization. |
|
||||||
replaceFields: bool | *false |
|
||||||
} |
|
||||||
} |
|
@ -1,16 +0,0 @@ |
|||||||
package transformations |
|
||||||
|
|
||||||
// Reorder, hide, or rename fields/columns. |
|
||||||
#Organize: { |
|
||||||
// Transformation ID. |
|
||||||
id: string | *"organize" |
|
||||||
// Configuration options. |
|
||||||
options: { |
|
||||||
// Exclude fields by name. |
|
||||||
excludeByName: {} |
|
||||||
// Set field order by name. |
|
||||||
indexByName: {} |
|
||||||
// Rename a field by name. |
|
||||||
renameByName: {} |
|
||||||
} |
|
||||||
} |
|
@ -1,9 +0,0 @@ |
|||||||
package variables |
|
||||||
|
|
||||||
// Custom variables are for values that do not change. |
|
||||||
#Custom: _variable & { |
|
||||||
// Options as comma separated values. |
|
||||||
query: string |
|
||||||
// Variable type. |
|
||||||
type: string | *"custom" |
|
||||||
} |
|
@ -1,18 +0,0 @@ |
|||||||
package variables |
|
||||||
|
|
||||||
// Data source variables allow you to quickly change the data source for an |
|
||||||
// entire dashboard. |
|
||||||
#Datasource: _variable & { |
|
||||||
// Data source type. |
|
||||||
query: string |
|
||||||
// Query value. |
|
||||||
queryValue: string | *"" |
|
||||||
// Refresh. |
|
||||||
refresh: int | *1 |
|
||||||
// Regex filter for which data source instances to choose |
|
||||||
// from in the variable value dropdown. Leave empty for |
|
||||||
// all. |
|
||||||
regex: string |
|
||||||
// Variable type. |
|
||||||
type: string | *"datasource" |
|
||||||
} |
|
@ -1,30 +0,0 @@ |
|||||||
package variables |
|
||||||
|
|
||||||
// Query variables allow you to write a data source query that can return a |
|
||||||
// list of metric names, tag values, or keys. |
|
||||||
#Query: _variable & { |
|
||||||
// Data source to use. |
|
||||||
datasource: string |
|
||||||
// Definition. |
|
||||||
definition?: string |
|
||||||
// Query. |
|
||||||
query: string |
|
||||||
// Refresh. |
|
||||||
refresh: int | *1 |
|
||||||
// Regex. |
|
||||||
regex?: string |
|
||||||
// * 0 - Disabled. |
|
||||||
// * 1 - Alphabetical (asc). |
|
||||||
// * 2 - Alphabetical (desc). |
|
||||||
// * 3 - Numerical (asc). |
|
||||||
// * 4 - Numerical (desc). |
|
||||||
// * 5 - Alphabetical (case-insensitive, asc). |
|
||||||
// * 6 - Alphabetical (case-insensitive, desc). |
|
||||||
sort: int >= 0 <= 6 | *0 |
|
||||||
tagValuesQuery?: string |
|
||||||
tags: [...string] | *[] |
|
||||||
tagsQuery?: string |
|
||||||
// Variable type. |
|
||||||
type: "query" |
|
||||||
useTags: bool | *false |
|
||||||
} |
|
@ -1,33 +0,0 @@ |
|||||||
package variables |
|
||||||
|
|
||||||
_variable: { |
|
||||||
// Currently selected value. |
|
||||||
current: { |
|
||||||
selected: bool | *false |
|
||||||
text: string | [...string] |
|
||||||
value: string | [...string] |
|
||||||
} |
|
||||||
// Whether to hide the label and variable. |
|
||||||
// * 0 - Show all. |
|
||||||
// * 1 - Hide label. |
|
||||||
// * 2 - Hide label and variable. |
|
||||||
hide: int >= 0 <= 2 | *0 |
|
||||||
// Enable include all option. |
|
||||||
includeAll: bool | *false |
|
||||||
// When includeAll is enabled, this sets its value. |
|
||||||
allValue?: string |
|
||||||
// Optional display name. |
|
||||||
label?: string |
|
||||||
// Allows mutltiple values to be selected at the same time. |
|
||||||
multi: bool | *false |
|
||||||
// Variable name. |
|
||||||
name: string |
|
||||||
// Options for variable. |
|
||||||
options: [...{ |
|
||||||
selected: bool |
|
||||||
text: string |
|
||||||
value: string |
|
||||||
}] |
|
||||||
// Skip URL sync. |
|
||||||
skipUrlSync: bool | *false |
|
||||||
} |
|
@ -0,0 +1,21 @@ |
|||||||
|
package grafana |
||||||
|
|
||||||
|
import ( |
||||||
|
"embed" |
||||||
|
"io/fs" |
||||||
|
) |
||||||
|
|
||||||
|
// CoreSchema embeds all CUE files within the cue/ subdirectory.
|
||||||
|
//
|
||||||
|
// TODO good rule about where to search
|
||||||
|
//
|
||||||
|
//go:embed cue/*/*.cue
|
||||||
|
var CoreSchema embed.FS |
||||||
|
|
||||||
|
// TODO good rule about where to search
|
||||||
|
//
|
||||||
|
//go:embed public/app/plugins/*/*/*.cue public/app/plugins/*/*/plugin.json
|
||||||
|
var base embed.FS |
||||||
|
|
||||||
|
// PluginSchema embeds all CUE files within the public/ subdirectory.
|
||||||
|
var PluginSchema, _ = fs.Sub(base, "public/app/plugins") |
@ -0,0 +1,213 @@ |
|||||||
|
package grafanaschema |
||||||
|
|
||||||
|
import "github.com/grafana/grafana/cue/scuemata" |
||||||
|
|
||||||
|
Family: scuemata.#Family & { |
||||||
|
lineages: [ |
||||||
|
[ |
||||||
|
{ // 0.0 |
||||||
|
// Unique numeric identifier for the dashboard. |
||||||
|
// TODO must isolate or remove identifiers local to a Grafana instance...? |
||||||
|
id?: number |
||||||
|
// Unique dashboard identifier that can be generated by anyone. string (8-40) |
||||||
|
uid: string |
||||||
|
// Title of dashboard. |
||||||
|
title?: string |
||||||
|
// Description of dashboard. |
||||||
|
description?: string |
||||||
|
|
||||||
|
gnetId?: string |
||||||
|
// Tags associated with dashboard. |
||||||
|
tags?: [...string] |
||||||
|
// Theme of dashboard. |
||||||
|
style: *"light" | "dark" |
||||||
|
// Timezone of dashboard, |
||||||
|
timezone?: *"browser" | "utc" |
||||||
|
// Whether a dashboard is editable or not. |
||||||
|
editable: bool | *true |
||||||
|
// 0 for no shared crosshair or tooltip (default). |
||||||
|
// 1 for shared crosshair. |
||||||
|
// 2 for shared crosshair AND shared tooltip. |
||||||
|
graphTooltip: >=0 & <=2 | *0 |
||||||
|
// Time range for dashboard, e.g. last 6 hours, last 7 days, etc |
||||||
|
time?: { |
||||||
|
from: string | *"now-6h" |
||||||
|
to: string | *"now" |
||||||
|
} |
||||||
|
// Timepicker metadata. |
||||||
|
timepicker?: { |
||||||
|
// Whether timepicker is collapsed or not. |
||||||
|
collapse: bool | *false |
||||||
|
// Whether timepicker is enabled or not. |
||||||
|
enable: bool | *true |
||||||
|
// Whether timepicker is visible or not. |
||||||
|
hidden: bool | *false |
||||||
|
// Selectable intervals for auto-refresh. |
||||||
|
refresh_intervals: [...string] | *["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"] |
||||||
|
} |
||||||
|
// Templating. |
||||||
|
templating?: list: [...{...}] |
||||||
|
// Annotations. |
||||||
|
annotations?: list: [...{ |
||||||
|
builtIn: number | *0 |
||||||
|
// Datasource to use for annotation. |
||||||
|
datasource: string |
||||||
|
// Whether annotation is enabled. |
||||||
|
enable?: bool | *true |
||||||
|
// Whether to hide annotation. |
||||||
|
hide?: bool | *false |
||||||
|
// Annotation icon color. |
||||||
|
iconColor?: string |
||||||
|
// Name of annotation. |
||||||
|
name?: string |
||||||
|
type: string | *"dashboard" |
||||||
|
// Query for annotation data. |
||||||
|
rawQuery?: string |
||||||
|
showIn: number | *0 |
||||||
|
}] |
||||||
|
// Auto-refresh interval. |
||||||
|
refresh?: string |
||||||
|
// Version of the JSON schema, incremented each time a Grafana update brings |
||||||
|
// changes to said schema. |
||||||
|
schemaVersion: number | *25 |
||||||
|
// Version of the dashboard, incremented each time the dashboard is updated. |
||||||
|
version?: number |
||||||
|
panels?: [...#Panel] |
||||||
|
|
||||||
|
// Dashboard panels. Panels are canonically defined inline |
||||||
|
// because they share a version timeline with the dashboard |
||||||
|
// schema; they do not vary independently. We create a separate, |
||||||
|
// synthetic Family to represent them in Go, for ease of generating |
||||||
|
// e.g. JSON Schema. |
||||||
|
#Panel: { |
||||||
|
// The panel plugin type id. |
||||||
|
type: !="" |
||||||
|
|
||||||
|
// Internal - the exact major and minor versions of the panel plugin |
||||||
|
// schema. Hidden and therefore not a part of the data model, but |
||||||
|
// expected to be filled with panel plugin schema versions so that it's |
||||||
|
// possible to figure out which schema version matched on a successful |
||||||
|
// unification. |
||||||
|
// _pv: { maj: int, min: int } |
||||||
|
// The major and minor versions of the panel plugin for this schema. |
||||||
|
// TODO 2-tuple list instead of struct? |
||||||
|
panelSchema: { maj: number, min: number } |
||||||
|
|
||||||
|
// Panel title. |
||||||
|
title?: string |
||||||
|
// Description. |
||||||
|
description?: string |
||||||
|
// Whether to display the panel without a background. |
||||||
|
transparent: bool | *false |
||||||
|
// Name of default datasource. |
||||||
|
datasource?: string |
||||||
|
// Grid position. |
||||||
|
gridPos?: { |
||||||
|
// Panel |
||||||
|
h: number & >0 | *9 |
||||||
|
// Panel |
||||||
|
w: number & >0 & <=24 | *12 |
||||||
|
// Panel x |
||||||
|
x: number & >=0 & <24 | *0 |
||||||
|
// Panel y |
||||||
|
y: number & >=0 | *0 |
||||||
|
// true if fixed |
||||||
|
static?: bool |
||||||
|
} |
||||||
|
// Panel links. |
||||||
|
// links?: [..._panelLink] |
||||||
|
// Name of template variable to repeat for. |
||||||
|
repeat?: string |
||||||
|
// Direction to repeat in if 'repeat' is set. |
||||||
|
// "h" for horizontal, "v" for vertical. |
||||||
|
repeatDirection: *"h" | "v" |
||||||
|
// 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. |
||||||
|
targets?: [...{}] |
||||||
|
|
||||||
|
// The values depend on panel type |
||||||
|
options: {...} |
||||||
|
|
||||||
|
fieldConfig: { |
||||||
|
defaults: { |
||||||
|
// The display value for this field. This supports template variables blank is auto |
||||||
|
displayName?: string |
||||||
|
|
||||||
|
// 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 |
||||||
|
|
||||||
|
// Human readable field metadata |
||||||
|
description?: string |
||||||
|
|
||||||
|
// 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 |
||||||
|
|
||||||
|
// True if data source can write a value to the path. Auth/authz are supported separately |
||||||
|
writeable?: bool |
||||||
|
|
||||||
|
// True if data source field supports ad-hoc filters |
||||||
|
filterable?: bool |
||||||
|
|
||||||
|
// Numeric Options |
||||||
|
unit?: string |
||||||
|
|
||||||
|
// Significant digits (for display) |
||||||
|
decimals?: number |
||||||
|
|
||||||
|
min?: number |
||||||
|
max?: number |
||||||
|
|
||||||
|
// // Convert input values into a display string |
||||||
|
// mappings?: ValueMapping[]; |
||||||
|
|
||||||
|
// // Map numeric values to states |
||||||
|
// thresholds?: ThresholdsConfig; |
||||||
|
|
||||||
|
// // Map values to a display color |
||||||
|
// color?: FieldColor; |
||||||
|
|
||||||
|
// // Used when reducing field values |
||||||
|
// nullValueMode?: NullValueMode; |
||||||
|
|
||||||
|
// // The behavior when clicking on a result |
||||||
|
// links?: DataLink[]; |
||||||
|
|
||||||
|
// Alternative to empty string |
||||||
|
noValue?: string |
||||||
|
|
||||||
|
// Can always exist. Valid fields within this are |
||||||
|
// defined by the panel plugin - that's the |
||||||
|
// PanelFieldConfig that comes from the plugin. |
||||||
|
custom?: {...} |
||||||
|
} |
||||||
|
overrides: [...{ |
||||||
|
matcher: { |
||||||
|
id: string | *"" |
||||||
|
options?: _ |
||||||
|
} |
||||||
|
properties: [...{ |
||||||
|
id: string | *"" |
||||||
|
value?: _ |
||||||
|
}] |
||||||
|
}] |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
] |
||||||
|
] |
||||||
|
} |
||||||
|
|
||||||
|
#Latest: { |
||||||
|
#Dashboard: dashboardFamily.latest |
||||||
|
#Panel: dashboardFamily.latest._Panel |
||||||
|
} |
@ -0,0 +1,3 @@ |
|||||||
|
package grafanaschema |
||||||
|
|
||||||
|
TooltipMode: "single" | "multi" | "none" @cuetsy(targetType="type") |
@ -0,0 +1,6 @@ |
|||||||
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
// NOTE: This file will be auto generated from models.cue
|
||||||
|
// It is currenty hand written but will serve as the target for cuetsy
|
||||||
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
export type TooltipMode = 'single' | 'multi' | 'none'; |
@ -0,0 +1,11 @@ |
|||||||
|
package grafanaschema |
||||||
|
|
||||||
|
|
||||||
|
// TODO Relative imports are flatly disallowed by CUE, but that's what's |
||||||
|
// currently done in the corresponding typescript code. We'll have to make |
||||||
|
// cuetsy handle this with import mappings. |
||||||
|
import tooltip "github.com/grafana/grafana/packages/grafana-ui/src/components/Chart:grafanaschema" |
||||||
|
|
||||||
|
GraphTooltipOptions: { |
||||||
|
mode: tooltip.TooltipMode |
||||||
|
} @cuetsy(targetType="interface") |
@ -0,0 +1,10 @@ |
|||||||
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
// NOTE: This file will be auto generated from models.cue
|
||||||
|
// It is currenty hand written but will serve as the target for cuetsy
|
||||||
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
import { TooltipMode } from '../../Chart/models.gen'; |
||||||
|
|
||||||
|
export interface GraphTooltipOptions { |
||||||
|
mode: TooltipMode; |
||||||
|
} |
@ -0,0 +1,28 @@ |
|||||||
|
package grafanaschema |
||||||
|
|
||||||
|
FieldTextAlignment: "auto" | "left" | "right" | "center" @cuetsy(targetType="type") |
||||||
|
|
||||||
|
TableCellDisplayMode: { |
||||||
|
Auto: "auto", |
||||||
|
ColorText: "color-text", |
||||||
|
ColorBackground: "color-background", |
||||||
|
GradientGauge: "gradient-gauge", |
||||||
|
LcdGauge: "lcd-gauge", |
||||||
|
JSONView: "json-view", |
||||||
|
BasicGauge: "basic", |
||||||
|
Image: "image", |
||||||
|
} @cuetsy(targetType="enum") |
||||||
|
|
||||||
|
|
||||||
|
TableFieldOptions: { |
||||||
|
width?: number |
||||||
|
align: FieldTextAlignment | *"auto" |
||||||
|
displayMode: TableCellDisplayMode | *"auto" |
||||||
|
hidden?: bool // ?? default is missing or false ?? |
||||||
|
} @cuetsy(targetType="interface") |
||||||
|
|
||||||
|
|
||||||
|
TableSortByFieldState: { |
||||||
|
displayName: string |
||||||
|
desc?: bool |
||||||
|
} @cuetsy(targetType="interface") |
@ -0,0 +1,12 @@ |
|||||||
|
package grafanaschema |
||||||
|
|
||||||
|
LegendPlacement: "bottom" | "right" @cuetsy(targetType="type") |
||||||
|
LegendDisplayMode: "list" | "table" | "hidden" @cuetsy(targetType="enum") |
||||||
|
|
||||||
|
VizLegendOptions: { |
||||||
|
displayMode: LegendDisplayMode |
||||||
|
placement: LegendPlacement |
||||||
|
calcs: [...string] |
||||||
|
} @cuetsy(targetType="interface") |
||||||
|
|
||||||
|
// TODO this excludes all the types that include function definitions |
@ -0,0 +1,16 @@ |
|||||||
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
// NOTE: This file will be auto generated from models.cue
|
||||||
|
// It is currenty hand written but will serve as the target for cuetsy
|
||||||
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
export type LegendPlacement = 'bottom' | 'right'; |
||||||
|
export enum LegendDisplayMode { |
||||||
|
Hidden = 'hidden', |
||||||
|
List = 'list', |
||||||
|
Table = 'table', |
||||||
|
} |
||||||
|
export interface VizLegendOptions { |
||||||
|
calcs: string[]; |
||||||
|
displayMode: LegendDisplayMode; |
||||||
|
placement: LegendPlacement; |
||||||
|
} |
@ -0,0 +1,60 @@ |
|||||||
|
package grafanaschema |
||||||
|
|
||||||
|
AxisPlacement: "auto" | "top" | "right" | "bottom" | "left" | "hidden" @cuetsy(targetType="enum") |
||||||
|
PointVisibility: "auto" | "never" | "always" @cuetsy(targetType="enum") |
||||||
|
DrawStyle: "line" | "bars" | "points" @cuetsy(targetType="enum") |
||||||
|
LineInterpolation: "linear" | "smooth" | "stepBefore" | "stepAfter" @cuetsy(targetType="enum") |
||||||
|
ScaleDistribution: "linear" | "log" @cuetsy(targetType="enum") |
||||||
|
GraphGradientMode: "none" | "opacity" | "hue" | "scheme" @cuetsy(targetType="enum") |
||||||
|
|
||||||
|
LineStyle: { |
||||||
|
fill?: "solid" | "dash" | "dot" | "square" |
||||||
|
dash?: [...number] |
||||||
|
} @cuetsy(targetType="interface") |
||||||
|
|
||||||
|
LineConfig: { |
||||||
|
lineColor?: string |
||||||
|
lineWidth?: number |
||||||
|
lineInterpolation?: LineInterpolation |
||||||
|
lineStyle?: LineStyle |
||||||
|
spanNulls?: bool |
||||||
|
} @cuetsy(targetType="interface") |
||||||
|
|
||||||
|
FillConfig: { |
||||||
|
fillColor?: string |
||||||
|
fillOpacity?: number |
||||||
|
fillBelowTo?: string |
||||||
|
} @cuetsy(targetType="interface") |
||||||
|
|
||||||
|
PointsConfig: { |
||||||
|
showPoints?: PointVisibility |
||||||
|
pointSize?: number |
||||||
|
pointColor?: string |
||||||
|
pointSymbol?: string |
||||||
|
} @cuetsy(targetType="interface") |
||||||
|
|
||||||
|
ScaleDistributionConfig: { |
||||||
|
type: ScaleDistribution |
||||||
|
log?: number |
||||||
|
} @cuetsy(targetType="interface") |
||||||
|
|
||||||
|
AxisConfig: { |
||||||
|
axisPlacement?: AxisPlacement |
||||||
|
axisLabel?: string |
||||||
|
axisWidth?: number |
||||||
|
axisSoftMin?: number |
||||||
|
axisSoftMax?: number |
||||||
|
scaleDistribution?: ScaleDistributionConfig |
||||||
|
} @cuetsy(targetType="interface") |
||||||
|
|
||||||
|
HideSeriesConfig: { |
||||||
|
tooltip: bool |
||||||
|
legend: bool |
||||||
|
graph: bool |
||||||
|
} @cuetsy(targetType="interface") |
||||||
|
|
||||||
|
GraphFieldConfig: LineConfig & FillConfig & PointsConfig & AxisConfig & { |
||||||
|
drawStyle?: DrawStyle |
||||||
|
gradientMode?: GraphGradientMode |
||||||
|
hideFrom?: HideSeriesConfig |
||||||
|
} @cuetsy(targetType="interface") |
@ -0,0 +1,83 @@ |
|||||||
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
// NOTE: This file will be auto generated from models.cue
|
||||||
|
// It is currenty hand written but will serve as the target for cuetsy
|
||||||
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
export enum AxisPlacement { |
||||||
|
Auto = 'auto', |
||||||
|
Bottom = 'bottom', |
||||||
|
Hidden = 'hidden', |
||||||
|
Left = 'left', |
||||||
|
Right = 'right', |
||||||
|
Top = 'top', |
||||||
|
} |
||||||
|
export enum PointVisibility { |
||||||
|
Always = 'always', |
||||||
|
Auto = 'auto', |
||||||
|
Never = 'never', |
||||||
|
} |
||||||
|
export enum DrawStyle { |
||||||
|
Bars = 'bars', |
||||||
|
Line = 'line', |
||||||
|
Points = 'points', |
||||||
|
} |
||||||
|
export enum LineInterpolation { |
||||||
|
Linear = 'linear', |
||||||
|
Smooth = 'smooth', |
||||||
|
StepAfter = 'stepAfter', |
||||||
|
StepBefore = 'stepBefore', |
||||||
|
} |
||||||
|
export enum ScaleDistribution { |
||||||
|
Linear = 'linear', |
||||||
|
Log = 'log', |
||||||
|
} |
||||||
|
export enum GraphGradientMode { |
||||||
|
Hue = 'hue', |
||||||
|
None = 'none', |
||||||
|
Opacity = 'opacity', |
||||||
|
Scheme = 'scheme', |
||||||
|
} |
||||||
|
export interface LineStyle { |
||||||
|
dash?: number[]; |
||||||
|
fill?: 'solid' | 'dash' | 'dot' | 'square'; |
||||||
|
} |
||||||
|
export interface LineConfig { |
||||||
|
lineColor?: string; |
||||||
|
lineInterpolation?: LineInterpolation; |
||||||
|
lineStyle?: LineStyle; |
||||||
|
lineWidth?: number; |
||||||
|
spanNulls?: boolean; |
||||||
|
} |
||||||
|
export interface FillConfig { |
||||||
|
fillBelowTo?: string; |
||||||
|
fillColor?: string; |
||||||
|
fillOpacity?: number; |
||||||
|
} |
||||||
|
export interface PointsConfig { |
||||||
|
pointColor?: string; |
||||||
|
pointSize?: number; |
||||||
|
pointSymbol?: string; |
||||||
|
showPoints?: PointVisibility; |
||||||
|
} |
||||||
|
export interface ScaleDistributionConfig { |
||||||
|
log?: number; |
||||||
|
type: ScaleDistribution; |
||||||
|
} |
||||||
|
export interface AxisConfig { |
||||||
|
axisLabel?: string; |
||||||
|
axisPlacement?: AxisPlacement; |
||||||
|
axisSoftMax?: number; |
||||||
|
axisSoftMin?: number; |
||||||
|
axisWidth?: number; |
||||||
|
scaleDistribution?: ScaleDistributionConfig; |
||||||
|
} |
||||||
|
export interface HideSeriesConfig { |
||||||
|
graph: boolean; |
||||||
|
legend: boolean; |
||||||
|
tooltip: boolean; |
||||||
|
} |
||||||
|
export interface GraphFieldConfig extends LineConfig, FillConfig, PointsConfig, AxisConfig { |
||||||
|
drawStyle?: DrawStyle; |
||||||
|
gradientMode?: GraphGradientMode; |
||||||
|
hideFrom?: HideSeriesConfig; |
||||||
|
} |
@ -0,0 +1,87 @@ |
|||||||
|
package load |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
"io" |
||||||
|
"io/fs" |
||||||
|
"path/filepath" |
||||||
|
|
||||||
|
"cuelang.org/go/cue" |
||||||
|
"cuelang.org/go/cue/load" |
||||||
|
) |
||||||
|
|
||||||
|
var rt = &cue.Runtime{} |
||||||
|
|
||||||
|
// Families can have variants, where more typing information narrows the
|
||||||
|
// possible values for certain keys in schemas. These are a meta-property
|
||||||
|
// of the schema, effectively encoded in these loaders.
|
||||||
|
//
|
||||||
|
// We can generally define three variants:
|
||||||
|
// - "Base": strictly core schema files, no plugins. (go:embed-able)
|
||||||
|
// - "Dist": "Base" + plugins that ship with vanilla Grafana (go:embed-able)
|
||||||
|
// - "Instance": "Dist" + the non-core plugins available in an actual, running Grafana
|
||||||
|
|
||||||
|
// BaseLoadPaths contains the configuration for loading a DistDashboard
|
||||||
|
type BaseLoadPaths struct { |
||||||
|
// BaseCueFS should be rooted at a directory containing the filesystem layout
|
||||||
|
// expected to exist at github.com/grafana/grafana/cue.
|
||||||
|
BaseCueFS fs.FS |
||||||
|
|
||||||
|
// DistPluginCueFS should point to some fs path (TBD) under which all core
|
||||||
|
// plugins live.
|
||||||
|
DistPluginCueFS fs.FS |
||||||
|
|
||||||
|
// InstanceCueFS should point to a root dir in which non-core plugins live.
|
||||||
|
// Normal case will be that this only happens when an actual Grafana
|
||||||
|
// instance is making the call, and has a plugin dir to offer - though
|
||||||
|
// external tools could always create their own dirs shaped like a Grafana
|
||||||
|
// plugin dir, and point to those.
|
||||||
|
InstanceCueFS fs.FS |
||||||
|
} |
||||||
|
|
||||||
|
// toOverlay converts all .cue files in the fs.FS into Source entries in an
|
||||||
|
// overlay map, as expected by load.Config.
|
||||||
|
//
|
||||||
|
// Each entry is placed in the map with the provided prefix - which must be an
|
||||||
|
// absolute path - ahead of the actual path of the added file within the fs.FS.
|
||||||
|
//
|
||||||
|
// The function writes into the provided overlay map, to facilitate the
|
||||||
|
// construction of a single overlay map from multiple fs.FS.
|
||||||
|
//
|
||||||
|
// All files reachable by walking the provided fs.FS are added to the overlay
|
||||||
|
// map, on the premise that control over the FS is sufficient to allow any
|
||||||
|
// desired filtering.
|
||||||
|
func toOverlay(prefix string, vfs fs.FS, overlay map[string]load.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 |
||||||
|
} |
||||||
|
|
||||||
|
b, err := io.ReadAll(f) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
overlay[filepath.Join(prefix, path)] = load.FromBytes(b) |
||||||
|
return nil |
||||||
|
})) |
||||||
|
|
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
@ -0,0 +1,200 @@ |
|||||||
|
package load |
||||||
|
|
||||||
|
import ( |
||||||
|
"errors" |
||||||
|
"fmt" |
||||||
|
|
||||||
|
"cuelang.org/go/cue" |
||||||
|
"cuelang.org/go/cue/load" |
||||||
|
"github.com/grafana/grafana/pkg/schema" |
||||||
|
) |
||||||
|
|
||||||
|
var panelSubpath cue.Path = cue.MakePath(cue.Def("#Panel")) |
||||||
|
|
||||||
|
func defaultOverlay(p BaseLoadPaths) (map[string]load.Source, error) { |
||||||
|
overlay := make(map[string]load.Source) |
||||||
|
if err := toOverlay("/", p.BaseCueFS, overlay); err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
if err := toOverlay("/", p.DistPluginCueFS, overlay); err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
return overlay, nil |
||||||
|
} |
||||||
|
|
||||||
|
// BaseDashboardFamily loads the family of schema representing the "Base" variant of
|
||||||
|
// a Grafana dashboard: the core-defined dashboard schema that applies universally to
|
||||||
|
// all dashboards, independent of any plugins.
|
||||||
|
//
|
||||||
|
// The returned VersionedCueSchema will always be the oldest schema in the
|
||||||
|
// family: the 0.0 schema. schema.Find() provides easy traversal to newer schema
|
||||||
|
// versions.
|
||||||
|
func BaseDashboardFamily(p BaseLoadPaths) (schema.VersionedCueSchema, error) { |
||||||
|
overlay, err := defaultOverlay(p) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
cfg := &load.Config{Overlay: overlay} |
||||||
|
inst, err := rt.Build(load.Instances([]string{"/cue/data/gen.cue"}, cfg)[0]) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
famval := inst.Value().LookupPath(cue.MakePath(cue.Str("Family"))) |
||||||
|
if !famval.Exists() { |
||||||
|
return nil, errors.New("dashboard schema family did not exist at expected path in expected file") |
||||||
|
} |
||||||
|
|
||||||
|
return buildGenericScuemata(famval) |
||||||
|
} |
||||||
|
|
||||||
|
// DistDashboardFamily loads the family of schema representing the "Dist"
|
||||||
|
// variant of a Grafana dashboard: the "Base" variant (see
|
||||||
|
// BaseDashboardFamily()), but constrained such that all substructures (e.g.
|
||||||
|
// panels) must be valid with respect to the schemas provided by the core
|
||||||
|
// plugins that ship with Grafana.
|
||||||
|
//
|
||||||
|
// The returned VersionedCueSchema will always be the oldest schema in the
|
||||||
|
// family: the 0.0 schema. schema.Find() provides easy traversal to newer schema
|
||||||
|
// versions.
|
||||||
|
func DistDashboardFamily(p BaseLoadPaths) (schema.VersionedCueSchema, error) { |
||||||
|
head, err := BaseDashboardFamily(p) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
scuemap, err := readPanelModels(p) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
dj, err := disjunctPanelScuemata(scuemap) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
// Stick this into a dummy struct so that we can unify it into place, as
|
||||||
|
// Value.Fill() can't target definitions. Need new method based on cue.Path;
|
||||||
|
// a CL has been merged that creates FillPath and will be in the next
|
||||||
|
// release of CUE.
|
||||||
|
dummy, _ := rt.Compile("mergeStruct", ` |
||||||
|
obj: {} |
||||||
|
dummy: { |
||||||
|
#Panel: obj |
||||||
|
} |
||||||
|
`) |
||||||
|
filled := dummy.Value().Fill(dj, "obj") |
||||||
|
ddj := filled.LookupPath(cue.MakePath(cue.Str("dummy"))) |
||||||
|
|
||||||
|
var first, prev *compositeDashboardSchema |
||||||
|
for head != nil { |
||||||
|
cds := &compositeDashboardSchema{ |
||||||
|
base: head, |
||||||
|
actual: head.CUE().Unify(ddj), |
||||||
|
panelFams: scuemap, |
||||||
|
// TODO migrations
|
||||||
|
migration: terminalMigrationFunc, |
||||||
|
} |
||||||
|
|
||||||
|
if prev == nil { |
||||||
|
first = cds |
||||||
|
} else { |
||||||
|
prev.next = cds |
||||||
|
} |
||||||
|
|
||||||
|
prev = cds |
||||||
|
head = head.Successor() |
||||||
|
} |
||||||
|
|
||||||
|
return first, nil |
||||||
|
} |
||||||
|
|
||||||
|
type compositeDashboardSchema struct { |
||||||
|
// The base/root dashboard schema
|
||||||
|
base schema.VersionedCueSchema |
||||||
|
actual cue.Value |
||||||
|
next *compositeDashboardSchema |
||||||
|
migration migrationFunc |
||||||
|
panelFams map[string]schema.VersionedCueSchema |
||||||
|
} |
||||||
|
|
||||||
|
// Validate checks that the resource is correct with respect to the schema.
|
||||||
|
func (cds *compositeDashboardSchema) Validate(r schema.Resource) error { |
||||||
|
rv, err := rt.Compile("resource", r.Value) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
return cds.actual.Unify(rv.Value()).Validate(cue.Concrete(true)) |
||||||
|
} |
||||||
|
|
||||||
|
// ApplyDefaults returns a new, concrete copy of the Resource with all paths
|
||||||
|
// that are 1) missing in the Resource AND 2) specified by the schema,
|
||||||
|
// filled with default values specified by the schema.
|
||||||
|
func (cds *compositeDashboardSchema) ApplyDefaults(_ schema.Resource) (schema.Resource, error) { |
||||||
|
panic("not implemented") // TODO: Implement
|
||||||
|
} |
||||||
|
|
||||||
|
// TrimDefaults returns a new, concrete copy of the Resource where all paths
|
||||||
|
// in the where the values at those paths are the same as the default value
|
||||||
|
// given in the schema.
|
||||||
|
func (cds *compositeDashboardSchema) TrimDefaults(_ schema.Resource) (schema.Resource, error) { |
||||||
|
panic("not implemented") // TODO: Implement
|
||||||
|
} |
||||||
|
|
||||||
|
// CUE returns the cue.Value representing the actual schema.
|
||||||
|
func (cds *compositeDashboardSchema) CUE() cue.Value { |
||||||
|
return cds.actual |
||||||
|
} |
||||||
|
|
||||||
|
// Version reports the major and minor versions of the schema.
|
||||||
|
func (cds *compositeDashboardSchema) Version() (major int, minor int) { |
||||||
|
return cds.base.Version() |
||||||
|
} |
||||||
|
|
||||||
|
// Returns the next VersionedCueSchema
|
||||||
|
func (cds *compositeDashboardSchema) Successor() schema.VersionedCueSchema { |
||||||
|
if cds.next == nil { |
||||||
|
// Untyped nil, allows `<sch> == nil` checks to work as people expect
|
||||||
|
return nil |
||||||
|
} |
||||||
|
return cds.next |
||||||
|
} |
||||||
|
|
||||||
|
func (cds *compositeDashboardSchema) Migrate(x schema.Resource) (schema.Resource, schema.VersionedCueSchema, error) { // TODO restrict input/return type to concrete
|
||||||
|
r, sch, err := cds.migration(x.Value) |
||||||
|
if err != nil || sch == nil { |
||||||
|
// TODO fix sloppy types
|
||||||
|
r = x.Value.(cue.Value) |
||||||
|
} |
||||||
|
|
||||||
|
return schema.Resource{Value: r}, sch, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (cds *compositeDashboardSchema) LatestPanelSchemaFor(id string) (schema.VersionedCueSchema, error) { |
||||||
|
// So much slop rn, but it's OK because i FINALLY know where this is going!
|
||||||
|
psch, has := cds.panelFams[id] |
||||||
|
if !has { |
||||||
|
// TODO typed errors
|
||||||
|
return nil, fmt.Errorf("unknown panel plugin type %q", id) |
||||||
|
} |
||||||
|
|
||||||
|
latest := schema.Find(psch, schema.Latest()) |
||||||
|
sch := &genericVersionedSchema{ |
||||||
|
actual: cds.base.CUE().LookupPath(panelSubpath).Unify(mapPanelModel(id, latest)), |
||||||
|
} |
||||||
|
sch.major, sch.minor = latest.Version() |
||||||
|
|
||||||
|
return sch, nil |
||||||
|
} |
||||||
|
|
||||||
|
// One-off special interface for dashboard composite schema, until the composite
|
||||||
|
// dashboard schema pattern is fully generalized.
|
||||||
|
//
|
||||||
|
// NOTE: THIS IS A TEMPORARY TYPE. IT WILL BE REPLACED WITH A GENERIC INTERFACE
|
||||||
|
// TO REPRESENT COMPOSITIONAL SCHEMA FAMILY PRIOR TO GRAFANA 8. UPDATING WILL
|
||||||
|
// SHOULD BE TRIVIAL, BUT IT WILL CAUSE BREAKAGES.
|
||||||
|
type CompositeDashboardSchema interface { |
||||||
|
schema.VersionedCueSchema |
||||||
|
LatestPanelSchemaFor(id string) (schema.VersionedCueSchema, error) |
||||||
|
} |
@ -0,0 +1,180 @@ |
|||||||
|
package load |
||||||
|
|
||||||
|
import ( |
||||||
|
"cuelang.org/go/cue" |
||||||
|
"cuelang.org/go/cue/load" |
||||||
|
"github.com/grafana/grafana/pkg/schema" |
||||||
|
) |
||||||
|
|
||||||
|
// getBaseScuemata attempts to load the base scuemata family and schema
|
||||||
|
// definitions on which all Grafana scuemata rely.
|
||||||
|
//
|
||||||
|
// TODO probably cache this or something
|
||||||
|
func getBaseScuemata(p BaseLoadPaths) (*cue.Instance, error) { |
||||||
|
overlay := make(map[string]load.Source) |
||||||
|
if err := toOverlay("/grafana", p.BaseCueFS, overlay); err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
cfg := &load.Config{ |
||||||
|
Overlay: overlay, |
||||||
|
Package: "scuemata", |
||||||
|
// TODO Semantics of loading instances is quite confusing. This 'Dir'
|
||||||
|
// field is a case in point. It must be set to "/" in order for the
|
||||||
|
// overlay to be searched and have all files loaded in the cue/scuemata
|
||||||
|
// directory. (This isn't necessary when loading individual .cue files.)
|
||||||
|
// But anchoring a search at root seems like we're begging for
|
||||||
|
// vulnerabilities where Grafana can read and print out anything on the
|
||||||
|
// filesystem, which can be a disclosure problem, unless we're
|
||||||
|
// absolutely sure the search is within a virtual filesystem. Which i'm
|
||||||
|
// not.
|
||||||
|
//
|
||||||
|
// And no, changing the toOverlay() to have a subpath and the
|
||||||
|
// load.Instances to mirror that subpath does not allow us to get rid of
|
||||||
|
// this "/".
|
||||||
|
Dir: "/", |
||||||
|
} |
||||||
|
return rt.Build(load.Instances([]string{"/grafana/cue/scuemata"}, cfg)[0]) |
||||||
|
} |
||||||
|
|
||||||
|
func buildGenericScuemata(famval cue.Value) (schema.VersionedCueSchema, error) { |
||||||
|
// TODO verify subsumption by #Family; renders many
|
||||||
|
// error checks below unnecessary
|
||||||
|
majiter, err := famval.LookupPath(cue.MakePath(cue.Str("lineages"))).List() |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
var major int |
||||||
|
var first, lastgvs *genericVersionedSchema |
||||||
|
for majiter.Next() { |
||||||
|
var minor int |
||||||
|
miniter, _ := majiter.Value().List() |
||||||
|
for miniter.Next() { |
||||||
|
gvs := &genericVersionedSchema{ |
||||||
|
actual: miniter.Value(), |
||||||
|
major: major, |
||||||
|
minor: minor, |
||||||
|
// This gets overwritten on all but the very final schema
|
||||||
|
migration: terminalMigrationFunc, |
||||||
|
} |
||||||
|
|
||||||
|
if minor != 0 { |
||||||
|
// TODO Verify that this schema is backwards compat with prior.
|
||||||
|
// Create an implicit migration operation on the prior schema.
|
||||||
|
lastgvs.migration = implicitMigration(gvs.actual, gvs) |
||||||
|
lastgvs.next = gvs |
||||||
|
} else if major != 0 { |
||||||
|
lastgvs.next = gvs |
||||||
|
// x.0. There should exist an explicit migration definition;
|
||||||
|
// load it up and ready it for use, and place it on the final
|
||||||
|
// schema in the prior sequence.
|
||||||
|
//
|
||||||
|
// Also...should at least try to make sure it's pointing at the
|
||||||
|
// expected schema, to maintain our invariants?
|
||||||
|
|
||||||
|
// TODO impl
|
||||||
|
} else { |
||||||
|
first = gvs |
||||||
|
} |
||||||
|
lastgvs = gvs |
||||||
|
minor++ |
||||||
|
} |
||||||
|
major++ |
||||||
|
} |
||||||
|
|
||||||
|
return first, nil |
||||||
|
} |
||||||
|
|
||||||
|
type genericVersionedSchema struct { |
||||||
|
actual cue.Value |
||||||
|
major int |
||||||
|
minor int |
||||||
|
next *genericVersionedSchema |
||||||
|
migration migrationFunc |
||||||
|
} |
||||||
|
|
||||||
|
// Validate checks that the resource is correct with respect to the schema.
|
||||||
|
func (gvs *genericVersionedSchema) Validate(r schema.Resource) error { |
||||||
|
rv, err := rt.Compile("resource", r.Value) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
return gvs.actual.Unify(rv.Value()).Validate(cue.Concrete(true)) |
||||||
|
} |
||||||
|
|
||||||
|
// ApplyDefaults returns a new, concrete copy of the Resource with all paths
|
||||||
|
// that are 1) missing in the Resource AND 2) specified by the schema,
|
||||||
|
// filled with default values specified by the schema.
|
||||||
|
func (gvs *genericVersionedSchema) ApplyDefaults(_ schema.Resource) (schema.Resource, error) { |
||||||
|
panic("not implemented") // TODO: Implement
|
||||||
|
} |
||||||
|
|
||||||
|
// TrimDefaults returns a new, concrete copy of the Resource where all paths
|
||||||
|
// in the where the values at those paths are the same as the default value
|
||||||
|
// given in the schema.
|
||||||
|
func (gvs *genericVersionedSchema) TrimDefaults(_ schema.Resource) (schema.Resource, error) { |
||||||
|
panic("not implemented") // TODO: Implement
|
||||||
|
} |
||||||
|
|
||||||
|
// CUE returns the cue.Value representing the actual schema.
|
||||||
|
func (gvs *genericVersionedSchema) CUE() cue.Value { |
||||||
|
return gvs.actual |
||||||
|
} |
||||||
|
|
||||||
|
// Version reports the major and minor versions of the schema.
|
||||||
|
func (gvs *genericVersionedSchema) Version() (major int, minor int) { |
||||||
|
return gvs.major, gvs.minor |
||||||
|
} |
||||||
|
|
||||||
|
// Returns the next VersionedCueSchema
|
||||||
|
func (gvs *genericVersionedSchema) Successor() schema.VersionedCueSchema { |
||||||
|
if gvs.next == nil { |
||||||
|
// Untyped nil, allows `<sch> == nil` checks to work as people expect
|
||||||
|
return nil |
||||||
|
} |
||||||
|
return gvs.next |
||||||
|
} |
||||||
|
|
||||||
|
// Migrate transforms a resource into a new Resource that is correct with
|
||||||
|
// respect to its Successor schema.
|
||||||
|
func (gvs *genericVersionedSchema) Migrate(x schema.Resource) (schema.Resource, schema.VersionedCueSchema, error) { // TODO restrict input/return type to concrete
|
||||||
|
r, sch, err := gvs.migration(x.Value) |
||||||
|
if err != nil || sch == nil { |
||||||
|
r = x.Value.(cue.Value) |
||||||
|
} |
||||||
|
|
||||||
|
return schema.Resource{Value: r}, sch, nil |
||||||
|
} |
||||||
|
|
||||||
|
type migrationFunc func(x interface{}) (cue.Value, schema.VersionedCueSchema, error) |
||||||
|
|
||||||
|
var terminalMigrationFunc = func(x interface{}) (cue.Value, schema.VersionedCueSchema, error) { |
||||||
|
// TODO send back the input
|
||||||
|
return cue.Value{}, nil, nil |
||||||
|
} |
||||||
|
|
||||||
|
// panic if called
|
||||||
|
// var panicMigrationFunc = func(x interface{}) (cue.Value, schema.VersionedCueSchema, error) {
|
||||||
|
// panic("migrations are not yet implemented")
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Creates a func to perform a "migration" that simply unifies the input
|
||||||
|
// artifact (which is expected to have already have been validated against an
|
||||||
|
// earlier schema) with a later schema.
|
||||||
|
func implicitMigration(v cue.Value, next schema.VersionedCueSchema) migrationFunc { |
||||||
|
return func(x interface{}) (cue.Value, schema.VersionedCueSchema, error) { |
||||||
|
w := v.Fill(x) |
||||||
|
// TODO is it possible that migration would be successful, but there
|
||||||
|
// still exists some error here? Need to better understand internal CUE
|
||||||
|
// erroring rules? seems like incomplete cue.Value may always an Err()?
|
||||||
|
//
|
||||||
|
// TODO should check concreteness here? Or can we guarantee a priori it
|
||||||
|
// can be made concrete simply by looking at the schema, before
|
||||||
|
// implicitMigration() is called to create this function?
|
||||||
|
if w.Err() != nil { |
||||||
|
return w, nil, w.Err() |
||||||
|
} |
||||||
|
return w, next, w.Err() |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,125 @@ |
|||||||
|
package load |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
"io/fs" |
||||||
|
"os" |
||||||
|
"path/filepath" |
||||||
|
"testing" |
||||||
|
|
||||||
|
"github.com/grafana/grafana" |
||||||
|
"github.com/grafana/grafana/pkg/schema" |
||||||
|
"github.com/stretchr/testify/require" |
||||||
|
) |
||||||
|
|
||||||
|
var p BaseLoadPaths = BaseLoadPaths{ |
||||||
|
BaseCueFS: grafana.CoreSchema, |
||||||
|
DistPluginCueFS: grafana.PluginSchema, |
||||||
|
} |
||||||
|
|
||||||
|
// Basic well-formedness tests on core scuemata.
|
||||||
|
func TestScuemataBasics(t *testing.T) { |
||||||
|
all := make(map[string]schema.VersionedCueSchema) |
||||||
|
|
||||||
|
dash, err := BaseDashboardFamily(p) |
||||||
|
require.NoError(t, err, "error while loading base dashboard scuemata") |
||||||
|
all["basedash"] = dash |
||||||
|
|
||||||
|
ddash, err := DistDashboardFamily(p) |
||||||
|
require.NoError(t, err, "error while loading dist dashboard scuemata") |
||||||
|
all["distdash"] = ddash |
||||||
|
|
||||||
|
for set, sch := range all { |
||||||
|
t.Run(set, func(t *testing.T) { |
||||||
|
require.NotNil(t, sch, "scuemata for %q linked to empty chain", set) |
||||||
|
|
||||||
|
maj, min := sch.Version() |
||||||
|
t.Run(fmt.Sprintf("%v.%v", maj, min), func(t *testing.T) { |
||||||
|
cv := sch.CUE() |
||||||
|
t.Run("Exists", func(t *testing.T) { |
||||||
|
require.True(t, cv.Exists(), "cue value for schema does not exist") |
||||||
|
}) |
||||||
|
t.Run("Validate", func(t *testing.T) { |
||||||
|
require.NoError(t, cv.Validate(), "all schema should be valid with respect to basic CUE rules") |
||||||
|
}) |
||||||
|
}) |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func TestDashboardValidity(t *testing.T) { |
||||||
|
// TODO FIXME remove this once we actually have dashboard schema filled in
|
||||||
|
// enough that the tests pass, lol
|
||||||
|
t.Skip() |
||||||
|
validdir := os.DirFS(filepath.Join("testdata", "artifacts", "dashboards")) |
||||||
|
|
||||||
|
dash, err := BaseDashboardFamily(p) |
||||||
|
require.NoError(t, err, "error while loading base dashboard scuemata") |
||||||
|
|
||||||
|
ddash, err := DistDashboardFamily(p) |
||||||
|
require.NoError(t, err, "error while loading dist dashboard scuemata") |
||||||
|
|
||||||
|
require.NoError(t, fs.WalkDir(validdir, ".", func(path string, d fs.DirEntry, err error) error { |
||||||
|
require.NoError(t, err) |
||||||
|
|
||||||
|
if d.IsDir() || filepath.Ext(d.Name()) != ".json" { |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
t.Run(path, func(t *testing.T) { |
||||||
|
b, err := validdir.Open(path) |
||||||
|
require.NoError(t, err, "failed to open dashboard file") |
||||||
|
|
||||||
|
t.Run("base", func(t *testing.T) { |
||||||
|
_, err := schema.SearchAndValidate(dash, b) |
||||||
|
require.NoError(t, err, "dashboard failed validation") |
||||||
|
}) |
||||||
|
t.Run("dist", func(t *testing.T) { |
||||||
|
_, err := schema.SearchAndValidate(ddash, b) |
||||||
|
require.NoError(t, err, "dashboard failed validation") |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
return nil |
||||||
|
})) |
||||||
|
} |
||||||
|
|
||||||
|
func TestPanelValidity(t *testing.T) { |
||||||
|
validdir := os.DirFS(filepath.Join("testdata", "artifacts", "panels")) |
||||||
|
|
||||||
|
// dash, err := BaseDashboardFamily(p)
|
||||||
|
// require.NoError(t, err, "error while loading base dashboard scuemata")
|
||||||
|
|
||||||
|
ddash, err := DistDashboardFamily(p) |
||||||
|
require.NoError(t, err, "error while loading dist dashboard scuemata") |
||||||
|
|
||||||
|
// TODO hmm, it's awkward for this test's structure to have to pick just one
|
||||||
|
// type of panel plugin, but we can change the test structure. However, is
|
||||||
|
// there any other situation where we want the panel subschema with all
|
||||||
|
// possible disjunctions? If so, maybe the interface needs work. Or maybe
|
||||||
|
// just defer that until the proper generic composite scuemata impl.
|
||||||
|
dpan, err := ddash.(CompositeDashboardSchema).LatestPanelSchemaFor("table") |
||||||
|
require.NoError(t, err, "error while loading panel subschema") |
||||||
|
|
||||||
|
require.NoError(t, fs.WalkDir(validdir, ".", func(path string, d fs.DirEntry, err error) error { |
||||||
|
require.NoError(t, err) |
||||||
|
|
||||||
|
if d.IsDir() || filepath.Ext(d.Name()) != ".json" { |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
t.Run(path, func(t *testing.T) { |
||||||
|
// TODO FIXME stop skipping once we actually have the schema filled in
|
||||||
|
// enough that the tests pass, lol
|
||||||
|
t.Skip() |
||||||
|
|
||||||
|
b, err := validdir.Open(path) |
||||||
|
require.NoError(t, err, "failed to open panel file") |
||||||
|
|
||||||
|
err = dpan.Validate(schema.Resource{Value: b}) |
||||||
|
require.NoError(t, err, "panel failed validation") |
||||||
|
}) |
||||||
|
|
||||||
|
return nil |
||||||
|
})) |
||||||
|
} |
@ -0,0 +1,161 @@ |
|||||||
|
package load |
||||||
|
|
||||||
|
import ( |
||||||
|
"encoding/json" |
||||||
|
"errors" |
||||||
|
"fmt" |
||||||
|
"io/fs" |
||||||
|
"io/ioutil" |
||||||
|
"path/filepath" |
||||||
|
|
||||||
|
"cuelang.org/go/cue" |
||||||
|
"cuelang.org/go/cue/load" |
||||||
|
"github.com/grafana/grafana/pkg/schema" |
||||||
|
) |
||||||
|
|
||||||
|
// Returns a disjunction of structs representing each panel schema version
|
||||||
|
// (post-mapping from on-disk #PanelModel form) from each scuemata in the map.
|
||||||
|
func disjunctPanelScuemata(scuemap map[string]schema.VersionedCueSchema) (cue.Value, error) { |
||||||
|
partsi, err := rt.Compile("panelDisjunction", ` |
||||||
|
allPanels: [Name=_]: {} |
||||||
|
parts: or([for v in allPanels { v }]) |
||||||
|
`) |
||||||
|
if err != nil { |
||||||
|
return cue.Value{}, err |
||||||
|
} |
||||||
|
|
||||||
|
parts := partsi.Value() |
||||||
|
for id, sch := range scuemap { |
||||||
|
for sch != nil { |
||||||
|
cv := mapPanelModel(id, sch) |
||||||
|
|
||||||
|
mjv, miv := sch.Version() |
||||||
|
parts = parts.Fill(cv, "allPanels", fmt.Sprintf("%s@%v.%v", id, mjv, miv)) |
||||||
|
sch = sch.Successor() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return parts.LookupPath(cue.MakePath(cue.Str("parts"))), nil |
||||||
|
} |
||||||
|
|
||||||
|
// mapPanelModel maps a schema from the #PanelModel form in which it's declared
|
||||||
|
// in a plugin's model.cue to the structure in which it actually appears in the
|
||||||
|
// dashboard schema.
|
||||||
|
func mapPanelModel(id string, vcs schema.VersionedCueSchema) cue.Value { |
||||||
|
maj, min := vcs.Version() |
||||||
|
// Ignore err return, this can't fail to compile
|
||||||
|
inter, _ := rt.Compile("typedPanel", fmt.Sprintf(` |
||||||
|
in: { |
||||||
|
type: %q |
||||||
|
v: { |
||||||
|
maj: %d |
||||||
|
min: %d |
||||||
|
} |
||||||
|
model: {...} |
||||||
|
} |
||||||
|
result: { |
||||||
|
type: in.type, |
||||||
|
panelSchema: maj: in.v.maj |
||||||
|
panelSchema: min: in.v.min |
||||||
|
options: in.model.PanelOptions |
||||||
|
fieldConfig: defaults: custom: in.model.PanelFieldConfig |
||||||
|
} |
||||||
|
`, id, maj, min)) |
||||||
|
|
||||||
|
// TODO validate, especially with #PanelModel
|
||||||
|
return inter.Value().Fill(vcs.CUE(), "in", "model").LookupPath(cue.MakePath(cue.Str(("result")))) |
||||||
|
} |
||||||
|
|
||||||
|
func readPanelModels(p BaseLoadPaths) (map[string]schema.VersionedCueSchema, error) { |
||||||
|
overlay := make(map[string]load.Source) |
||||||
|
if err := toOverlay("/", p.BaseCueFS, overlay); err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
if err := toOverlay("/", p.DistPluginCueFS, overlay); err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
base, err := getBaseScuemata(p) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
pmf := base.Value().LookupPath(cue.MakePath(cue.Def("#PanelFamily"))) |
||||||
|
if !pmf.Exists() { |
||||||
|
return nil, errors.New("could not locate #PanelFamily definition") |
||||||
|
} |
||||||
|
|
||||||
|
all := make(map[string]schema.VersionedCueSchema) |
||||||
|
err = fs.WalkDir(p.DistPluginCueFS, ".", func(path string, d fs.DirEntry, err error) error { |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
if d.IsDir() || d.Name() != "plugin.json" { |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
dpath := filepath.Dir(path) |
||||||
|
// For now, skip plugins without a models.cue
|
||||||
|
_, err = p.DistPluginCueFS.Open(filepath.Join(dpath, "models.cue")) |
||||||
|
if err != nil { |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
fi, err := p.DistPluginCueFS.Open(path) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
b, err := ioutil.ReadAll(fi) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
jmap := make(map[string]interface{}) |
||||||
|
err = json.Unmarshal(b, &jmap) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
iid, has := jmap["id"] |
||||||
|
if !has || jmap["type"] != "panel" { |
||||||
|
return errors.New("no type field in plugin.json or not a panel type plugin") |
||||||
|
} |
||||||
|
id := iid.(string) |
||||||
|
|
||||||
|
cfg := &load.Config{ |
||||||
|
Package: "grafanaschema", |
||||||
|
Overlay: overlay, |
||||||
|
} |
||||||
|
|
||||||
|
li := load.Instances([]string{filepath.Join("/", dpath, "models.cue")}, cfg) |
||||||
|
imod, err := rt.Build(li[0]) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
// Get the Family declaration in the models.cue file...
|
||||||
|
pmod := imod.Value().LookupPath(cue.MakePath(cue.Str("Family"))) |
||||||
|
if !pmod.Exists() { |
||||||
|
return fmt.Errorf("%s does not contain a declaration of its models at path 'Family'", path) |
||||||
|
} |
||||||
|
|
||||||
|
// Ensure the declared value is subsumed by/correct wrt #PanelFamily
|
||||||
|
if err := pmf.Subsume(pmod); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
// Create a generic schema family to represent the whole of the
|
||||||
|
fam, err := buildGenericScuemata(pmod) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
all[id] = fam |
||||||
|
return nil |
||||||
|
}) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
return all, nil |
||||||
|
} |
@ -0,0 +1,3 @@ |
|||||||
|
All artifact JSON contained in these subdirectories should be valid. Invalid |
||||||
|
JSON is handled elsewhere, as it must be coupled with expected error messages |
||||||
|
for testing purposes. |
@ -0,0 +1,153 @@ |
|||||||
|
{ |
||||||
|
"__inputs": [ |
||||||
|
{ |
||||||
|
"name": "DS_GDEV-TESTDATA", |
||||||
|
"label": "gdev-testdata", |
||||||
|
"description": "", |
||||||
|
"type": "datasource", |
||||||
|
"pluginId": "testdata", |
||||||
|
"pluginName": "TestData DB" |
||||||
|
} |
||||||
|
], |
||||||
|
"__requires": [ |
||||||
|
{ |
||||||
|
"type": "grafana", |
||||||
|
"id": "grafana", |
||||||
|
"name": "Grafana", |
||||||
|
"version": "7.5.0-pre" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"type": "panel", |
||||||
|
"id": "table", |
||||||
|
"name": "Table", |
||||||
|
"version": "" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"type": "datasource", |
||||||
|
"id": "testdata", |
||||||
|
"name": "TestData DB", |
||||||
|
"version": "1.0.0" |
||||||
|
} |
||||||
|
], |
||||||
|
"annotations": { |
||||||
|
"list": [ |
||||||
|
{ |
||||||
|
"builtIn": 1, |
||||||
|
"datasource": "-- Grafana --", |
||||||
|
"enable": true, |
||||||
|
"hide": true, |
||||||
|
"iconColor": "rgba(0, 211, 255, 1)", |
||||||
|
"name": "Annotations & Alerts", |
||||||
|
"rawQuery": "wtf", |
||||||
|
"showIn": 0, |
||||||
|
"type": "dashboard" |
||||||
|
} |
||||||
|
] |
||||||
|
}, |
||||||
|
"editable": true, |
||||||
|
"graphTooltip": 0, |
||||||
|
"id": 42, |
||||||
|
"links": [], |
||||||
|
"panels": [ |
||||||
|
{ |
||||||
|
"datasource": "${DS_GDEV-TESTDATA}", |
||||||
|
"fieldConfig": { |
||||||
|
"defaults": { |
||||||
|
"custom": { |
||||||
|
"align": "right", |
||||||
|
"filterable": false |
||||||
|
}, |
||||||
|
"decimals": 3, |
||||||
|
"mappings": [], |
||||||
|
"unit": "watt" |
||||||
|
}, |
||||||
|
"overrides": [ |
||||||
|
{ |
||||||
|
"matcher": { |
||||||
|
"id": "byName", |
||||||
|
"options": "Max" |
||||||
|
}, |
||||||
|
"properties": [ |
||||||
|
{ |
||||||
|
"id": "custom.displayMode", |
||||||
|
"value": "lcd-gauge" |
||||||
|
} |
||||||
|
] |
||||||
|
}, |
||||||
|
{ |
||||||
|
"matcher": { |
||||||
|
"id": "byName", |
||||||
|
"options": "A" |
||||||
|
}, |
||||||
|
"properties": [ |
||||||
|
{ |
||||||
|
"id": "custom.width", |
||||||
|
"value": 200 |
||||||
|
} |
||||||
|
] |
||||||
|
} |
||||||
|
] |
||||||
|
}, |
||||||
|
"gridPos": { |
||||||
|
"h": 9, |
||||||
|
"w": 12, |
||||||
|
"x": 0, |
||||||
|
"y": 0 |
||||||
|
}, |
||||||
|
"id": 2, |
||||||
|
"options": { |
||||||
|
"showHeader": true, |
||||||
|
"sortBy": [] |
||||||
|
}, |
||||||
|
"pluginVersion": "7.5.0-pre", |
||||||
|
"targets": [ |
||||||
|
{ |
||||||
|
"alias": "", |
||||||
|
"csvWave": { |
||||||
|
"timeStep": 60, |
||||||
|
"valuesCSV": "0,0,2,2,1,1" |
||||||
|
}, |
||||||
|
"lines": 10, |
||||||
|
"points": [], |
||||||
|
"pulseWave": { |
||||||
|
"offCount": 3, |
||||||
|
"offValue": 1, |
||||||
|
"onCount": 3, |
||||||
|
"onValue": 2, |
||||||
|
"timeStep": 60 |
||||||
|
}, |
||||||
|
"refId": "A", |
||||||
|
"scenarioId": "random_walk_table", |
||||||
|
"stream": { |
||||||
|
"bands": 1, |
||||||
|
"noise": 2.2, |
||||||
|
"speed": 250, |
||||||
|
"spread": 3.5, |
||||||
|
"type": "signal" |
||||||
|
}, |
||||||
|
"stringInput": "" |
||||||
|
} |
||||||
|
], |
||||||
|
"title": "Panel Title", |
||||||
|
"type": "table", |
||||||
|
"panelSchema": { |
||||||
|
"maj": 0, |
||||||
|
"min": 0 |
||||||
|
} |
||||||
|
} |
||||||
|
], |
||||||
|
"schemaVersion": 27, |
||||||
|
"style": "dark", |
||||||
|
"tags": [], |
||||||
|
"templating": { |
||||||
|
"list": [] |
||||||
|
}, |
||||||
|
"time": { |
||||||
|
"from": "now-6h", |
||||||
|
"to": "now" |
||||||
|
}, |
||||||
|
"timezone": "browser", |
||||||
|
"title": "with table", |
||||||
|
"uid": "emal8gQMz", |
||||||
|
"version": 2 |
||||||
|
} |
@ -0,0 +1,85 @@ |
|||||||
|
{ |
||||||
|
"datasource": "${DS_GDEV-TESTDATA}", |
||||||
|
"fieldConfig": { |
||||||
|
"defaults": { |
||||||
|
"custom": { |
||||||
|
"align": "right", |
||||||
|
"filterable": false |
||||||
|
}, |
||||||
|
"decimals": 3, |
||||||
|
"mappings": [], |
||||||
|
"unit": "watt" |
||||||
|
}, |
||||||
|
"overrides": [ |
||||||
|
{ |
||||||
|
"matcher": { |
||||||
|
"id": "byName", |
||||||
|
"options": "Max" |
||||||
|
}, |
||||||
|
"properties": [ |
||||||
|
{ |
||||||
|
"id": "custom.displayMode", |
||||||
|
"value": "lcd-gauge" |
||||||
|
} |
||||||
|
] |
||||||
|
}, |
||||||
|
{ |
||||||
|
"matcher": { |
||||||
|
"id": "byName", |
||||||
|
"options": "A" |
||||||
|
}, |
||||||
|
"properties": [ |
||||||
|
{ |
||||||
|
"id": "custom.width", |
||||||
|
"value": 200 |
||||||
|
} |
||||||
|
] |
||||||
|
} |
||||||
|
] |
||||||
|
}, |
||||||
|
"gridPos": { |
||||||
|
"h": 9, |
||||||
|
"w": 12, |
||||||
|
"x": 0, |
||||||
|
"y": 0 |
||||||
|
}, |
||||||
|
"options": { |
||||||
|
"showHeader": true, |
||||||
|
"sortBy": [] |
||||||
|
}, |
||||||
|
"pluginVersion": "7.5.0-pre", |
||||||
|
"targets": [ |
||||||
|
{ |
||||||
|
"alias": "", |
||||||
|
"csvWave": { |
||||||
|
"timeStep": 60, |
||||||
|
"valuesCSV": "0,0,2,2,1,1" |
||||||
|
}, |
||||||
|
"lines": 10, |
||||||
|
"points": [], |
||||||
|
"pulseWave": { |
||||||
|
"offCount": 3, |
||||||
|
"offValue": 1, |
||||||
|
"onCount": 3, |
||||||
|
"onValue": 2, |
||||||
|
"timeStep": 60 |
||||||
|
}, |
||||||
|
"refId": "A", |
||||||
|
"scenarioId": "random_walk_table", |
||||||
|
"stream": { |
||||||
|
"bands": 1, |
||||||
|
"noise": 2.2, |
||||||
|
"speed": 250, |
||||||
|
"spread": 3.5, |
||||||
|
"type": "signal" |
||||||
|
}, |
||||||
|
"stringInput": "" |
||||||
|
} |
||||||
|
], |
||||||
|
"title": "Panel Title", |
||||||
|
"type": "table", |
||||||
|
"panelSchema": { |
||||||
|
"maj": 0, |
||||||
|
"min": 0 |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,90 @@ |
|||||||
|
package grafanaschema |
||||||
|
|
||||||
|
import ( |
||||||
|
ui "github.com/grafana/grafana/cue/ui:grafanaschema" |
||||||
|
) |
||||||
|
|
||||||
|
// TODO should we remove Family, and make lineages and migrations top-level values? |
||||||
|
// It's easy to do, and arguably increases clarity of this crucial file by |
||||||
|
// reducing one layer of nesting. But it sorta requires understanding that CUE |
||||||
|
// also thinks of an entire file (aka, an "instance") as a struct in order for |
||||||
|
// it to make sense that the file itself is schematized by #PanelFamily. What's |
||||||
|
// the best DX here? |
||||||
|
|
||||||
|
// "Family" must be an instance of the #PanelFamily type, defined in |
||||||
|
// cue/scuemata/panel-plugin.cue. This ensures some key invariants: |
||||||
|
// |
||||||
|
// - lineages is an array of arrays. Outer array is major version, inner is minor. |
||||||
|
// (This IS NOT semver, though.) |
||||||
|
// - Within a single seq, each successive schema is backwards compatible with |
||||||
|
// the prior schema. (See, it's not semver. No special rules for v0.) |
||||||
|
// - For each seq/major version after the first, there exists a migration |
||||||
|
// that allows us to transform a resource compliant with the old version of |
||||||
|
// the schema into one compliant with the new one. |
||||||
|
// |
||||||
|
// That's right, we've schematized our schema declarations. Not all above |
||||||
|
// invariants are enforced right now, but they must be before launch. |
||||||
|
// |
||||||
|
// Grafana won't need to rely on multiple versions of schema until after this |
||||||
|
// system is released with Grafana 8. But it needs to be in place at the moment |
||||||
|
// Grafana 8 is released - especially for plugins, which have their own release |
||||||
|
// cycle, and could need to make breaking changes very shortly after v8's release. |
||||||
|
Family: { |
||||||
|
lineages: [ |
||||||
|
[ |
||||||
|
{ // v0.0. The actual schema is the contents of this struct. |
||||||
|
PanelOptions: { |
||||||
|
frameIndex: number | *0 |
||||||
|
showHeader: bool | *true |
||||||
|
sortBy?: [...ui.TableSortByFieldState] |
||||||
|
} |
||||||
|
PanelFieldConfig: { |
||||||
|
width?: int |
||||||
|
align?: *null | string |
||||||
|
displayMode?: string | *"auto" // TODO? TableCellDisplayMode |
||||||
|
filterable?: bool |
||||||
|
} |
||||||
|
}, |
||||||
|
{ // v0.1 |
||||||
|
lineages[0][0] |
||||||
|
PanelOptions: foo: string | *"foo" |
||||||
|
} |
||||||
|
], |
||||||
|
[ |
||||||
|
{ // v1.0 - breaking changes vs. v0.1 in this struct. |
||||||
|
PanelOptions: { |
||||||
|
frameIndex: number | *0 |
||||||
|
includeHeader: bool | *true |
||||||
|
sortBy?: [...ui.TableSortByFieldState] |
||||||
|
} |
||||||
|
PanelFieldConfig: { |
||||||
|
width?: int |
||||||
|
align?: string |
||||||
|
displayMode?: string |
||||||
|
} |
||||||
|
} |
||||||
|
], |
||||||
|
] |
||||||
|
migrations: [ |
||||||
|
{ // maps from v0.1 to v1.0 |
||||||
|
// TODO it's not good that the user has to specify these. Should be |
||||||
|
// implicit, since we don't want to allow any actual choice here. |
||||||
|
// But NOT having it also means CUE can't actually tell if the |
||||||
|
// _rel definition makes any sense at all. UGHHH. Would it be |
||||||
|
// better to put these directly on the lineages? |
||||||
|
from: lineages[0][1] |
||||||
|
to: lineages[1][0] |
||||||
|
rel: { |
||||||
|
PanelOptions: { |
||||||
|
frameIndex: from.PanelOptions.frameIndex |
||||||
|
includeHeader: from.PanelOptions.showHeader |
||||||
|
if from.PanelOptions.sortBy != _|_ { |
||||||
|
sortBy: from.PanelOptions.sortBy | *null |
||||||
|
} |
||||||
|
} |
||||||
|
PanelFieldConfig: from.PanelFieldConfig |
||||||
|
} |
||||||
|
result: rel & to |
||||||
|
} |
||||||
|
] |
||||||
|
} |
@ -0,0 +1,9 @@ |
|||||||
|
{ |
||||||
|
"type": "panel", |
||||||
|
"name": "Sample plugin with lineage", |
||||||
|
"id": "with-lineage", |
||||||
|
|
||||||
|
"info": { |
||||||
|
"description": "Show how complex history may work" |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,274 @@ |
|||||||
|
package schema |
||||||
|
|
||||||
|
import ( |
||||||
|
"errors" |
||||||
|
"fmt" |
||||||
|
"math/bits" |
||||||
|
|
||||||
|
"cuelang.org/go/cue" |
||||||
|
) |
||||||
|
|
||||||
|
// CueSchema represents a single, complete CUE-based schema that can perform
|
||||||
|
// operations on Resources.
|
||||||
|
//
|
||||||
|
// All CueSchema MUST EITHER:
|
||||||
|
// - Be a VersionedCueSchema, and be the latest version in the latest lineage in a Family
|
||||||
|
// - Return non-nil from Successor(), and a procedure to Migrate() a Resource to that successor schema
|
||||||
|
//
|
||||||
|
// By definition, VersionedCueSchema are within a lineage. As long as lineage
|
||||||
|
// backwards compatibility invariants hold, migration to a VersionedCueSchema to
|
||||||
|
// a successor schema in their lineage is trivial: simply unify the Resource
|
||||||
|
// with the successor schema.
|
||||||
|
type CueSchema interface { |
||||||
|
// Validate checks that the resource is correct with respect to the schema.
|
||||||
|
Validate(Resource) error |
||||||
|
|
||||||
|
// ApplyDefaults returns a new, concrete copy of the Resource with all paths
|
||||||
|
// that are 1) missing in the Resource AND 2) specified by the schema,
|
||||||
|
// filled with default values specified by the schema.
|
||||||
|
ApplyDefaults(Resource) (Resource, error) |
||||||
|
|
||||||
|
// TrimDefaults returns a new, concrete copy of the Resource where all paths
|
||||||
|
// in the where the values at those paths are the same as the default value
|
||||||
|
// given in the schema.
|
||||||
|
TrimDefaults(Resource) (Resource, error) |
||||||
|
|
||||||
|
// Migrate transforms a Resource into a new Resource that is correct with
|
||||||
|
// respect to its Successor schema. It returns the transformed resource,
|
||||||
|
// the schema to which the resource now conforms, and any errors that
|
||||||
|
// may have occurred during the migration.
|
||||||
|
//
|
||||||
|
// No migration occurs and the input Resource is returned in two cases:
|
||||||
|
//
|
||||||
|
// - The migration encountered an error; the third return is non-nil.
|
||||||
|
// - There exists no schema to migrate to; the second and third return are nil.
|
||||||
|
//
|
||||||
|
// Note that the returned schema is always a VersionedCueSchema. This
|
||||||
|
// reflects a key design invariant of the system: all migrations, whether
|
||||||
|
// they begin from a schema inside or outside of the Family, must land
|
||||||
|
// somewhere on a Family's sequence of schemata.
|
||||||
|
Migrate(Resource) (Resource, VersionedCueSchema, error) |
||||||
|
|
||||||
|
// Successor returns the VersionedCueSchema to which this CueSchema can migrate a
|
||||||
|
// Resource.
|
||||||
|
Successor() VersionedCueSchema |
||||||
|
|
||||||
|
// CUE returns the cue.Value representing the actual schema.
|
||||||
|
CUE() cue.Value |
||||||
|
} |
||||||
|
|
||||||
|
// VersionedCueSchema are CueSchema that are part of a backwards-compatible
|
||||||
|
// versioned lineage.
|
||||||
|
type VersionedCueSchema interface { |
||||||
|
CueSchema |
||||||
|
|
||||||
|
// Version reports the major and minor versions of the schema.
|
||||||
|
Version() (major, minor int) |
||||||
|
} |
||||||
|
|
||||||
|
// SearchAndValidate traverses the family of schemas reachable from the provided
|
||||||
|
// VersionedCueSchema. For each schema, it attempts to validate the provided
|
||||||
|
// value, which may be a byte slice representing valid JSON (TODO YAML), a Go
|
||||||
|
// struct, or cue.Value. If providing a cue.Value that is not fully concrete,
|
||||||
|
// the result is undefined.
|
||||||
|
//
|
||||||
|
// Traversal is performed from the newest schema to the oldest. However, because
|
||||||
|
// newer VersionedCueSchema have no way of directly accessing their predecessors
|
||||||
|
// (they form a singly-linked list), the oldest possible schema should always be
|
||||||
|
// provided - typically, the one returned from the family loader function.
|
||||||
|
//
|
||||||
|
// Failure to validate against any schema in the family is indicated by a
|
||||||
|
// non-nil error return. Success is indicated by a non-nil VersionedCueSchema.
|
||||||
|
// If successful, the returned VersionedCueSchema will be the first one against
|
||||||
|
// which the provided resource passed validation.
|
||||||
|
func SearchAndValidate(s VersionedCueSchema, v interface{}) (VersionedCueSchema, error) { |
||||||
|
arr := AsArray(s) |
||||||
|
|
||||||
|
// Work from latest to earliest
|
||||||
|
var err error |
||||||
|
for o := len(arr) - 1; o >= 0; o-- { |
||||||
|
for i := len(arr[o]) - 1; i >= 0; i-- { |
||||||
|
if err = arr[o][i].Validate(Resource{Value: v}); err == nil { |
||||||
|
return arr[o][i], nil |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// TODO sloppy, return more than last error. Need our own error type that
|
||||||
|
// collates all the individual errors, relates them to the schema that
|
||||||
|
// produced them, and ideally deduplicates repeated errors across each
|
||||||
|
// schema.
|
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
// AsArray collates all VersionedCueSchema in a Family into a two-dimensional
|
||||||
|
// array. The outer array index corresponds to major version number and inner
|
||||||
|
// array index to minor version number.
|
||||||
|
func AsArray(sch VersionedCueSchema) [][]VersionedCueSchema { |
||||||
|
var ret [][]VersionedCueSchema |
||||||
|
var flat []VersionedCueSchema |
||||||
|
|
||||||
|
// two loops. lazy day, today
|
||||||
|
for sch != nil { |
||||||
|
flat = append(flat, sch) |
||||||
|
sch = sch.Successor() |
||||||
|
} |
||||||
|
|
||||||
|
for _, sch := range flat { |
||||||
|
maj, _ := sch.Version() |
||||||
|
if len(ret) == maj { |
||||||
|
ret = append(ret, []VersionedCueSchema{}) |
||||||
|
} |
||||||
|
ret[maj] = append(ret[maj], sch) |
||||||
|
} |
||||||
|
|
||||||
|
return ret |
||||||
|
} |
||||||
|
|
||||||
|
// Find traverses the chain of VersionedCueSchema until the criteria in the
|
||||||
|
// SearchOption is met.
|
||||||
|
//
|
||||||
|
// If no schema is found that fulfills the criteria, nil is returned. Latest()
|
||||||
|
// and LatestInCurrentMajor() will always succeed, unless the input schema is
|
||||||
|
// nil.
|
||||||
|
func Find(s VersionedCueSchema, opt SearchOption) VersionedCueSchema { |
||||||
|
if s == nil { |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
p := &ssopt{} |
||||||
|
opt(p) |
||||||
|
if err := p.validate(); err != nil { |
||||||
|
panic(fmt.Sprint("unreachable:", err)) |
||||||
|
} |
||||||
|
|
||||||
|
switch { |
||||||
|
case p.latest: |
||||||
|
for ; s.Successor() != nil; s = s.Successor() { |
||||||
|
} |
||||||
|
return s |
||||||
|
|
||||||
|
case p.latestInCurrentMajor: |
||||||
|
p.latestInMajor, _ = s.Version() |
||||||
|
fallthrough |
||||||
|
|
||||||
|
case p.hasLatestInMajor: |
||||||
|
imaj, _ := s.Version() |
||||||
|
if imaj > p.latestInMajor { |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
var last VersionedCueSchema |
||||||
|
for imaj <= p.latestInMajor { |
||||||
|
last, s = s, s.Successor() |
||||||
|
if s == nil { |
||||||
|
if imaj == p.latestInMajor { |
||||||
|
return last |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
imaj, _ = s.Version() |
||||||
|
} |
||||||
|
return last |
||||||
|
|
||||||
|
default: // exact
|
||||||
|
for s != nil { |
||||||
|
maj, min := s.Version() |
||||||
|
if p.exact == [2]int{maj, min} { |
||||||
|
return s |
||||||
|
} |
||||||
|
s = s.Successor() |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// SearchOption indicates how far along a chain of schemas an operation should
|
||||||
|
// proceed.
|
||||||
|
type SearchOption sso |
||||||
|
|
||||||
|
type sso func(p *ssopt) |
||||||
|
|
||||||
|
type ssopt struct { |
||||||
|
latest bool |
||||||
|
latestInMajor int |
||||||
|
hasLatestInMajor bool |
||||||
|
latestInCurrentMajor bool |
||||||
|
exact [2]int |
||||||
|
} |
||||||
|
|
||||||
|
func (p *ssopt) validate() error { |
||||||
|
var which uint16 |
||||||
|
if p.latest { |
||||||
|
which = which + 1<<1 |
||||||
|
} |
||||||
|
if p.exact != [2]int{0, 0} { |
||||||
|
which = which + 1<<2 |
||||||
|
} |
||||||
|
if p.hasLatestInMajor { |
||||||
|
if p.latestInMajor != -1 { |
||||||
|
which = which + 1<<3 |
||||||
|
} |
||||||
|
} else if p.latestInMajor != 0 { |
||||||
|
// Disambiguate real zero from default zero
|
||||||
|
return fmt.Errorf("latestInMajor should never be non-zero if hasLatestInMajor is false, got %v", p.latestInMajor) |
||||||
|
} |
||||||
|
if p.latestInCurrentMajor { |
||||||
|
which = which + 1<<4 |
||||||
|
} |
||||||
|
|
||||||
|
if bits.OnesCount16(which) != 1 { |
||||||
|
return errors.New("may only pass one SchemaSearchOption") |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// Latest indicates that traversal will continue to the newest schema in the
|
||||||
|
// newest lineage.
|
||||||
|
func Latest() SearchOption { |
||||||
|
return func(p *ssopt) { |
||||||
|
p.latest = true |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// LatestInMajor will find the latest schema within the provided major version
|
||||||
|
// lineage. If no lineage exists corresponding to the provided number, traversal
|
||||||
|
// will terminate with an error.
|
||||||
|
func LatestInMajor(maj int) SearchOption { |
||||||
|
return func(p *ssopt) { |
||||||
|
p.latestInMajor = maj |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// LatestInCurrentMajor will find the newest schema having the same major
|
||||||
|
// version as the schema from which the search begins.
|
||||||
|
func LatestInCurrentMajor() SearchOption { |
||||||
|
return func(p *ssopt) { |
||||||
|
p.latestInCurrentMajor = true |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Exact will find the schema with the exact major and minor version number
|
||||||
|
// provided.
|
||||||
|
func Exact(maj, min int) SearchOption { |
||||||
|
return func(p *ssopt) { |
||||||
|
p.exact = [2]int{maj, min} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// A Resource represents a concrete data object - e.g., JSON
|
||||||
|
// representing a dashboard.
|
||||||
|
//
|
||||||
|
// This type mostly exists to improve readability for users. Having a type that
|
||||||
|
// differentiates cue.Value that represent a schema from cue.Value that
|
||||||
|
// represent a concrete object is quite helpful. It also gives us a working type
|
||||||
|
// for a resource that can be reused across multiple calls, so that re-parsing
|
||||||
|
// isn't necessary.
|
||||||
|
//
|
||||||
|
// TODO this is a terrible way to do this, refactor
|
||||||
|
type Resource struct { |
||||||
|
Value interface{} |
||||||
|
} |
||||||
|
|
||||||
|
// TODO add migrator with SearchOption for stopping criteria
|
@ -0,0 +1,4 @@ |
|||||||
|
package schema |
||||||
|
|
||||||
|
// TODO tests for this stuff! Everything in this package is totally generic,
|
||||||
|
// nothing is specific to Grafana
|
@ -0,0 +1,26 @@ |
|||||||
|
package grafanaschema |
||||||
|
|
||||||
|
import ( |
||||||
|
ui "github.com/grafana/grafana/cue/ui:grafanaschema" |
||||||
|
) |
||||||
|
|
||||||
|
Family: { |
||||||
|
lineages: [ |
||||||
|
[ |
||||||
|
{ |
||||||
|
PanelOptions: { |
||||||
|
frameIndex: number | *0 |
||||||
|
showHeader: bool | *true |
||||||
|
sortBy?: [...ui.TableSortByFieldState] |
||||||
|
} |
||||||
|
PanelFieldConfig: { |
||||||
|
width?: int |
||||||
|
align?: *null | string |
||||||
|
displayMode?: string | *"auto" // TODO? TableCellDisplayMode |
||||||
|
filterable?: bool |
||||||
|
} |
||||||
|
}, |
||||||
|
] |
||||||
|
] |
||||||
|
migrations: [] |
||||||
|
} |
@ -0,0 +1,34 @@ |
|||||||
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
// NOTE: This file will be auto generated from models.cue
|
||||||
|
// It is currenty hand written but will serve as the target for cuetsy
|
||||||
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
import { TableCellDisplayMode, TableSortByFieldState } from '@grafana/ui'; |
||||||
|
|
||||||
|
// Only the latest schema version is translated to TypeScript, on the premise
|
||||||
|
// that either the dashboard loading process, or (eventually) CUE-defined
|
||||||
|
// migrations ensure that bulk of the frontend application only ever
|
||||||
|
// need directly consider the most recent version of the schema.
|
||||||
|
export const modelVersion = Object.freeze([1, 0]); |
||||||
|
|
||||||
|
export interface PanelOptions { |
||||||
|
frameIndex: number; |
||||||
|
showHeader: boolean; |
||||||
|
sortBy?: TableSortByFieldState[]; |
||||||
|
} |
||||||
|
|
||||||
|
export const defaultPanelOptions: PanelOptions = { |
||||||
|
frameIndex: 0, |
||||||
|
showHeader: true, |
||||||
|
}; |
||||||
|
|
||||||
|
export interface PanelFieldConfig { |
||||||
|
width?: number; |
||||||
|
align?: string; |
||||||
|
displayMode?: TableCellDisplayMode; |
||||||
|
filterable?: boolean; |
||||||
|
} |
||||||
|
|
||||||
|
export const defaultPanelFieldConfig: PanelFieldConfig = { |
||||||
|
displayMode: TableCellDisplayMode.Auto, |
||||||
|
}; |
@ -1,17 +0,0 @@ |
|||||||
import { TableSortByFieldState } from '@grafana/ui'; |
|
||||||
|
|
||||||
export interface Options { |
|
||||||
frameIndex: number; |
|
||||||
showHeader: boolean; |
|
||||||
sortBy?: TableSortByFieldState[]; |
|
||||||
} |
|
||||||
|
|
||||||
export interface TableSortBy { |
|
||||||
displayName: string; |
|
||||||
desc: boolean; |
|
||||||
} |
|
||||||
|
|
||||||
export interface CustomFieldConfig { |
|
||||||
width: number; |
|
||||||
displayMode: string; |
|
||||||
} |
|
@ -0,0 +1,93 @@ |
|||||||
|
#!/usr/bin/env bash |
||||||
|
set -eo pipefail |
||||||
|
|
||||||
|
# MUST BE RUN FROM GRAFANA ROOT DIR |
||||||
|
test -d cue |
||||||
|
|
||||||
|
# Must have latest cue and cuetsy |
||||||
|
if ! command -v cue &> /dev/null; then |
||||||
|
echo "must install cue on PATH" |
||||||
|
exit 1 |
||||||
|
fi |
||||||
|
if ! command -v cuetsy &> /dev/null; then |
||||||
|
echo "must install cuetsy on PATH" |
||||||
|
exit 1 |
||||||
|
fi |
||||||
|
|
||||||
|
# TODO Everything here needs to be moved into custom CUE logic in a Go program. |
||||||
|
# It _might_ be possible to do what we want with some CUE tools magic |
||||||
|
# (https://pkg.go.dev/cuelang.org/go@v0.3.0-beta.5/pkg/tool), but unless that |
||||||
|
# turns out to be pretty straightforward, it's probably better to encode our |
||||||
|
# filesystem semantics there. |
||||||
|
|
||||||
|
# Enumerate and move all CUE files under packages/grafana-{data,ui} into |
||||||
|
# respective cue subdir. These subdirs are where we place assembled |
||||||
|
# definitions, where Go loads from, and where other CUE - including CUE defined |
||||||
|
# in plugins - import from. |
||||||
|
mkdir -p cue/ui cue/data |
||||||
|
rm -f {cue/ui/gen.cue,cue/data/gen.cue} |
||||||
|
|
||||||
|
# TODO decide if multiple or single files seems like better ergonomics |
||||||
|
# shellcheck disable=SC2046 |
||||||
|
cue def -s $(find packages/grafana-ui -type f -name "*.cue") > cue/ui/gen.cue |
||||||
|
# shellcheck disable=SC2046 |
||||||
|
cue def -s $(find packages/grafana-data -type f -name "*.cue") > cue/data/gen.cue |
||||||
|
|
||||||
|
# Horrible hack to remove import statements. |
||||||
|
# |
||||||
|
# HACK-IMPOSED CONSTRAINT: Only works for single-line imports, so we can ONLY use |
||||||
|
# single-line imports in CUE files until this is improved! Expressly only here |
||||||
|
# as a hack because we can't make this better with vanilla cue. |
||||||
|
# |
||||||
|
# HACK-IMPOSED CONSTRAINT: Can't import between @grafana/ui and @grafana/data, |
||||||
|
# because those imports will also be removed |
||||||
|
# |
||||||
|
# TODO move a more careful import-elimination check into a Go tool |
||||||
|
# |
||||||
|
# It's important to understand why this is necessary, though. We are expecting |
||||||
|
# that these core components may depend on each other - e.g., how |
||||||
|
# GraphTooltipOptions composes in TooltipMode. We have to preserve those |
||||||
|
# literal identifiers in our assembled CUE, so that when a panel plugin's |
||||||
|
# models.cue imports and references something like GraphTooltipOptions in CUE, |
||||||
|
# it's still the same identifier as appeared in the original core models.cue |
||||||
|
# files, AND therefore is exactly the identifier that appears in |
||||||
|
# cuetsy-generated @grafana/{ui,data} packages. That is, as long as we preserve |
||||||
|
# the relation between the identifier "GraphTooltipOptions" as a top-level |
||||||
|
# importable thing at all stages on the CUE side, then everything on the |
||||||
|
# TypeScript side will line up. |
||||||
|
sed -i -e 's/^import.*//g' {cue/ui/gen.cue,cue/data/gen.cue} |
||||||
|
|
||||||
|
# Remove all qualified identifiers |
||||||
|
# (https://cuelang.org/docs/references/spec/#qualified-identifiers) from the |
||||||
|
# generated CUE files. |
||||||
|
# |
||||||
|
# Even worse hack than the above, but part and parcel with having imports. By |
||||||
|
# assembling the CUE inputs together into a single dir in a single package (and |
||||||
|
# even in a single file, though single dir is sufficient), we've obviated the |
||||||
|
# need for imports and qualified identifiers; CUE's loader logic concats |
||||||
|
# everything into a single instance. |
||||||
|
# |
||||||
|
# HACK-IMPOSED CONSTRAINT: No selectors (foo.bar, |
||||||
|
# https://cuelang.org/docs/references/spec/#qualified-identifiers), at all. |
||||||
|
# Thus, no nested identifiers. This is a horrible sledgehammer. It makes it |
||||||
|
# impossible to correctly consume a CUE file that references a nested |
||||||
|
# identifier (foo.bar), because this stupid logic can't disambiguate between |
||||||
|
# those and referencing a label from an import. |
||||||
|
# |
||||||
|
# HACK-IMPOSED CONSTRAINT: We can't experiment with the sort of complex |
||||||
|
# structures necessary for revisioning as long as we're doing this, as they're |
||||||
|
# necessarily going to involve some nesting. |
||||||
|
# |
||||||
|
# TODO move into grafana-cli and do a more careful check that we're only |
||||||
|
# eliminating qualified identifiers from imports we're also eliminating |
||||||
|
sed -i -e "s/[A-Za-z]*\.\([A-Za-z]*\)/\1/g" {cue/ui/gen.cue,cue/data/gen.cue} |
||||||
|
|
||||||
|
# uuuugghhhh OSX sed |
||||||
|
rm -f {cue/ui/gen.cue-e,cue/data/gen.cue-e} |
||||||
|
|
||||||
|
# Check that our output is still valid CUE. |
||||||
|
cue eval -E {cue/ui/gen.cue,cue/data/gen.cue} > /dev/null |
||||||
|
|
||||||
|
# Run cuetsy over all core .cue files. |
||||||
|
find packages -type f -name '*.cue' -exec cuetsy {} \; |
||||||
|
find public/app/plugins -type f -name '*.cue' -exec cuetsy {} \; |
Loading…
Reference in new issue