Introduce "scuemata" system for CUE-based specification of Grafana objects (#32527)

This commit is contained in:
sam boyer 2021-04-08 04:11:11 -04:00 committed by GitHub
parent 7351645d63
commit bba4d9bd7f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
87 changed files with 2446 additions and 1136 deletions

View File

@ -51,13 +51,6 @@ steps:
depends_on:
- initialize
- name: check-dashboard-schemas
image: grafana/build-container:1.4.1
commands:
- cue export --out openapi -o - ./dashboard-schemas/...
depends_on:
- initialize
- name: test-backend
image: grafana/build-container:1.4.1
commands:
@ -114,7 +107,6 @@ steps:
- test-frontend
- codespell
- shellcheck
- check-dashboard-schemas
- name: package
image: grafana/build-container:1.4.1
@ -305,13 +297,6 @@ steps:
depends_on:
- initialize
- name: check-dashboard-schemas
image: grafana/build-container:1.4.1
commands:
- cue export --out openapi -o - ./dashboard-schemas/...
depends_on:
- initialize
- name: test-backend
image: grafana/build-container:1.4.1
commands:
@ -382,7 +367,6 @@ steps:
- test-frontend
- codespell
- shellcheck
- check-dashboard-schemas
- name: package
image: grafana/build-container:1.4.1
@ -761,13 +745,6 @@ steps:
depends_on:
- initialize
- name: check-dashboard-schemas
image: grafana/build-container:1.4.1
commands:
- cue export --out openapi -o - ./dashboard-schemas/...
depends_on:
- initialize
- name: test-backend
image: grafana/build-container:1.4.1
commands:
@ -830,7 +807,6 @@ steps:
- test-frontend
- codespell
- shellcheck
- check-dashboard-schemas
- name: package
image: grafana/build-container:1.4.1
@ -1137,13 +1113,6 @@ steps:
depends_on:
- initialize
- name: check-dashboard-schemas
image: grafana/build-container:1.4.1
commands:
- cue export --out openapi -o - ./dashboard-schemas/...
depends_on:
- initialize
- name: test-backend
image: grafana/build-container:1.4.1
commands:
@ -1237,7 +1206,6 @@ steps:
- test-frontend
- codespell
- shellcheck
- check-dashboard-schemas
- build-backend-enterprise2
- test-backend-enterprise2
@ -1712,13 +1680,6 @@ steps:
depends_on:
- initialize
- name: check-dashboard-schemas
image: grafana/build-container:1.4.1
commands:
- cue export --out openapi -o - ./dashboard-schemas/...
depends_on:
- initialize
- name: test-backend
image: grafana/build-container:1.4.1
commands:
@ -1781,7 +1742,6 @@ steps:
- test-frontend
- codespell
- shellcheck
- check-dashboard-schemas
- name: package
image: grafana/build-container:1.4.1
@ -2077,13 +2037,6 @@ steps:
depends_on:
- initialize
- name: check-dashboard-schemas
image: grafana/build-container:1.4.1
commands:
- cue export --out openapi -o - ./dashboard-schemas/...
depends_on:
- initialize
- name: test-backend
image: grafana/build-container:1.4.1
commands:
@ -2177,7 +2130,6 @@ steps:
- test-frontend
- codespell
- shellcheck
- check-dashboard-schemas
- build-backend-enterprise2
- test-backend-enterprise2
@ -2645,13 +2597,6 @@ steps:
depends_on:
- initialize
- name: check-dashboard-schemas
image: grafana/build-container:1.4.1
commands:
- cue export --out openapi -o - ./dashboard-schemas/...
depends_on:
- initialize
- name: test-backend
image: grafana/build-container:1.4.1
commands:
@ -2711,7 +2656,6 @@ steps:
- test-frontend
- codespell
- shellcheck
- check-dashboard-schemas
- name: package
image: grafana/build-container:1.4.1
@ -2981,13 +2925,6 @@ steps:
depends_on:
- initialize
- name: check-dashboard-schemas
image: grafana/build-container:1.4.1
commands:
- cue export --out openapi -o - ./dashboard-schemas/...
depends_on:
- initialize
- name: test-backend
image: grafana/build-container:1.4.1
commands:
@ -3075,7 +3012,6 @@ steps:
- test-frontend
- codespell
- shellcheck
- check-dashboard-schemas
- build-backend-enterprise2
- test-backend-enterprise2

1
cue.mod/module.cue Normal file
View File

@ -0,0 +1 @@
module: "github.com/grafana/grafana"

213
cue/data/gen.cue Normal file
View File

@ -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
}

