codegen: Introduce TS codegen veneer (#54816)

* Split all named types out into defs, etc.

* Use latest cuetsy, refactor generators accordingly

* Return AST type from plugin TS generator

* Near-complete checkin of TS veneer code generator

* First full completed pass

* Improve the attribute name

* Defer use of the dashboard veneer type to follow-up

* Remove dummy index, prettier on veneer

* Fix merge errors in gen.go

* Add match field to SpecialValueMap

* Fix backend lint errors
pull/55517/head^2
sam boyer 3 years ago committed by GitHub
parent f8240e4b0a
commit e2ff875976
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      go.mod
  2. 4
      go.sum
  3. 76
      packages/grafana-schema/src/index.gen.ts
  4. 1
      packages/grafana-schema/src/index.ts
  5. 676
      packages/grafana-schema/src/raw/dashboard/x/dashboard.gen.ts
  6. 274
      packages/grafana-schema/src/schema/dashboard/dashboard_experimental.gen.ts
  7. 180
      packages/grafana-schema/src/schema/mudball.gen.ts
  8. 32
      packages/grafana-schema/src/veneer/dashboard.types.ts
  9. 93
      pkg/codegen/coremodel.go
  10. 105
      pkg/codegen/pluggen.go
  11. 17
      pkg/codegen/tmpl.go
  12. 7
      pkg/codegen/tmpl/cuetsy_multi.tmpl
  13. 207
      pkg/coremodel/dashboard/coremodel.cue
  14. 329
      pkg/coremodel/dashboard/dashboard_gen.go
  15. 335
      pkg/framework/coremodel/gen.go
  16. 7
      public/app/plugins/gen.go
  17. 4
      public/app/plugins/panel/annolist/models.gen.ts
  18. 47
      public/app/plugins/panel/barchart/models.gen.ts
  19. 3
      public/app/plugins/panel/bargauge/models.gen.ts
  20. 4
      public/app/plugins/panel/dashlist/models.gen.ts
  21. 3
      public/app/plugins/panel/gauge/models.gen.ts
  22. 22
      public/app/plugins/panel/histogram/models.gen.ts
  23. 5
      public/app/plugins/panel/news/models.gen.ts
  24. 21
      public/app/plugins/panel/piechart/models.gen.ts
  25. 3
      public/app/plugins/panel/stat/models.gen.ts
  26. 5
      public/app/plugins/panel/text/models.gen.ts

@ -54,7 +54,7 @@ require (
github.com/google/wire v0.5.0
github.com/gorilla/websocket v1.5.0
github.com/gosimple/slug v1.12.0
github.com/grafana/cuetsy v0.0.4-0.20220714174355-ebd987fdab27
github.com/grafana/cuetsy v0.1.1
github.com/grafana/grafana-aws-sdk v0.10.8
github.com/grafana/grafana-azure-sdk-go v1.3.0
github.com/grafana/grafana-plugin-sdk-go v0.139.0
@ -275,6 +275,7 @@ require (
github.com/gosimple/unidecode v1.0.1 // indirect
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
github.com/hashicorp/memberlist v0.3.1 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/mattn/go-colorable v0.1.12 // indirect
github.com/mitchellh/mapstructure v1.4.3 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect

@ -1368,8 +1368,8 @@ github.com/gosimple/slug v1.12.0 h1:xzuhj7G7cGtd34NXnW/yF0l+AGNfWqwgh/IXgFy7dnc=
github.com/gosimple/slug v1.12.0/go.mod h1:UiRaFH+GEilHstLUmcBgWcI42viBN7mAb818JrYOeFQ=
github.com/gosimple/unidecode v1.0.1 h1:hZzFTMMqSswvf0LBJZCZgThIZrpDHFXux9KeGmn6T/o=
github.com/gosimple/unidecode v1.0.1/go.mod h1:CP0Cr1Y1kogOtx0bJblKzsVWrqYaqfNOnHzpgWw4Awc=
github.com/grafana/cuetsy v0.0.4-0.20220714174355-ebd987fdab27 h1:r0ZSb0gSKEf1hmdYljn7ezOPJyfrC0qcky576U82yEk=
github.com/grafana/cuetsy v0.0.4-0.20220714174355-ebd987fdab27/go.mod h1:7OoEYb42s7PbSYtNUTy1DoCeJ3LAOTafsZbndvikTj8=
github.com/grafana/cuetsy v0.1.1 h1:+1jaDDYCpvKlcOWJgBRbkc5+VZIClCEn5mbI+4PLZqM=
github.com/grafana/cuetsy v0.1.1/go.mod h1:4KWkUOslwvRTpEv7wdQG0jDFTuJmU+0L9x0h4kWxa2A=
github.com/grafana/dskit v0.0.0-20211011144203-3a88ec0b675f h1:FvvSVEbnGeM2bUivGmsiXTi8URJyBU7TcFEEoRe5wWI=
github.com/grafana/dskit v0.0.0-20211011144203-3a88ec0b675f/go.mod h1:uPG2nyK4CtgNDmWv7qyzYcdI+S90kHHRWvHnBtEMBXM=
github.com/grafana/go-mssqldb v0.0.0-20210326084033-d0ce3c521036 h1:GplhUk6Xes5JIhUUrggPcPBhOn+eT8+WsHiebvq7GgA=

@ -0,0 +1,76 @@
// This file is autogenerated. DO NOT EDIT.
//
// Generated by pkg/framework/coremodel/gen.go
//
// Run `make gen-cue` from repository root to regenerate.
// Raw generated types from dashboard entity type.
export type {
AnnotationQuery,
VariableModel,
DashboardLink,
DashboardLinkType,
VariableType,
FieldColorModeId,
FieldColorSeriesByMode,
FieldColor,
GridPos,
Threshold,
ThresholdsMode,
ThresholdsConfig,
ValueMapping,
MappingType,
ValueMap,
RangeMap,
RegexMap,
SpecialValueMap,
SpecialValueMatch,
ValueMappingResult,
Transformation,
DashboardCursorSync,
MatcherConfig,
RowPanel
} from './raw/dashboard/x/dashboard.gen';
// Raw generated default consts from dashboard entity type.
export {
defaultAnnotationQuery,
defaultDashboardLink,
defaultGridPos,
defaultThresholdsConfig,
defaultDashboardCursorSync,
defaultMatcherConfig,
defaultRowPanel
} from './raw/dashboard/x/dashboard.gen';
// The following exported declarations correspond to types in the dashboard@0.0 schema with
// attribute @grafana(TSVeneer="type"). (lineage declared in file: pkg/coremodel/dashboard/coremodel.cue)
//
// The handwritten file for these type and default veneers is expected to be at
// packages/grafana-schema/src/veneer/dashboard.types.ts.
// This re-export declaration enforces that the handwritten veneer file exists,
// and exports all the symbols in the list.
//
// TODO generate code such that tsc enforces type compatibility between raw and veneer decls
export type {
Dashboard,
Panel,
FieldConfigSource,
FieldConfig
} from './veneer/dashboard.types';
// The following exported declarations correspond to types in the dashboard@0.0 schema with
// attribute @grafana(TSVeneer="type"). (lineage declared in file: pkg/coremodel/dashboard/coremodel.cue)
//
// The handwritten file for these type and default veneers is expected to be at
// packages/grafana-schema/src/veneer/dashboard.types.ts.
// This re-export declaration enforces that the handwritten veneer file exists,
// and exports all the symbols in the list.
//
// TODO generate code such that tsc enforces type compatibility between raw and veneer decls
export {
defaultDashboard,
defaultPanel,
defaultFieldConfigSource,
defaultFieldConfig
} from './veneer/dashboard.types';

@ -4,3 +4,4 @@
* @packageDocumentation
*/
export * from './schema/mudball.gen';
export * from './index.gen';

@ -0,0 +1,676 @@
// This file is autogenerated. DO NOT EDIT.
//
// Generated by pkg/framework/coremodel/gen.go
//
// Derived from the Thema lineage declared in pkg/coremodel/dashboard/coremodel.cue
//
// Run `make gen-cue` from repository root to regenerate.
/**
* TODO docs
* FROM: AnnotationQuery in grafana-data/src/types/annotations.ts
*/
export interface AnnotationQuery {
builtIn: number;
/**
* Datasource to use for annotation.
*/
datasource: {
type?: string;
uid?: string;
};
/**
* Whether annotation is enabled.
*/
enable: boolean;
/**
* Whether to hide annotation.
*/
hide?: boolean;
/**
* Annotation icon color.
*/
iconColor?: string;
/**
* Name of annotation.
*/
name?: string;
/**
* Query for annotation data.
*/
rawQuery?: string;
showIn: number;
target?: Record<string, unknown>;
type: string;
}
export const defaultAnnotationQuery: Partial<AnnotationQuery> = {
builtIn: 0,
enable: true,
hide: false,
showIn: 0,
type: 'dashboard',
};
/**
* FROM: packages/grafana-data/src/types/templateVars.ts
* TODO docs
* TODO what about what's in public/app/features/types.ts?
* TODO there appear to be a lot of different kinds of [template] vars here? if so need a disjunction
*/
export interface VariableModel {
label?: string;
name: string;
type: VariableType;
}
/**
* FROM public/app/features/dashboard/state/DashboardModels.ts - ish
* TODO docs
*/
export interface DashboardLink {
asDropdown: boolean;
icon?: string;
includeVars: boolean;
keepTime: boolean;
tags: Array<string>;
targetBlank: boolean;
title: string;
tooltip?: string;
type: DashboardLinkType;
url?: string;
}
export const defaultDashboardLink: Partial<DashboardLink> = {
asDropdown: false,
includeVars: false,
keepTime: false,
tags: [],
targetBlank: false,
};
/**
* TODO docs
*/
export type DashboardLinkType = ('link' | 'dashboards');
/**
* FROM: packages/grafana-data/src/types/templateVars.ts
* TODO docs
* TODO this implies some wider pattern/discriminated union, probably?
*/
export type VariableType = ('query' | 'adhoc' | 'constant' | 'datasource' | 'interval' | 'textbox' | 'custom' | 'system');
/**
* TODO docs
*/
export enum FieldColorModeId {
ContinuousGrYlRd = 'continuous-GrYlRd',
Fixed = 'fixed',
PaletteClassic = 'palette-classic',
PaletteSaturated = 'palette-saturated',
Thresholds = 'thresholds',
}
/**
* TODO docs
*/
export type FieldColorSeriesByMode = ('min' | 'max' | 'last');
/**
* TODO docs
*/
export interface FieldColor {
/**
* Stores the fixed color value if mode is fixed
*/
fixedColor?: string;
/**
* The main color scheme mode
*/
mode: FieldColorModeId;
/**
* Some visualizations need to know how to assign a series color from by value color schemes
*/
seriesBy?: FieldColorSeriesByMode;
}
export interface GridPos {
/**
* Panel
*/
h: number;
/**
* true if fixed
*/
static?: boolean;
/**
* Panel
*/
w: number;
/**
* Panel x
*/
x: number;
/**
* Panel y
*/
y: number;
}
export const defaultGridPos: Partial<GridPos> = {
h: 9,
w: 12,
x: 0,
y: 0,
};
/**
* TODO docs
*/
export interface Threshold {
/**
* TODO docs
*/
color: string;
/**
* TODO docs
* TODO are the values here enumerable into a disjunction?
* Some seem to be listed in typescript comment
*/
state?: string;
/**
* TODO docs
* FIXME the corresponding typescript field is required/non-optional, but nulls currently appear here when serializing -Infinity to JSON
*/
value?: number;
}
export enum ThresholdsMode {
Absolute = 'absolute',
Percentage = 'percentage',
}
export interface ThresholdsConfig {
mode: ThresholdsMode;
/**
* Must be sorted by 'value', first value is always -Infinity
*/
steps: Array<Threshold>;
}
export const defaultThresholdsConfig: Partial<ThresholdsConfig> = {
steps: [],
};
/**
* TODO docs
*/
export type ValueMapping = (ValueMap | RangeMap | RegexMap | SpecialValueMap);
/**
* TODO docs
*/
export enum MappingType {
RangeToText = 'range',
RegexToText = 'regex',
SpecialValue = 'special',
ValueToText = 'value',
}
/**
* TODO docs
*/
export interface ValueMap {
options: Record<string, unknown>;
type: MappingType.ValueToText;
}
/**
* TODO docs
*/
export interface RangeMap {
options: {
/**
* to and from are `number | null` in current ts, really not sure what to do
*/
from: number;
to: number;
result: ValueMappingResult;
};
type: MappingType.RangeToText;
}
/**
* TODO docs
*/
export interface RegexMap {
options: {
pattern: string;
result: ValueMappingResult;
};
type: MappingType.RegexToText;
}
/**
* TODO docs
*/
export interface SpecialValueMap {
options: {
match: ('true' | 'false');
pattern: string;
result: ValueMappingResult;
};
type: MappingType.SpecialValue;
}
/**
* TODO docs
*/
export enum SpecialValueMatch {
Empty = 'empty',
False = 'false',
NaN = 'nan',
Null = 'null',
NullAndNan = 'null+nan',
True = 'true',
}
/**
* TODO docs
*/
export interface ValueMappingResult {
color?: string;
icon?: string;
index?: number;
text?: string;
}
/**
* TODO docs
* FIXME this is extremely underspecfied; wasn't obvious which typescript types corresponded to it
*/
export interface Transformation {
id: string;
options: Record<string, unknown>;
}
/**
* 0 for no shared crosshair or tooltip (default).
* 1 for shared crosshair.
* 2 for shared crosshair AND shared tooltip.
*/
export enum DashboardCursorSync {
Crosshair = 1,
Off = 0,
Tooltip = 2,
}
export const defaultDashboardCursorSync: DashboardCursorSync = DashboardCursorSync.Off;
/**
* Dashboard panels. Panels are canonically defined inline
* because they share a version timeline with the dashboard
* schema; they do not evolve independently.
*/
export interface Panel {
/**
* The datasource used in all targets.
*/
datasource?: {
type?: string;
uid?: string;
};
/**
* Description.
*/
description?: string;
fieldConfig: FieldConfigSource;
/**
* Grid position.
*/
gridPos?: GridPos;
/**
* TODO docs
*/
id?: number;
/**
* TODO docs
* TODO tighter constraint
*/
interval?: string;
/**
* Panel links.
* TODO fill this out - seems there are a couple variants?
*/
links?: Array<DashboardLink>;
/**
* TODO docs
*/
maxDataPoints?: number;
/**
* options is specified by the PanelOptions field in panel
* plugin schemas.
*/
options: Record<string, unknown>;
/**
* FIXME this almost certainly has to be changed in favor of scuemata versions
*/
pluginVersion?: string;
/**
* 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');
/**
* TODO docs
*/
tags?: Array<string>;
/**
* TODO docs
*/
targets?: Array<Record<string, unknown>>;
/**
* TODO docs - seems to be an old field from old dashboard alerts?
*/
thresholds?: Array<any>;
/**
* TODO docs
* TODO tighter constraint
*/
timeFrom?: string;
/**
* TODO docs
*/
timeRegions?: Array<any>;
/**
* TODO docs
* TODO tighter constraint
*/
timeShift?: string;
/**
* Panel title.
*/
title?: string;
transformations: Array<Transformation>;
/**
* Whether to display the panel without a background.
*/
transparent: boolean;
/**
* The panel plugin type id. May not be empty.
*/
type: string;
}
export const defaultPanel: Partial<Panel> = {
links: [],
repeatDirection: 'h',
tags: [],
targets: [],
thresholds: [],
timeRegions: [],
transformations: [],
transparent: false,
};
export interface FieldConfigSource {
defaults: FieldConfig;
overrides: Array<{
matcher: MatcherConfig;
properties: Array<{
id: string;
value?: any;
}>;
}>;
}
export const defaultFieldConfigSource: Partial<FieldConfigSource> = {
overrides: [],
};
export interface MatcherConfig {
id: string;
options?: any;
}
export const defaultMatcherConfig: Partial<MatcherConfig> = {
id: '',
};
export interface FieldConfig {
/**
* Map values to a display color
*/
color?: FieldColor;
/**
* custom is specified by the PanelFieldConfig field
* in panel plugin schemas.
*/
custom?: Record<string, unknown>;
/**
* Significant digits (for display)
*/
decimals?: number;
/**
* Human readable field metadata
*/
description?: string;
/**
* 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;
/**
* True if data source field supports ad-hoc filters
*/
filterable?: boolean;
/**
* The behavior when clicking on a result
*/
links?: Array<any>;
/**
* Convert input values into a display string
*/
mappings?: Array<ValueMapping>;
max?: number;
min?: number;
/**
* Alternative to empty string
*/
noValue?: 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;
/**
* Map numeric values to states
*/
thresholds?: ThresholdsConfig;
/**
* Numeric Options
*/
unit?: string;
/**
* True if data source can write a value to the path. Auth/authz are supported separately
*/
writeable?: boolean;
}
export const defaultFieldConfig: Partial<FieldConfig> = {
links: [],
mappings: [],
};
/**
* Row panel
*/
export interface RowPanel {
collapsed: boolean;
/**
* Name of default datasource.
*/
datasource?: {
type?: string;
uid?: string;
};
gridPos?: GridPos;
id: number;
panels: Array<(Panel | {
type: 'graph';
} | {
type: 'heatmap';
})>;
/**
* Name of template variable to repeat for.
*/
repeat?: string;
title?: string;
type: 'row';
}
export const defaultRowPanel: Partial<RowPanel> = {
collapsed: false,
panels: [],
};
export interface Dashboard {
/**
* TODO docs
*/
annotations?: {
list: Array<AnnotationQuery>;
};
/**
* Description of dashboard.
*/
description?: string;
/**
* Whether a dashboard is editable or not.
*/
editable: boolean;
/**
* TODO docs
*/
fiscalYearStartMonth?: number;
gnetId?: string;
graphTooltip: DashboardCursorSync;
/**
* Unique numeric identifier for the dashboard.
* TODO must isolate or remove identifiers local to a Grafana instance...?
*/
id?: number;
/**
* TODO docs
*/
links?: Array<DashboardLink>;
/**
* TODO docs
*/
liveNow?: boolean;
panels?: Array<(Panel | RowPanel | {
type: 'graph';
} | {
type: 'heatmap';
})>;
/**
* TODO docs
*/
refresh?: (string | false);
/**
* Version of the JSON schema, incremented each time a Grafana update brings
* changes to said schema.
* TODO this is the existing schema numbering system. It will be replaced by Thema's themaVersion
*/
schemaVersion: number;
/**
* Theme of dashboard.
*/
style: ('light' | 'dark');
/**
* Tags associated with dashboard.
*/
tags?: Array<string>;
/**
* TODO docs
*/
templating?: {
list: Array<VariableModel>;
};
/**
* Time range for dashboard, e.g. last 6 hours, last 7 days, etc
*/
time?: {
from: string;
to: string;
};
/**
* TODO docs
* TODO this appears to be spread all over in the frontend. Concepts will likely need tidying in tandem with schema changes
*/
timepicker?: {
/**
* Whether timepicker is collapsed or not.
*/
collapse: boolean;
/**
* Whether timepicker is enabled or not.
*/
enable: boolean;
/**
* Whether timepicker is visible or not.
*/
hidden: boolean;
/**
* Selectable intervals for auto-refresh.
*/
refresh_intervals: Array<string>;
/**
* TODO docs
*/
time_options: Array<string>;
};
/**
* Timezone of dashboard,
*/
timezone?: ('browser' | 'utc' | '');
/**
* Title of dashboard.
*/
title?: string;
/**
* Unique dashboard identifier that can be generated by anyone. string (8-40)
*/
uid?: string;
/**
* Version of the dashboard, incremented each time the dashboard is updated.
*/
version?: number;
/**
* TODO docs
*/
weekStart?: string;
}
export const defaultDashboard: Partial<Dashboard> = {
editable: true,
graphTooltip: DashboardCursorSync.Off,
links: [],
panels: [],
schemaVersion: 36,
style: 'dark',
tags: [],
timezone: 'browser',
};

@ -1,274 +0,0 @@
// This file is autogenerated. DO NOT EDIT.
//
// Generated by pkg/framework/coremodel/gen.go
//
// Derived from the Thema lineage declared in pkg/coremodel/dashboard/coremodel.cue
//
// Run `make gen-cue` from repository root to regenerate.
// This model is a WIP and not yet canonical. Consequently, its members are
// not exported to exclude it from grafana-schema's public API surface.
interface AnnotationQuery {
builtIn: number;
datasource: {
type?: string;
uid?: string;
};
enable: boolean;
hide?: boolean;
iconColor?: string;
name?: string;
rawQuery?: string;
showIn: number;
target?: {};
type: string;
}
const defaultAnnotationQuery: Partial<AnnotationQuery> = {
builtIn: 0,
enable: true,
hide: false,
showIn: 0,
type: 'dashboard',
};
interface VariableModel {
label?: string;
name: string;
type: VariableType;
}
interface DashboardLink {
asDropdown: boolean;
icon?: string;
includeVars: boolean;
keepTime: boolean;
tags: string[];
targetBlank: boolean;
title: string;
tooltip?: string;
type: DashboardLinkType;
url?: string;
}
const defaultDashboardLink: Partial<DashboardLink> = {
asDropdown: false,
includeVars: false,
keepTime: false,
tags: [],
targetBlank: false,
};
type DashboardLinkType = ('link' | 'dashboards');
type VariableType = ('query' | 'adhoc' | 'constant' | 'datasource' | 'interval' | 'textbox' | 'custom' | 'system');
enum FieldColorModeId {
ContinuousGrYlRd = 'continuous-GrYlRd',
Fixed = 'fixed',
PaletteClassic = 'palette-classic',
PaletteSaturated = 'palette-saturated',
Thresholds = 'thresholds',
}
type FieldColorSeriesByMode = ('min' | 'max' | 'last');
interface FieldColor {
fixedColor?: string;
mode: (FieldColorModeId | string);
seriesBy?: FieldColorSeriesByMode;
}
interface GridPos {
h: number;
static?: boolean;
w: number;
x: number;
y: number;
}
const defaultGridPos: Partial<GridPos> = {
h: 9,
w: 12,
x: 0,
y: 0,
};
interface Threshold {
color: string;
state?: string;
value?: number;
}
enum ThresholdsMode {
Absolute = 'absolute',
Percentage = 'percentage',
}
interface ThresholdsConfig {
mode: ThresholdsMode;
steps: Threshold[];
}
const defaultThresholdsConfig: Partial<ThresholdsConfig> = {
steps: [],
};
interface Transformation {
id: string;
options: {};
}
enum DashboardCursorSync {
Crosshair = 1,
Off = 0,
Tooltip = 2,
}
const defaultDashboardCursorSync: DashboardCursorSync = DashboardCursorSync.Off;
interface Panel {
datasource?: {
type?: string;
uid?: string;
};
description?: string;
fieldConfig: {
defaults: {
displayName?: string;
displayNameFromDS?: string;
description?: string;
path?: string;
writeable?: boolean;
filterable?: boolean;
unit?: string;
decimals?: number;
min?: number;
max?: number;
mappings?: {}[];
thresholds?: ThresholdsConfig;
color?: FieldColor;
links?: any[];
noValue?: string;
custom?: {};
};
overrides: {
matcher: {
id: string;
options?: any;
};
properties: {
id: string;
value?: any;
}[];
}[];
};
gridPos?: GridPos;
id?: number;
interval?: string;
links?: DashboardLink[];
maxDataPoints?: number;
options: {};
pluginVersion?: string;
repeat?: string;
repeatDirection: ('h' | 'v');
tags?: string[];
targets?: {}[];
thresholds?: any[];
timeFrom?: string;
timeRegions?: any[];
timeShift?: string;
title?: string;
transformations: Transformation[];
transparent: boolean;
type: string;
}
const defaultPanel: Partial<Panel> = {
links: [],
repeatDirection: 'h',
tags: [],
targets: [],
thresholds: [],
timeRegions: [],
transformations: [],
transparent: false,
};
interface RowPanel {
collapsed: boolean;
datasource?: {
type?: string;
uid?: string;
};
gridPos?: GridPos;
id: number;
panels: (Panel | {
type: 'graph';
} | {
type: 'heatmap';
})[];
repeat?: string;
title?: string;
type: 'row';
}
const defaultRowPanel: Partial<RowPanel> = {
collapsed: false,
panels: [],
};
interface Dashboard {
annotations?: {
list: AnnotationQuery[];
};
description?: string;
editable: boolean;
fiscalYearStartMonth?: number;
gnetId?: string;
graphTooltip: DashboardCursorSync;
id?: number;
links?: DashboardLink[];
liveNow?: boolean;
panels?: (Panel | RowPanel | {
type: 'graph';
} | {
type: 'heatmap';
})[];
refresh?: (string | false);
schemaVersion: number;
style: ('light' | 'dark');
tags?: string[];
templating?: {
list: VariableModel[];
};
time?: {
from: string;
to: string;
};
timepicker?: {
collapse: boolean;
enable: boolean;
hidden: boolean;
refresh_intervals: string[];
time_options: string[];
};
timezone?: ('browser' | 'utc' | '');
title?: string;
uid?: string;
version?: number;
weekStart?: string;
}
const defaultDashboard: Partial<Dashboard> = {
editable: true,
graphTooltip: DashboardCursorSync.Off,
links: [],
panels: [],
schemaVersion: 36,
style: 'dark',
tags: [],
timezone: 'browser',
};

@ -4,6 +4,9 @@
// To regenerate, run "make gen-cue" from the repository root.
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/**
* TODO docs
*/
export enum AxisPlacement {
Auto = 'auto',
Bottom = 'bottom',
@ -13,28 +16,43 @@ export enum AxisPlacement {
Top = 'top',
}
/**
* TODO docs
*/
export enum AxisColorMode {
Series = 'series',
Text = 'text',
}
/**
* TODO docs
*/
export enum VisibilityMode {
Always = 'always',
Auto = 'auto',
Never = 'never',
}
/**
* TODO docs
*/
export enum GraphDrawStyle {
Bars = 'bars',
Line = 'line',
Points = 'points',
}
/**
* TODO docs
*/
export enum GraphTransform {
Constant = 'constant',
NegativeY = 'negative-Y',
}
/**
* TODO docs
*/
export enum LineInterpolation {
Linear = 'linear',
Smooth = 'smooth',
@ -42,6 +60,9 @@ export enum LineInterpolation {
StepBefore = 'stepBefore',
}
/**
* TODO docs
*/
export enum ScaleDistribution {
Linear = 'linear',
Log = 'log',
@ -49,6 +70,9 @@ export enum ScaleDistribution {
Symlog = 'symlog',
}
/**
* TODO docs
*/
export enum GraphGradientMode {
Hue = 'hue',
None = 'none',
@ -56,23 +80,35 @@ export enum GraphGradientMode {
Scheme = 'scheme',
}
/**
* TODO docs
*/
export enum StackingMode {
None = 'none',
Normal = 'normal',
Percent = 'percent',
}
/**
* TODO docs
*/
export enum BarAlignment {
After = 1,
Before = -1,
Center = 0,
}
/**
* TODO docs
*/
export enum ScaleOrientation {
Horizontal = 0,
Vertical = 1,
}
/**
* TODO docs
*/
export enum ScaleDirection {
Down = -1,
Left = -1,
@ -80,8 +116,11 @@ export enum ScaleDirection {
Up = 1,
}
/**
* TODO docs
*/
export interface LineStyle {
dash?: number[];
dash?: Array<number>;
fill?: ('solid' | 'dash' | 'dot' | 'square');
}
@ -89,26 +128,43 @@ export const defaultLineStyle: Partial<LineStyle> = {
dash: [],
};
/**
* TODO docs
*/
export interface LineConfig {
lineColor?: string;
lineInterpolation?: LineInterpolation;
lineStyle?: LineStyle;
lineWidth?: number;
/**
* Indicate if null values should be treated as gaps or connected.
* When the value is a number, it represents the maximum delta in the
* X axis that should be considered connected. For timeseries, this is milliseconds
*/
spanNulls?: (boolean | number);
}
/**
* TODO docs
*/
export interface BarConfig {
barAlignment?: BarAlignment;
barMaxWidth?: number;
barWidthFactor?: number;
}
/**
* TODO docs
*/
export interface FillConfig {
fillBelowTo?: string;
fillColor?: string;
fillOpacity?: number;
}
/**
* TODO docs
*/
export interface PointsConfig {
pointColor?: string;
pointSize?: number;
@ -116,12 +172,18 @@ export interface PointsConfig {
showPoints?: VisibilityMode;
}
/**
* TODO docs
*/
export interface ScaleDistributionConfig {
linearThreshold?: number;
log?: number;
type: ScaleDistribution;
}
/**
* TODO docs
*/
export interface AxisConfig {
axisCenteredZero?: boolean;
axisColorMode?: AxisColorMode;
@ -134,25 +196,40 @@ export interface AxisConfig {
scaleDistribution?: ScaleDistributionConfig;
}
/**
* TODO docs
*/
export interface HideSeriesConfig {
legend: boolean;
tooltip: boolean;
viz: boolean;
}
/**
* TODO docs
*/
export interface StackingConfig {
group?: string;
mode?: StackingMode;
}
/**
* TODO docs
*/
export interface StackableFieldConfig {
stacking?: StackingConfig;
}
/**
* TODO docs
*/
export interface HideableFieldConfig {
hideFrom?: HideSeriesConfig;
}
/**
* TODO docs
*/
export enum GraphTresholdsStyleMode {
Area = 'area',
Line = 'line',
@ -161,32 +238,63 @@ export enum GraphTresholdsStyleMode {
Series = 'series',
}
/**
* TODO docs
*/
export interface GraphThresholdsStyleConfig {
mode: GraphTresholdsStyleMode;
}
/**
* TODO docs
*/
export type LegendPlacement = ('bottom' | 'right');
/**
* TODO docs
* Note: "hidden" needs to remain as an option for plugins compatibility
*/
export enum LegendDisplayMode {
Hidden = 'hidden',
List = 'list',
Table = 'table',
}
/**
* TODO docs
*/
export interface TableSortByFieldState {
desc?: boolean;
displayName: string;
}
/**
* TODO docs
*/
export interface SingleStatBaseOptions extends OptionsWithTextFormatting {
orientation: VizOrientation;
reduceOptions: ReduceDataOptions;
}
/**
* TODO docs
*/
export interface ReduceDataOptions {
calcs: string[];
/**
* When !values, pick one value for the whole field
*/
calcs: Array<string>;
/**
* Which fields to show. By default this is only numeric fields
*/
fields?: string;
/**
* if showing all values limit
*/
limit?: number;
/**
* If true show each row value
*/
values?: boolean;
}
@ -194,49 +302,76 @@ export const defaultReduceDataOptions: Partial<ReduceDataOptions> = {
calcs: [],
};
/**
* TODO docs
*/
export enum VizOrientation {
Auto = 'auto',
Horizontal = 'horizontal',
Vertical = 'vertical',
}
/**
* TODO docs
*/
export interface OptionsWithTooltip {
tooltip: VizTooltipOptions;
}
/**
* TODO docs
*/
export interface OptionsWithLegend {
legend: VizLegendOptions;
}
/**
* TODO docs
*/
export interface OptionsWithTimezones {
timezone?: string[];
timezone?: Array<string>;
}
export const defaultOptionsWithTimezones: Partial<OptionsWithTimezones> = {
timezone: [],
};
/**
* TODO docs
*/
export interface OptionsWithTextFormatting {
text?: VizTextDisplayOptions;
}
/**
* TODO docs
*/
export enum BigValueColorMode {
Background = 'background',
None = 'none',
Value = 'value',
}
/**
* TODO docs
*/
export enum BigValueGraphMode {
Area = 'area',
Line = 'line',
None = 'none',
}
/**
* TODO docs
*/
export enum BigValueJustifyMode {
Auto = 'auto',
Center = 'center',
}
/**
* TODO docs
*/
export enum BigValueTextMode {
Auto = 'auto',
Name = 'name',
@ -245,8 +380,15 @@ export enum BigValueTextMode {
ValueAndName = 'value_and_name',
}
/**
* TODO -- should not be table specific!
* TODO docs
*/
export type FieldTextAlignment = ('auto' | 'left' | 'right' | 'center');
/**
* TODO docs
*/
export enum TableCellDisplayMode {
Auto = 'auto',
BasicGauge = 'basic',
@ -259,23 +401,41 @@ export enum TableCellDisplayMode {
LcdGauge = 'lcd-gauge',
}
/**
* TODO docs
*/
export interface VizTextDisplayOptions {
/**
* Explicit title text size
*/
titleSize?: number;
/**
* Explicit value text size
*/
valueSize?: number;
}
/**
* TODO docs
*/
export enum TooltipDisplayMode {
Multi = 'multi',
None = 'none',
Single = 'single',
}
/**
* TODO docs
*/
export enum SortOrder {
Ascending = 'asc',
Descending = 'desc',
None = 'none',
}
/**
* TODO docs
*/
export interface GraphFieldConfig extends LineConfig, FillConfig, PointsConfig, AxisConfig, BarConfig, StackableFieldConfig, HideableFieldConfig {
drawStyle?: GraphDrawStyle;
gradientMode?: GraphGradientMode;
@ -283,9 +443,12 @@ export interface GraphFieldConfig extends LineConfig, FillConfig, PointsConfig,
transform?: GraphTransform;
}
/**
* TODO docs
*/
export interface VizLegendOptions {
asTable?: boolean;
calcs: string[];
calcs: Array<string>;
displayMode: LegendDisplayMode;
isVisible?: boolean;
placement: LegendPlacement;
@ -299,12 +462,18 @@ export const defaultVizLegendOptions: Partial<VizLegendOptions> = {
calcs: [],
};
/**
* TODO docs
*/
export enum BarGaugeDisplayMode {
Basic = 'basic',
Gradient = 'gradient',
Lcd = 'lcd',
}
/**
* TODO docs
*/
export interface TableFieldOptions {
align: FieldTextAlignment;
displayMode: TableCellDisplayMode;
@ -321,6 +490,9 @@ export const defaultTableFieldOptions: Partial<TableFieldOptions> = {
inspect: false,
};
/**
* TODO docs
*/
export interface VizTooltipOptions {
mode: TooltipDisplayMode;
sort: SortOrder;

@ -0,0 +1,32 @@
import * as raw from '../raw/dashboard/x/dashboard.gen';
export interface Dashboard extends raw.Dashboard {
panels?: Array<
| Panel
| raw.RowPanel
| {
type: 'graph';
}
| {
type: 'heatmap';
}
>;
}
export interface Panel<TOptions = Record<string, unknown>, TCustomFieldConfig = Record<string, unknown>>
extends raw.Panel {
fieldConfig: FieldConfigSource<TCustomFieldConfig>;
}
export interface FieldConfig<TOptions = Record<string, unknown>> extends raw.FieldConfig {
custom?: TOptions & Record<string, unknown>;
}
export interface FieldConfigSource<TOptions = Record<string, unknown>> extends raw.FieldConfigSource {
defaults: FieldConfig<TOptions>;
}
export const defaultDashboard: Partial<Dashboard> = raw.defaultDashboard;
export const defaultPanel: Partial<Panel> = raw.defaultPanel;
export const defaultFieldConfig: Partial<FieldConfig> = raw.defaultFieldConfig;
export const defaultFieldConfigSource: Partial<FieldConfigSource> = raw.defaultFieldConfigSource;

@ -17,14 +17,15 @@ import (
"github.com/deepmap/oapi-codegen/pkg/codegen"
"github.com/getkin/kin-openapi/openapi3"
"github.com/grafana/cuetsy"
tsast "github.com/grafana/cuetsy/ts/ast"
"github.com/grafana/grafana/pkg/cuectx"
"github.com/grafana/thema"
"github.com/grafana/thema/encoding/openapi"
)
// ExtractedLineage contains the results of statically analyzing a Grafana
// CoremodelDeclaration contains the results of statically analyzing a Grafana
// directory for a Thema lineage.
type ExtractedLineage struct {
type CoremodelDeclaration struct {
Lineage thema.Lineage
// Absolute path to the coremodel's coremodel.cue file.
LineagePath string
@ -48,12 +49,12 @@ type ExtractedLineage struct {
// This loading approach is intended primarily for use with code generators, or
// other use cases external to grafana-server backend. For code within
// grafana-server, prefer lineage loaders provided in e.g. pkg/coremodel/*.
func ExtractLineage(path string, lib thema.Library) (*ExtractedLineage, error) {
func ExtractLineage(path string, lib thema.Library) (*CoremodelDeclaration, error) {
if !filepath.IsAbs(path) {
return nil, fmt.Errorf("must provide an absolute path, got %q", path)
}
ec := &ExtractedLineage{
ec := &CoremodelDeclaration{
LineagePath: path,
}
@ -107,14 +108,14 @@ func ExtractLineage(path string, lib thema.Library) (*ExtractedLineage, error) {
}
// toTemplateObj extracts creates a struct with all the useful strings for template generation.
func (ls *ExtractedLineage) toTemplateObj() tplVars {
lin := ls.Lineage
func (cd *CoremodelDeclaration) toTemplateObj() tplVars {
lin := cd.Lineage
sch := thema.SchemaP(lin, thema.LatestVersion(lin))
return tplVars{
Name: lin.Name(),
LineagePath: ls.RelativePath,
PkgPath: filepath.ToSlash(filepath.Join("github.com/grafana/grafana", filepath.Dir(ls.RelativePath))),
LineagePath: cd.RelativePath,
PkgPath: filepath.ToSlash(filepath.Join("github.com/grafana/grafana", filepath.Dir(cd.RelativePath))),
TitleName: strings.Title(lin.Name()), // nolint
LatestSeqv: sch.Version()[0],
LatestSchv: sch.Version()[1],
@ -139,13 +140,22 @@ var nonAPITypes = map[string]bool{
"pluginmeta": true,
}
// PathVersion returns the string path element to use for the latest schema.
// "x" if not yet canonical, otherwise, "v<major>"
func (cd *CoremodelDeclaration) PathVersion() string {
if !cd.IsCanonical {
return "x"
}
return fmt.Sprintf("v%v", thema.LatestVersion(cd.Lineage)[0])
}
// GenerateGoCoremodel generates a standard Go model struct and coremodel
// implementation from a coremodel CUE declaration.
//
// The provided path must be a directory. Generated code files will be written
// to that path. The final element of the path must match the Lineage.Name().
func (ls *ExtractedLineage) GenerateGoCoremodel(path string) (WriteDiffer, error) {
lin, lib := ls.Lineage, ls.Lineage.Library()
func (cd *CoremodelDeclaration) GenerateGoCoremodel(path string) (WriteDiffer, error) {
lin, lib := cd.Lineage, cd.Lineage.Library()
_, name := filepath.Split(path)
if name != lin.Name() {
return nil, fmt.Errorf("lineage name %q must match final element of path, got %q", lin.Name(), path)
@ -190,7 +200,7 @@ func (ls *ExtractedLineage) GenerateGoCoremodel(path string) (WriteDiffer, error
buf := new(bytes.Buffer)
if err = tmpls.Lookup("autogen_header.tmpl").Execute(buf, tvars_autogen_header{
LineagePath: ls.RelativePath,
LineagePath: cd.RelativePath,
GeneratorPath: "pkg/framework/coremodel/gen.go", // FIXME hardcoding is not OK
}); err != nil {
return nil, fmt.Errorf("error executing header template: %w", err)
@ -198,7 +208,7 @@ func (ls *ExtractedLineage) GenerateGoCoremodel(path string) (WriteDiffer, error
fmt.Fprint(buf, "\n", gostr)
vars := ls.toTemplateObj()
vars := cd.toTemplateObj()
err = tmpls.Lookup("addenda.tmpl").Execute(buf, vars)
if err != nil {
panic(err)
@ -228,59 +238,38 @@ type tplVars struct {
IsComposed bool
}
func (ls *ExtractedLineage) GenerateTypescriptCoremodel(path string) (WriteDiffer, error) {
_, name := filepath.Split(path)
if name != ls.Lineage.Name() {
return nil, fmt.Errorf("lineage name %q must match final element of path, got %q", ls.Lineage.Name(), path)
}
func (cd *CoremodelDeclaration) GenerateTypescriptCoremodel() (*tsast.File, error) {
schv := thema.SchemaP(cd.Lineage, thema.LatestVersion(cd.Lineage)).UnwrapCUE()
schv := thema.SchemaP(ls.Lineage, thema.LatestVersion(ls.Lineage)).UnwrapCUE()
parts, err := cuetsy.GenerateAST(schv, cuetsy.Config{})
tf, err := cuetsy.GenerateAST(schv, cuetsy.Config{
Export: true,
})
if err != nil {
return nil, fmt.Errorf("cuetsy parts gen failed: %w", err)
return nil, fmt.Errorf("cuetsy tf gen failed: %w", err)
}
top, err := cuetsy.GenerateSingleAST(strings.Title(ls.Lineage.Name()), schv, cuetsy.TypeInterface)
top, err := cuetsy.GenerateSingleAST(strings.Title(cd.Lineage.Name()), schv, cuetsy.TypeInterface)
if err != nil {
return nil, fmt.Errorf("cuetsy top gen failed: %s", cerrors.Details(err, nil))
}
// TODO until cuetsy can toposort its outputs, put the top/parent type at the bottom of the file.
parts.Nodes = append(parts.Nodes, top.T)
if top.D != nil {
parts.Nodes = append(parts.Nodes, top.D)
}
var strb strings.Builder
var str string
fpath := ls.Lineage.Name() + ".gen.ts"
if err := tmpls.Lookup("autogen_header.tmpl").Execute(&strb, tvars_autogen_header{
LineagePath: ls.RelativePath,
buf := new(bytes.Buffer)
if err := tmpls.Lookup("autogen_header.tmpl").Execute(buf, tvars_autogen_header{
LineagePath: cd.RelativePath,
GeneratorPath: "pkg/framework/coremodel/gen.go", // FIXME hardcoding is not OK
}); err != nil {
return nil, fmt.Errorf("error executing header template: %w", err)
}
if !ls.IsCanonical {
fpath = fmt.Sprintf("%s_experimental.gen.ts", ls.Lineage.Name())
strb.WriteString(`
// This model is a WIP and not yet canonical. Consequently, its members are
// not exported to exclude it from grafana-schema's public API surface.
`)
strb.WriteString(fmt.Sprint(parts))
// TODO replace this regexp with cuetsy config for whether members are exported
re := regexp.MustCompile(`(?m)^export `)
str = re.ReplaceAllLiteralString(strb.String(), "")
} else {
strb.WriteString(fmt.Sprint(parts))
str = strb.String()
tf.Doc = &tsast.Comment{
Text: buf.String(),
}
wd := NewWriteDiffer()
wd[filepath.Join(path, fpath)] = []byte(str)
return wd, nil
// TODO until cuetsy can toposort its outputs, put the top/parent type at the bottom of the file.
tf.Nodes = append(tf.Nodes, top.T)
if top.D != nil {
tf.Nodes = append(tf.Nodes, top.D)
}
return tf, nil
}
type prefixDropper struct {
@ -319,7 +308,7 @@ func (d prefixDropper) Visit(n ast.Node) ast.Visitor {
// GenerateCoremodelRegistry produces Go files that define a registry with
// references to all the Go code that is expected to be generated from the
// provided lineages.
func GenerateCoremodelRegistry(path string, ecl []*ExtractedLineage) (WriteDiffer, error) {
func GenerateCoremodelRegistry(path string, ecl []*CoremodelDeclaration) (WriteDiffer, error) {
var cml []tplVars
for _, ec := range ecl {
cml = append(cml, ec.toTemplateObj())

@ -14,6 +14,7 @@ import (
"github.com/deepmap/oapi-codegen/pkg/codegen"
"github.com/getkin/kin-openapi/openapi3"
"github.com/grafana/cuetsy"
tsast "github.com/grafana/cuetsy/ts/ast"
"github.com/grafana/grafana/pkg/framework/coremodel"
"github.com/grafana/grafana/pkg/plugins/pfs"
"github.com/grafana/thema"
@ -101,15 +102,22 @@ type PluginTreeOrErr struct {
// It is, for now, tailored specifically to Grafana core's codegen needs.
type PluginTree pfs.Tree
func (pt *PluginTree) GenerateTS(path string) (WriteDiffer, error) {
func (pt *PluginTree) GenerateTypeScriptAST() (*tsast.File, error) {
t := (*pfs.Tree)(pt)
f := &tsast.File{}
// TODO replace with cuetsy's TS AST
f := &tvars_cuetsy_multi{
Header: tvars_autogen_header{
GeneratorPath: "public/app/plugins/gen.go", // FIXME hardcoding is not OK
LineagePath: "models.cue",
},
tf := tvars_autogen_header{
GeneratorPath: "public/app/plugins/gen.go", // FIXME hardcoding is not OK
LineagePath: "models.cue",
}
var buf bytes.Buffer
err := tmpls.Lookup("autogen_header.tmpl").Execute(&buf, tf)
if err != nil {
return nil, fmt.Errorf("error executing header template: %w", err)
}
f.Doc = &tsast.Comment{
Text: buf.String(),
}
pi := t.RootPlugin()
@ -118,7 +126,9 @@ func (pt *PluginTree) GenerateTS(path string) (WriteDiffer, error) {
return nil, nil
}
for _, im := range pi.CUEImports() {
if tsim := convertImport(im); tsim != nil {
if tsim, err := convertImport(im); err != nil {
return nil, err
} else if tsim.From.Value != "" {
f.Imports = append(f.Imports, tsim)
}
}
@ -126,37 +136,36 @@ func (pt *PluginTree) GenerateTS(path string) (WriteDiffer, error) {
for slotname, lin := range slotimps {
v := thema.LatestVersion(lin)
sch := thema.SchemaP(lin, v)
// TODO need call expressions in cuetsy tsast to be able to do these
sec := tsSection{
V: v,
ModelName: slotname,
}
// Inject a node for the const with the version
f.Nodes = append(f.Nodes, tsast.Raw{
// TODO need call expressions in cuetsy tsast to be able to do these properly
Data: fmt.Sprintf("export const %sModelVersion = Object.freeze([%v, %v]);", slotname, v[0], v[1]),
})
// TODO this is hardcoded for now, but should ultimately be a property of
// whether the slot is a grouped lineage:
// https://github.com/grafana/thema/issues/62
if isGroupLineage(slotname) {
b, err := cuetsy.Generate(sch.UnwrapCUE(), cuetsy.Config{})
tsf, err := cuetsy.GenerateAST(sch.UnwrapCUE(), cuetsy.Config{
Export: true,
})
if err != nil {
return nil, fmt.Errorf("%s: error translating %s lineage to TypeScript: %w", path, slotname, err)
return nil, fmt.Errorf("error translating %s lineage to TypeScript: %w", slotname, err)
}
sec.Body = string(b)
f.Nodes = append(f.Nodes, tsf.Nodes...)
} else {
a, err := cuetsy.GenerateSingleAST(strings.Title(lin.Name()), sch.UnwrapCUE(), cuetsy.TypeInterface)
pair, err := cuetsy.GenerateSingleAST(strings.Title(lin.Name()), sch.UnwrapCUE(), cuetsy.TypeInterface)
if err != nil {
return nil, fmt.Errorf("%s: error translating %s lineage to TypeScript: %w", path, slotname, err)
return nil, fmt.Errorf("error translating %s lineage to TypeScript: %w", slotname, err)
}
f.Nodes = append(f.Nodes, pair.T)
if pair.D != nil {
f.Nodes = append(f.Nodes, pair.D)
}
sec.Body = fmt.Sprint(a)
}
f.Sections = append(f.Sections, sec)
}
wd := NewWriteDiffer()
var buf bytes.Buffer
err := tmpls.Lookup("cuetsy_multi.tmpl").Execute(&buf, f)
if err != nil {
return nil, fmt.Errorf("%s: error executing plugin TS generator template: %w", path, err)
}
wd[filepath.Join(path, "models.gen.ts")] = buf.Bytes()
return wd, nil
return f, nil
}
func isGroupLineage(slotname string) bool {
@ -414,41 +423,27 @@ type TreeAndPath struct {
}
// TODO convert this to use cuetsy ts types, once import * form is supported
func convertImport(im *ast.ImportSpec) *tsImport {
var err error
tsim := &tsImport{}
tsim.Pkg, err = MapCUEImportToTS(strings.Trim(im.Path.Value, "\""))
if err != nil {
// should be unreachable if paths has been verified already
panic(err)
}
if tsim.Pkg == "" {
func convertImport(im *ast.ImportSpec) (tsast.ImportSpec, error) {
tsim := tsast.ImportSpec{}
pkg, err := MapCUEImportToTS(strings.Trim(im.Path.Value, "\""))
if err != nil || pkg == "" {
// err should be unreachable if paths has been verified already
// Empty string mapping means skip it
return nil
return tsim, err
}
tsim.From = tsast.Str{Value: pkg}
if im.Name != nil && im.Name.String() != "" {
tsim.Ident = im.Name.String()
tsim.AsName = im.Name.String()
} else {
sl := strings.Split(im.Path.Value, "/")
final := sl[len(sl)-1]
if idx := strings.Index(final, ":"); idx != -1 {
tsim.Pkg = final[idx:]
tsim.AsName = final[idx:]
} else {
tsim.Pkg = final
tsim.AsName = final
}
}
return tsim
}
type tsSection struct {
V thema.SyntacticVersion
ModelName string
Body string
}
type tsImport struct {
Ident string
Pkg string
return tsim, nil
}

@ -1,6 +1,7 @@
package codegen
import (
"bytes"
"embed"
"text/template"
"time"
@ -48,11 +49,6 @@ type (
SlotImpls []tvars_plugin_lineage_binding
Header tvars_autogen_header
}
tvars_cuetsy_multi struct {
Header tvars_autogen_header
Imports []*tsImport
Sections []tsSection
}
tvars_plugin_registry struct {
Header tvars_autogen_header
Plugins []struct {
@ -63,3 +59,14 @@ type (
}
}
)
type HeaderVars = tvars_autogen_header
// GenGrafanaHeader creates standard header elements for generated Grafana files.
func GenGrafanaHeader(vars HeaderVars) string {
buf := new(bytes.Buffer)
if err := tmpls.Lookup("autogen_header.tmpl").Execute(buf, vars); err != nil {
panic(err)
}
return buf.String()
}

@ -1,7 +1,2 @@
{{ template "autogen_header.tmpl" .Header -}}
{{range .Imports}}
import * as {{.Ident}} from '{{.Pkg}}';{{end}}
{{range .Sections}}{{if ne .ModelName "" }}
export const {{.ModelName}}ModelVersion = Object.freeze([{{index .V 0}}, {{index .V 1}}]);
{{end}}
{{.Body}}{{end}}
{{ .Body }}

@ -12,6 +12,8 @@ seqs: [
{
schemas: [
{// 0.0
@grafana(TSVeneer="type")
// Unique numeric identifier for the dashboard.
// TODO must isolate or remove identifiers local to a Grafana instance...?
id?: int64
@ -192,6 +194,59 @@ seqs: [
steps: [...#Threshold] @reviewme()
} @cuetsy(kind="interface") @reviewme()
// TODO docs
#ValueMapping: #ValueMap | #RangeMap | #RegexMap | #SpecialValueMap @cuetsy(kind="type") @reviewme()
// TODO docs
#MappingType: "value" | "range" | "regex" | "special" @cuetsy(kind="enum",memberNames="ValueToText|RangeToText|RegexToText|SpecialValue") @reviewme()
// TODO docs
#ValueMap: {
type: #MappingType & "value"
options: [string]: #ValueMappingResult
} @cuetsy(kind="interface")
// TODO docs
#RangeMap: {
type: #MappingType & "range"
options: {
// to and from are `number | null` in current ts, really not sure what to do
from: int32 @reviewme()
to: int32 @reviewme()
result: #ValueMappingResult
}
} @cuetsy(kind="interface") @reviewme()
// TODO docs
#RegexMap: {
type: #MappingType & "regex"
options: {
pattern: string
result: #ValueMappingResult
}
} @cuetsy(kind="interface") @reviewme()
// TODO docs
#SpecialValueMap: {
type: #MappingType & "special"
options: {
match: "true" | "false"
pattern: string
result: #ValueMappingResult
}
} @cuetsy(kind="interface") @reviewme()
// TODO docs
#SpecialValueMatch: "true" | "false" | "null" | "nan" | "null+nan" | "empty" @cuetsy(kind="enum",memberNames="True|False|Null|NaN|NullAndNan|Empty")
// TODO docs
#ValueMappingResult: {
text?: string
color?: string
icon?: string
index?: int32
} @cuetsy(kind="interface")
// TODO docs
// FIXME this is extremely underspecfied; wasn't obvious which typescript types corresponded to it
#Transformation: {
@ -282,82 +337,82 @@ seqs: [
// plugin schemas.
options: {...} @reviewme()
fieldConfig: {
defaults: {
// The display value for this field. This supports template variables blank is auto
displayName?: string @reviewme()
// 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 @reviewme()
// Human readable field metadata
description?: string @reviewme()
// 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 @reviewme()
// True if data source can write a value to the path. Auth/authz are supported separately
writeable?: bool @reviewme()
// True if data source field supports ad-hoc filters
filterable?: bool @reviewme()
// Numeric Options
unit?: string @reviewme()
// Significant digits (for display)
decimals?: number @reviewme()
min?: number @reviewme()
max?: number @reviewme()
// Convert input values into a display string
//
// TODO this one corresponds to a complex type with
// generics on the typescript side. Ouch. Will
// either need special care, or we'll just need to
// accept a very loosely specified schema. It's very
// unlikely we'll be able to translate cue to
// typescript generics in the general case, though
// this particular one *may* be able to work.
mappings?: [...{...}] @reviewme()
// Map numeric values to states
thresholds?: #ThresholdsConfig @reviewme()
// // Map values to a display color
color?: #FieldColor @reviewme()
// // Used when reducing field values
// nullValueMode?: NullValueMode
// // The behavior when clicking on a result
links?: [...] @reviewme()
// Alternative to empty string
noValue?: string @reviewme()
// custom is specified by the PanelFieldConfig field
// in panel plugin schemas.
custom?: {...} @reviewme()
} @reviewme()
overrides: [...{
matcher: {
id: string | *"" @reviewme()
options?: _ @reviewme()
}
properties: [...{
id: string | *"" @reviewme()
value?: _ @reviewme()
}]
}] @reviewme()
}
} @cuetsy(kind="interface") @reviewme()
fieldConfig: #FieldConfigSource
} @cuetsy(kind="interface") @grafana(TSVeneer="type") @reviewme()
#FieldConfigSource: {
defaults: #FieldConfig
overrides: [...{
matcher: #MatcherConfig
properties: [...#DynamicConfigValue]
}] @reviewme()
} @cuetsy(kind="interface") @grafana(TSVeneer="type") @reviewme()
#MatcherConfig: {
id: string | *"" @reviewme()
options?: _ @reviewme()
} @cuetsy(kind="interface")
#DynamicConfigValue: {
id: string | *"" @reviewme()
value?: _ @reviewme()
}
#FieldConfig: {
// The display value for this field. This supports template variables blank is auto
displayName?: string @reviewme()
// 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 @reviewme()
// Human readable field metadata
description?: string @reviewme()
// 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 @reviewme()
// True if data source can write a value to the path. Auth/authz are supported separately
writeable?: bool @reviewme()
// True if data source field supports ad-hoc filters
filterable?: bool @reviewme()
// Numeric Options
unit?: string @reviewme()
// Significant digits (for display)
decimals?: number @reviewme()
min?: number @reviewme()
max?: number @reviewme()
// Convert input values into a display string
mappings?: [...#ValueMapping] @reviewme()
// Map numeric values to states
thresholds?: #ThresholdsConfig @reviewme()
// Map values to a display color
color?: #FieldColor @reviewme()
// Used when reducing field values
// nullValueMode?: NullValueMode
// The behavior when clicking on a result
links?: [...] @reviewme()
// Alternative to empty string
noValue?: string @reviewme()
// custom is specified by the PanelFieldConfig field
// in panel plugin schemas.
custom?: {...} @reviewme()
} @cuetsy(kind="interface") @grafana(TSVeneer="type") @reviewme()
// Row panel
#RowPanel: {

@ -90,6 +90,17 @@ const (
HeatmapPanelTypeHeatmap HeatmapPanelType = "heatmap"
)
// Defines values for MappingType.
const (
MappingTypeRange MappingType = "range"
MappingTypeRegex MappingType = "regex"
MappingTypeSpecial MappingType = "special"
MappingTypeValue MappingType = "value"
)
// Defines values for PanelRepeatDirection.
const (
PanelRepeatDirectionH PanelRepeatDirection = "h"
@ -97,11 +108,48 @@ const (
PanelRepeatDirectionV PanelRepeatDirection = "v"
)
// Defines values for RangeMapType.
const (
RangeMapTypeRange RangeMapType = "range"
)
// Defines values for RegexMapType.
const (
RegexMapTypeRegex RegexMapType = "regex"
)
// Defines values for RowPanelType.
const (
RowPanelTypeRow RowPanelType = "row"
)
// Defines values for SpecialValueMapOptionsMatch.
const (
SpecialValueMapOptionsMatchFalse SpecialValueMapOptionsMatch = "false"
SpecialValueMapOptionsMatchTrue SpecialValueMapOptionsMatch = "true"
)
// Defines values for SpecialValueMapType.
const (
SpecialValueMapTypeSpecial SpecialValueMapType = "special"
)
// Defines values for SpecialValueMatch.
const (
SpecialValueMatchEmpty SpecialValueMatch = "empty"
SpecialValueMatchFalse SpecialValueMatch = "false"
SpecialValueMatchNan SpecialValueMatch = "nan"
SpecialValueMatchNull SpecialValueMatch = "null"
SpecialValueMatchNullNan SpecialValueMatch = "null+nan"
SpecialValueMatchTrue SpecialValueMatch = "true"
)
// Defines values for ThresholdsConfigMode.
const (
ThresholdsConfigModeAbsolute ThresholdsConfigMode = "absolute"
@ -116,6 +164,11 @@ const (
ThresholdsModePercentage ThresholdsMode = "percentage"
)
// Defines values for ValueMapType.
const (
ValueMapTypeValue ValueMapType = "value"
)
// Defines values for VariableModelType.
const (
VariableModelTypeAdhoc VariableModelType = "adhoc"
@ -336,6 +389,15 @@ type DashboardLink struct {
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
type DashboardLinkType string
// DynamicConfigValue is the Go representation of a dashboard.DynamicConfigValue.
//
// THIS TYPE IS INTENDED FOR INTERNAL USE BY THE GRAFANA BACKEND, AND IS SUBJECT TO BREAKING CHANGES.
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
type DynamicConfigValue struct {
Id string `json:"id"`
Value *interface{} `json:"value,omitempty"`
}
// TODO docs
//
// THIS TYPE IS INTENDED FOR INTERNAL USE BY THE GRAFANA BACKEND, AND IS SUBJECT TO BREAKING CHANGES.
@ -363,6 +425,126 @@ type FieldColorModeId string
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
type FieldColorSeriesByMode string
// FieldConfig is the Go representation of a dashboard.FieldConfig.
//
// THIS TYPE IS INTENDED FOR INTERNAL USE BY THE GRAFANA BACKEND, AND IS SUBJECT TO BREAKING CHANGES.
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
type FieldConfig struct {
// TODO docs
Color *FieldColor `json:"color,omitempty"`
// custom is specified by the PanelFieldConfig field
// in panel plugin schemas.
Custom *map[string]interface{} `json:"custom,omitempty"`
// Significant digits (for display)
Decimals *float32 `json:"decimals,omitempty"`
// Human readable field metadata
Description *string `json:"description,omitempty"`
// The display value for this field. This supports template variables blank is auto
DisplayName *string `json:"displayName,omitempty"`
// This can be used by data sources that return and explicit naming structure for values and labels
// When this property is configured, this value is used rather than the default naming strategy.
DisplayNameFromDS *string `json:"displayNameFromDS,omitempty"`
// True if data source field supports ad-hoc filters
Filterable *bool `json:"filterable,omitempty"`
// The behavior when clicking on a result
Links *[]interface{} `json:"links,omitempty"`
// Convert input values into a display string
Mappings *[]ValueMapping `json:"mappings,omitempty"`
Max *float32 `json:"max,omitempty"`
Min *float32 `json:"min,omitempty"`
// Alternative to empty string
NoValue *string `json:"noValue,omitempty"`
// An explict path to the field in the datasource. When the frame meta includes a path,
// This will default to `${frame.meta.path}/${field.name}
//
// When defined, this value can be used as an identifier within the datasource scope, and
// may be used to update the results
Path *string `json:"path,omitempty"`
Thresholds *ThresholdsConfig `json:"thresholds,omitempty"`
// Numeric Options
Unit *string `json:"unit,omitempty"`
// True if data source can write a value to the path. Auth/authz are supported separately
Writeable *bool `json:"writeable,omitempty"`
}
// FieldConfigSource is the Go representation of a dashboard.FieldConfigSource.
//
// THIS TYPE IS INTENDED FOR INTERNAL USE BY THE GRAFANA BACKEND, AND IS SUBJECT TO BREAKING CHANGES.
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
type FieldConfigSource struct {
Defaults struct {
// TODO docs
Color *FieldColor `json:"color,omitempty"`
// custom is specified by the PanelFieldConfig field
// in panel plugin schemas.
Custom *map[string]interface{} `json:"custom,omitempty"`
// Significant digits (for display)
Decimals *float32 `json:"decimals,omitempty"`
// Human readable field metadata
Description *string `json:"description,omitempty"`
// The display value for this field. This supports template variables blank is auto
DisplayName *string `json:"displayName,omitempty"`
// This can be used by data sources that return and explicit naming structure for values and labels
// When this property is configured, this value is used rather than the default naming strategy.
DisplayNameFromDS *string `json:"displayNameFromDS,omitempty"`
// True if data source field supports ad-hoc filters
Filterable *bool `json:"filterable,omitempty"`
// The behavior when clicking on a result
Links *[]interface{} `json:"links,omitempty"`
// Convert input values into a display string
Mappings *[]ValueMapping `json:"mappings,omitempty"`
Max *float32 `json:"max,omitempty"`
Min *float32 `json:"min,omitempty"`
// Alternative to empty string
NoValue *string `json:"noValue,omitempty"`
// An explict path to the field in the datasource. When the frame meta includes a path,
// This will default to `${frame.meta.path}/${field.name}
//
// When defined, this value can be used as an identifier within the datasource scope, and
// may be used to update the results
Path *string `json:"path,omitempty"`
Thresholds *ThresholdsConfig `json:"thresholds,omitempty"`
// Numeric Options
Unit *string `json:"unit,omitempty"`
// True if data source can write a value to the path. Auth/authz are supported separately
Writeable *bool `json:"writeable,omitempty"`
} `json:"defaults"`
Overrides []struct {
Matcher struct {
Id string `json:"id"`
Options *interface{} `json:"options,omitempty"`
} `json:"matcher"`
Properties []struct {
Id string `json:"id"`
Value *interface{} `json:"value,omitempty"`
} `json:"properties"`
} `json:"overrides"`
}
// GraphPanel is the Go representation of a dashboard.GraphPanel.
//
// THIS TYPE IS INTENDED FOR INTERNAL USE BY THE GRAFANA BACKEND, AND IS SUBJECT TO BREAKING CHANGES.
@ -413,6 +595,21 @@ type HeatmapPanel struct {
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
type HeatmapPanelType string
// TODO docs
//
// THIS TYPE IS INTENDED FOR INTERNAL USE BY THE GRAFANA BACKEND, AND IS SUBJECT TO BREAKING CHANGES.
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
type MappingType string
// MatcherConfig is the Go representation of a dashboard.MatcherConfig.
//
// THIS TYPE IS INTENDED FOR INTERNAL USE BY THE GRAFANA BACKEND, AND IS SUBJECT TO BREAKING CHANGES.
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
type MatcherConfig struct {
Id string `json:"id"`
Options *interface{} `json:"options,omitempty"`
}
// Model panels. Panels are canonically defined inline
// because they share a version timeline with the dashboard
// schema; they do not evolve independently.
@ -453,21 +650,13 @@ type Panel struct {
// True if data source field supports ad-hoc filters
Filterable *bool `json:"filterable,omitempty"`
// // The behavior when clicking on a result
// The behavior when clicking on a result
Links *[]interface{} `json:"links,omitempty"`
// Convert input values into a display string
//
// TODO this one corresponds to a complex type with
// generics on the typescript side. Ouch. Will
// either need special care, or we'll just need to
// accept a very loosely specified schema. It's very
// unlikely we'll be able to translate cue to
// typescript generics in the general case, though
// this particular one *may* be able to work.
Mappings *[]map[string]interface{} `json:"mappings,omitempty"`
Max *float32 `json:"max,omitempty"`
Min *float32 `json:"min,omitempty"`
Mappings *[]ValueMapping `json:"mappings,omitempty"`
Max *float32 `json:"max,omitempty"`
Min *float32 `json:"min,omitempty"`
// Alternative to empty string
NoValue *string `json:"noValue,omitempty"`
@ -568,6 +757,54 @@ type Panel struct {
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
type PanelRepeatDirection string
// TODO docs
//
// THIS TYPE IS INTENDED FOR INTERNAL USE BY THE GRAFANA BACKEND, AND IS SUBJECT TO BREAKING CHANGES.
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
type RangeMap struct {
Options struct {
// to and from are `number | null` in current ts, really not sure what to do
From int32 `json:"from"`
Result struct {
Color *string `json:"color,omitempty"`
Icon *string `json:"icon,omitempty"`
Index *int32 `json:"index,omitempty"`
Text *string `json:"text,omitempty"`
} `json:"result"`
To int32 `json:"to"`
} `json:"options"`
Type RangeMapType `json:"type"`
}
// RangeMapType is the Go representation of a RangeMap.Type.
//
// THIS TYPE IS INTENDED FOR INTERNAL USE BY THE GRAFANA BACKEND, AND IS SUBJECT TO BREAKING CHANGES.
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
type RangeMapType string
// TODO docs
//
// THIS TYPE IS INTENDED FOR INTERNAL USE BY THE GRAFANA BACKEND, AND IS SUBJECT TO BREAKING CHANGES.
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
type RegexMap struct {
Options struct {
Pattern string `json:"pattern"`
Result struct {
Color *string `json:"color,omitempty"`
Icon *string `json:"icon,omitempty"`
Index *int32 `json:"index,omitempty"`
Text *string `json:"text,omitempty"`
} `json:"result"`
} `json:"options"`
Type RegexMapType `json:"type"`
}
// RegexMapType is the Go representation of a RegexMap.Type.
//
// THIS TYPE IS INTENDED FOR INTERNAL USE BY THE GRAFANA BACKEND, AND IS SUBJECT TO BREAKING CHANGES.
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
type RegexMapType string
// Row panel
//
// THIS TYPE IS INTENDED FOR INTERNAL USE BY THE GRAFANA BACKEND, AND IS SUBJECT TO BREAKING CHANGES.
@ -596,6 +833,42 @@ type RowPanel struct {
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
type RowPanelType string
// TODO docs
//
// THIS TYPE IS INTENDED FOR INTERNAL USE BY THE GRAFANA BACKEND, AND IS SUBJECT TO BREAKING CHANGES.
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
type SpecialValueMap struct {
Options struct {
Match SpecialValueMapOptionsMatch `json:"match"`
Pattern string `json:"pattern"`
Result struct {
Color *string `json:"color,omitempty"`
Icon *string `json:"icon,omitempty"`
Index *int32 `json:"index,omitempty"`
Text *string `json:"text,omitempty"`
} `json:"result"`
} `json:"options"`
Type SpecialValueMapType `json:"type"`
}
// SpecialValueMapOptionsMatch is the Go representation of a SpecialValueMap.Options.Match.
//
// THIS TYPE IS INTENDED FOR INTERNAL USE BY THE GRAFANA BACKEND, AND IS SUBJECT TO BREAKING CHANGES.
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
type SpecialValueMapOptionsMatch string
// SpecialValueMapType is the Go representation of a SpecialValueMap.Type.
//
// THIS TYPE IS INTENDED FOR INTERNAL USE BY THE GRAFANA BACKEND, AND IS SUBJECT TO BREAKING CHANGES.
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
type SpecialValueMapType string
// TODO docs
//
// THIS TYPE IS INTENDED FOR INTERNAL USE BY THE GRAFANA BACKEND, AND IS SUBJECT TO BREAKING CHANGES.
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
type SpecialValueMatch string
// Schema for panel targets is specified by datasource
// plugins. We use a placeholder definition, which the Go
// schema loader either left open/as-is with the Base
@ -671,6 +944,38 @@ type Transformation struct {
Options map[string]interface{} `json:"options"`
}
// TODO docs
//
// THIS TYPE IS INTENDED FOR INTERNAL USE BY THE GRAFANA BACKEND, AND IS SUBJECT TO BREAKING CHANGES.
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
type ValueMap struct {
Options map[string]interface{} `json:"options"`
Type ValueMapType `json:"type"`
}
// ValueMapType is the Go representation of a ValueMap.Type.
//
// THIS TYPE IS INTENDED FOR INTERNAL USE BY THE GRAFANA BACKEND, AND IS SUBJECT TO BREAKING CHANGES.
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
type ValueMapType string
// TODO docs
//
// THIS TYPE IS INTENDED FOR INTERNAL USE BY THE GRAFANA BACKEND, AND IS SUBJECT TO BREAKING CHANGES.
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
type ValueMapping interface{}
// TODO docs
//
// THIS TYPE IS INTENDED FOR INTERNAL USE BY THE GRAFANA BACKEND, AND IS SUBJECT TO BREAKING CHANGES.
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
type ValueMappingResult struct {
Color *string `json:"color,omitempty"`
Icon *string `json:"icon,omitempty"`
Index *int32 `json:"index,omitempty"`
Text *string `json:"text,omitempty"`
}
// FROM: packages/grafana-data/src/types/templateVars.ts
// TODO docs
// TODO what about what's in public/app/features/types.ts?

@ -7,29 +7,29 @@ package main
import (
"fmt"
"os"
"path"
"path/filepath"
"sort"
"strings"
"cuelang.org/go/cue"
"cuelang.org/go/cue/cuecontext"
"cuelang.org/go/cue/errors"
"cuelang.org/go/cue/load"
"github.com/grafana/cuetsy"
"github.com/grafana/thema"
"github.com/grafana/cuetsy/ts"
"github.com/grafana/cuetsy/ts/ast"
gcgen "github.com/grafana/grafana/pkg/codegen"
"github.com/grafana/thema"
)
var lib = thema.NewLibrary(cuecontext.New())
const sep = string(filepath.Separator)
// Generate Go and Typescript implementations for all coremodels, and populate the
// coremodel static registry.
func main() {
if len(os.Args) > 1 {
fmt.Fprintf(os.Stderr, "coremodel code generator does not currently accept any arguments\n, got %q", os.Args)
os.Exit(1)
}
var tsroot, cmroot, groot string
func init() {
cwd, err := os.Getwd()
if err != nil {
fmt.Fprintf(os.Stderr, "could not get working directory: %s", err)
@ -37,10 +37,19 @@ func main() {
}
// TODO this binds us to only having coremodels in a single directory. If we need more, compgen is the way
groot := filepath.Dir(filepath.Dir(filepath.Dir(cwd))) // the working dir is <grafana_dir>/pkg/framework/coremodel. Going up 3 dirs we get the grafana root
groot = filepath.Dir(filepath.Dir(filepath.Dir(cwd))) // the working dir is <grafana_dir>/pkg/framework/coremodel. Going up 3 dirs we get the grafana root
cmroot := filepath.Join(groot, "pkg", "coremodel")
tsroot := filepath.Join(groot, "packages", "grafana-schema", "src", "schema")
cmroot = filepath.Join(groot, "pkg", "coremodel")
tsroot = filepath.Join(groot, "packages", "grafana-schema", "src")
}
// Generate Go and Typescript implementations for all coremodels, and populate the
// coremodel static registry.
func main() {
if len(os.Args) > 1 {
fmt.Fprintf(os.Stderr, "coremodel code generator does not currently accept any arguments\n, got %q", os.Args)
os.Exit(1)
}
items, err := os.ReadDir(cmroot)
if err != nil {
@ -48,7 +57,7 @@ func main() {
os.Exit(1)
}
var lins []*gcgen.ExtractedLineage
var lins []*gcgen.CoremodelDeclaration
for _, item := range items {
if item.IsDir() {
lin, err := gcgen.ExtractLineage(filepath.Join(cmroot, item.Name(), "coremodel.cue"), lib)
@ -64,6 +73,9 @@ func main() {
return lins[i].Lineage.Name() < lins[j].Lineage.Name()
})
// The typescript veneer index.gen.ts file, which we'll build up over time
// from the exported types.
tsvidx := new(ast.File)
wd := gcgen.NewWriteDiffer()
for _, ls := range lins {
gofiles, err := ls.GenerateGoCoremodel(filepath.Join(cmroot, ls.Lineage.Name()))
@ -75,15 +87,26 @@ func main() {
// Only generate TS for API types
if ls.IsAPIType {
tsfiles, err := ls.GenerateTypescriptCoremodel(filepath.Join(tsroot, ls.Lineage.Name()))
tsf, err := ls.GenerateTypescriptCoremodel()
if err != nil {
fmt.Fprintf(os.Stderr, "error generating TypeScript for %s: %s\n", ls.Lineage.Name(), err)
os.Exit(1)
}
tsf.Doc = mkTSHeader(ls)
wd[filepath.FromSlash(filepath.Join(tsroot, rawTSGenPath(ls)))] = []byte(tsf.String())
decls, err := extractTSIndexVeneerElements(ls, tsf)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to generate TypeScript for %s: %s\n", ls.Lineage.Name(), err)
fmt.Fprintf(os.Stderr, "error generating TypeScript veneer for %s: %s\n", ls.Lineage.Name(), errors.Details(err, nil))
os.Exit(1)
}
wd.Merge(tsfiles)
tsvidx.Nodes = append(tsvidx.Nodes, decls...)
}
}
tsvidx.Doc = mkTSHeader(nil)
wd[filepath.Join(tsroot, "index.gen.ts")] = []byte(tsvidx.String())
regfiles, err := gcgen.GenerateCoremodelRegistry(filepath.Join(groot, "pkg", "framework", "coremodel", "registry", "registry_gen.go"), lins)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to generate coremodel registry: %s\n", err)
@ -113,6 +136,26 @@ func main() {
}
}
}
// generates the path relative to packages/grafana-schema/src at which the raw
// type definitions should be exported for the latest schema of this type
func rawTSGenPath(cm *gcgen.CoremodelDeclaration) string {
return fmt.Sprintf("raw/%s/%s/%s.gen.ts", cm.Lineage.Name(), cm.PathVersion(), cm.Lineage.Name())
}
func mkTSHeader(cm *gcgen.CoremodelDeclaration) *ast.Comment {
v := gcgen.HeaderVars{
GeneratorPath: "pkg/framework/coremodel/gen.go",
}
if cm != nil {
v.LineagePath = cm.RelativePath
}
v.GeneratorPath = "pkg/framework/coremodel/gen.go"
return &ast.Comment{
Text: strings.TrimSpace(gcgen.GenGrafanaHeader(v)),
}
}
func genSharedSchemas(groot string) (gcgen.WriteDiffer, error) {
abspath := filepath.Join(groot, "packages", "grafana-schema", "src", "schema")
cfg := &load.Config{
@ -132,7 +175,9 @@ func genSharedSchemas(groot string) (gcgen.WriteDiffer, error) {
return nil, fmt.Errorf("errors while building CUE in %s: %s", abspath, v.Err())
}
b, err := cuetsy.Generate(v, cuetsy.Config{})
b, err := cuetsy.Generate(v, cuetsy.Config{
Export: true,
})
if err != nil {
return nil, fmt.Errorf("failed to generate TS: %w", err)
}
@ -146,3 +191,259 @@ func genSharedSchemas(groot string) (gcgen.WriteDiffer, error) {
`), b...)
return wd, nil
}
// TODO make this more generic and reusable
func extractTSIndexVeneerElements(cm *gcgen.CoremodelDeclaration, tf *ast.File) ([]ast.Decl, error) {
lin := cm.Lineage
sch := thema.SchemaP(lin, thema.LatestVersion(lin))
// Check the root, then walk the tree
rootv := sch.UnwrapCUE()
var raw, custom, rawD, customD ast.Idents
var terr errors.Error
visit := func(p cue.Path, wv cue.Value) bool {
var name string
sels := p.Selectors()
switch len(sels) {
case 0:
name = strings.Title(cm.Lineage.Name())
fallthrough
case 1:
// Only deal with subpaths that are definitions, for now
// TODO incorporate smarts about grouped lineages here
if name == "" {
if !sels[0].IsDefinition() {
return false
}
// It might seem to make sense that we'd strip out the leading # here for
// definitions. However, cuetsy's tsast actually has the # still present in its
// Ident types, stripping it out on the fly when stringifying.
name = sels[0].String()
}
// Search the generated TS AST for the type and default decl nodes
pair := findDeclNode(name, tf)
if pair.T == nil {
// No generated type for this item, skip it
return false
}
cust, perr := getCustomVeneerAttr(wv)
if perr != nil {
terr = errors.Append(terr, errors.Promote(perr, fmt.Sprintf("%s: ", p.String())))
}
var has bool
for _, tgt := range cust {
has = has || tgt.target == "type"
}
if has {
custom = append(custom, *pair.T)
if pair.D != nil {
customD = append(customD, *pair.D)
}
} else {
raw = append(raw, *pair.T)
if pair.D != nil {
rawD = append(rawD, *pair.D)
}
}
}
return true
}
walk(rootv, visit, nil)
if len(errors.Errors(terr)) != 0 {
return nil, terr
}
ret := make([]ast.Decl, 0)
if len(raw) > 0 {
ret = append(ret, ast.ExportSet{
CommentList: []ast.Comment{ts.CommentFromString(fmt.Sprintf("Raw generated types from %s entity type.", cm.Lineage.Name()), 80, false)},
TypeOnly: true,
Exports: raw,
From: ast.Str{Value: fmt.Sprintf("./raw/%s/%s/%s.gen", cm.Lineage.Name(), cm.PathVersion(), cm.Lineage.Name())},
})
}
if len(rawD) > 0 {
ret = append(ret, ast.ExportSet{
CommentList: []ast.Comment{ts.CommentFromString(fmt.Sprintf("Raw generated default consts from %s entity type.", cm.Lineage.Name()), 80, false)},
TypeOnly: false,
Exports: rawD,
From: ast.Str{Value: fmt.Sprintf("./raw/%s/%s/%s.gen", cm.Lineage.Name(), cm.PathVersion(), cm.Lineage.Name())},
})
}
vtfile := fmt.Sprintf("./veneer/%s.types", cm.Lineage.Name())
customstr := fmt.Sprintf(`// The following exported declarations correspond to types in the %s@%s schema with
// attribute @grafana(TSVeneer="type"). (lineage declared in file: %s)
//
// The handwritten file for these type and default veneers is expected to be at
// %s.ts.
// This re-export declaration enforces that the handwritten veneer file exists,
// and exports all the symbols in the list.
//
// TODO generate code such that tsc enforces type compatibility between raw and veneer decls`,
cm.Lineage.Name(), thema.LatestVersion(cm.Lineage), cm.RelativePath, filepath.Clean(path.Join("packages", "grafana-schema", "src", vtfile)))
customComments := []ast.Comment{{Text: customstr}}
if len(custom) > 0 {
ret = append(ret, ast.ExportSet{
CommentList: customComments,
TypeOnly: true,
Exports: custom,
From: ast.Str{Value: vtfile},
})
}
if len(customD) > 0 {
ret = append(ret, ast.ExportSet{
CommentList: customComments,
TypeOnly: false,
Exports: customD,
From: ast.Str{Value: vtfile},
})
}
// TODO emit a decl in the index.gen.ts that ensures any custom veneer types are "compatible" with current version raw types
return ret, nil
}
type declPair struct {
T, D *ast.Ident
}
func findDeclNode(name string, tf *ast.File) declPair {
var p declPair
for _, decl := range tf.Nodes {
// Peer through export keywords
if ex, is := decl.(ast.ExportKeyword); is {
decl = ex.Decl
}
switch x := decl.(type) {
case ast.TypeDecl:
if x.Name.Name == name {
p.T = &x.Name
}
case ast.VarDecl:
if x.Names.Idents[0].Name == "default"+name {
p.D = &x.Names.Idents[0]
}
}
}
return p
}
type tsVeneerAttr struct {
target string
}
func walk(v cue.Value, before func(cue.Path, cue.Value) bool, after func(cue.Path, cue.Value)) {
innerWalk(cue.MakePath(), v, before, after)
}
func innerWalk(p cue.Path, v cue.Value, before func(cue.Path, cue.Value) bool, after func(cue.Path, cue.Value)) {
// switch v.IncompleteKind() {
switch v.Kind() {
default:
if before != nil && !before(p, v) {
return
}
case cue.StructKind:
if before != nil && !before(p, v) {
return
}
iter, err := v.Fields(cue.All())
if err != nil {
panic(err)
}
for iter.Next() {
innerWalk(appendPath(p, iter.Selector()), iter.Value(), before, after)
}
if lv := v.LookupPath(cue.MakePath(cue.AnyString)); lv.Exists() {
innerWalk(appendPath(p, cue.AnyString), lv, before, after)
}
case cue.ListKind:
if before != nil && !before(p, v) {
return
}
list, err := v.List()
if err != nil {
panic(err)
}
for i := 0; list.Next(); i++ {
innerWalk(appendPath(p, cue.Index(i)), list.Value(), before, after)
}
if lv := v.LookupPath(cue.MakePath(cue.AnyIndex)); lv.Exists() {
innerWalk(appendPath(p, cue.AnyString), lv, before, after)
}
}
if after != nil {
after(p, v)
}
}
func appendPath(p cue.Path, sel cue.Selector) cue.Path {
return cue.MakePath(append(p.Selectors(), sel)...)
}
var allowedTSVeneers = map[string]bool{
"type": true,
}
func allowedTSVeneersString() string {
var list []string
for tgt := range allowedTSVeneers {
list = append(list, tgt)
}
sort.Strings(list)
return strings.Join(list, "|")
}
func getCustomVeneerAttr(v cue.Value) ([]tsVeneerAttr, error) {
var attrs []tsVeneerAttr
for _, a := range v.Attributes(cue.ValueAttr) {
if a.Name() != "grafana" {
continue
}
for i := 0; i < a.NumArgs(); i++ {
key, av := a.Arg(i)
if key != "TSVeneer" {
return nil, valError(v, "attribute 'grafana' only allows the arg 'TSVeneer'")
}
aterr := valError(v, "@grafana(TSVeneer=\"x\") requires one or more of the following separated veneer types for x: %s", allowedTSVeneersString())
var some bool
for _, tgt := range strings.Split(av, "|") {
some = true
if !allowedTSVeneers[tgt] {
return nil, aterr
}
attrs = append(attrs, tsVeneerAttr{
target: tgt,
})
}
if !some {
return nil, aterr
}
}
}
sort.Slice(attrs, func(i, j int) bool {
return attrs[i].target < attrs[j].target
})
return attrs, nil
}
func valError(v cue.Value, format string, args ...interface{}) error {
s := v.Source()
if s == nil {
return fmt.Errorf(format, args...)
}
return errors.Newf(s.Pos(), format, args...)
}

@ -89,12 +89,15 @@ func main() {
var wdm codegen.WriteDiffer
for _, ptp := range ptrees {
wdm, err = ptp.Tree.GenerateTS(ptp.Path)
tfast, err := ptp.Tree.GenerateTypeScriptAST()
if err != nil {
fmt.Fprintf(os.Stderr, "generating typescript failed for %s: %s\n", ptp.Path, err)
os.Exit(1)
}
wd.Merge(wdm)
// nil return if there was nothing to generate (no slot implementations)
if tfast != nil {
wd[filepath.Join(ptp.Path, "models.gen.ts")] = []byte(tfast.String())
}
relp, _ := filepath.Rel(groot, ptp.Path)
wdm, err = ptp.Tree.GenerateGo(ptp.Path, codegen.GoGenConfig{

@ -10,7 +10,6 @@
export const PanelModelVersion = Object.freeze([0, 0]);
export interface PanelOptions {
limit: number;
navigateAfter: string;
@ -21,7 +20,7 @@ export interface PanelOptions {
showTags: boolean;
showTime: boolean;
showUser: boolean;
tags: string[];
tags: Array<string>;
}
export const defaultPanelOptions: Partial<PanelOptions> = {
@ -36,4 +35,3 @@ export const defaultPanelOptions: Partial<PanelOptions> = {
showUser: true,
tags: [],
};

@ -7,22 +7,56 @@
// Run `make gen-cue` from repository root to regenerate.
import * as ui from '@grafana/schema';
export const PanelModelVersion = Object.freeze([0, 0]);
export interface PanelOptions extends ui.OptionsWithLegend, ui.OptionsWithTooltip, ui.OptionsWithTextFormatting {
/**
* TODO docs
*/
barRadius?: number;
/**
* Controls the width of bars. 1 = Max width, 0 = Min width.
*/
barWidth: number;
/**
* TODO docs
*/
colorByField?: string;
/**
* Controls the width of groups. 1 = max with, 0 = min width.
*/
groupWidth: number;
/**
* TODO docs
*/
orientation: ui.VizOrientation;
/**
* This controls whether values are shown on top or to the left of bars.
*/
showValue: ui.VisibilityMode;
/**
* TODO docs
*/
stacking: ui.StackingMode;
/**
* TODO docs
*/
xField?: string;
/**
* TODO docs
*/
xTickLabelMaxLength: number;
/**
* TODO docs
*/
xTickLabelRotation: number;
/**
* TODO docs
* negative values indicate backwards skipping behavior
*/
xTickLabelSpacing?: number;
}
@ -38,8 +72,18 @@ export const defaultPanelOptions: Partial<PanelOptions> = {
};
export interface PanelFieldConfig extends ui.AxisConfig, ui.HideableFieldConfig {
/**
* Controls the fill opacity of the bars.
*/
fillOpacity?: number;
/**
* Set the mode of the gradient fill. Fill gradient is based on the line color. To change the color, use the standard color scheme field option.
* Gradient appearance is influenced by the Fill opacity setting.
*/
gradientMode?: ui.GraphGradientMode;
/**
* Controls line width of the bars.
*/
lineWidth?: number;
}
@ -48,4 +92,3 @@ export const defaultPanelFieldConfig: Partial<PanelFieldConfig> = {
gradientMode: ui.GraphGradientMode.None,
lineWidth: 1,
};

@ -7,11 +7,11 @@
// Run `make gen-cue` from repository root to regenerate.
import * as ui from '@grafana/schema';
export const PanelModelVersion = Object.freeze([0, 0]);
export interface PanelOptions extends ui.SingleStatBaseOptions {
displayMode: ui.BarGaugeDisplayMode;
minVizHeight: number;
@ -25,4 +25,3 @@ export const defaultPanelOptions: Partial<PanelOptions> = {
minVizWidth: 0,
showUnfilled: true,
};

@ -10,7 +10,6 @@
export const PanelModelVersion = Object.freeze([0, 0]);
export enum PanelLayout {
List = 'list',
Previews = 'previews',
@ -25,7 +24,7 @@ export interface PanelOptions {
showRecentlyViewed: boolean;
showSearch: boolean;
showStarred: boolean;
tags: string[];
tags: Array<string>;
}
export const defaultPanelOptions: Partial<PanelOptions> = {
@ -38,4 +37,3 @@ export const defaultPanelOptions: Partial<PanelOptions> = {
showStarred: true,
tags: [],
};

@ -7,11 +7,11 @@
// Run `make gen-cue` from repository root to regenerate.
import * as ui from '@grafana/schema';
export const PanelModelVersion = Object.freeze([0, 0]);
export interface PanelOptions extends ui.SingleStatBaseOptions {
showThresholdLabels: boolean;
showThresholdMarkers: boolean;
@ -21,4 +21,3 @@ export const defaultPanelOptions: Partial<PanelOptions> = {
showThresholdLabels: false,
showThresholdMarkers: true,
};

@ -7,14 +7,23 @@
// Run `make gen-cue` from repository root to regenerate.
import * as ui from '@grafana/schema';
export const PanelModelVersion = Object.freeze([0, 0]);
export interface PanelOptions extends ui.OptionsWithLegend, ui.OptionsWithTooltip {
/**
* Offset buckets by this amount
*/
bucketOffset?: number;
/**
* Size of each bucket
*/
bucketSize?: number;
/**
* Combines multiple series into a single histogram
*/
combine?: boolean;
}
@ -23,8 +32,18 @@ export const defaultPanelOptions: Partial<PanelOptions> = {
};
export interface PanelFieldConfig extends ui.AxisConfig, ui.HideableFieldConfig {
/**
* Controls the fill opacity of the bars.
*/
fillOpacity?: number;
/**
* Set the mode of the gradient fill. Fill gradient is based on the line color. To change the color, use the standard color scheme field option.
* Gradient appearance is influenced by the Fill opacity setting.
*/
gradientMode?: ui.GraphGradientMode;
/**
* Controls line width of the bars.
*/
lineWidth?: number;
}
@ -33,4 +52,3 @@ export const defaultPanelFieldConfig: Partial<PanelFieldConfig> = {
gradientMode: ui.GraphGradientMode.None,
lineWidth: 1,
};

@ -10,8 +10,10 @@
export const PanelModelVersion = Object.freeze([0, 0]);
export interface PanelOptions {
/**
* empty/missing will default to grafana blog
*/
feedUrl?: string;
showImage?: boolean;
}
@ -19,4 +21,3 @@ export interface PanelOptions {
export const defaultPanelOptions: Partial<PanelOptions> = {
showImage: true,
};

@ -7,29 +7,43 @@
// Run `make gen-cue` from repository root to regenerate.
import * as ui from '@grafana/schema';
export const PanelModelVersion = Object.freeze([0, 0]);
/**
* Select the pie chart display style.
*/
export enum PieChartType {
Donut = 'donut',
Pie = 'pie',
}
/**
* Select labels to display on the pie chart.
* - Name - The series or field name.
* - Percent - The percentage of the whole.
* - Value - The raw numerical value.
*/
export enum PieChartLabels {
Name = 'name',
Percent = 'percent',
Value = 'value',
}
/**
* Select values to display in the legend.
* - Percent: The percentage of the whole.
* - Value: The raw numerical value.
*/
export enum PieChartLegendValues {
Percent = 'percent',
Value = 'value',
}
export interface PieChartLegendOptions extends ui.VizLegendOptions {
values: PieChartLegendValues[];
values: Array<PieChartLegendValues>;
}
export const defaultPieChartLegendOptions: Partial<PieChartLegendOptions> = {
@ -37,7 +51,7 @@ export const defaultPieChartLegendOptions: Partial<PieChartLegendOptions> = {
};
export interface PanelOptions extends ui.OptionsWithTooltip, ui.SingleStatBaseOptions {
displayLabels: PieChartLabels[];
displayLabels: Array<PieChartLabels>;
legend: PieChartLegendOptions;
pieType: PieChartType;
}
@ -47,4 +61,3 @@ export const defaultPanelOptions: Partial<PanelOptions> = {
};
export interface PanelFieldConfig extends ui.HideableFieldConfig {}

@ -7,11 +7,11 @@
// Run `make gen-cue` from repository root to regenerate.
import * as ui from '@grafana/schema';
export const PanelModelVersion = Object.freeze([0, 0]);
export interface PanelOptions extends ui.SingleStatBaseOptions {
colorMode: ui.BigValueColorMode;
graphMode: ui.BigValueGraphMode;
@ -25,4 +25,3 @@ export const defaultPanelOptions: Partial<PanelOptions> = {
justifyMode: ui.BigValueJustifyMode.Auto,
textMode: ui.BigValueTextMode.Auto,
};

@ -10,7 +10,6 @@
export const PanelModelVersion = Object.freeze([0, 0]);
export enum TextMode {
Code = 'code',
HTML = 'html',
@ -32,6 +31,9 @@ export enum CodeLanguage {
export const defaultCodeLanguage: CodeLanguage = CodeLanguage.Plaintext;
export interface CodeOptions {
/**
* The language passed to monaco code editor
*/
language: CodeLanguage;
showLineNumbers: boolean;
showMiniMap: boolean;
@ -55,4 +57,3 @@ export const defaultPanelOptions: Partial<PanelOptions> = {
For markdown syntax help: [commonmark.org/help](https://commonmark.org/help/)`,
mode: TextMode.Markdown,
};

Loading…
Cancel
Save