View File

@ -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]
}

60
cue/scuemata/scuemata.cue Normal file
View File

@ -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
}

93
cue/ui/gen.cue Normal file
View File

@ -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")

View File

@ -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?: [...{}]
}

View File

@ -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> ...
```

View File

@ -1 +0,0 @@
module: "github.com/grafana/grafana/dashboard-schemas"

View File

@ -1,5 +0,0 @@
module github.com/grafana/grafana/dashboard-schemas
go 1.15
require cuelang.org/go v0.2.2

View File

@ -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=

View File

@ -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
}

View File

@ -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)
}
}
}

View File

@ -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"
}

View File

@ -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. Its 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
}
}

View File

@ -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"
}

View File

@ -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
}

View File

@ -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

View File

@ -1,11 +0,0 @@
package panels
_mapping: {
id: int
from: string
operator: string
to: string
text: string
type: int
value: string
}

View File

@ -1,12 +0,0 @@
package panels
_override: {
matcher: {
id: string
options: string
}
properties: [...{
id: string
value: int
}]
}

View File

@ -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?: [...{}]
}

View File

@ -1,11 +0,0 @@
package panels
_thresholds: {
// Threshold mode.
mode: string | *"absolute"
// Threshold steps.
steps: [...{
color: string
value: number
}]
}

View File

@ -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"
}

View File

@ -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
}
}

View File

@ -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: {}
}
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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
}

View File

@ -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
}

21
embed.go Normal file
View File

@ -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")

4
go.mod
View File

@ -1,6 +1,6 @@
module github.com/grafana/grafana
go 1.15
go 1.16
// Override xorm's outdated go-mssqldb dependency, since we can't upgrade to current xorm (due to breaking changes).
// We need a more current go-mssqldb so we get rid of a version of apache/thrift with vulnerabilities.
@ -13,6 +13,7 @@ replace k8s.io/client-go => k8s.io/client-go v0.18.8
require (
cloud.google.com/go/storage v1.14.0
cuelang.org/go v0.3.0-beta.6
github.com/BurntSushi/toml v0.3.1
github.com/VividCortex/mysqlerr v0.0.0-20170204212430-6c6b55f8796f
github.com/aws/aws-sdk-go v1.38.12
@ -83,6 +84,7 @@ require (
github.com/yudai/gojsondiff v1.0.0
go.opentelemetry.io/collector v0.22.0
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
golang.org/x/exp v0.0.0-20210220032938-85be41e4509f // indirect
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110
golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c

25
go.sum
View File

@ -50,7 +50,10 @@ code.cloudfoundry.org/bytefmt v0.0.0-20200131002437-cf55d5288a48/go.mod h1:wN/zk
collectd.org v0.3.0/go.mod h1:A/8DzQBkF6abtvrT2j/AU/4tiBgJWYyh0y/oB/4MlWE=
contrib.go.opencensus.io/exporter/ocagent v0.6.0/go.mod h1:zmKjrJcdo0aYcVS7bmEeSEBLPA9YJp5bjrofdU3pIXs=
contrib.go.opencensus.io/exporter/prometheus v0.2.0/go.mod h1:TYmVAyE8Tn1lyPcltF5IYYfWp2KHu7lQGIZnj8iZMys=
cuelang.org/go v0.3.0-beta.6 h1:od1S/Hbl2S45TLSONl95X3O4TXN1za6CUSD13bTxCVk=
cuelang.org/go v0.3.0-beta.6/go.mod h1:Ikvs157igkGV5gFUdYSFa+lWp/CDteVhubPTXyvPRtA=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20201218220906-28db891af037/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
github.com/Azure/azure-pipeline-go v0.2.1/go.mod h1:UGSo8XybXnIGZ3epmeBw7Jdz+HiUVpqIlpz/HKHylF4=
@ -275,7 +278,10 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
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/cockroachdb/cockroach-go v0.0.0-20181001143604-e0a95dfd547c/go.mod h1:XGLbWH/ujMcbPbhZq52Nv6UrCghb1yGn//133kEsvDk=
github.com/cockroachdb/datadriven v0.0.0-20190531201743-edce55837238/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
@ -389,6 +395,8 @@ github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkg
github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
github.com/ema/qdisc v0.0.0-20190904071900-b82c76788043/go.mod h1:ix4kG2zvdUd8kEKSW0ZTr1XLks0epFpI4j745DXxlNE=
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
github.com/emicklei/proto v1.6.15 h1:XbpwxmuOPrdES97FrSfpyy67SSCV/wBIKXqgJzh6hNw=
github.com/emicklei/proto v1.6.15/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A=
github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
@ -1028,7 +1036,6 @@ github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E
github.com/jung-kurt/gofpdf v1.16.2 h1:jgbatWHfRlPYiK85qgevsZTHviWXKwB1TTiKdz5PtRc=
github.com/jung-kurt/gofpdf v1.16.2/go.mod h1:1hl7y57EsiPAkLbOwzpzqgx1A30nQCk/YmFV8S2vmK0=
github.com/jwilder/encoding v0.0.0-20170811194829-b4e1701a28ef/go.mod h1:Ct9fl0F6iIOGgxJ5npU/IUOhOhqlVrGjyIZc8/MagT0=
github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 h1:uC1QfSlInpQF+M0ao65imhwqKnz3Q2z/d8PWZRMQvDM=
github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k=
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw=
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
@ -1222,6 +1229,8 @@ github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOA
github.com/mozilla/tls-observatory v0.0.0-20190404164649-a3c1b6cfecfd/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk=
github.com/mozillazg/go-cos v0.13.0/go.mod h1:Zp6DvvXn0RUOXGJ2chmWt2bLEqRAnJnS3DnAZsJsoaE=
github.com/mozillazg/go-httpheader v0.2.1/go.mod h1:jJ8xECTlalr6ValeXYdOF8fFUISeBAdw6E61aqQma60=
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/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg=
github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
@ -1451,6 +1460,8 @@ github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6L
github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.6.2-0.20200830194709-1115b6af0369 h1:wdCVGtPadWC/ZuuLC7Hv58VQ5UF7V98ewE71n5mJfrM=
github.com/rogpeppe/go-internal v1.6.2-0.20200830194709-1115b6af0369/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik=
github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
@ -1530,6 +1541,7 @@ github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkU
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/cobra v0.0.7/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
@ -1759,6 +1771,7 @@ golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL
golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191029154019-8994fa331a53/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
@ -1768,8 +1781,10 @@ golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u0
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw=
golang.org/x/exp v0.0.0-20200821190819-94841d0725da h1:vfV2BR+q1+/jmgJR30Ms3RHbryruQ3Yd83lLAAue9cs=
golang.org/x/exp v0.0.0-20200821190819-94841d0725da/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/exp v0.0.0-20210126221216-84987778548c/go.mod h1:I6l2HNBLBZEcrOoCpyKLdY2lHoRZ8lI4x60KMCQDft4=
golang.org/x/exp v0.0.0-20210220032938-85be41e4509f h1:GrkO5AtFUU9U/1f5ctbIBXtBGeSJbWwIYfIsTcFMaX4=
golang.org/x/exp v0.0.0-20210220032938-85be41e4509f/go.mod h1:I6l2HNBLBZEcrOoCpyKLdY2lHoRZ8lI4x60KMCQDft4=
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
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=
@ -1788,12 +1803,15 @@ golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 h1:2M3HP5CCK1Si9FQhwnzYhXdG
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mobile v0.0.0-20201217150744-e6ae53a27f4f/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
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.1.1-0.20191209134235-331c550502dd/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.1-0.20200828183125-ce943fd02449/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1 h1:Kvvh58BN8Y9/lBi7hTekvtMpm07eUZ0ck5pRHpsMWrY=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
@ -2071,6 +2089,7 @@ golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapK
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200108203644-89082a384178/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
@ -2090,6 +2109,7 @@ golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roY
golang.org/x/tools v0.0.0-20200513201620-d5fe73897c97/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200603131246-cc40288be839/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200612220849-54c614fe050c/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200710042808-f1c4188a97a1/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
@ -2329,6 +2349,7 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200121175148-a6ecf24a6d71/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200603094226-e3079894b1e8/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -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
}

View File

@ -2,7 +2,7 @@ import { toDataFrame, FieldType, VizOrientation } from '@grafana/data';
import React from 'react';
import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
import { BarChart } from './BarChart';
import { LegendDisplayMode } from '../VizLegend/types';
import { LegendDisplayMode } from '../VizLegend/models.gen';
import { prepDataForStorybook } from '../../utils/storybook/data';
import { useTheme } from '../../themes';
import { select } from '@storybook/addon-knobs';

View File

@ -10,7 +10,7 @@ import { BarChartOptions } from './types';
import { withTheme } from '../../themes';
import { preparePlotConfigBuilder, preparePlotFrame } from './utils';
import { preparePlotData } from '../uPlot/utils';
import { LegendDisplayMode } from '../VizLegend/types';
import { LegendDisplayMode } from '../VizLegend/models.gen';
import { PlotLegend } from '../uPlot/PlotLegend';
/**

View File

@ -1,6 +1,6 @@
import { VizOrientation } from '@grafana/data';
import { AxisConfig, GraphGradientMode, HideableFieldConfig } from '../uPlot/config';
import { VizLegendOptions } from '../VizLegend/types';
import { VizLegendOptions } from '../VizLegend/models.gen';
/**
* @alpha

View File

@ -2,7 +2,7 @@ import { preparePlotConfigBuilder, preparePlotFrame } from './utils';
import { FieldConfig, FieldType, GrafanaTheme, MutableDataFrame, VizOrientation } from '@grafana/data';
import { BarChartFieldConfig, BarChartOptions, BarStackingMode, BarValueVisibility } from './types';
import { GraphGradientMode } from '../uPlot/config';
import { LegendDisplayMode } from '../VizLegend/types';
import { LegendDisplayMode } from '../VizLegend/models.gen';
function mockDataFrame() {
const df1 = new MutableDataFrame({

View File

@ -5,8 +5,7 @@ import { Dimensions, TimeZone } from '@grafana/data';
import { FlotPosition } from '../Graph/types';
import { TooltipContainer } from './TooltipContainer';
import { useStyles } from '../../themes';
export type TooltipMode = 'single' | 'multi' | 'none';
import { TooltipMode } from './models.gen';
// Describes active dimensions user interacts with
// It's a key-value pair where:

View File

@ -0,0 +1,3 @@
package grafanaschema
TooltipMode: "single" | "multi" | "none" @cuetsy(targetType="type")

View File

@ -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';

View File

@ -4,7 +4,8 @@ import Chart from '../Chart';
import { dateTime, ArrayVector, FieldType, GraphSeriesXY, FieldColorModeId } from '@grafana/data';
import { Story } from '@storybook/react';
import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
import { TooltipContentProps, TooltipMode } from '../Chart/Tooltip';
import { TooltipContentProps } from '../Chart/Tooltip';
import { TooltipMode } from '../Chart/models.gen';
import { JSONFormatter } from '../JSONFormatter/JSONFormatter';
import { GraphProps } from './Graph';

View File

@ -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")

View File

@ -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;
}

View File

@ -1,5 +1,6 @@
import { ActiveDimensions, TooltipMode } from '../../Chart/Tooltip';
import { ActiveDimensions } from '../../Chart/Tooltip';
import { Dimension, Dimensions, TimeZone } from '@grafana/data';
import { TooltipMode } from '../../Chart/models.gen';
export interface GraphTooltipOptions {
mode: TooltipMode;

View File

@ -3,7 +3,7 @@ import React from 'react';
import { Story } from '@storybook/react';
import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
import { GraphWithLegend, GraphWithLegendProps } from './GraphWithLegend';
import { LegendDisplayMode } from '../VizLegend/types';
import { LegendDisplayMode } from '../VizLegend/models.gen';
import { GraphSeriesXY, FieldType, ArrayVector, dateTime, FieldColorModeId } from '@grafana/data';
export default {

View File

@ -5,7 +5,8 @@ import { css } from '@emotion/css';
import { GraphSeriesValue } from '@grafana/data';
import { Graph, GraphProps } from './Graph';
import { VizLegendItem, LegendDisplayMode, SeriesColorChangeHandler, LegendPlacement } from '../VizLegend/types';
import { VizLegendItem, SeriesColorChangeHandler } from '../VizLegend/types';
import { LegendDisplayMode, LegendPlacement } from '../VizLegend/models.gen';
import { VizLegend } from '../VizLegend/VizLegend';
import { CustomScrollbar } from '../CustomScrollbar/CustomScrollbar';
import { stylesFactory } from '../../themes';

View File

@ -2,7 +2,7 @@ import { FieldColorModeId, toDataFrame, dateTime } from '@grafana/data';
import React from 'react';
import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
import { GraphNG, GraphNGProps } from './GraphNG';
import { LegendDisplayMode, LegendPlacement } from '../VizLegend/types';
import { LegendDisplayMode, LegendPlacement } from '../VizLegend/models.gen';
import { prepDataForStorybook } from '../../utils/storybook/data';
import { useTheme } from '../../themes';
import { Story } from '@storybook/react';

View File

@ -20,7 +20,7 @@ import { preparePlotConfigBuilder, preparePlotFrame } from './utils';
import { preparePlotData } from '../uPlot/utils';
import { PlotLegend } from '../uPlot/PlotLegend';
import { UPlotChart } from '../uPlot/Plot';
import { LegendDisplayMode, VizLegendOptions } from '../VizLegend/types';
import { LegendDisplayMode, VizLegendOptions } from '../VizLegend/models.gen';
import { VizLayout } from '../VizLayout/VizLayout';
/**

View File

@ -11,7 +11,7 @@ import { useComponentInstanceId } from '../../utils/useComponetInstanceId';
import { css } from '@emotion/css';
import { VizLegend, VizLegendItem } from '..';
import { VizLayout } from '../VizLayout/VizLayout';
import { LegendDisplayMode, VizLegendOptions } from '../VizLegend/types';
import { LegendDisplayMode, VizLegendOptions } from '../VizLegend/models.gen';
import { DataLinksContextMenu } from '../DataLinks/DataLinksContextMenu';
import { UseTooltipParams } from '@visx/tooltip/lib/hooks/useTooltip';

View File

@ -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")

View File

@ -7,7 +7,7 @@ import { preparePlotConfigBuilder, preparePlotFrame } from './utils'; // << prep
import { preparePlotData } from '../uPlot/utils';
import { PlotLegend } from '../uPlot/PlotLegend';
import { UPlotChart } from '../uPlot/Plot';
import { LegendDisplayMode } from '../VizLegend/types';
import { LegendDisplayMode } from '../VizLegend/models.gen';
import { VizLayout } from '../VizLayout/VizLayout';
import { TimelineProps } from './types';

View File

@ -1,6 +1,6 @@
import { GraphNGProps } from '../GraphNG/GraphNG';
import { GraphGradientMode, HideableFieldConfig } from '../uPlot/config';
import { VizLegendOptions } from '../VizLegend/types';
import { VizLegendOptions } from '../VizLegend/models.gen';
/**
* @alpha

View File

@ -4,7 +4,8 @@ import { number, select } from '@storybook/addon-knobs';
import {} from './VizLegendListItem';
import { DisplayValue, getColorForTheme, GrafanaTheme } from '@grafana/data';
import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
import { LegendDisplayMode, VizLegendItem, LegendPlacement } from './types';
import { VizLegendItem } from './types';
import { LegendDisplayMode, LegendPlacement } from './models.gen';
const getStoriesKnobs = (table = false) => {
const seriesCount = number('Number of series', 5);

View File

@ -1,5 +1,6 @@
import React from 'react';
import { LegendProps, LegendDisplayMode } from './types';
import { LegendProps } from './types';
import { LegendDisplayMode } from './models.gen';
import { VizLegendTable } from './VizLegendTable';
import { VizLegendList } from './VizLegendList';

View File

@ -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

View File

@ -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;
}

View File

@ -1,4 +1,5 @@
import { DataFrameFieldIndex, DisplayValue } from '@grafana/data';
import { LegendDisplayMode, LegendPlacement } from './models.gen';
export interface VizLegendBaseProps {
placement: LegendPlacement;
@ -30,19 +31,5 @@ export interface VizLegendItem {
fieldIndex?: DataFrameFieldIndex;
}
export enum LegendDisplayMode {
List = 'list',
Table = 'table',
Hidden = 'hidden',
}
export type LegendPlacement = 'bottom' | 'right';
export interface VizLegendOptions {
displayMode: LegendDisplayMode;
placement: LegendPlacement;
calcs: string[];
}
export type SeriesOptionChangeHandler<TOption> = (label: string, option: TOption) => void;
export type SeriesColorChangeHandler = SeriesOptionChangeHandler<string>;

View File

@ -83,7 +83,8 @@ export { VizRepeater, VizRepeaterRenderValueProps } from './VizRepeater/VizRepea
export { graphTimeFormat, graphTickFormatter } from './Graph/utils';
export { PanelChrome, PanelChromeProps, PanelPadding, PanelChromeType } from './PanelChrome';
export { VizLayout, VizLayoutComponentType, VizLayoutLegendProps, VizLayoutProps } from './VizLayout/VizLayout';
export { VizLegendItem, LegendPlacement, LegendDisplayMode, VizLegendOptions } from './VizLegend/types';
export { VizLegendItem } from './VizLegend/types';
export { LegendPlacement, LegendDisplayMode, VizLegendOptions } from './VizLegend/models.gen';
export { VizLegend } from './VizLegend/VizLegend';
export { Alert, AlertVariant } from './Alert/Alert';

View File

@ -1,7 +1,8 @@
import React, { useCallback } from 'react';
import { DataFrame, DisplayValue, fieldReducers, getFieldDisplayName, reduceField } from '@grafana/data';
import { UPlotConfigBuilder } from './config/UPlotConfigBuilder';
import { VizLegendItem, VizLegendOptions } from '../VizLegend/types';
import { VizLegendItem } from '../VizLegend/types';
import { VizLegendOptions } from '../VizLegend/models.gen';
import { AxisPlacement } from './config';
import { VizLayout, VizLayoutLegendProps } from '../VizLayout/VizLayout';
import { mapMouseEventToMode } from '../GraphNG/utils';

View File

@ -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")

View File

@ -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;
}

View File

@ -12,7 +12,7 @@ import {
TimeZone,
} from '@grafana/data';
import { TooltipContainer } from '../../Chart/TooltipContainer';
import { TooltipMode } from '../../Chart/Tooltip';
import { TooltipMode } from '../../Chart/models.gen';
import { useGraphNGContext } from '../../GraphNG/hooks';
interface TooltipPluginProps {

87
pkg/schema/load/common.go Normal file
View File

@ -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
}

View File

@ -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)
}

180
pkg/schema/load/generic.go Normal file
View File

@ -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()
}
}

View File

@ -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
}))
}

161
pkg/schema/load/panel.go Normal file
View File

@ -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
}

View File

@ -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.

View File

@ -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
}

View File

View File

@ -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
}
}

View File

@ -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
}
]
}

View File

@ -0,0 +1,9 @@
{
"type": "panel",
"name": "Sample plugin with lineage",
"id": "with-lineage",
"info": {
"description": "Show how complex history may work"
}
}

274
pkg/schema/schema.go Normal file
View File

@ -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

View File

@ -0,0 +1,4 @@
package schema
// TODO tests for this stuff! Everything in this package is totally generic,
// nothing is specific to Grafana

View File

@ -2,7 +2,7 @@ import React, { Component } from 'react';
import { Select, Table } from '@grafana/ui';
import { DataFrame, FieldMatcherID, getFrameDisplayName, PanelProps, SelectableValue } from '@grafana/data';
import { Options } from './types';
import { PanelOptions } from './models.gen';
import { css } from '@emotion/css';
import { config } from 'app/core/config';
import { FilterItem, TableSortByFieldState } from '@grafana/ui/src/components/Table/types';
@ -10,7 +10,7 @@ import { dispatch } from '../../../store/store';
import { applyFilterFromTable } from '../../../features/variables/adhoc/actions';
import { getDashboardSrv } from '../../../features/dashboard/services/DashboardSrv';
interface Props extends PanelProps<Options> {}
interface Props extends PanelProps<PanelOptions> {}
export class TablePanel extends Component<Props> {
constructor(props: Props) {

View File

@ -11,14 +11,14 @@ import omitBy from 'lodash/omitBy';
import isNil from 'lodash/isNil';
import isNumber from 'lodash/isNumber';
import defaultTo from 'lodash/defaultTo';
import { Options } from './types';
import { PanelOptions } from './models.gen';
/**
* At 7.0, the `table` panel was swapped from an angular implementation to a react one.
* The models do not match, so this process will delegate to the old implementation when
* a saved table configuration exists.
*/
export const tableMigrationHandler = (panel: PanelModel<Options>): Partial<Options> => {
export const tableMigrationHandler = (panel: PanelModel<PanelOptions>): Partial<PanelOptions> => {
// Table was saved as an angular table, lets just swap to the 'table-old' panel
if (!panel.pluginVersion && (panel as any).columns) {
console.log('Was angular table', panel);
@ -74,7 +74,7 @@ const generateThresholds = (thresholds: string[], colors: string[]) => {
};
const migrateTransformations = (
panel: PanelModel<Partial<Options>> | any,
panel: PanelModel<Partial<PanelOptions>> | any,
oldOpts: { columns: any; transform: Transformations }
) => {
const transformations: Transformation[] = panel.transformations ?? [];
@ -221,7 +221,7 @@ const migrateDefaults = (prevDefaults: Style) => {
* This is called when the panel changes from another panel
*/
export const tablePanelChangedHandler = (
panel: PanelModel<Partial<Options>> | any,
panel: PanelModel<Partial<PanelOptions>> | any,
prevPluginId: string,
prevOptions: any
) => {

View File

@ -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: []
}

View File

@ -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,
};

View File

@ -1,10 +1,10 @@
import { PanelPlugin } from '@grafana/data';
import { TablePanel } from './TablePanel';
import { CustomFieldConfig, Options } from './types';
import { PanelOptions, PanelFieldConfig, defaultPanelOptions, defaultPanelFieldConfig } from './models.gen';
import { tableMigrationHandler, tablePanelChangedHandler } from './migrations';
import { TableCellDisplayMode } from '@grafana/ui';
export const plugin = new PanelPlugin<Options, CustomFieldConfig>(TablePanel)
export const plugin = new PanelPlugin<PanelOptions, PanelFieldConfig>(TablePanel)
.setPanelChangeHandler(tablePanelChangedHandler)
.setMigrationHandler(tableMigrationHandler)
.setNoPadding()
@ -20,6 +20,7 @@ export const plugin = new PanelPlugin<Options, CustomFieldConfig>(TablePanel)
max: 300,
},
shouldApply: () => true,
defaultValue: defaultPanelFieldConfig.width,
})
.addRadio({
path: 'align',
@ -32,7 +33,7 @@ export const plugin = new PanelPlugin<Options, CustomFieldConfig>(TablePanel)
{ label: 'right', value: 'right' },
],
},
defaultValue: null,
defaultValue: defaultPanelFieldConfig.align,
})
.addSelect({
path: 'displayMode',
@ -51,12 +52,13 @@ export const plugin = new PanelPlugin<Options, CustomFieldConfig>(TablePanel)
{ value: TableCellDisplayMode.Image, label: 'Image' },
],
},
defaultValue: defaultPanelFieldConfig.displayMode,
})
.addBooleanSwitch({
path: 'filterable',
name: 'Column filter',
description: 'Enables/disables field filters in table',
defaultValue: false,
defaultValue: defaultPanelFieldConfig.filterable,
});
},
})
@ -65,6 +67,6 @@ export const plugin = new PanelPlugin<Options, CustomFieldConfig>(TablePanel)
path: 'showHeader',
name: 'Show header',
description: "To display table's header or not to display",
defaultValue: true,
defaultValue: defaultPanelOptions.showHeader,
});
});

View File

@ -3,6 +3,11 @@
"name": "Table",
"id": "table",
"models": {
"version": [1, 0],
"changed": "2021/03/30"
},
"info": {
"description": "Table Panel for Grafana",
"author": {

View File

@ -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;
}

93
scripts/cuegen.sh Executable file
View File

@ -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 {} \;

View File

@ -526,18 +526,6 @@ def shellcheck_step():
],
}
def dashboard_schemas_check():
return {
'name': 'check-dashboard-schemas',
'image': build_image,
'depends_on': [
'initialize',
],
'commands': [
'cue export --out openapi -o - ./dashboard-schemas/...',
],
}
def gen_version_step(ver_mode, include_enterprise2=False, is_downstream=False):
deps = [
'build-backend',
@ -547,7 +535,6 @@ def gen_version_step(ver_mode, include_enterprise2=False, is_downstream=False):
'test-frontend',
'codespell',
'shellcheck',
'check-dashboard-schemas',
]
if include_enterprise2:
sfx = '-enterprise2'

View File

@ -4,7 +4,6 @@ load(
'lint_backend_step',
'codespell_step',
'shellcheck_step',
'dashboard_schemas_check',
'test_backend_step',
'test_frontend_step',
'build_backend_step',
@ -47,7 +46,6 @@ def get_steps(edition, is_downstream=False):
lint_backend_step(edition=edition),
codespell_step(),
shellcheck_step(),
dashboard_schemas_check(),
test_backend_step(edition=edition),
test_frontend_step(),
frontend_metrics_step(edition=edition),

View File

@ -4,7 +4,6 @@ load(
'lint_backend_step',
'codespell_step',
'shellcheck_step',
'dashboard_schemas_check',
'test_backend_step',
'test_frontend_step',
'build_backend_step',
@ -38,7 +37,6 @@ def pr_pipelines(edition):
lint_backend_step(edition=edition),
codespell_step(),
shellcheck_step(),
dashboard_schemas_check(),
test_backend_step(edition=edition),
test_frontend_step(),
build_backend_step(edition=edition, ver_mode=ver_mode, variants=variants),

View File

@ -7,7 +7,6 @@ load(
'lint_backend_step',
'codespell_step',
'shellcheck_step',
'dashboard_schemas_check',
'test_backend_step',
'test_frontend_step',
'build_backend_step',
@ -72,7 +71,6 @@ def get_steps(edition, ver_mode):
lint_backend_step(edition=edition),
codespell_step(),
shellcheck_step(),
dashboard_schemas_check(),
test_backend_step(edition=edition),
test_frontend_step(),
build_backend_step(edition=edition, ver_mode=ver_mode),