diff --git a/embed.go b/embed.go index 3f014983c5e..12878a11060 100644 --- a/embed.go +++ b/embed.go @@ -6,5 +6,5 @@ import ( // CueSchemaFS embeds all schema-related CUE files in the Grafana project. // -//go:embed cue.mod/module.cue kinds/*.cue kinds/*/*.cue packages/grafana-schema/src/common/*.cue public/app/plugins/*/*/*.cue public/app/plugins/*/*/plugin.json pkg/kindsys/*.cue pkg/plugins/plugindef/*.cue +//go:embed cue.mod/module.cue kinds/*.cue kinds/*/*.cue packages/grafana-schema/src/common/*.cue public/app/plugins/*/*/*.cue public/app/plugins/*/*/plugin.json pkg/kindsys/*.cue pkg/plugins/*/*.cue var CueSchemaFS embed.FS diff --git a/kinds/dashboard/dashboard_kind.cue b/kinds/dashboard/dashboard_kind.cue index cd1f9286522..62a905c3dea 100644 --- a/kinds/dashboard/dashboard_kind.cue +++ b/kinds/dashboard/dashboard_kind.cue @@ -151,7 +151,7 @@ lineage: seqs: [ // Specific datasource instance uid?: string @grafanamaturity(NeedsExpertReview) - } @cuetsy(kind="interface") @grafanamaturity(NeedsExpertReview) + } @cuetsy(kind="interface") @grafana(TSVeneer="type") @grafanamaturity(NeedsExpertReview) // FROM public/app/features/dashboard/state/DashboardModels.ts - ish // TODO docs diff --git a/kinds/gen.go b/kinds/gen.go index 0e5b2bf1344..638647b7492 100644 --- a/kinds/gen.go +++ b/kinds/gen.go @@ -11,12 +11,13 @@ import ( "io/fs" "os" "path/filepath" + "regexp" "sort" + "strings" "cuelang.org/go/cue/errors" "github.com/grafana/codejen" "github.com/grafana/cuetsy" - "github.com/grafana/grafana/pkg/codegen" "github.com/grafana/grafana/pkg/cuectx" "github.com/grafana/grafana/pkg/kindsys" @@ -83,11 +84,10 @@ func main() { if err != nil { die(fmt.Errorf("core kinddirs codegen failed: %w", err)) } - sharedf, err := dummyCommonJenny{}.Generate(nil) - if err != nil { - die(fmt.Errorf("common schemas failed")) - } - if err = jfs.Add(elsedie(header(*sharedf))("couldn't inject header")); err != nil { + + commfsys := elsedie(genCommon(filepath.Join(groot, "pkg", "kindsys")))("common schemas failed") + commfsys = elsedie(commfsys.Map(header))("failed gen header on common fsys") + if err = jfs.Merge(commfsys); err != nil { die(err) } @@ -116,25 +116,48 @@ func nameFor(m kindsys.SomeKindProperties) string { type dummyCommonJenny struct{} -func (j dummyCommonJenny) JennyName() string { - return "CommonSchemaJenny" -} +func genCommon(kp string) (*codejen.FS, error) { + fsys := codejen.NewFS() -func (j dummyCommonJenny) Generate(dummy any) (*codejen.File, error) { + // kp := filepath.Join("pkg", "kindsys") path := filepath.Join("packages", "grafana-schema", "src", "common") + // Grab all the common_* files from kindsys and load them in + dfsys := os.DirFS(kp) + matches := elsedie(fs.Glob(dfsys, "common_*.cue"))("could not glob kindsys cue files") + for _, fname := range matches { + fpath := filepath.Join(path, strings.TrimPrefix(fname, "common_")) + fpath = fpath[:len(fpath)-4] + "_gen.cue" + data := elsedie(fs.ReadFile(dfsys, fname))("error reading " + fname) + _ = fsys.Add(*codejen.NewFile(fpath, data, dummyCommonJenny{})) + } + fsys = elsedie(fsys.Map(packageMapper))("failed remapping fs") + v, err := cuectx.BuildGrafanaInstance(nil, path, "", nil) if err != nil { return nil, err } - b, err := cuetsy.Generate(v, cuetsy.Config{ + b := elsedie(cuetsy.Generate(v, cuetsy.Config{ Export: true, - }) - if err != nil { - return nil, fmt.Errorf("failed to generate TS: %w", err) - } + }))("failed to generate common schema TS") - return codejen.NewFile(filepath.Join(path, "common.gen.ts"), b, dummyCommonJenny{}), nil + _ = fsys.Add(*codejen.NewFile(filepath.Join(path, "common.gen.ts"), b, dummyCommonJenny{})) + return fsys, nil +} + +func (j dummyCommonJenny) JennyName() string { + return "CommonSchemaJenny" +} + +func (j dummyCommonJenny) Generate(dummy any) ([]codejen.File, error) { + return nil, nil +} + +var pkgReplace = regexp.MustCompile("^package kindsys") + +func packageMapper(f codejen.File) (codejen.File, error) { + f.Data = pkgReplace.ReplaceAllLiteral(f.Data, []byte("package common")) + return f, nil } func elsedie[T any](t T, err error) func(msg string) T { diff --git a/packages/grafana-data/src/types/query.ts b/packages/grafana-data/src/types/query.ts index 2e1802b8c28..18476dd54d1 100644 --- a/packages/grafana-data/src/types/query.ts +++ b/packages/grafana-data/src/types/query.ts @@ -1,3 +1,15 @@ +import { DataQuery as SchemaDataQuery, DataSourceRef as SchemaDataSourceRef } from '@grafana/schema'; + +/** + * @deprecated use the type from @grafana/schema + */ +export interface DataQuery extends SchemaDataQuery {} + +/** + * @deprecated use the type from @grafana/schema + */ +export interface DataSourceRef extends SchemaDataSourceRef {} + /** * Attached to query results (not persisted) * @@ -7,57 +19,11 @@ export enum DataTopic { Annotations = 'annotations', } -/** - * @public - */ -export interface DataSourceRef { - /** The plugin type-id */ - type?: string; - - /** Specific datasource instance */ - uid?: string; -} - -/** - * These are the common properties available to all queries in all datasources - * Specific implementations will *extend* this interface adding the required properties - * for the given context - * - * @public - */ -export interface DataQuery { - /** - * A - Z - */ - refId: string; - - /** - * true if query is disabled (ie should not be returned to the dashboard) - */ - hide?: boolean; - - /** - * Unique, guid like, string used in explore mode - */ - key?: string; - - /** - * Specify the query flavor - */ - queryType?: string; - - /** - * For mixed data sources the selected datasource is on the query level. - * For non mixed scenarios this is undefined. - */ - datasource?: DataSourceRef | null; -} - /** * Abstract representation of any label-based query * @internal */ -export interface AbstractQuery extends DataQuery { +export interface AbstractQuery extends SchemaDataQuery { labelMatchers: AbstractLabelMatcher[]; } @@ -83,21 +49,21 @@ export type AbstractLabelMatcher = { /** * @internal */ -export interface DataSourceWithQueryImportSupport { +export interface DataSourceWithQueryImportSupport { importFromAbstractQueries(labelBasedQuery: AbstractQuery[]): Promise; } /** * @internal */ -export interface DataSourceWithQueryExportSupport { +export interface DataSourceWithQueryExportSupport { exportToAbstractQueries(query: TQuery[]): Promise; } /** * @internal */ -export const hasQueryImportSupport = ( +export const hasQueryImportSupport = ( datasource: unknown ): datasource is DataSourceWithQueryImportSupport => { return (datasource as DataSourceWithQueryImportSupport).importFromAbstractQueries !== undefined; @@ -106,7 +72,7 @@ export const hasQueryImportSupport = ( /** * @internal */ -export const hasQueryExportSupport = ( +export const hasQueryExportSupport = ( datasource: unknown ): datasource is DataSourceWithQueryExportSupport => { return (datasource as DataSourceWithQueryExportSupport).exportToAbstractQueries !== undefined; diff --git a/packages/grafana-schema/src/common/common.gen.ts b/packages/grafana-schema/src/common/common.gen.ts index b419bbae0f2..ab3f4bae139 100644 --- a/packages/grafana-schema/src/common/common.gen.ts +++ b/packages/grafana-schema/src/common/common.gen.ts @@ -8,6 +8,38 @@ // Run 'make gen-cue' from repository root to regenerate. +/** + * These are the common properties available to all queries in all datasources. + * Specific implementations will *extend* this interface, adding the required + * properties for the given context. + */ +export interface DataQuery { + /** + * For mixed data sources the selected datasource is on the query level. + * For non mixed scenarios this is undefined. + * TODO find a better way to do this ^ that's friendly to schema + * TODO this shouldn't be unknown but DataSourceRef | null + */ + datasource?: unknown; + /** + * true if query is disabled (ie should not be returned to the dashboard) + */ + hide?: boolean; + /** + * Unique, guid like, string used in explore mode + */ + key?: string; + /** + * Specify the query flavor + * TODO make this required and give it a default + */ + queryType?: string; + /** + * A - Z + */ + refId: string; +} + /** * TODO docs */ @@ -552,6 +584,17 @@ export type TimeZoneUtc = 'utc'; */ export type TimeZoneBrowser = 'browser'; +export interface DataSourceRef { + /** + * The plugin type-id + */ + type?: string; + /** + * Specific datasource instance + */ + uid?: string; +} + /** * TODO docs */ diff --git a/packages/grafana-schema/src/common/dataquery_gen.cue b/packages/grafana-schema/src/common/dataquery_gen.cue new file mode 100644 index 00000000000..25b5556961b --- /dev/null +++ b/packages/grafana-schema/src/common/dataquery_gen.cue @@ -0,0 +1,46 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. +// +// Generated by: +// kinds/gen.go +// Using jennies: +// CommonSchemaJenny +// +// Run 'make gen-cue' from repository root to regenerate. + +package common + +// Canonically defined in pkg/kindsys/dataquery.cue FOR NOW to avoid having any external imports +// in kindsys. Code generation copies this file to the common schemas in packages/grafana-schema/src/common. +// +// NOTE make gen-cue must be run twice when updating this file + +// These are the common properties available to all queries in all datasources. +// Specific implementations will *extend* this interface, adding the required +// properties for the given context. +DataQuery: { + // A - Z + refId: string + + // true if query is disabled (ie should not be returned to the dashboard) + hide?: bool + + // Unique, guid like, string used in explore mode + key?: string + + // Specify the query flavor + // TODO make this required and give it a default + queryType?: string + + // For mixed data sources the selected datasource is on the query level. + // For non mixed scenarios this is undefined. + // TODO find a better way to do this ^ that's friendly to schema + // TODO this shouldn't be unknown but DataSourceRef | null + datasource?: _ +} @cuetsy(kind="interface") + +DataSourceRef: { + // The plugin type-id + type?: string + // Specific datasource instance + uid?: string +} @cuetsy(kind="interface") diff --git a/packages/grafana-schema/src/common/mudball.cue b/packages/grafana-schema/src/common/mudball.cue index 34b3a72d367..ad648cccb68 100644 --- a/packages/grafana-schema/src/common/mudball.cue +++ b/packages/grafana-schema/src/common/mudball.cue @@ -246,3 +246,5 @@ VizTooltipOptions: { mode: TooltipDisplayMode sort: SortOrder } @cuetsy(kind="interface") + + diff --git a/packages/grafana-schema/src/index.gen.ts b/packages/grafana-schema/src/index.gen.ts index ef9f810612d..2fac4368b56 100644 --- a/packages/grafana-schema/src/index.gen.ts +++ b/packages/grafana-schema/src/index.gen.ts @@ -11,7 +11,6 @@ export type { AnnotationTarget, AnnotationQuery, - DataSourceRef, DashboardLink, DashboardLinkType, VariableType, @@ -63,6 +62,7 @@ export { export type { Dashboard, VariableModel, + DataSourceRef, Panel, FieldConfigSource, FieldConfig diff --git a/packages/grafana-schema/src/index.ts b/packages/grafana-schema/src/index.ts index fb70bf484a3..505aded6a29 100644 --- a/packages/grafana-schema/src/index.ts +++ b/packages/grafana-schema/src/index.ts @@ -3,5 +3,5 @@ * * @packageDocumentation */ -export * from './common/common.gen'; +export * from './veneer/common.types'; export * from './index.gen'; diff --git a/packages/grafana-schema/src/veneer/common.types.ts b/packages/grafana-schema/src/veneer/common.types.ts new file mode 100644 index 00000000000..cf65607d001 --- /dev/null +++ b/packages/grafana-schema/src/veneer/common.types.ts @@ -0,0 +1,8 @@ +import * as raw from '../common/common.gen'; + +export interface DataQuery extends raw.DataQuery { + // TODO remove explicit nulls + datasource?: raw.DataSourceRef | null; +} + +export * from '../common/common.gen'; diff --git a/packages/grafana-schema/src/veneer/dashboard.types.ts b/packages/grafana-schema/src/veneer/dashboard.types.ts index b2e7b53433e..53b1d22921c 100644 --- a/packages/grafana-schema/src/veneer/dashboard.types.ts +++ b/packages/grafana-schema/src/veneer/dashboard.types.ts @@ -1,5 +1,8 @@ +import { DataSourceRef as CommonDataSourceRef } from '../common/common.gen'; import * as raw from '../raw/dashboard/x/dashboard_types.gen'; +export type { CommonDataSourceRef as DataSourceRef }; + export interface Panel, TCustomFieldConfig = Record> extends raw.Panel { fieldConfig: FieldConfigSource; @@ -14,11 +17,15 @@ export enum VariableHide { export interface VariableModel extends Omit { // Overrides nullable properties because CUE doesn't support null values + // TODO remove explicit nulls rootStateKey: string | null; + // TODO remove explicit nulls error: any | null; + // TODO remove explicit nulls description: string | null; hide: VariableHide; - datasource: raw.DataSourceRef | null; + // TODO remove explicit nulls + datasource: CommonDataSourceRef | null; } export interface Dashboard extends Omit { @@ -39,11 +46,15 @@ export interface FieldConfigSource> extends r export const defaultDashboard = raw.defaultDashboard as Dashboard; export const defaultVariableModel = { ...raw.defaultVariableModel, + // TODO remove explicit nulls rootStateKey: null, + // TODO remove explicit nulls error: null, + // TODO remove explicit nulls description: null, hide: VariableHide.dontHide, state: raw.LoadingState.NotStarted, + // TODO remove explicit nulls datasource: null, } as VariableModel; export const defaultPanel: Partial = raw.defaultPanel; diff --git a/pkg/codegen/jenny_gotypes.go b/pkg/codegen/jenny_gotypes.go index 972dd0eb818..2891bab56c8 100644 --- a/pkg/codegen/jenny_gotypes.go +++ b/pkg/codegen/jenny_gotypes.go @@ -1,15 +1,18 @@ package codegen import ( + copenapi "cuelang.org/go/encoding/openapi" "github.com/dave/dst/dstutil" "github.com/grafana/codejen" "github.com/grafana/thema/encoding/gocode" + "github.com/grafana/thema/encoding/openapi" ) // GoTypesJenny creates a [OneToOne] that produces Go types for the provided // [thema.Schema]. type GoTypesJenny struct { - ApplyFuncs []dstutil.ApplyFunc + ApplyFuncs []dstutil.ApplyFunc + ExpandReferences bool } func (j GoTypesJenny) JennyName() string { @@ -20,6 +23,12 @@ func (j GoTypesJenny) Generate(sfg SchemaForGen) (*codejen.File, error) { // TODO allow using name instead of machine name in thema generator b, err := gocode.GenerateTypesOpenAPI(sfg.Schema, &gocode.TypeConfigOpenAPI{ // TODO will need to account for sanitizing e.g. dashes here at some point + Config: &openapi.Config{ + Group: sfg.IsGroup, + Config: &copenapi.Config{ + ExpandReferences: j.ExpandReferences, + }, + }, PackageName: sfg.Schema.Lineage().Name(), ApplyFuncs: append(j.ApplyFuncs, PrefixDropper(sfg.Name), DecoderCompactor()), }) diff --git a/pkg/codegen/tmpl/plugin_registry.tmpl b/pkg/codegen/tmpl/plugin_registry.tmpl deleted file mode 100644 index 38854645c6a..00000000000 --- a/pkg/codegen/tmpl/plugin_registry.tmpl +++ /dev/null @@ -1,31 +0,0 @@ -{{ template "autogen_header.tmpl" .Header -}} -package corelist - -import ( - "fmt" - "io/fs" - "sync" - "github.com/grafana/grafana" - "github.com/grafana/grafana/pkg/plugins/pfs" - "github.com/grafana/thema" -) - -func makeTreeOrPanic(path string, pkgname string, rt *thema.Runtime) *pfs.Tree { - sub, err := fs.Sub(grafana.CueSchemaFS, path) - if err != nil { - panic("could not create fs sub to " + path) - } - tree, err := pfs.ParsePluginFS(sub, rt) - if err != nil { - panic(fmt.Sprintf("error parsing plugin metadata for %s: %s", pkgname, err)) - } - return tree -} - -func coreTreeList(rt *thema.Runtime) pfs.TreeList{ - return pfs.TreeList{ - {{- range .Plugins }} - makeTreeOrPanic("{{ .Path }}", "{{ .PkgName }}", rt), - {{- end }} - } -} diff --git a/pkg/cuectx/ctx.go b/pkg/cuectx/ctx.go index c4084fdd6f6..db61d5b7438 100644 --- a/pkg/cuectx/ctx.go +++ b/pkg/cuectx/ctx.go @@ -95,13 +95,19 @@ func LoadGrafanaInstancesWithThema(path string, cueFS fs.FS, rt *thema.Runtime, // // The returned fs.FS is suitable for passing to a CUE loader, such as [load.InstanceWithThema]. func prefixWithGrafanaCUE(prefix string, inputfs fs.FS) (fs.FS, error) { - m := fstest.MapFS{ - // fstest can recognize only forward slashes. - // filepath.ToSlash(filepath.Join("cue.mod", "module.cue")): &fstest.MapFile{Data: []byte(`module: "github.com/grafana/grafana"`)}, + m, err := prefixFS(prefix, inputfs) + if err != nil { + return nil, err } + return merged_fs.NewMergedFS(m, grafana.CueSchemaFS), nil +} + +// TODO such a waste, replace with stateless impl that just transforms paths on the fly +func prefixFS(prefix string, fsys fs.FS) (fs.FS, error) { + m := make(fstest.MapFS) prefix = filepath.FromSlash(prefix) - err := fs.WalkDir(inputfs, ".", func(path string, d fs.DirEntry, err error) error { + err := fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error { if err != nil { return err } @@ -110,7 +116,7 @@ func prefixWithGrafanaCUE(prefix string, inputfs fs.FS) (fs.FS, error) { return nil } - b, err := fs.ReadFile(inputfs, path) + b, err := fs.ReadFile(fsys, path) if err != nil { return err } @@ -118,10 +124,7 @@ func prefixWithGrafanaCUE(prefix string, inputfs fs.FS) (fs.FS, error) { m[filepath.ToSlash(filepath.Join(prefix, path))] = &fstest.MapFile{Data: b} return nil }) - if err != nil { - return nil, err - } - return merged_fs.NewMergedFS(m, grafana.CueSchemaFS), nil + return m, err } // LoadGrafanaInstance wraps [load.InstanceWithThema] to load a @@ -141,7 +144,7 @@ func prefixWithGrafanaCUE(prefix string, inputfs fs.FS) (fs.FS, error) { // ["cuelang.org/go/cue/load".Config.Package]. If the CUE package to be loaded // is the same as the parent directory name, it should be omitted. // -// NOTE this function will be removed in favor of a more generic loader +// NOTE this function will be deprecated in favor of a more generic loader func LoadGrafanaInstance(relpath string, pkg string, overlay fs.FS) (*build.Instance, error) { // notes about how this crap needs to work // @@ -175,10 +178,13 @@ func LoadGrafanaInstance(relpath string, pkg string, overlay fs.FS) (*build.Inst } // BuildGrafanaInstance wraps [LoadGrafanaInstance], additionally building -// the returned [*build.Instance], if valid, into a [cue.Value] that is checked -// for errors before returning. +// the returned [*build.Instance] into a [cue.Value]. // -// NOTE this function will be removed in favor of a more generic loader +// An error is returned if: +// - The underlying call to [LoadGrafanaInstance] returns an error +// - The built [cue.Value] has an error ([cue.Value.Err] returns non-nil) +// +// NOTE this function will be deprecated in favor of a more generic builder func BuildGrafanaInstance(ctx *cue.Context, relpath string, pkg string, overlay fs.FS) (cue.Value, error) { bi, err := LoadGrafanaInstance(relpath, pkg, overlay) if err != nil { @@ -194,3 +200,39 @@ func BuildGrafanaInstance(ctx *cue.Context, relpath string, pkg string, overlay } return v, nil } + +// LoadInstanceWithGrafana loads a [*build.Instance] from .cue files +// in the provided modFS as ["cuelang.org/go/cue/load".Instances], but +// fulfilling any imports of CUE packages under: +// +// - github.com/grafana/grafana +// - github.com/grafana/thema +// +// This function is modeled after [load.InstanceWithThema]. It has the same +// signature and expectations for the modFS. +// +// Attempting to use this func to load files within the +// github.com/grafana/grafana CUE module will result in an error. Use +// [LoadGrafanaInstance] instead. +// +// NOTE This function will be deprecated in favor of a more generic loader +func LoadInstanceWithGrafana(fsys fs.FS, dir string, opts ...load.Option) (*build.Instance, error) { + if modf, err := fs.ReadFile(fsys, filepath.Join("cue.mod", "module.cue")); err != nil { + // delegate error handling + return load.InstanceWithThema(fsys, dir, opts...) + } else if modname, err := cuecontext.New().CompileBytes(modf).LookupPath(cue.MakePath(cue.Str("module"))).String(); err != nil { + // delegate error handling + return load.InstanceWithThema(fsys, dir, opts...) + } else if modname == "github.com/grafana/grafana" { + return nil, fmt.Errorf("use cuectx.LoadGrafanaInstance to load .cue files within github.com/grafana/grafana CUE module") + } + + // TODO wasteful, doing this every time - make that stateless prefixfs! + depFS, err := prefixFS("cue.mod/pkg/github.com/grafana/grafana", grafana.CueSchemaFS) + if err != nil { + panic(err) + } + + // FIXME remove grafana from cue.mod/pkg if it exists, otherwise external thing can inject files to be loaded + return load.InstanceWithThema(merged_fs.NewMergedFS(depFS, fsys), dir, opts...) +} diff --git a/pkg/kindsys/bind.go b/pkg/kindsys/bind.go new file mode 100644 index 00000000000..f6acdded8b9 --- /dev/null +++ b/pkg/kindsys/bind.go @@ -0,0 +1,48 @@ +package kindsys + +import ( + "github.com/grafana/thema" +) + +var _ Composable = genericComposable{} + +type genericComposable struct { + decl Decl[ComposableProperties] + lin thema.Lineage +} + +func (k genericComposable) Props() SomeKindProperties { + return k.decl.Properties +} + +func (k genericComposable) Name() string { + return k.decl.Properties.Name +} + +func (k genericComposable) MachineName() string { + return k.decl.Properties.MachineName +} + +func (k genericComposable) Maturity() Maturity { + return k.decl.Properties.Maturity +} + +func (k genericComposable) Decl() Decl[ComposableProperties] { + return k.decl +} + +func (k genericComposable) Lineage() thema.Lineage { + return k.lin +} + +func BindComposable(rt *thema.Runtime, decl Decl[ComposableProperties], opts ...thema.BindOption) (Composable, error) { + lin, err := decl.Some().BindKindLineage(rt, opts...) + if err != nil { + return nil, err + } + + return genericComposable{ + decl: decl, + lin: lin, + }, nil +} diff --git a/pkg/kindsys/common_dataquery.cue b/pkg/kindsys/common_dataquery.cue new file mode 100644 index 00000000000..4d56c22f497 --- /dev/null +++ b/pkg/kindsys/common_dataquery.cue @@ -0,0 +1,37 @@ +package kindsys + +// Canonically defined in pkg/kindsys/dataquery.cue FOR NOW to avoid having any external imports +// in kindsys. Code generation copies this file to the common schemas in packages/grafana-schema/src/common. +// +// NOTE make gen-cue must be run twice when updating this file + +// These are the common properties available to all queries in all datasources. +// Specific implementations will *extend* this interface, adding the required +// properties for the given context. +DataQuery: { + // A - Z + refId: string + + // true if query is disabled (ie should not be returned to the dashboard) + hide?: bool + + // Unique, guid like, string used in explore mode + key?: string + + // Specify the query flavor + // TODO make this required and give it a default + queryType?: string + + // For mixed data sources the selected datasource is on the query level. + // For non mixed scenarios this is undefined. + // TODO find a better way to do this ^ that's friendly to schema + // TODO this shouldn't be unknown but DataSourceRef | null + datasource?: _ +} @cuetsy(kind="interface") + +DataSourceRef: { + // The plugin type-id + type?: string + // Specific datasource instance + uid?: string +} @cuetsy(kind="interface") diff --git a/pkg/kindsys/errors.go b/pkg/kindsys/errors.go index a02fb887ed9..18deb8ae54c 100644 --- a/pkg/kindsys/errors.go +++ b/pkg/kindsys/errors.go @@ -9,27 +9,10 @@ var ( ErrValueNotExist = errors.New("cue value does not exist") // ErrValueNotAKind indicates that a provided CUE value is not any variety of - // Interface. This is almost always an end-user error - they oops'd and provided the + // Interface. This is almost always a user error - they oops'd and provided the // wrong path, file, etc. ErrValueNotAKind = errors.New("not a kind") + + // ErrInvalidCUE indicates that the CUE representing the kind is invalid. + ErrInvalidCUE = errors.New("CUE syntax error") ) - -func ewrap(actual, is error) error { - return &errPassthrough{ - actual: actual, - is: is, - } -} - -type errPassthrough struct { - actual error - is error -} - -func (e *errPassthrough) Is(err error) bool { - return errors.Is(err, e.actual) || errors.Is(err, e.is) -} - -func (e *errPassthrough) Error() string { - return e.actual.Error() -} diff --git a/pkg/kindsys/kindcat_composable.cue b/pkg/kindsys/kindcat_composable.cue index 1169b74bca0..2813de68f76 100644 --- a/pkg/kindsys/kindcat_composable.cue +++ b/pkg/kindsys/kindcat_composable.cue @@ -15,15 +15,17 @@ Composable: S={ // schemaInterface is the name of the Grafana schema interface implemented by // this Composable kind. The set is open to ensure forward compatibility of // Grafana and tooling with any additional schema interfaces that may be added. -// schemaInterface: or([ for k, _ in schemaInterfaces {k}, string]) schemaInterface: string + // TODO is it worth doing something like below, given that we have to keep this set open for forward compatibility? +// schemaInterface: or([ for k, _ in schemaInterfaces {k}, string]) let schif = schemaInterfaces[S.schemaInterface] // lineage is the Thema lineage containing all the schemas that have existed for this kind. // The name of the lineage is constrained to the name of the schema interface being implemented. -// lineage: thema.#Lineage & {name: S.schemaInterface, joinSchema: schif.interface} - lineage: { joinSchema: (schif.interface | *{}) } +// FIXME cuetsy currently gets confused by all the unifications - maybe openapi too. Do something like the following after thema separates joinSchema/constraint expression +// lineage: { joinSchema: schif.interface } +// lineage: { joinSchema: (schif.interface | *{}) } - lineageIsGroup: schif.group | *false + lineageIsGroup: schif.group } diff --git a/pkg/kindsys/kindcats.cue b/pkg/kindsys/kindcats.cue index f0cd4005d82..cbf7077d327 100644 --- a/pkg/kindsys/kindcats.cue +++ b/pkg/kindsys/kindcats.cue @@ -45,7 +45,7 @@ _sharedKind: { // In addition to lowercase normalization, dashes are transformed to underscores. machineName: strings.ToLower(strings.Replace(name, "-", "_", -1)) - // pluralName is the pluralized form of name. Defaults to name + "s". + // pluralName is the pluralized form of name. Defaults to name + "s". pluralName: =~"^([A-Z][a-zA-Z0-9-]{0,61}[a-zA-Z])$" | *(name + "s") // pluralMachineName is the pluralized form of [machineName]. The same case @@ -57,8 +57,8 @@ _sharedKind: { // grouped lineage, each top-level field in the schema specifies a discrete // object that is expected to exist in the wild // - // This field is set at the framework level, and cannot be in the declaration of - // any individual kind. + // This value of this field is set by the kindsys framework. It cannot be changed + // in the declaration of any individual kind. // // This is likely to eventually become a first-class property in Thema: // https://github.com/grafana/thema/issues/62 @@ -71,7 +71,7 @@ _sharedKind: { // schema in lineage. currentVersion: thema.#SyntacticVersion & (thema.#LatestVersion & {lin: lineage}).out - maturity: #Maturity + maturity: Maturity // The kind system itself is not mature enough yet for any single // kind to advance beyond "experimental" @@ -81,7 +81,7 @@ _sharedKind: { // Maturity indicates the how far a given kind declaration is in its initial // journey. Mature kinds still evolve, but with guarantees about compatibility. -#Maturity: "merged" | "experimental" | "stable" | "mature" +Maturity: "merged" | "experimental" | "stable" | "mature" // Core specifies the kind category for core-defined arbitrary types. // Familiar types and functional resources in Grafana, such as dashboards and diff --git a/pkg/kindsys/kindmetas.go b/pkg/kindsys/kindmetas.go index c0d498e0e49..fb0eb6505fc 100644 --- a/pkg/kindsys/kindmetas.go +++ b/pkg/kindsys/kindmetas.go @@ -47,7 +47,8 @@ func (m CustomProperties) Common() CommonProperties { // excludes Thema schemas. type ComposableProperties struct { CommonProperties - CurrentVersion thema.SyntacticVersion `json:"currentVersion"` + CurrentVersion thema.SyntacticVersion `json:"currentVersion"` + SchemaInterface string `json:"schemaInterface"` } func (m ComposableProperties) _private() {} diff --git a/pkg/kindsys/load.go b/pkg/kindsys/load.go index efb3ef9298b..44f1592bb50 100644 --- a/pkg/kindsys/load.go +++ b/pkg/kindsys/load.go @@ -102,9 +102,10 @@ func ToKindProps[T KindProperties](v cue.Value) (T, error) { } item := v.Unify(kdef) - if err := item.Validate(cue.Concrete(false), cue.All()); err != nil { - return *props, ewrap(item.Err(), ErrValueNotAKind) + if item.Err() != nil { + return *props, errors.Wrap(errors.Promote(ErrValueNotAKind, ""), item.Err()) } + if err := item.Decode(props); err != nil { // Should only be reachable if CUE and Go framework types have diverged panic(errors.Details(err, nil)) @@ -113,11 +114,10 @@ func ToKindProps[T KindProperties](v cue.Value) (T, error) { return *props, nil } -// SomeDecl represents a single kind declaration, having been loaded -// and validated by a func such as [LoadCoreKind]. +// SomeDecl represents a single kind declaration, having been loaded and +// validated by a func such as [LoadCoreKind]. // -// The underlying type of the Properties field indicates the category of -// kind. +// The underlying type of the Properties field indicates the category of kind. type SomeDecl struct { // V is the cue.Value containing the entire Kind declaration. V cue.Value @@ -134,12 +134,7 @@ func (decl SomeDecl) BindKindLineage(rt *thema.Runtime, opts ...thema.BindOption if rt == nil { rt = cuectx.GrafanaThemaRuntime() } - switch decl.Properties.(type) { - case CoreProperties, CustomProperties, ComposableProperties: - return thema.BindLineage(decl.V.LookupPath(cue.MakePath(cue.Str("lineage"))), rt, opts...) - default: - panic("unreachable") - } + return thema.BindLineage(decl.V.LookupPath(cue.MakePath(cue.Str("lineage"))), rt, opts...) } // IsCore indicates whether the represented kind is a core kind. diff --git a/pkg/kindsys/report.go b/pkg/kindsys/report.go index 0f7e65e8db7..a554f685674 100644 --- a/pkg/kindsys/report.go +++ b/pkg/kindsys/report.go @@ -14,9 +14,9 @@ import ( "strings" "github.com/grafana/codejen" - "github.com/grafana/grafana/pkg/kindsys" "github.com/grafana/grafana/pkg/plugins/pfs/corelist" + "github.com/grafana/grafana/pkg/plugins/plugindef" "github.com/grafana/grafana/pkg/registry/corekind" ) @@ -140,13 +140,14 @@ func buildKindStateReport() *KindStateReport { } all := kindsys.SchemaInterfaces(nil) - // TODO this is all hacks until #59001, which will unite plugins with kindsys - for _, tree := range corelist.New(nil) { - rp := tree.RootPlugin() + for _, pp := range corelist.New(nil) { for _, si := range all { - if si.Should(string(rp.Meta().Type)) { - n := fmt.Sprintf("%s-%s", strings.Title(rp.Meta().Id), si.Name()) + if ck, has := pp.ComposableKinds[si.Name()]; has { + r.add(ck.Props(), "composable") + } else if may := si.Should(string(pp.Properties.Type)); may { + n := plugindef.DerivePascalName(pp.Properties) + si.Name() props := kindsys.ComposableProperties{ + SchemaInterface: si.Name(), CommonProperties: kindsys.CommonProperties{ Name: n, PluralName: n + "s", @@ -156,10 +157,6 @@ func buildKindStateReport() *KindStateReport { Maturity: "planned", }, } - if ck, has := rp.SlotImplementations()[si.Name()]; has { - props.CommonProperties.Maturity = "merged" - props.CurrentVersion = ck.Latest().Version() - } r.add(props, "composable") } } diff --git a/pkg/kindsys/report.json b/pkg/kindsys/report.json index be2bc7fa00f..ac2f97db3a5 100644 --- a/pkg/kindsys/report.json +++ b/pkg/kindsys/report.json @@ -1,64 +1,69 @@ { "kinds": { - "alertgroups_panel": { - "name": "AlertGroups-Panel", - "pluralName": "AlertGroups-Panels", - "machineName": "alertgroups_panel", - "pluralMachineName": "alertgroups_panels", + "alertgroupspanelcfg": { + "name": "AlertGroupsPanelCfg", + "pluralName": "AlertGroupsPanelCfgs", + "machineName": "alertgroupspanelcfg", + "pluralMachineName": "alertgroupspanelcfgs", "lineageIsGroup": true, "maturity": "planned", "currentVersion": [ 0, 0 - ] + ], + "schemaInterface": "PanelCfg" }, - "alertlist_panel": { - "name": "Alertlist-Panel", - "pluralName": "Alertlist-Panels", - "machineName": "alertlist_panel", - "pluralMachineName": "alertlist_panels", + "alertlistpanelcfg": { + "name": "AlertListPanelCfg", + "pluralName": "AlertListPanelCfgs", + "machineName": "alertlistpanelcfg", + "pluralMachineName": "alertlistpanelcfgs", "lineageIsGroup": true, "maturity": "planned", "currentVersion": [ 0, 0 - ] + ], + "schemaInterface": "PanelCfg" }, - "alertmanager_dsoptions": { - "name": "Alertmanager-DSOptions", - "pluralName": "Alertmanager-DSOptionss", - "machineName": "alertmanager_dsoptions", - "pluralMachineName": "alertmanager_dsoptionss", + "alertmanagerdataquery": { + "name": "AlertmanagerDataQuery", + "pluralName": "AlertmanagerDataQuerys", + "machineName": "alertmanagerdataquery", + "pluralMachineName": "alertmanagerdataquerys", + "lineageIsGroup": false, + "maturity": "planned", + "currentVersion": [ + 0, + 0 + ], + "schemaInterface": "DataQuery" + }, + "alertmanagerdatasourcecfg": { + "name": "AlertmanagerDataSourceCfg", + "pluralName": "AlertmanagerDataSourceCfgs", + "machineName": "alertmanagerdatasourcecfg", + "pluralMachineName": "alertmanagerdatasourcecfgs", "lineageIsGroup": true, "maturity": "planned", "currentVersion": [ 0, 0 - ] + ], + "schemaInterface": "DataSourceCfg" }, - "alertmanager_query": { - "name": "Alertmanager-Query", - "pluralName": "Alertmanager-Querys", - "machineName": "alertmanager_query", - "pluralMachineName": "alertmanager_querys", + "annotationslistpanelcfg": { + "name": "AnnotationsListPanelCfg", + "pluralName": "AnnotationsListPanelCfgs", + "machineName": "annotationslistpanelcfg", + "pluralMachineName": "annotationslistpanelcfgs", "lineageIsGroup": true, - "maturity": "planned", + "maturity": "experimental", "currentVersion": [ 0, 0 - ] - }, - "annolist_panel": { - "name": "Annolist-Panel", - "pluralName": "Annolist-Panels", - "machineName": "annolist_panel", - "pluralMachineName": "annolist_panels", - "lineageIsGroup": true, - "maturity": "merged", - "currentVersion": [ - 0, - 0 - ] + ], + "schemaInterface": "PanelCfg" }, "apikey": { "name": "APIKey", @@ -72,53 +77,83 @@ 0 ] }, - "barchart_panel": { - "name": "Barchart-Panel", - "pluralName": "Barchart-Panels", - "machineName": "barchart_panel", - "pluralMachineName": "barchart_panels", - "lineageIsGroup": true, - "maturity": "merged", + "azuremonitordataquery": { + "name": "AzureMonitorDataQuery", + "pluralName": "AzureMonitorDataQuerys", + "machineName": "azuremonitordataquery", + "pluralMachineName": "azuremonitordataquerys", + "lineageIsGroup": false, + "maturity": "planned", "currentVersion": [ 0, 0 - ] + ], + "schemaInterface": "DataQuery" }, - "bargauge_panel": { - "name": "Bargauge-Panel", - "pluralName": "Bargauge-Panels", - "machineName": "bargauge_panel", - "pluralMachineName": "bargauge_panels", - "lineageIsGroup": true, - "maturity": "merged", - "currentVersion": [ - 0, - 0 - ] - }, - "cloudwatch_dsoptions": { - "name": "Cloudwatch-DSOptions", - "pluralName": "Cloudwatch-DSOptionss", - "machineName": "cloudwatch_dsoptions", - "pluralMachineName": "cloudwatch_dsoptionss", + "azuremonitordatasourcecfg": { + "name": "AzureMonitorDataSourceCfg", + "pluralName": "AzureMonitorDataSourceCfgs", + "machineName": "azuremonitordatasourcecfg", + "pluralMachineName": "azuremonitordatasourcecfgs", "lineageIsGroup": true, "maturity": "planned", "currentVersion": [ 0, 0 - ] + ], + "schemaInterface": "DataSourceCfg" }, - "cloudwatch_query": { - "name": "Cloudwatch-Query", - "pluralName": "Cloudwatch-Querys", - "machineName": "cloudwatch_query", - "pluralMachineName": "cloudwatch_querys", + "barchartpanelcfg": { + "name": "BarChartPanelCfg", + "pluralName": "BarChartPanelCfgs", + "machineName": "barchartpanelcfg", + "pluralMachineName": "barchartpanelcfgs", + "lineageIsGroup": true, + "maturity": "experimental", + "currentVersion": [ + 0, + 0 + ], + "schemaInterface": "PanelCfg" + }, + "bargaugepanelcfg": { + "name": "BarGaugePanelCfg", + "pluralName": "BarGaugePanelCfgs", + "machineName": "bargaugepanelcfg", + "pluralMachineName": "bargaugepanelcfgs", + "lineageIsGroup": true, + "maturity": "experimental", + "currentVersion": [ + 0, + 0 + ], + "schemaInterface": "PanelCfg" + }, + "cloudwatchdataquery": { + "name": "CloudWatchDataQuery", + "pluralName": "CloudWatchDataQuerys", + "machineName": "cloudwatchdataquery", + "pluralMachineName": "cloudwatchdataquerys", + "lineageIsGroup": false, + "maturity": "planned", + "currentVersion": [ + 0, + 0 + ], + "schemaInterface": "DataQuery" + }, + "cloudwatchdatasourcecfg": { + "name": "CloudWatchDataSourceCfg", + "pluralName": "CloudWatchDataSourceCfgs", + "machineName": "cloudwatchdatasourcecfg", + "pluralMachineName": "cloudwatchdatasourcecfgs", "lineageIsGroup": true, "maturity": "planned", "currentVersion": [ 0, 0 - ] + ], + "schemaInterface": "DataSourceCfg" }, "dashboard": { "name": "Dashboard", @@ -132,41 +167,44 @@ 0 ] }, - "dashboard_dsoptions": { - "name": "Dashboard-DSOptions", - "pluralName": "Dashboard-DSOptionss", - "machineName": "dashboard_dsoptions", - "pluralMachineName": "dashboard_dsoptionss", + "dashboarddataquery": { + "name": "DashboardDataQuery", + "pluralName": "DashboardDataQuerys", + "machineName": "dashboarddataquery", + "pluralMachineName": "dashboarddataquerys", + "lineageIsGroup": false, + "maturity": "planned", + "currentVersion": [ + 0, + 0 + ], + "schemaInterface": "DataQuery" + }, + "dashboarddatasourcecfg": { + "name": "DashboardDataSourceCfg", + "pluralName": "DashboardDataSourceCfgs", + "machineName": "dashboarddatasourcecfg", + "pluralMachineName": "dashboarddatasourcecfgs", "lineageIsGroup": true, "maturity": "planned", "currentVersion": [ 0, 0 - ] + ], + "schemaInterface": "DataSourceCfg" }, - "dashboard_query": { - "name": "Dashboard-Query", - "pluralName": "Dashboard-Querys", - "machineName": "dashboard_query", - "pluralMachineName": "dashboard_querys", + "dashboardlistpanelcfg": { + "name": "DashboardListPanelCfg", + "pluralName": "DashboardListPanelCfgs", + "machineName": "dashboardlistpanelcfg", + "pluralMachineName": "dashboardlistpanelcfgs", "lineageIsGroup": true, - "maturity": "planned", + "maturity": "experimental", "currentVersion": [ 0, 0 - ] - }, - "dashlist_panel": { - "name": "Dashlist-Panel", - "pluralName": "Dashlist-Panels", - "machineName": "dashlist_panel", - "pluralMachineName": "dashlist_panels", - "lineageIsGroup": true, - "maturity": "merged", - "currentVersion": [ - 0, - 0 - ] + ], + "schemaInterface": "PanelCfg" }, "datasource": { "name": "DataSource", @@ -180,53 +218,57 @@ 0 ] }, - "debug_panel": { - "name": "Debug-Panel", - "pluralName": "Debug-Panels", - "machineName": "debug_panel", - "pluralMachineName": "debug_panels", + "debugpanelcfg": { + "name": "DebugPanelCfg", + "pluralName": "DebugPanelCfgs", + "machineName": "debugpanelcfg", + "pluralMachineName": "debugpanelcfgs", "lineageIsGroup": true, "maturity": "planned", "currentVersion": [ 0, 0 - ] + ], + "schemaInterface": "PanelCfg" }, - "elasticsearch_dsoptions": { - "name": "Elasticsearch-DSOptions", - "pluralName": "Elasticsearch-DSOptionss", - "machineName": "elasticsearch_dsoptions", - "pluralMachineName": "elasticsearch_dsoptionss", - "lineageIsGroup": true, + "elasticsearchdataquery": { + "name": "ElasticsearchDataQuery", + "pluralName": "ElasticsearchDataQuerys", + "machineName": "elasticsearchdataquery", + "pluralMachineName": "elasticsearchdataquerys", + "lineageIsGroup": false, "maturity": "planned", "currentVersion": [ 0, 0 - ] + ], + "schemaInterface": "DataQuery" }, - "elasticsearch_query": { - "name": "Elasticsearch-Query", - "pluralName": "Elasticsearch-Querys", - "machineName": "elasticsearch_query", - "pluralMachineName": "elasticsearch_querys", + "elasticsearchdatasourcecfg": { + "name": "ElasticsearchDataSourceCfg", + "pluralName": "ElasticsearchDataSourceCfgs", + "machineName": "elasticsearchdatasourcecfg", + "pluralMachineName": "elasticsearchdatasourcecfgs", "lineageIsGroup": true, "maturity": "planned", "currentVersion": [ 0, 0 - ] + ], + "schemaInterface": "DataSourceCfg" }, - "flamegraph_panel": { - "name": "Flamegraph-Panel", - "pluralName": "Flamegraph-Panels", - "machineName": "flamegraph_panel", - "pluralMachineName": "flamegraph_panels", + "flamegraphpanelcfg": { + "name": "FlameGraphPanelCfg", + "pluralName": "FlameGraphPanelCfgs", + "machineName": "flamegraphpanelcfg", + "pluralMachineName": "flamegraphpanelcfgs", "lineageIsGroup": true, "maturity": "planned", "currentVersion": [ 0, 0 - ] + ], + "schemaInterface": "PanelCfg" }, "folder": { "name": "Folder", @@ -240,353 +282,382 @@ 0 ] }, - "gauge_panel": { - "name": "Gauge-Panel", - "pluralName": "Gauge-Panels", - "machineName": "gauge_panel", - "pluralMachineName": "gauge_panels", + "gaugepanelcfg": { + "name": "GaugePanelCfg", + "pluralName": "GaugePanelCfgs", + "machineName": "gaugepanelcfg", + "pluralMachineName": "gaugepanelcfgs", "lineageIsGroup": true, - "maturity": "merged", + "maturity": "experimental", "currentVersion": [ 0, 0 - ] + ], + "schemaInterface": "PanelCfg" }, - "geomap_panel": { - "name": "Geomap-Panel", - "pluralName": "Geomap-Panels", - "machineName": "geomap_panel", - "pluralMachineName": "geomap_panels", + "geomappanelcfg": { + "name": "GeomapPanelCfg", + "pluralName": "GeomapPanelCfgs", + "machineName": "geomappanelcfg", + "pluralMachineName": "geomappanelcfgs", "lineageIsGroup": true, "maturity": "planned", "currentVersion": [ 0, 0 - ] + ], + "schemaInterface": "PanelCfg" }, - "gettingstarted_panel": { - "name": "Gettingstarted-Panel", - "pluralName": "Gettingstarted-Panels", - "machineName": "gettingstarted_panel", - "pluralMachineName": "gettingstarted_panels", + "gettingstartedpanelcfg": { + "name": "GettingStartedPanelCfg", + "pluralName": "GettingStartedPanelCfgs", + "machineName": "gettingstartedpanelcfg", + "pluralMachineName": "gettingstartedpanelcfgs", "lineageIsGroup": true, "maturity": "planned", "currentVersion": [ 0, 0 - ] + ], + "schemaInterface": "PanelCfg" }, - "grafana_azure_monitor_datasource_dsoptions": { - "name": "Grafana-Azure-Monitor-Datasource-DSOptions", - "pluralName": "Grafana-Azure-Monitor-Datasource-DSOptionss", - "machineName": "grafana_azure_monitor_datasource_dsoptions", - "pluralMachineName": "grafana_azure_monitor_datasource_dsoptionss", + "googlecloudmonitoringdataquery": { + "name": "GoogleCloudMonitoringDataQuery", + "pluralName": "GoogleCloudMonitoringDataQuerys", + "machineName": "googlecloudmonitoringdataquery", + "pluralMachineName": "googlecloudmonitoringdataquerys", + "lineageIsGroup": false, + "maturity": "planned", + "currentVersion": [ + 0, + 0 + ], + "schemaInterface": "DataQuery" + }, + "googlecloudmonitoringdatasourcecfg": { + "name": "GoogleCloudMonitoringDataSourceCfg", + "pluralName": "GoogleCloudMonitoringDataSourceCfgs", + "machineName": "googlecloudmonitoringdatasourcecfg", + "pluralMachineName": "googlecloudmonitoringdatasourcecfgs", "lineageIsGroup": true, "maturity": "planned", "currentVersion": [ 0, 0 - ] + ], + "schemaInterface": "DataSourceCfg" }, - "grafana_azure_monitor_datasource_query": { - "name": "Grafana-Azure-Monitor-Datasource-Query", - "pluralName": "Grafana-Azure-Monitor-Datasource-Querys", - "machineName": "grafana_azure_monitor_datasource_query", - "pluralMachineName": "grafana_azure_monitor_datasource_querys", + "grafanadataquery": { + "name": "GrafanaDataQuery", + "pluralName": "GrafanaDataQuerys", + "machineName": "grafanadataquery", + "pluralMachineName": "grafanadataquerys", + "lineageIsGroup": false, + "maturity": "planned", + "currentVersion": [ + 0, + 0 + ], + "schemaInterface": "DataQuery" + }, + "grafanadatasourcecfg": { + "name": "GrafanaDataSourceCfg", + "pluralName": "GrafanaDataSourceCfgs", + "machineName": "grafanadatasourcecfg", + "pluralMachineName": "grafanadatasourcecfgs", "lineageIsGroup": true, "maturity": "planned", "currentVersion": [ 0, 0 - ] + ], + "schemaInterface": "DataSourceCfg" }, - "grafana_dsoptions": { - "name": "Grafana-DSOptions", - "pluralName": "Grafana-DSOptionss", - "machineName": "grafana_dsoptions", - "pluralMachineName": "grafana_dsoptionss", + "graphitedataquery": { + "name": "GraphiteDataQuery", + "pluralName": "GraphiteDataQuerys", + "machineName": "graphitedataquery", + "pluralMachineName": "graphitedataquerys", + "lineageIsGroup": false, + "maturity": "planned", + "currentVersion": [ + 0, + 0 + ], + "schemaInterface": "DataQuery" + }, + "graphitedatasourcecfg": { + "name": "GraphiteDataSourceCfg", + "pluralName": "GraphiteDataSourceCfgs", + "machineName": "graphitedatasourcecfg", + "pluralMachineName": "graphitedatasourcecfgs", "lineageIsGroup": true, "maturity": "planned", "currentVersion": [ 0, 0 - ] + ], + "schemaInterface": "DataSourceCfg" }, - "grafana_query": { - "name": "Grafana-Query", - "pluralName": "Grafana-Querys", - "machineName": "grafana_query", - "pluralMachineName": "grafana_querys", + "grapholdpanelcfg": { + "name": "GraphOldPanelCfg", + "pluralName": "GraphOldPanelCfgs", + "machineName": "grapholdpanelcfg", + "pluralMachineName": "grapholdpanelcfgs", "lineageIsGroup": true, "maturity": "planned", "currentVersion": [ 0, 0 - ] + ], + "schemaInterface": "PanelCfg" }, - "graph_panel": { - "name": "Graph-Panel", - "pluralName": "Graph-Panels", - "machineName": "graph_panel", - "pluralMachineName": "graph_panels", + "histogrampanelcfg": { + "name": "HistogramPanelCfg", + "pluralName": "HistogramPanelCfgs", + "machineName": "histogrampanelcfg", + "pluralMachineName": "histogrampanelcfgs", + "lineageIsGroup": true, + "maturity": "experimental", + "currentVersion": [ + 0, + 0 + ], + "schemaInterface": "PanelCfg" + }, + "iconpanelcfg": { + "name": "IconPanelCfg", + "pluralName": "IconPanelCfgs", + "machineName": "iconpanelcfg", + "pluralMachineName": "iconpanelcfgs", "lineageIsGroup": true, "maturity": "planned", "currentVersion": [ 0, 0 - ] + ], + "schemaInterface": "PanelCfg" }, - "graphite_dsoptions": { - "name": "Graphite-DSOptions", - "pluralName": "Graphite-DSOptionss", - "machineName": "graphite_dsoptions", - "pluralMachineName": "graphite_dsoptionss", + "jaegerdataquery": { + "name": "JaegerDataQuery", + "pluralName": "JaegerDataQuerys", + "machineName": "jaegerdataquery", + "pluralMachineName": "jaegerdataquerys", + "lineageIsGroup": false, + "maturity": "planned", + "currentVersion": [ + 0, + 0 + ], + "schemaInterface": "DataQuery" + }, + "jaegerdatasourcecfg": { + "name": "JaegerDataSourceCfg", + "pluralName": "JaegerDataSourceCfgs", + "machineName": "jaegerdatasourcecfg", + "pluralMachineName": "jaegerdatasourcecfgs", "lineageIsGroup": true, "maturity": "planned", "currentVersion": [ 0, 0 - ] + ], + "schemaInterface": "DataSourceCfg" }, - "graphite_query": { - "name": "Graphite-Query", - "pluralName": "Graphite-Querys", - "machineName": "graphite_query", - "pluralMachineName": "graphite_querys", + "livepanelcfg": { + "name": "LivePanelCfg", + "pluralName": "LivePanelCfgs", + "machineName": "livepanelcfg", + "pluralMachineName": "livepanelcfgs", "lineageIsGroup": true, "maturity": "planned", "currentVersion": [ 0, 0 - ] + ], + "schemaInterface": "PanelCfg" }, - "histogram_panel": { - "name": "Histogram-Panel", - "pluralName": "Histogram-Panels", - "machineName": "histogram_panel", - "pluralMachineName": "histogram_panels", - "lineageIsGroup": true, - "maturity": "merged", - "currentVersion": [ - 0, - 0 - ] - }, - "icon_panel": { - "name": "Icon-Panel", - "pluralName": "Icon-Panels", - "machineName": "icon_panel", - "pluralMachineName": "icon_panels", + "logspanelcfg": { + "name": "LogsPanelCfg", + "pluralName": "LogsPanelCfgs", + "machineName": "logspanelcfg", + "pluralMachineName": "logspanelcfgs", "lineageIsGroup": true, "maturity": "planned", "currentVersion": [ 0, 0 - ] + ], + "schemaInterface": "PanelCfg" }, - "jaeger_dsoptions": { - "name": "Jaeger-DSOptions", - "pluralName": "Jaeger-DSOptionss", - "machineName": "jaeger_dsoptions", - "pluralMachineName": "jaeger_dsoptionss", + "lokidataquery": { + "name": "LokiDataQuery", + "pluralName": "LokiDataQuerys", + "machineName": "lokidataquery", + "pluralMachineName": "lokidataquerys", + "lineageIsGroup": false, + "maturity": "planned", + "currentVersion": [ + 0, + 0 + ], + "schemaInterface": "DataQuery" + }, + "lokidatasourcecfg": { + "name": "LokiDataSourceCfg", + "pluralName": "LokiDataSourceCfgs", + "machineName": "lokidatasourcecfg", + "pluralMachineName": "lokidatasourcecfgs", "lineageIsGroup": true, "maturity": "planned", "currentVersion": [ 0, 0 - ] + ], + "schemaInterface": "DataSourceCfg" }, - "jaeger_query": { - "name": "Jaeger-Query", - "pluralName": "Jaeger-Querys", - "machineName": "jaeger_query", - "pluralMachineName": "jaeger_querys", + "microsoftsqlserverdataquery": { + "name": "MicrosoftSQLServerDataQuery", + "pluralName": "MicrosoftSQLServerDataQuerys", + "machineName": "microsoftsqlserverdataquery", + "pluralMachineName": "microsoftsqlserverdataquerys", + "lineageIsGroup": false, + "maturity": "planned", + "currentVersion": [ + 0, + 0 + ], + "schemaInterface": "DataQuery" + }, + "microsoftsqlserverdatasourcecfg": { + "name": "MicrosoftSQLServerDataSourceCfg", + "pluralName": "MicrosoftSQLServerDataSourceCfgs", + "machineName": "microsoftsqlserverdatasourcecfg", + "pluralMachineName": "microsoftsqlserverdatasourcecfgs", "lineageIsGroup": true, "maturity": "planned", "currentVersion": [ 0, 0 - ] + ], + "schemaInterface": "DataSourceCfg" }, - "live_panel": { - "name": "Live-Panel", - "pluralName": "Live-Panels", - "machineName": "live_panel", - "pluralMachineName": "live_panels", + "mysqldataquery": { + "name": "MySQLDataQuery", + "pluralName": "MySQLDataQuerys", + "machineName": "mysqldataquery", + "pluralMachineName": "mysqldataquerys", + "lineageIsGroup": false, + "maturity": "planned", + "currentVersion": [ + 0, + 0 + ], + "schemaInterface": "DataQuery" + }, + "mysqldatasourcecfg": { + "name": "MySQLDataSourceCfg", + "pluralName": "MySQLDataSourceCfgs", + "machineName": "mysqldatasourcecfg", + "pluralMachineName": "mysqldatasourcecfgs", "lineageIsGroup": true, "maturity": "planned", "currentVersion": [ 0, 0 - ] + ], + "schemaInterface": "DataSourceCfg" }, - "logs_panel": { - "name": "Logs-Panel", - "pluralName": "Logs-Panels", - "machineName": "logs_panel", - "pluralMachineName": "logs_panels", + "newspanelcfg": { + "name": "NewsPanelCfg", + "pluralName": "NewsPanelCfgs", + "machineName": "newspanelcfg", + "pluralMachineName": "newspanelcfgs", + "lineageIsGroup": true, + "maturity": "experimental", + "currentVersion": [ + 0, + 0 + ], + "schemaInterface": "PanelCfg" + }, + "nodegraphpanelcfg": { + "name": "NodeGraphPanelCfg", + "pluralName": "NodeGraphPanelCfgs", + "machineName": "nodegraphpanelcfg", + "pluralMachineName": "nodegraphpanelcfgs", "lineageIsGroup": true, "maturity": "planned", "currentVersion": [ 0, 0 - ] + ], + "schemaInterface": "PanelCfg" }, - "loki_dsoptions": { - "name": "Loki-DSOptions", - "pluralName": "Loki-DSOptionss", - "machineName": "loki_dsoptions", - "pluralMachineName": "loki_dsoptionss", + "parcadataquery": { + "name": "ParcaDataQuery", + "pluralName": "ParcaDataQuerys", + "machineName": "parcadataquery", + "pluralMachineName": "parcadataquerys", + "lineageIsGroup": false, + "maturity": "planned", + "currentVersion": [ + 0, + 0 + ], + "schemaInterface": "DataQuery" + }, + "parcadatasourcecfg": { + "name": "ParcaDataSourceCfg", + "pluralName": "ParcaDataSourceCfgs", + "machineName": "parcadatasourcecfg", + "pluralMachineName": "parcadatasourcecfgs", "lineageIsGroup": true, "maturity": "planned", "currentVersion": [ 0, 0 - ] + ], + "schemaInterface": "DataSourceCfg" }, - "loki_query": { - "name": "Loki-Query", - "pluralName": "Loki-Querys", - "machineName": "loki_query", - "pluralMachineName": "loki_querys", + "phlaredataquery": { + "name": "PhlareDataQuery", + "pluralName": "PhlareDataQuerys", + "machineName": "phlaredataquery", + "pluralMachineName": "phlaredataquerys", + "lineageIsGroup": false, + "maturity": "planned", + "currentVersion": [ + 0, + 0 + ], + "schemaInterface": "DataQuery" + }, + "phlaredatasourcecfg": { + "name": "PhlareDataSourceCfg", + "pluralName": "PhlareDataSourceCfgs", + "machineName": "phlaredatasourcecfg", + "pluralMachineName": "phlaredatasourcecfgs", "lineageIsGroup": true, "maturity": "planned", "currentVersion": [ 0, 0 - ] + ], + "schemaInterface": "DataSourceCfg" }, - "mssql_dsoptions": { - "name": "Mssql-DSOptions", - "pluralName": "Mssql-DSOptionss", - "machineName": "mssql_dsoptions", - "pluralMachineName": "mssql_dsoptionss", + "piechartpanelcfg": { + "name": "PieChartPanelCfg", + "pluralName": "PieChartPanelCfgs", + "machineName": "piechartpanelcfg", + "pluralMachineName": "piechartpanelcfgs", "lineageIsGroup": true, - "maturity": "planned", + "maturity": "experimental", "currentVersion": [ 0, 0 - ] - }, - "mssql_query": { - "name": "Mssql-Query", - "pluralName": "Mssql-Querys", - "machineName": "mssql_query", - "pluralMachineName": "mssql_querys", - "lineageIsGroup": true, - "maturity": "planned", - "currentVersion": [ - 0, - 0 - ] - }, - "mysql_dsoptions": { - "name": "Mysql-DSOptions", - "pluralName": "Mysql-DSOptionss", - "machineName": "mysql_dsoptions", - "pluralMachineName": "mysql_dsoptionss", - "lineageIsGroup": true, - "maturity": "planned", - "currentVersion": [ - 0, - 0 - ] - }, - "mysql_query": { - "name": "Mysql-Query", - "pluralName": "Mysql-Querys", - "machineName": "mysql_query", - "pluralMachineName": "mysql_querys", - "lineageIsGroup": true, - "maturity": "planned", - "currentVersion": [ - 0, - 0 - ] - }, - "news_panel": { - "name": "News-Panel", - "pluralName": "News-Panels", - "machineName": "news_panel", - "pluralMachineName": "news_panels", - "lineageIsGroup": true, - "maturity": "merged", - "currentVersion": [ - 0, - 0 - ] - }, - "nodegraph_panel": { - "name": "NodeGraph-Panel", - "pluralName": "NodeGraph-Panels", - "machineName": "nodegraph_panel", - "pluralMachineName": "nodegraph_panels", - "lineageIsGroup": true, - "maturity": "planned", - "currentVersion": [ - 0, - 0 - ] - }, - "parca_dsoptions": { - "name": "Parca-DSOptions", - "pluralName": "Parca-DSOptionss", - "machineName": "parca_dsoptions", - "pluralMachineName": "parca_dsoptionss", - "lineageIsGroup": true, - "maturity": "planned", - "currentVersion": [ - 0, - 0 - ] - }, - "parca_query": { - "name": "Parca-Query", - "pluralName": "Parca-Querys", - "machineName": "parca_query", - "pluralMachineName": "parca_querys", - "lineageIsGroup": true, - "maturity": "planned", - "currentVersion": [ - 0, - 0 - ] - }, - "phlare_dsoptions": { - "name": "Phlare-DSOptions", - "pluralName": "Phlare-DSOptionss", - "machineName": "phlare_dsoptions", - "pluralMachineName": "phlare_dsoptionss", - "lineageIsGroup": true, - "maturity": "planned", - "currentVersion": [ - 0, - 0 - ] - }, - "phlare_query": { - "name": "Phlare-Query", - "pluralName": "Phlare-Querys", - "machineName": "phlare_query", - "pluralMachineName": "phlare_querys", - "lineageIsGroup": true, - "maturity": "planned", - "currentVersion": [ - 0, - 0 - ] - }, - "piechart_panel": { - "name": "Piechart-Panel", - "pluralName": "Piechart-Panels", - "machineName": "piechart_panel", - "pluralMachineName": "piechart_panels", - "lineageIsGroup": true, - "maturity": "merged", - "currentVersion": [ - 0, - 0 - ] + ], + "schemaInterface": "PanelCfg" }, "playlist": { "name": "Playlist", @@ -600,53 +671,57 @@ 0 ] }, - "postgres_dsoptions": { - "name": "Postgres-DSOptions", - "pluralName": "Postgres-DSOptionss", - "machineName": "postgres_dsoptions", - "pluralMachineName": "postgres_dsoptionss", - "lineageIsGroup": true, + "postgresqldataquery": { + "name": "PostgreSQLDataQuery", + "pluralName": "PostgreSQLDataQuerys", + "machineName": "postgresqldataquery", + "pluralMachineName": "postgresqldataquerys", + "lineageIsGroup": false, "maturity": "planned", "currentVersion": [ 0, 0 - ] + ], + "schemaInterface": "DataQuery" }, - "postgres_query": { - "name": "Postgres-Query", - "pluralName": "Postgres-Querys", - "machineName": "postgres_query", - "pluralMachineName": "postgres_querys", + "postgresqldatasourcecfg": { + "name": "PostgreSQLDataSourceCfg", + "pluralName": "PostgreSQLDataSourceCfgs", + "machineName": "postgresqldatasourcecfg", + "pluralMachineName": "postgresqldatasourcecfgs", "lineageIsGroup": true, "maturity": "planned", "currentVersion": [ 0, 0 - ] + ], + "schemaInterface": "DataSourceCfg" }, - "prometheus_dsoptions": { - "name": "Prometheus-DSOptions", - "pluralName": "Prometheus-DSOptionss", - "machineName": "prometheus_dsoptions", - "pluralMachineName": "prometheus_dsoptionss", - "lineageIsGroup": true, + "prometheusdataquery": { + "name": "PrometheusDataQuery", + "pluralName": "PrometheusDataQuerys", + "machineName": "prometheusdataquery", + "pluralMachineName": "prometheusdataquerys", + "lineageIsGroup": false, "maturity": "planned", "currentVersion": [ 0, 0 - ] + ], + "schemaInterface": "DataQuery" }, - "prometheus_query": { - "name": "Prometheus-Query", - "pluralName": "Prometheus-Querys", - "machineName": "prometheus_query", - "pluralMachineName": "prometheus_querys", + "prometheusdatasourcecfg": { + "name": "PrometheusDataSourceCfg", + "pluralName": "PrometheusDataSourceCfgs", + "machineName": "prometheusdatasourcecfg", + "pluralMachineName": "prometheusdatasourcecfgs", "lineageIsGroup": true, "maturity": "planned", "currentVersion": [ 0, 0 - ] + ], + "schemaInterface": "DataSourceCfg" }, "query": { "name": "Query", @@ -684,53 +759,31 @@ 0 ] }, - "stackdriver_dsoptions": { - "name": "Stackdriver-DSOptions", - "pluralName": "Stackdriver-DSOptionss", - "machineName": "stackdriver_dsoptions", - "pluralMachineName": "stackdriver_dsoptionss", + "statpanelcfg": { + "name": "StatPanelCfg", + "pluralName": "StatPanelCfgs", + "machineName": "statpanelcfg", + "pluralMachineName": "statpanelcfgs", + "lineageIsGroup": true, + "maturity": "experimental", + "currentVersion": [ + 0, + 0 + ], + "schemaInterface": "PanelCfg" + }, + "tableoldpanelcfg": { + "name": "TableOldPanelCfg", + "pluralName": "TableOldPanelCfgs", + "machineName": "tableoldpanelcfg", + "pluralMachineName": "tableoldpanelcfgs", "lineageIsGroup": true, "maturity": "planned", "currentVersion": [ 0, 0 - ] - }, - "stackdriver_query": { - "name": "Stackdriver-Query", - "pluralName": "Stackdriver-Querys", - "machineName": "stackdriver_query", - "pluralMachineName": "stackdriver_querys", - "lineageIsGroup": true, - "maturity": "planned", - "currentVersion": [ - 0, - 0 - ] - }, - "stat_panel": { - "name": "Stat-Panel", - "pluralName": "Stat-Panels", - "machineName": "stat_panel", - "pluralMachineName": "stat_panels", - "lineageIsGroup": true, - "maturity": "merged", - "currentVersion": [ - 0, - 0 - ] - }, - "table_old_panel": { - "name": "Table-Old-Panel", - "pluralName": "Table-Old-Panels", - "machineName": "table_old_panel", - "pluralMachineName": "table_old_panels", - "lineageIsGroup": true, - "maturity": "planned", - "currentVersion": [ - 0, - 0 - ] + ], + "schemaInterface": "PanelCfg" }, "team": { "name": "Team", @@ -744,65 +797,70 @@ 0 ] }, - "tempo_dsoptions": { - "name": "Tempo-DSOptions", - "pluralName": "Tempo-DSOptionss", - "machineName": "tempo_dsoptions", - "pluralMachineName": "tempo_dsoptionss", + "tempodataquery": { + "name": "TempoDataQuery", + "pluralName": "TempoDataQuerys", + "machineName": "tempodataquery", + "pluralMachineName": "tempodataquerys", + "lineageIsGroup": false, + "maturity": "planned", + "currentVersion": [ + 0, + 0 + ], + "schemaInterface": "DataQuery" + }, + "tempodatasourcecfg": { + "name": "TempoDataSourceCfg", + "pluralName": "TempoDataSourceCfgs", + "machineName": "tempodatasourcecfg", + "pluralMachineName": "tempodatasourcecfgs", "lineageIsGroup": true, "maturity": "planned", "currentVersion": [ 0, 0 - ] + ], + "schemaInterface": "DataSourceCfg" }, - "tempo_query": { - "name": "Tempo-Query", - "pluralName": "Tempo-Querys", - "machineName": "tempo_query", - "pluralMachineName": "tempo_querys", + "testdatadbdataquery": { + "name": "TestDataDBDataQuery", + "pluralName": "TestDataDBDataQuerys", + "machineName": "testdatadbdataquery", + "pluralMachineName": "testdatadbdataquerys", + "lineageIsGroup": false, + "maturity": "planned", + "currentVersion": [ + 0, + 0 + ], + "schemaInterface": "DataQuery" + }, + "testdatadbdatasourcecfg": { + "name": "TestDataDBDataSourceCfg", + "pluralName": "TestDataDBDataSourceCfgs", + "machineName": "testdatadbdatasourcecfg", + "pluralMachineName": "testdatadbdatasourcecfgs", "lineageIsGroup": true, "maturity": "planned", "currentVersion": [ 0, 0 - ] + ], + "schemaInterface": "DataSourceCfg" }, - "testdata_dsoptions": { - "name": "Testdata-DSOptions", - "pluralName": "Testdata-DSOptionss", - "machineName": "testdata_dsoptions", - "pluralMachineName": "testdata_dsoptionss", + "textpanelcfg": { + "name": "TextPanelCfg", + "pluralName": "TextPanelCfgs", + "machineName": "textpanelcfg", + "pluralMachineName": "textpanelcfgs", "lineageIsGroup": true, - "maturity": "planned", + "maturity": "experimental", "currentVersion": [ 0, 0 - ] - }, - "testdata_query": { - "name": "Testdata-Query", - "pluralName": "Testdata-Querys", - "machineName": "testdata_query", - "pluralMachineName": "testdata_querys", - "lineageIsGroup": true, - "maturity": "planned", - "currentVersion": [ - 0, - 0 - ] - }, - "text_panel": { - "name": "Text-Panel", - "pluralName": "Text-Panels", - "machineName": "text_panel", - "pluralMachineName": "text_panels", - "lineageIsGroup": true, - "maturity": "merged", - "currentVersion": [ - 0, - 0 - ] + ], + "schemaInterface": "PanelCfg" }, "thumb": { "name": "Thumb", @@ -816,17 +874,18 @@ 0 ] }, - "traces_panel": { - "name": "Traces-Panel", - "pluralName": "Traces-Panels", - "machineName": "traces_panel", - "pluralMachineName": "traces_panels", + "tracespanelcfg": { + "name": "TracesPanelCfg", + "pluralName": "TracesPanelCfgs", + "machineName": "tracespanelcfg", + "pluralMachineName": "tracespanelcfgs", "lineageIsGroup": true, "maturity": "planned", "currentVersion": [ 0, 0 - ] + ], + "schemaInterface": "PanelCfg" }, "user": { "name": "User", @@ -840,53 +899,57 @@ 0 ] }, - "welcome_panel": { - "name": "Welcome-Panel", - "pluralName": "Welcome-Panels", - "machineName": "welcome_panel", - "pluralMachineName": "welcome_panels", + "welcomepanelcfg": { + "name": "WelcomePanelCfg", + "pluralName": "WelcomePanelCfgs", + "machineName": "welcomepanelcfg", + "pluralMachineName": "welcomepanelcfgs", "lineageIsGroup": true, "maturity": "planned", "currentVersion": [ 0, 0 - ] + ], + "schemaInterface": "PanelCfg" }, - "xychart_panel": { - "name": "Xychart-Panel", - "pluralName": "Xychart-Panels", - "machineName": "xychart_panel", - "pluralMachineName": "xychart_panels", + "xychartpanelcfg": { + "name": "XYChartPanelCfg", + "pluralName": "XYChartPanelCfgs", + "machineName": "xychartpanelcfg", + "pluralMachineName": "xychartpanelcfgs", "lineageIsGroup": true, "maturity": "planned", "currentVersion": [ 0, 0 - ] + ], + "schemaInterface": "PanelCfg" }, - "zipkin_dsoptions": { - "name": "Zipkin-DSOptions", - "pluralName": "Zipkin-DSOptionss", - "machineName": "zipkin_dsoptions", - "pluralMachineName": "zipkin_dsoptionss", - "lineageIsGroup": true, + "zipkindataquery": { + "name": "ZipkinDataQuery", + "pluralName": "ZipkinDataQuerys", + "machineName": "zipkindataquery", + "pluralMachineName": "zipkindataquerys", + "lineageIsGroup": false, "maturity": "planned", "currentVersion": [ 0, 0 - ] + ], + "schemaInterface": "DataQuery" }, - "zipkin_query": { - "name": "Zipkin-Query", - "pluralName": "Zipkin-Querys", - "machineName": "zipkin_query", - "pluralMachineName": "zipkin_querys", + "zipkindatasourcecfg": { + "name": "ZipkinDataSourceCfg", + "pluralName": "ZipkinDataSourceCfgs", + "machineName": "zipkindatasourcecfg", + "pluralMachineName": "zipkindatasourcecfgs", "lineageIsGroup": true, "maturity": "planned", "currentVersion": [ 0, 0 - ] + ], + "schemaInterface": "DataSourceCfg" } }, "dimensions": { @@ -894,69 +957,69 @@ "composable": { "name": "composable", "items": [ - "alertgroups_panel", - "alertlist_panel", - "alertmanager_dsoptions", - "alertmanager_query", - "annolist_panel", - "barchart_panel", - "bargauge_panel", - "cloudwatch_dsoptions", - "cloudwatch_query", - "dashboard_dsoptions", - "dashboard_query", - "dashlist_panel", - "debug_panel", - "elasticsearch_dsoptions", - "elasticsearch_query", - "flamegraph_panel", - "gauge_panel", - "geomap_panel", - "gettingstarted_panel", - "grafana_azure_monitor_datasource_dsoptions", - "grafana_azure_monitor_datasource_query", - "grafana_dsoptions", - "grafana_query", - "graph_panel", - "graphite_dsoptions", - "graphite_query", - "histogram_panel", - "icon_panel", - "jaeger_dsoptions", - "jaeger_query", - "live_panel", - "logs_panel", - "loki_dsoptions", - "loki_query", - "mssql_dsoptions", - "mssql_query", - "mysql_dsoptions", - "mysql_query", - "news_panel", - "nodegraph_panel", - "parca_dsoptions", - "parca_query", - "phlare_dsoptions", - "phlare_query", - "piechart_panel", - "postgres_dsoptions", - "postgres_query", - "prometheus_dsoptions", - "prometheus_query", - "stackdriver_dsoptions", - "stackdriver_query", - "stat_panel", - "table_old_panel", - "tempo_dsoptions", - "tempo_query", - "testdata_dsoptions", - "testdata_query", - "text_panel", - "traces_panel", - "welcome_panel", - "xychart_panel", - "zipkin_dsoptions", - "zipkin_query" + "alertgroupspanelcfg", + "alertlistpanelcfg", + "alertmanagerdataquery", + "alertmanagerdatasourcecfg", + "annotationslistpanelcfg", + "azuremonitordataquery", + "azuremonitordatasourcecfg", + "barchartpanelcfg", + "bargaugepanelcfg", + "cloudwatchdataquery", + "cloudwatchdatasourcecfg", + "dashboarddataquery", + "dashboarddatasourcecfg", + "dashboardlistpanelcfg", + "debugpanelcfg", + "elasticsearchdataquery", + "elasticsearchdatasourcecfg", + "flamegraphpanelcfg", + "gaugepanelcfg", + "geomappanelcfg", + "gettingstartedpanelcfg", + "googlecloudmonitoringdataquery", + "googlecloudmonitoringdatasourcecfg", + "grafanadataquery", + "grafanadatasourcecfg", + "graphitedataquery", + "graphitedatasourcecfg", + "grapholdpanelcfg", + "histogrampanelcfg", + "iconpanelcfg", + "jaegerdataquery", + "jaegerdatasourcecfg", + "livepanelcfg", + "logspanelcfg", + "lokidataquery", + "lokidatasourcecfg", + "microsoftsqlserverdataquery", + "microsoftsqlserverdatasourcecfg", + "mysqldataquery", + "mysqldatasourcecfg", + "newspanelcfg", + "nodegraphpanelcfg", + "parcadataquery", + "parcadatasourcecfg", + "phlaredataquery", + "phlaredatasourcecfg", + "piechartpanelcfg", + "postgresqldataquery", + "postgresqldatasourcecfg", + "prometheusdataquery", + "prometheusdatasourcecfg", + "statpanelcfg", + "tableoldpanelcfg", + "tempodataquery", + "tempodatasourcecfg", + "testdatadbdataquery", + "testdatadbdatasourcecfg", + "textpanelcfg", + "tracespanelcfg", + "welcomepanelcfg", + "xychartpanelcfg", + "zipkindataquery", + "zipkindatasourcecfg" ], "count": 63 }, @@ -982,9 +1045,19 @@ "experimental": { "name": "experimental", "items": [ - "dashboard" + "annotationslistpanelcfg", + "barchartpanelcfg", + "bargaugepanelcfg", + "dashboard", + "dashboardlistpanelcfg", + "gaugepanelcfg", + "histogrampanelcfg", + "newspanelcfg", + "piechartpanelcfg", + "statpanelcfg", + "textpanelcfg" ], - "count": 1 + "count": 11 }, "mature": { "name": "mature", @@ -994,85 +1067,75 @@ "merged": { "name": "merged", "items": [ - "annolist_panel", - "barchart_panel", - "bargauge_panel", - "dashlist_panel", - "gauge_panel", - "histogram_panel", - "news_panel", - "piechart_panel", "playlist", - "stat_panel", - "team", - "text_panel" + "team" ], - "count": 12 + "count": 2 }, "planned": { "name": "planned", "items": [ - "alertgroups_panel", - "alertlist_panel", - "alertmanager_dsoptions", - "alertmanager_query", + "alertgroupspanelcfg", + "alertlistpanelcfg", + "alertmanagerdataquery", + "alertmanagerdatasourcecfg", "apikey", - "cloudwatch_dsoptions", - "cloudwatch_query", - "dashboard_dsoptions", - "dashboard_query", + "azuremonitordataquery", + "azuremonitordatasourcecfg", + "cloudwatchdataquery", + "cloudwatchdatasourcecfg", + "dashboarddataquery", + "dashboarddatasourcecfg", "datasource", - "debug_panel", - "elasticsearch_dsoptions", - "elasticsearch_query", - "flamegraph_panel", + "debugpanelcfg", + "elasticsearchdataquery", + "elasticsearchdatasourcecfg", + "flamegraphpanelcfg", "folder", - "geomap_panel", - "gettingstarted_panel", - "grafana_azure_monitor_datasource_dsoptions", - "grafana_azure_monitor_datasource_query", - "grafana_dsoptions", - "grafana_query", - "graph_panel", - "graphite_dsoptions", - "graphite_query", - "icon_panel", - "jaeger_dsoptions", - "jaeger_query", - "live_panel", - "logs_panel", - "loki_dsoptions", - "loki_query", - "mssql_dsoptions", - "mssql_query", - "mysql_dsoptions", - "mysql_query", - "nodegraph_panel", - "parca_dsoptions", - "parca_query", - "phlare_dsoptions", - "phlare_query", - "postgres_dsoptions", - "postgres_query", - "prometheus_dsoptions", - "prometheus_query", + "geomappanelcfg", + "gettingstartedpanelcfg", + "googlecloudmonitoringdataquery", + "googlecloudmonitoringdatasourcecfg", + "grafanadataquery", + "grafanadatasourcecfg", + "graphitedataquery", + "graphitedatasourcecfg", + "grapholdpanelcfg", + "iconpanelcfg", + "jaegerdataquery", + "jaegerdatasourcecfg", + "livepanelcfg", + "logspanelcfg", + "lokidataquery", + "lokidatasourcecfg", + "microsoftsqlserverdataquery", + "microsoftsqlserverdatasourcecfg", + "mysqldataquery", + "mysqldatasourcecfg", + "nodegraphpanelcfg", + "parcadataquery", + "parcadatasourcecfg", + "phlaredataquery", + "phlaredatasourcecfg", + "postgresqldataquery", + "postgresqldatasourcecfg", + "prometheusdataquery", + "prometheusdatasourcecfg", "query", "queryhistory", "serviceaccount", - "stackdriver_dsoptions", - "stackdriver_query", - "table_old_panel", - "tempo_dsoptions", - "tempo_query", - "testdata_dsoptions", - "testdata_query", + "tableoldpanelcfg", + "tempodataquery", + "tempodatasourcecfg", + "testdatadbdataquery", + "testdatadbdatasourcecfg", "thumb", - "traces_panel", + "tracespanelcfg", "user", - "welcome_panel", - "xychart_panel", - "zipkin_dsoptions", - "zipkin_query" + "welcomepanelcfg", + "xychartpanelcfg", + "zipkindataquery", + "zipkindatasourcecfg" ], "count": 61 }, diff --git a/pkg/kindsys/schema_interface.cue b/pkg/kindsys/schema_interface.cue index 9c855884685..a2811b7dfe7 100644 --- a/pkg/kindsys/schema_interface.cue +++ b/pkg/kindsys/schema_interface.cue @@ -21,7 +21,7 @@ package kindsys // On the producer side, Grafana plugin authors may provide Thema lineages // within Composable kinds declared in .cue files adjacent to their // plugin.json, following a pattern (see -// github.com/grafana/grafana/pkg/plugins/pfs.#GrafanaPlugin.composableKinds) +// github.com/grafana/grafana/pkg/plugins/pfs.GrafanaPlugin.composableKinds) // corresponding to the name of the schema interface. Each such definition is // an answer to "what." // @@ -87,10 +87,14 @@ SchemaInterface: { group: bool | *true } +// alias the exported type because DataQuery is shadowed by the schema interface +// name where we need to use the type +let dq = DataQuery + // The canonical list of all Grafana schema interfaces. schemaInterfaces: [N=string]: SchemaInterface & { name: N } schemaInterfaces: { - Panel: { + PanelCfg: { interface: { // Defines plugin-specific options for a panel that should be persisted. Required, // though a panel without any options may specify an empty struct. @@ -109,22 +113,25 @@ schemaInterfaces: { // grouped b/c separate non-cross-referring elements always occur together in larger structure (panel) group: true } - Query: { - // The contract for the queries schema interface is itself a pattern: - // Each of its top-level fields must be represent a distinct query type for - // the datasource plugin. The queryType field acts as a discriminator, and - // is constrained to be the same as the name of the top-level field declaring it. - interface: [QT=string]: { - queryType?: QT + + // The DataQuery schema interface specifies how (datasource) plugins are expected to define + // the shape of their queries. + // + // It is expected that plugins may support multiple logically distinct query types within + // their single DataQuery composable kind. Implementations are generally free to model + // this as they please, with understanding that Grafana systems will look to the queryType + // field as a discriminator - each distinct value will be assumed, where possible, to + // identify a distinct type of query supported by the plugin. + DataQuery: { + interface: { + dq } pluginTypes: ["datasource"] - - // grouped b/c separate, non-cross-referring elements are actually themselves each impls of the concept - // and it avoids us having to put more levels in the slot system (uggghhh) - group: true + group: false } - DSOptions: { + + DataSourceCfg: { interface: { // Normal datasource configuration options. Options: {} diff --git a/pkg/plugins/codegen/jenny_plugintreelist.go b/pkg/plugins/codegen/jenny_plugintreelist.go index 35f44b5e273..fe737720434 100644 --- a/pkg/plugins/codegen/jenny_plugintreelist.go +++ b/pkg/plugins/codegen/jenny_plugintreelist.go @@ -14,7 +14,7 @@ import ( const prefix = "github.com/grafana/grafana/public/app/plugins" // PluginTreeListJenny creates a [codejen.ManyToOne] that produces Go code -// for loading a [pfs.TreeList] given [*kindsys.PluginDecl] as inputs. +// for loading a [pfs.PluginList] given [*kindsys.PluginDecl] as inputs. func PluginTreeListJenny() codejen.ManyToOne[*pfs.PluginDecl] { outputFile := filepath.Join("pkg", "plugins", "pfs", "corelist", "corelist_load_gen.go") diff --git a/pkg/plugins/codegen/tmpl/plugin_registry.tmpl b/pkg/plugins/codegen/tmpl/plugin_registry.tmpl index 79d80a73cfe..26ef440e02b 100644 --- a/pkg/plugins/codegen/tmpl/plugin_registry.tmpl +++ b/pkg/plugins/codegen/tmpl/plugin_registry.tmpl @@ -9,22 +9,22 @@ import ( "github.com/grafana/thema" ) -func makeTreeOrPanic(path string, pkgname string, rt *thema.Runtime) *pfs.Tree { +func parsePluginOrPanic(path string, pkgname string, rt *thema.Runtime) pfs.ParsedPlugin { sub, err := fs.Sub(grafana.CueSchemaFS, path) if err != nil { panic("could not create fs sub to " + path) } - tree, err := pfs.ParsePluginFS(sub, rt) + pp, err := pfs.ParsePluginFS(sub, rt) if err != nil { panic(fmt.Sprintf("error parsing plugin metadata for %s: %s", pkgname, err)) } - return tree + return pp } -func coreTreeList(rt *thema.Runtime) pfs.TreeList{ - return pfs.TreeList{ +func corePlugins(rt *thema.Runtime) []pfs.ParsedPlugin{ + return []pfs.ParsedPlugin{ {{- range .Plugins }} - makeTreeOrPanic("{{ .Path }}", "{{ .PkgName }}", rt), + parsePluginOrPanic("{{ .Path }}", "{{ .PkgName }}", rt), {{- end }} } } diff --git a/pkg/plugins/codegen/util_ts.go b/pkg/plugins/codegen/util_ts.go index 860d4ffd142..477aa07899d 100644 --- a/pkg/plugins/codegen/util_ts.go +++ b/pkg/plugins/codegen/util_ts.go @@ -14,7 +14,10 @@ import ( // indicates the import path should be dropped in the conversion to TS. Imports // not present in the list are not not allowed, and code generation will fail. var importMap = map[string]string{ - "github.com/grafana/thema": "", + "github.com/grafana/thema": "", + + "github.com/grafana/grafana/pkg/kindsys": "", + "github.com/grafana/grafana/pkg/plugins/pfs": "", "github.com/grafana/grafana/packages/grafana-schema/src/common": "@grafana/schema", } diff --git a/pkg/plugins/manager/testdata/disallowed-cue-import/composable.cue b/pkg/plugins/manager/testdata/disallowed-cue-import/composable.cue new file mode 100644 index 00000000000..9860217cda5 --- /dev/null +++ b/pkg/plugins/manager/testdata/disallowed-cue-import/composable.cue @@ -0,0 +1,25 @@ +package grafanaplugin + +import ( + "github.com/grafana/thema" + "github.com/grafana/grafana/kinds/dashboard:kind" +) + +_dummy: coremodel.slots + +composableKinds: PanelCfg: { + lineage: { + name: "disallowed_cue_import" + seqs: [ + { + schemas: [ + { + PanelOptions: { + foo: string + } @cuetsy(kind="interface") + }, + ] + }, + ] + } +} diff --git a/pkg/plugins/manager/testdata/disallowed-cue-import/models.cue b/pkg/plugins/manager/testdata/disallowed-cue-import/models.cue deleted file mode 100644 index f64db9cbda4..00000000000 --- a/pkg/plugins/manager/testdata/disallowed-cue-import/models.cue +++ /dev/null @@ -1,23 +0,0 @@ -package grafanaplugin - -import ( - "github.com/grafana/thema" - "github.com/grafana/grafana/kinds/dashboard:kind" -) - -_dummy: coremodel.slots - -Panel: thema.#Lineage & { - name: "disallowed_cue_import" - seqs: [ - { - schemas: [ - { - PanelOptions: { - foo: string - } @cuetsy(kind="interface") - }, - ] - }, - ] -} diff --git a/pkg/plugins/manager/testdata/mismatch/plugin.json b/pkg/plugins/manager/testdata/mismatch/plugin.json deleted file mode 100644 index 7231ca93492..00000000000 --- a/pkg/plugins/manager/testdata/mismatch/plugin.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "type": "panel", - "name": "Slot impl testing", - "id": "mismatch-panel", - "backend": true, - "state": "alpha", - "info": { - "description": "Test", - "author": { - "name": "Grafana Labs", - "url": "https://grafana.com" - } - } -} diff --git a/pkg/plugins/manager/testdata/missing-kind-datasource/models.cue b/pkg/plugins/manager/testdata/missing-kind-datasource/composable_dataquery.cue similarity index 50% rename from pkg/plugins/manager/testdata/missing-kind-datasource/models.cue rename to pkg/plugins/manager/testdata/missing-kind-datasource/composable_dataquery.cue index 7adee7855f3..a439bb18469 100644 --- a/pkg/plugins/manager/testdata/missing-kind-datasource/models.cue +++ b/pkg/plugins/manager/testdata/missing-kind-datasource/composable_dataquery.cue @@ -1,9 +1,6 @@ package grafanaplugin -import "github.com/grafana/thema" - -Query: thema.#Lineage & { - name: "missing_kind_datasource" +composableKinds: DataQuery: lineage: { seqs: [ { schemas: [ diff --git a/pkg/plugins/manager/testdata/mismatch/models.cue b/pkg/plugins/manager/testdata/name-mismatch-panel/composable_panelcfg.cue similarity index 73% rename from pkg/plugins/manager/testdata/mismatch/models.cue rename to pkg/plugins/manager/testdata/name-mismatch-panel/composable_panelcfg.cue index 08266f79747..afa95827f18 100644 --- a/pkg/plugins/manager/testdata/mismatch/models.cue +++ b/pkg/plugins/manager/testdata/name-mismatch-panel/composable_panelcfg.cue @@ -1,8 +1,6 @@ package grafanaplugin -import "github.com/grafana/thema" - -Panel: thema.#Lineage & { +composableKinds: PanelCfg: lineage: { name: "doesnamatch" seqs: [ { diff --git a/pkg/plugins/manager/testdata/name-id-mismatch/plugin.json b/pkg/plugins/manager/testdata/name-mismatch-panel/plugin.json similarity index 100% rename from pkg/plugins/manager/testdata/name-id-mismatch/plugin.json rename to pkg/plugins/manager/testdata/name-mismatch-panel/plugin.json diff --git a/pkg/plugins/manager/testdata/panel-conflicting-joinschema/composable.cue b/pkg/plugins/manager/testdata/panel-conflicting-joinschema/composable.cue new file mode 100644 index 00000000000..d93b7e57c78 --- /dev/null +++ b/pkg/plugins/manager/testdata/panel-conflicting-joinschema/composable.cue @@ -0,0 +1,25 @@ +package grafanaplugin + +import "github.com/grafana/thema" + +composableKinds: PanelCfg: { + lineage: { + joinSchema: { + PanelOptions: {...} + PanelFieldConfig: string + } + name: "panel_conflicting_joinschema" + seqs: [ + { + schemas: [ + { + PanelOptions: { + foo: string + } @cuetsy(kind="interface") + PanelFieldConfig: string + }, + ] + }, + ] + } +} diff --git a/pkg/plugins/manager/testdata/panel-conflicting-joinschema/models.cue b/pkg/plugins/manager/testdata/panel-conflicting-joinschema/models.cue deleted file mode 100644 index ee7058de418..00000000000 --- a/pkg/plugins/manager/testdata/panel-conflicting-joinschema/models.cue +++ /dev/null @@ -1,23 +0,0 @@ -package grafanaplugin - -import "github.com/grafana/thema" - -Panel: thema.#Lineage & { - joinSchema: { - PanelOptions: {...} - PanelFieldConfig: string - } - name: "panel_conflicting_joinschema" - seqs: [ - { - schemas: [ - { - PanelOptions: { - foo: string - } @cuetsy(kind="interface") - PanelFieldConfig: string - }, - ] - }, - ] -} diff --git a/pkg/plugins/manager/testdata/panel-does-not-follow-slot-joinschema/composable.cue b/pkg/plugins/manager/testdata/panel-does-not-follow-slot-joinschema/composable.cue new file mode 100644 index 00000000000..9c504db59e6 --- /dev/null +++ b/pkg/plugins/manager/testdata/panel-does-not-follow-slot-joinschema/composable.cue @@ -0,0 +1,21 @@ +package grafanaplugin + +import "github.com/grafana/thema" + +composableKinds: PanelCfg: { + lineage: { + name: "panel_does_not_follow_slot_joinschema" + seqs: [ + { + schemas: [ + { + PanelOptions: { + foo: string + } @cuetsy(kind="interface") + PanelFieldConfig: string + }, + ] + }, + ] + } +} diff --git a/pkg/plugins/manager/testdata/panel-does-not-follow-slot-joinschema/models.cue b/pkg/plugins/manager/testdata/panel-does-not-follow-slot-joinschema/models.cue deleted file mode 100644 index afda6245cea..00000000000 --- a/pkg/plugins/manager/testdata/panel-does-not-follow-slot-joinschema/models.cue +++ /dev/null @@ -1,19 +0,0 @@ -package grafanaplugin - -import "github.com/grafana/thema" - -Panel: thema.#Lineage & { - name: "panel_does_not_follow_slot_joinschema" - seqs: [ - { - schemas: [ - { - PanelOptions: { - foo: string - } @cuetsy(kind="interface") - PanelFieldConfig: string - }, - ] - }, - ] -} diff --git a/pkg/plugins/manager/testdata/valid-model-datasource/composable_dataquery.cue b/pkg/plugins/manager/testdata/valid-model-datasource/composable_dataquery.cue new file mode 100644 index 00000000000..a439bb18469 --- /dev/null +++ b/pkg/plugins/manager/testdata/valid-model-datasource/composable_dataquery.cue @@ -0,0 +1,13 @@ +package grafanaplugin + +composableKinds: DataQuery: lineage: { + seqs: [ + { + schemas: [ + { + foo: string + }, + ] + }, + ] +} diff --git a/pkg/plugins/manager/testdata/valid-model-datasource/composable_datasourcecfg.cue b/pkg/plugins/manager/testdata/valid-model-datasource/composable_datasourcecfg.cue new file mode 100644 index 00000000000..b51d0852dac --- /dev/null +++ b/pkg/plugins/manager/testdata/valid-model-datasource/composable_datasourcecfg.cue @@ -0,0 +1,18 @@ +package grafanaplugin + +composableKinds: DataSourceCfg: lineage: { + seqs: [ + { + schemas: [ + { + Options: { + foo: string + } + SecureOptions: { + bar: string + } + }, + ] + }, + ] +} diff --git a/pkg/plugins/manager/testdata/valid-model-datasource/models.cue b/pkg/plugins/manager/testdata/valid-model-datasource/models.cue deleted file mode 100644 index b0dc27a1c69..00000000000 --- a/pkg/plugins/manager/testdata/valid-model-datasource/models.cue +++ /dev/null @@ -1,34 +0,0 @@ -package grafanaplugin - -import "github.com/grafana/thema" - -Query: thema.#Lineage & { - name: "valid_model_datasource" - seqs: [ - { - schemas: [ - { - foo: string - }, - ] - }, - ] -} - -DSOptions: thema.#Lineage & { - name: "valid_model_datasource" - seqs: [ - { - schemas: [ - { - Options: { - foo: string - } - SecureOptions: { - bar: string - } - }, - ] - }, - ] -} diff --git a/pkg/plugins/manager/testdata/name-id-mismatch/models.cue b/pkg/plugins/manager/testdata/valid-model-panel/composable.cue similarity index 65% rename from pkg/plugins/manager/testdata/name-id-mismatch/models.cue rename to pkg/plugins/manager/testdata/valid-model-panel/composable.cue index d75e5370e33..a33fdf7cc19 100644 --- a/pkg/plugins/manager/testdata/name-id-mismatch/models.cue +++ b/pkg/plugins/manager/testdata/valid-model-panel/composable.cue @@ -1,9 +1,6 @@ package grafanaplugin -import "github.com/grafana/thema" - -Panel: thema.#Lineage & { - name: "mismatch" +composableKinds: PanelCfg: lineage: { seqs: [ { schemas: [ diff --git a/pkg/plugins/manager/testdata/valid-model-panel/models.cue b/pkg/plugins/manager/testdata/valid-model-panel/models.cue deleted file mode 100644 index bda82c8db3b..00000000000 --- a/pkg/plugins/manager/testdata/valid-model-panel/models.cue +++ /dev/null @@ -1,18 +0,0 @@ -package grafanaplugin - -import "github.com/grafana/thema" - -Panel: thema.#Lineage & { - name: "valid_model_panel" - seqs: [ - { - schemas: [ - { - PanelOptions: { - foo: string - } @cuetsy(kind="interface") - }, - ] - }, - ] -} diff --git a/pkg/plugins/manager/testdata/wrong-slot-panel/models.cue b/pkg/plugins/manager/testdata/wrong-slot-panel/models.cue deleted file mode 100644 index 0c155a436f0..00000000000 --- a/pkg/plugins/manager/testdata/wrong-slot-panel/models.cue +++ /dev/null @@ -1,31 +0,0 @@ -package grafanaplugin - -import "github.com/grafana/thema" - -Query: thema.#Lineage & { - name: "wrong_slot_panel" - seqs: [ - { - schemas: [ - { - foo: string - }, - ] - }, - ] -} - -Panel: thema.#Lineage & { - name: "wrong_slot_panel" - seqs: [ - { - schemas: [ - { - PanelOptions: { - foo: string - } @cuetsy(kind="interface") - }, - ] - }, - ] -} diff --git a/pkg/plugins/manager/testdata/wrong-slot-panel/plugin.json b/pkg/plugins/manager/testdata/wrong-slot-panel/plugin.json deleted file mode 100644 index 70423da62c2..00000000000 --- a/pkg/plugins/manager/testdata/wrong-slot-panel/plugin.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "type": "panel", - "name": "Wrong slot for type", - "id": "wrong-slot-panel", - "backend": true, - "state": "alpha", - "info": { - "description": "Test", - "author": { - "name": "Grafana Labs", - "url": "https://grafana.com" - } - } -} diff --git a/pkg/plugins/pfs/corelist/corelist.go b/pkg/plugins/pfs/corelist/corelist.go index bbc6aa50a32..5113e3e63ab 100644 --- a/pkg/plugins/pfs/corelist/corelist.go +++ b/pkg/plugins/pfs/corelist/corelist.go @@ -8,23 +8,23 @@ import ( "github.com/grafana/thema" ) -var coreTrees pfs.TreeList +var coreTrees []pfs.ParsedPlugin var coreOnce sync.Once -// New returns a pfs.TreeList containing the plugin trees for all core plugins +// New returns a pfs.PluginList containing the plugin trees for all core plugins // in the current version of Grafana. // // Go code within the grafana codebase should only ever call this with nil. -func New(rt *thema.Runtime) pfs.TreeList { - var tl pfs.TreeList +func New(rt *thema.Runtime) []pfs.ParsedPlugin { + var pl []pfs.ParsedPlugin if rt == nil { coreOnce.Do(func() { - coreTrees = coreTreeList(cuectx.GrafanaThemaRuntime()) + coreTrees = corePlugins(cuectx.GrafanaThemaRuntime()) }) - tl = make(pfs.TreeList, len(coreTrees)) - copy(tl, coreTrees) + pl = make([]pfs.ParsedPlugin, len(coreTrees)) + copy(pl, coreTrees) } else { - return coreTreeList(rt) + return corePlugins(rt) } - return tl + return pl } diff --git a/pkg/plugins/pfs/corelist/corelist_load_gen.go b/pkg/plugins/pfs/corelist/corelist_load_gen.go index 8e0efb4c7d7..77b8a15304c 100644 --- a/pkg/plugins/pfs/corelist/corelist_load_gen.go +++ b/pkg/plugins/pfs/corelist/corelist_load_gen.go @@ -18,63 +18,63 @@ import ( "github.com/grafana/thema" ) -func makeTreeOrPanic(path string, pkgname string, rt *thema.Runtime) *pfs.Tree { +func parsePluginOrPanic(path string, pkgname string, rt *thema.Runtime) pfs.ParsedPlugin { sub, err := fs.Sub(grafana.CueSchemaFS, path) if err != nil { panic("could not create fs sub to " + path) } - tree, err := pfs.ParsePluginFS(sub, rt) + pp, err := pfs.ParsePluginFS(sub, rt) if err != nil { panic(fmt.Sprintf("error parsing plugin metadata for %s: %s", pkgname, err)) } - return tree + return pp } -func coreTreeList(rt *thema.Runtime) pfs.TreeList { - return pfs.TreeList{ - makeTreeOrPanic("public/app/plugins/datasource/alertmanager", "alertmanager", rt), - makeTreeOrPanic("public/app/plugins/datasource/cloud-monitoring", "stackdriver", rt), - makeTreeOrPanic("public/app/plugins/datasource/cloudwatch", "cloudwatch", rt), - makeTreeOrPanic("public/app/plugins/datasource/dashboard", "dashboard", rt), - makeTreeOrPanic("public/app/plugins/datasource/elasticsearch", "elasticsearch", rt), - makeTreeOrPanic("public/app/plugins/datasource/grafana", "grafana", rt), - makeTreeOrPanic("public/app/plugins/datasource/grafana-azure-monitor-datasource", "grafana_azure_monitor_datasource", rt), - makeTreeOrPanic("public/app/plugins/datasource/graphite", "graphite", rt), - makeTreeOrPanic("public/app/plugins/datasource/jaeger", "jaeger", rt), - makeTreeOrPanic("public/app/plugins/datasource/loki", "loki", rt), - makeTreeOrPanic("public/app/plugins/datasource/mssql", "mssql", rt), - makeTreeOrPanic("public/app/plugins/datasource/mysql", "mysql", rt), - makeTreeOrPanic("public/app/plugins/datasource/parca", "parca", rt), - makeTreeOrPanic("public/app/plugins/datasource/phlare", "phlare", rt), - makeTreeOrPanic("public/app/plugins/datasource/postgres", "postgres", rt), - makeTreeOrPanic("public/app/plugins/datasource/prometheus", "prometheus", rt), - makeTreeOrPanic("public/app/plugins/datasource/tempo", "tempo", rt), - makeTreeOrPanic("public/app/plugins/datasource/testdata", "testdata", rt), - makeTreeOrPanic("public/app/plugins/datasource/zipkin", "zipkin", rt), - makeTreeOrPanic("public/app/plugins/panel/alertGroups", "alertGroups", rt), - makeTreeOrPanic("public/app/plugins/panel/alertlist", "alertlist", rt), - makeTreeOrPanic("public/app/plugins/panel/annolist", "annolist", rt), - makeTreeOrPanic("public/app/plugins/panel/barchart", "barchart", rt), - makeTreeOrPanic("public/app/plugins/panel/bargauge", "bargauge", rt), - makeTreeOrPanic("public/app/plugins/panel/dashlist", "dashlist", rt), - makeTreeOrPanic("public/app/plugins/panel/debug", "debug", rt), - makeTreeOrPanic("public/app/plugins/panel/flamegraph", "flamegraph", rt), - makeTreeOrPanic("public/app/plugins/panel/gauge", "gauge", rt), - makeTreeOrPanic("public/app/plugins/panel/geomap", "geomap", rt), - makeTreeOrPanic("public/app/plugins/panel/gettingstarted", "gettingstarted", rt), - makeTreeOrPanic("public/app/plugins/panel/graph", "graph", rt), - makeTreeOrPanic("public/app/plugins/panel/histogram", "histogram", rt), - makeTreeOrPanic("public/app/plugins/panel/icon", "icon", rt), - makeTreeOrPanic("public/app/plugins/panel/live", "live", rt), - makeTreeOrPanic("public/app/plugins/panel/logs", "logs", rt), - makeTreeOrPanic("public/app/plugins/panel/news", "news", rt), - makeTreeOrPanic("public/app/plugins/panel/nodeGraph", "nodeGraph", rt), - makeTreeOrPanic("public/app/plugins/panel/piechart", "piechart", rt), - makeTreeOrPanic("public/app/plugins/panel/stat", "stat", rt), - makeTreeOrPanic("public/app/plugins/panel/table-old", "table_old", rt), - makeTreeOrPanic("public/app/plugins/panel/text", "text", rt), - makeTreeOrPanic("public/app/plugins/panel/traces", "traces", rt), - makeTreeOrPanic("public/app/plugins/panel/welcome", "welcome", rt), - makeTreeOrPanic("public/app/plugins/panel/xychart", "xychart", rt), +func corePlugins(rt *thema.Runtime) []pfs.ParsedPlugin { + return []pfs.ParsedPlugin{ + parsePluginOrPanic("public/app/plugins/datasource/alertmanager", "alertmanager", rt), + parsePluginOrPanic("public/app/plugins/datasource/cloud-monitoring", "stackdriver", rt), + parsePluginOrPanic("public/app/plugins/datasource/cloudwatch", "cloudwatch", rt), + parsePluginOrPanic("public/app/plugins/datasource/dashboard", "dashboard", rt), + parsePluginOrPanic("public/app/plugins/datasource/elasticsearch", "elasticsearch", rt), + parsePluginOrPanic("public/app/plugins/datasource/grafana", "grafana", rt), + parsePluginOrPanic("public/app/plugins/datasource/grafana-azure-monitor-datasource", "grafana_azure_monitor_datasource", rt), + parsePluginOrPanic("public/app/plugins/datasource/graphite", "graphite", rt), + parsePluginOrPanic("public/app/plugins/datasource/jaeger", "jaeger", rt), + parsePluginOrPanic("public/app/plugins/datasource/loki", "loki", rt), + parsePluginOrPanic("public/app/plugins/datasource/mssql", "mssql", rt), + parsePluginOrPanic("public/app/plugins/datasource/mysql", "mysql", rt), + parsePluginOrPanic("public/app/plugins/datasource/parca", "parca", rt), + parsePluginOrPanic("public/app/plugins/datasource/phlare", "phlare", rt), + parsePluginOrPanic("public/app/plugins/datasource/postgres", "postgres", rt), + parsePluginOrPanic("public/app/plugins/datasource/prometheus", "prometheus", rt), + parsePluginOrPanic("public/app/plugins/datasource/tempo", "tempo", rt), + parsePluginOrPanic("public/app/plugins/datasource/testdata", "testdata", rt), + parsePluginOrPanic("public/app/plugins/datasource/zipkin", "zipkin", rt), + parsePluginOrPanic("public/app/plugins/panel/alertGroups", "alertGroups", rt), + parsePluginOrPanic("public/app/plugins/panel/alertlist", "alertlist", rt), + parsePluginOrPanic("public/app/plugins/panel/annolist", "annolist", rt), + parsePluginOrPanic("public/app/plugins/panel/barchart", "barchart", rt), + parsePluginOrPanic("public/app/plugins/panel/bargauge", "bargauge", rt), + parsePluginOrPanic("public/app/plugins/panel/dashlist", "dashlist", rt), + parsePluginOrPanic("public/app/plugins/panel/debug", "debug", rt), + parsePluginOrPanic("public/app/plugins/panel/flamegraph", "flamegraph", rt), + parsePluginOrPanic("public/app/plugins/panel/gauge", "gauge", rt), + parsePluginOrPanic("public/app/plugins/panel/geomap", "geomap", rt), + parsePluginOrPanic("public/app/plugins/panel/gettingstarted", "gettingstarted", rt), + parsePluginOrPanic("public/app/plugins/panel/graph", "graph", rt), + parsePluginOrPanic("public/app/plugins/panel/histogram", "histogram", rt), + parsePluginOrPanic("public/app/plugins/panel/icon", "icon", rt), + parsePluginOrPanic("public/app/plugins/panel/live", "live", rt), + parsePluginOrPanic("public/app/plugins/panel/logs", "logs", rt), + parsePluginOrPanic("public/app/plugins/panel/news", "news", rt), + parsePluginOrPanic("public/app/plugins/panel/nodeGraph", "nodeGraph", rt), + parsePluginOrPanic("public/app/plugins/panel/piechart", "piechart", rt), + parsePluginOrPanic("public/app/plugins/panel/stat", "stat", rt), + parsePluginOrPanic("public/app/plugins/panel/table-old", "table_old", rt), + parsePluginOrPanic("public/app/plugins/panel/text", "text", rt), + parsePluginOrPanic("public/app/plugins/panel/traces", "traces", rt), + parsePluginOrPanic("public/app/plugins/panel/welcome", "welcome", rt), + parsePluginOrPanic("public/app/plugins/panel/xychart", "xychart", rt), } } diff --git a/pkg/plugins/pfs/decl_parser.go b/pkg/plugins/pfs/decl_parser.go index 0e5a94591f1..db6f9f436ec 100644 --- a/pkg/plugins/pfs/decl_parser.go +++ b/pkg/plugins/pfs/decl_parser.go @@ -3,7 +3,6 @@ package pfs import ( "fmt" "io/fs" - "log" "os" "path/filepath" "sort" @@ -24,7 +23,9 @@ func NewDeclParser(rt *thema.Runtime, skip map[string]bool) *declParser { } } +// TODO convert this to be the new parser for Tree func (psr *declParser) Parse(root fs.FS) ([]*PluginDecl, error) { + // TODO remove hardcoded tree structure assumption, work from root of provided fs plugins, err := fs.Glob(root, "**/**/plugin.json") if err != nil { return nil, fmt.Errorf("error finding plugin dirs: %w", err) @@ -39,31 +40,26 @@ func (psr *declParser) Parse(root fs.FS) ([]*PluginDecl, error) { } dir := os.DirFS(path) - ptree, err := ParsePluginFS(dir, psr.rt) + pp, err := ParsePluginFS(dir, psr.rt) if err != nil { - log.Println(fmt.Errorf("parsing plugin failed for %s: %s", dir, err)) + return nil, fmt.Errorf("parsing plugin failed for %s: %s", dir, err) + } + + if len(pp.ComposableKinds) == 0 { + decls = append(decls, EmptyPluginDecl(path, pp.Properties)) continue } - p := ptree.RootPlugin() - slots := p.SlotImplementations() - - if len(slots) == 0 { - decls = append(decls, EmptyPluginDecl(path, p.Meta())) - continue - } - - for slotName, lin := range slots { + for slotName, kind := range pp.ComposableKinds { slot, err := kindsys.FindSchemaInterface(slotName) if err != nil { - log.Println(fmt.Errorf("parsing plugin failed for %s: %s", dir, err)) - continue + return nil, fmt.Errorf("parsing plugin failed for %s: %s", dir, err) } decls = append(decls, &PluginDecl{ SchemaInterface: &slot, - Lineage: lin, - Imports: p.CUEImports(), - PluginMeta: p.Meta(), + Lineage: kind.Lineage(), + Imports: pp.CUEImports, + PluginMeta: pp.Properties, PluginPath: path, }) } diff --git a/pkg/plugins/pfs/doc.go b/pkg/plugins/pfs/doc.go index 3ff6e05f44a..c95f398102f 100644 --- a/pkg/plugins/pfs/doc.go +++ b/pkg/plugins/pfs/doc.go @@ -1,3 +1,3 @@ -// Package pfs ("Plugin FS") defines a virtual filesystem representation of Grafana plugins. +// Package pfs ("ParsedPlugin FS") defines a virtual filesystem representation of Grafana plugins. package pfs diff --git a/pkg/plugins/pfs/errors.go b/pkg/plugins/pfs/errors.go index 4885d13f1e3..a65c588a0d7 100644 --- a/pkg/plugins/pfs/errors.go +++ b/pkg/plugins/pfs/errors.go @@ -11,23 +11,26 @@ var ErrNoRootFile = errors.New("no plugin.json at root of fs.fS") // ErrInvalidRootFile indicates that the root plugin.json file is invalid. var ErrInvalidRootFile = errors.New("plugin.json is invalid") -// ErrImplementedSlots indicates that a plugin has implemented the wrong set of -// slots for its type in models.cue. Either: -// - A slot is implemented that is not allowed for its type (e.g. datasource plugin implements Panel) -// - A required slot for its type is not implemented (e.g. panel plugin does not implemented Panel) -var ErrImplementedSlots = errors.New("slot implementation not allowed for this plugin type") +// ErrComposableNotExpected indicates that a plugin has a composable kind for a +// schema interface that is not expected, given the type of the plugin. (For +// example, a datasource plugin has a panelcfg composable kind) +var ErrComposableNotExpected = errors.New("plugin type should not produce composable kind for schema interface") -// ErrInvalidCUE indicates that a plugin's model.cue file contained invalid CUE. -var ErrInvalidCUE = errors.New("CUE syntax error") +// ErrExpectedComposable indicates that a plugin lacks a composable kind +// implementation for a schema interface that is expected for that plugin's +// type. (For example, a datasource plugin lacks a queries composable kind) +var ErrExpectedComposable = errors.New("plugin type should produce composable kind for schema interface") + +// ErrInvalidGrafanaPluginInstance indicates a plugin's set of .cue +// grafanaplugin package files are invalid with respect to the GrafanaPlugin +// spec. +var ErrInvalidGrafanaPluginInstance = errors.New("grafanaplugin cue instance is invalid") // ErrInvalidLineage indicates that the plugin contains an invalid lineage // declaration, according to Thema's validation rules in // ["github.com/grafana/thema".BindLineage]. var ErrInvalidLineage = errors.New("invalid lineage") -// ErrLineageNameMismatch indicates a plugin slot lineage name did not match the id of the plugin. -var ErrLineageNameMismatch = errors.New("lineage name not the same as plugin id") - -// ErrDisallowedCUEImport indicates that a plugin's models.cue file imports a -// CUE package that is not on the whitelist for safe imports. +// ErrDisallowedCUEImport indicates that a plugin's grafanaplugin cue package +// contains that are not on the allowlist. var ErrDisallowedCUEImport = errors.New("CUE import is not allowed") diff --git a/pkg/plugins/pfs/grafanaplugin.cue b/pkg/plugins/pfs/grafanaplugin.cue new file mode 100644 index 00000000000..fa839bb1a5c --- /dev/null +++ b/pkg/plugins/pfs/grafanaplugin.cue @@ -0,0 +1,31 @@ +package pfs + +import ( + "github.com/grafana/grafana/pkg/kindsys" +) + +// GrafanaPlugin specifies what plugins may declare in .cue files in a +// `grafanaplugin` CUE package in the plugin root directory (adjacent to plugin.json). +GrafanaPlugin: { + // id and pascalName are injected from plugin.json. Plugin authors can write + // values for them in .cue files, but the only valid values will be the ones + // given in plugin.json. + id: string + pascalName: string + + // A plugin defines its Composable kinds under this key. + // + // This struct is open for forwards compatibility - older versions of Grafana (or + // dependent tooling) should not break if new versions introduce additional schema interfaces. + composableKinds?: [Iface=string]: kindsys.Composable & { + name: pascalName + Iface + schemaInterface: Iface + lineage: name: pascalName + Iface + } + + // A plugin defines its Custom kinds under this key. + customKinds?: [Name=string]: kindsys.Custom & { + name: Name + } + ... +} diff --git a/pkg/plugins/pfs/pfs.go b/pkg/plugins/pfs/pfs.go index e892ea86484..f50aca6658e 100644 --- a/pkg/plugins/pfs/pfs.go +++ b/pkg/plugins/pfs/pfs.go @@ -3,14 +3,19 @@ package pfs import ( "fmt" "io/fs" + "path/filepath" "sort" "strings" + "sync" + "testing/fstest" "cuelang.org/go/cue" - "cuelang.org/go/cue/ast" + "cuelang.org/go/cue/build" + "cuelang.org/go/cue/cuecontext" "cuelang.org/go/cue/errors" "cuelang.org/go/cue/parser" - "github.com/grafana/grafana" + "cuelang.org/go/cue/token" + "github.com/grafana/grafana/pkg/cuectx" "github.com/grafana/grafana/pkg/kindsys" "github.com/grafana/grafana/pkg/plugins/plugindef" "github.com/grafana/thema" @@ -19,13 +24,41 @@ import ( "github.com/yalue/merged_fs" ) -// PermittedCUEImports returns the list of packages that may be imported in a -// plugin models.cue file. +// PackageName is the name of the CUE package that Grafana will load when +// looking for a Grafana plugin's kind declarations. +const PackageName = "grafanaplugin" + +var onceGP sync.Once +var defaultGP cue.Value + +func doLoadGP(ctx *cue.Context) cue.Value { + v, err := cuectx.BuildGrafanaInstance(ctx, filepath.Join("pkg", "plugins", "pfs"), "pfs", nil) + if err != nil { + // should be unreachable + panic(err) + } + return v.LookupPath(cue.MakePath(cue.Str("GrafanaPlugin"))) +} + +func loadGP(ctx *cue.Context) cue.Value { + if ctx == nil || ctx == cuectx.GrafanaCUEContext() { + onceGP.Do(func() { + defaultGP = doLoadGP(ctx) + }) + return defaultGP + } + return doLoadGP(ctx) +} + +// PermittedCUEImports returns the list of import paths that may be used in a +// plugin's grafanaplugin cue package. // // TODO probably move this into kindsys func PermittedCUEImports() []string { return []string{ "github.com/grafana/thema", + "github.com/grafana/grafana/pkg/kindsys", + "github.com/grafana/grafana/pkg/plugins/pfs", "github.com/grafana/grafana/packages/grafana-schema/src/common", } } @@ -41,12 +74,7 @@ func importAllowed(path string) bool { var allowedImportsStr string -type slotandname struct { - name string - slot kindsys.SchemaInterface -} - -var allslots []slotandname +var allsi []kindsys.SchemaInterface func init() { all := make([]string, 0, len(PermittedCUEImports())) @@ -55,268 +83,168 @@ func init() { } allowedImportsStr = strings.Join(all, "\n") - for n, s := range kindsys.SchemaInterfaces(nil) { - allslots = append(allslots, slotandname{ - name: n, - slot: s, - }) + for _, s := range kindsys.SchemaInterfaces(nil) { + allsi = append(allsi, s) } - sort.Slice(allslots, func(i, j int) bool { - return allslots[i].name < allslots[j].name + sort.Slice(allsi, func(i, j int) bool { + return allsi[i].Name() < allsi[j].Name() }) } -// Tree represents the contents of a plugin filesystem tree. -type Tree struct { - raw fs.FS - rootinfo PluginInfo -} - -func (t *Tree) FS() fs.FS { - return t.raw -} - -func (t *Tree) RootPlugin() PluginInfo { - return t.rootinfo -} - -// SubPlugins returned a map of the PluginInfos for subplugins -// within the tree, if any, keyed by subpath. -func (t *Tree) SubPlugins() map[string]PluginInfo { - // TODO implement these once ParsePluginFS descends - return nil -} - -// TreeList is a slice of validated plugin fs Trees with helper methods -// for filtering to particular subsets of its members. -type TreeList []*Tree - -// LineagesForSlot returns the set of plugin-defined lineages that implement a -// particular named Grafana slot (See ["github.com/grafana/grafana/pkg/framework/coremodel".SchemaInterface]). -func (tl TreeList) LineagesForSlot(slotname string) map[string]thema.Lineage { - m := make(map[string]thema.Lineage) - for _, tree := range tl { - rootp := tree.RootPlugin() - rid := rootp.Meta().Id - - if lin, has := rootp.SlotImplementations()[slotname]; has { - m[rid] = lin - } +// ParsePluginFS takes a virtual filesystem and checks that it contains a valid +// set of files that statically define a Grafana plugin. +// +// The fsys must contain a plugin.json at the root, which must be valid +// according to the [plugindef] schema. If any .cue files exist in the +// grafanaplugin package, these will also be loaded and validated according to +// the [GrafanaPlugin] specification. This includes the validation of any custom +// or composable kinds and their contained lineages, via [thema.BindLineage]. +// +// This function parses exactly one plugin. It does not descend into +// subdirectories to search for additional plugin.json or .cue files. +// +// Calling this with a nil [thema.Runtime] (the singleton returned from +// [cuectx.GrafanaThemaRuntime] is used) will memoize certain CUE operations. +// Prefer passing nil unless a different thema.Runtime is specifically required. +// +// [GrafanaPlugin]: https://github.com/grafana/grafana/blob/main/pkg/plugins/pfs/grafanaplugin.cue +func ParsePluginFS(fsys fs.FS, rt *thema.Runtime) (ParsedPlugin, error) { + if fsys == nil { + return ParsedPlugin{}, ErrEmptyFS + } + if rt == nil { + rt = cuectx.GrafanaThemaRuntime() } - return m -} - -// PluginInfo represents everything knowable about a single plugin from static -// analysis of its filesystem tree contents. -type PluginInfo struct { - meta plugindef.PluginDef - slotimpls map[string]thema.Lineage - imports []*ast.ImportSpec -} - -// CUEImports lists the CUE import statements in the plugin's models.cue file, -// if any. -func (pi PluginInfo) CUEImports() []*ast.ImportSpec { - return pi.imports -} - -// SlotImplementations returns a map of the plugin's Thema lineages that -// implement particular slots, keyed by the name of the slot. -// -// Returns an empty map if the plugin has not implemented any slots. -func (pi PluginInfo) SlotImplementations() map[string]thema.Lineage { - return pi.slotimpls -} - -// Meta returns the metadata declared in the plugin's plugin.json file. -func (pi PluginInfo) Meta() plugindef.PluginDef { - return pi.meta -} - -// ParsePluginFS takes an fs.FS and checks that it represents exactly one valid -// plugin fs tree, with the fs.FS root as the root of the tree. -// -// It does not descend into subdirectories to search for additional plugin.json -// files. -// -// Calling this with a nil thema.Runtime will take advantage of memoization. -// Prefer this approach unless a different thema.Runtime is specifically -// required. -// -// TODO no descent is ok for core plugins, but won't cut it in general -func ParsePluginFS(f fs.FS, rt *thema.Runtime) (*Tree, error) { - if f == nil { - return nil, ErrEmptyFS - } lin, err := plugindef.Lineage(rt) if err != nil { panic(fmt.Sprintf("plugindef lineage is invalid or broken, needs dev attention: %s", err)) } - mux := vmux.NewValueMux(lin.TypedSchema(), vmux.NewJSONCodec("plugin.json")) ctx := rt.Context() - b, err := fs.ReadFile(f, "plugin.json") + b, err := fs.ReadFile(fsys, "plugin.json") if err != nil { if errors.Is(err, fs.ErrNotExist) { - return nil, ErrNoRootFile + return ParsedPlugin{}, ErrNoRootFile } - return nil, fmt.Errorf("error reading plugin.json: %w", err) + return ParsedPlugin{}, fmt.Errorf("error reading plugin.json: %w", err) } - tree := &Tree{ - raw: f, - rootinfo: PluginInfo{ - slotimpls: make(map[string]thema.Lineage), - }, + pp := ParsedPlugin{ + ComposableKinds: make(map[string]kindsys.Composable), + // CustomKinds: make(map[string]kindsys.Custom), } - r := &tree.rootinfo // Pass the raw bytes into the muxer, get the populated PluginDef type out that we want. // TODO stop ignoring second return. (for now, lacunas are a WIP and can't occur until there's >1 schema in the plugindef lineage) - pmeta, _, err := mux(b) + pinst, _, err := vmux.NewTypedMux(lin.TypedSchema(), vmux.NewJSONCodec("plugin.json"))(b) if err != nil { - // TODO more nuanced error handling by class of Thema failure - return nil, ewrap(err, ErrInvalidRootFile) + return ParsedPlugin{}, errors.Wrap(errors.Promote(err, ""), ErrInvalidRootFile) + } + pp.Properties = *(pinst.ValueP()) + // FIXME remove this once it's being correctly populated coming out of lineage + if pp.Properties.PascalName == "" { + pp.Properties.PascalName = plugindef.DerivePascalName(pp.Properties) } - r.meta = *pmeta - if modbyt, err := fs.ReadFile(f, "models.cue"); err == nil { - // TODO introduce layered CUE dependency-injecting loader - // - // Until CUE has proper dependency management (and possibly even after), loading - // CUE files with non-stdlib imports requires injecting the imported packages - // into cue.mod/pkg/, unless the imports are within the same CUE - // module. Thema introduced a system for this for its dependers, which we use - // here, but we'll need to layer the same on top for importable Grafana packages. - // Needing to do this twice strongly suggests it needs a generic, standalone - // library. + if cuefiles, err := fs.Glob(fsys, "*.cue"); err != nil { + return ParsedPlugin{}, fmt.Errorf("error globbing for cue files in fsys: %w", err) + } else if len(cuefiles) == 0 { + return pp, nil + } - mfs := merged_fs.NewMergedFS(f, grafana.CueSchemaFS) + gpv := loadGP(rt.Context()) - // Note that this actually will load any .cue files in the fs.FS root dir in the plugindef.PkgName. - // That's...maybe good? But not what it says on the tin - bi, err := load.InstanceWithThema(mfs, "", load.Package(plugindef.PkgName)) - if err != nil { - return nil, fmt.Errorf("loading models.cue failed: %w", err) + fsys, err = ensureCueMod(fsys, pp.Properties) + if err != nil { + return ParsedPlugin{}, fmt.Errorf("%s has invalid cue.mod: %w", pp.Properties.Id, err) + } + + bi, err := cuectx.LoadInstanceWithGrafana(fsys, "", load.Package(PackageName)) + if err != nil || bi.Err != nil { + if err == nil { + err = bi.Err } + return ParsedPlugin{}, errors.Wrap(errors.Newf(token.NoPos, "%s did not load", pp.Properties.Id), err) + } - pf, _ := parser.ParseFile("models.cue", modbyt, parser.ParseComments) + f, _ := parser.ParseFile("plugin.json", fmt.Sprintf(`{ + "id": %q, + "pascalName": %q + }`, pp.Properties.Id, pp.Properties.PascalName)) - for _, im := range pf.Imports { + for _, f := range bi.Files { + for _, im := range f.Imports { ip := strings.Trim(im.Path.Value, "\"") if !importAllowed(ip) { - return nil, ewrap(errors.Newf(im.Pos(), "import %q in models.cue not allowed, plugins may only import from:\n%s\n", ip, allowedImportsStr), ErrDisallowedCUEImport) - } - r.imports = append(r.imports, im) - } - - val := ctx.BuildInstance(bi) - if val.Err() != nil { - return nil, ewrap(fmt.Errorf("models.cue is invalid CUE: %w", val.Err()), ErrInvalidCUE) - } - for _, s := range allslots { - iv := val.LookupPath(cue.ParsePath(s.slot.Name())) - if iv.Exists() { - lin, err := bindSlotLineage(iv, s.slot, r.meta, rt) - if lin != nil { - r.slotimpls[s.slot.Name()] = lin - } - if err != nil { - return nil, err - } + return ParsedPlugin{}, errors.Wrap(errors.Newf(im.Pos(), + "import of %q in grafanaplugin cue package not allowed, plugins may only import from:\n%s\n", ip, allowedImportsStr), + ErrDisallowedCUEImport) } + pp.CUEImports = append(pp.CUEImports, im) } } - return tree, nil -} + // build.Instance.Files has a comment indicating the CUE authors want to change + // its behavior. This is a tripwire to tell us if/when they do that - otherwise, if + // the change they make ends up making bi.Files empty, the above loop will silently + // become a no-op, and we'd lose enforcement of import restrictions in plugins without + // realizing it. + if len(bi.Files) != len(bi.BuildFiles) { + panic("Refactor required - upstream CUE implementation changed, bi.Files is no longer populated") + } -func bindSlotLineage(v cue.Value, s kindsys.SchemaInterface, meta plugindef.PluginDef, rt *thema.Runtime, opts ...thema.BindOption) (thema.Lineage, error) { - // temporarily keep this around, there are IMMEDIATE plans to refactor - var required bool - accept := s.Should(string(meta.Type)) - exists := v.Exists() + // Inject the JSON directly into the build so it gets loaded together + bi.BuildFiles = append(bi.BuildFiles, &build.File{ + Filename: "plugin.json", + Encoding: build.JSON, + Form: build.Data, + Source: b, + }) + bi.Files = append(bi.Files, f) - if !accept { - if exists { - // If it's not accepted for the type, but is declared, error out. This keeps a - // precise boundary on what's actually expected for plugins to do, which makes - // for clearer docs and guarantees for users. - return nil, ewrap(fmt.Errorf("%s: %s plugins may not provide a %s slot implementation in models.cue", meta.Id, meta.Type, s.Name()), ErrImplementedSlots) + gpi := ctx.BuildInstance(bi).Unify(gpv) + if gpi.Err() != nil { + return ParsedPlugin{}, errors.Wrap(errors.Promote(ErrInvalidGrafanaPluginInstance, pp.Properties.Id), gpi.Err()) + } + + for _, si := range allsi { + iv := gpi.LookupPath(cue.MakePath(cue.Str("composableKinds"), cue.Str(si.Name()))) + if !iv.Exists() { + continue } - return nil, nil - } - if !exists && required { - return nil, ewrap(fmt.Errorf("%s: %s plugins must provide a %s slot implementation in models.cue", meta.Id, meta.Type, s.Name()), ErrImplementedSlots) - } - - // TODO make this opt real in thema, then uncomment to enforce joinSchema - // lin, err := thema.BindLineage(iv, rt, thema.SatisfiesJoinSchema(s.MetaSchema())) - lin, err := thema.BindLineage(v, rt, opts...) - if err != nil { - return nil, ewrap(fmt.Errorf("%s: invalid thema lineage for slot %s: %w", meta.Id, s.Name(), err), ErrInvalidLineage) - } - - sanid := sanitizePluginId(meta.Id) - if lin.Name() != sanid { - errf := func(format string, args ...interface{}) error { - var errin error - if n := v.LookupPath(cue.ParsePath("name")).Source(); n != nil { - errin = errors.Newf(n.Pos(), format, args...) - } else { - errin = fmt.Errorf(format, args...) - } - return ewrap(errin, ErrLineageNameMismatch) + props, err := kindsys.ToKindProps[kindsys.ComposableProperties](iv) + if err != nil { + return ParsedPlugin{}, err } - if sanid != meta.Id { - return nil, errf("%s: %q slot lineage name must be the sanitized plugin id (%q), got %q", meta.Id, s.Name(), sanid, lin.Name()) - } else { - return nil, errf("%s: %q slot lineage name must be the plugin id, got %q", meta.Id, s.Name(), lin.Name()) + + compo, err := kindsys.BindComposable(rt, kindsys.Decl[kindsys.ComposableProperties]{ + Properties: props, + V: iv, + }) + if err != nil { + return ParsedPlugin{}, err } + pp.ComposableKinds[si.Name()] = compo } - return lin, nil + + // TODO custom kinds + return pp, nil } -// Plugin IDs are allowed to contain characters that aren't allowed in thema -// Lineage names, CUE package names, Go package names, TS or Go type names, etc. -func sanitizePluginId(s string) string { - return strings.Map(func(r rune) rune { - switch { - case r >= 'a' && r <= 'z': - fallthrough - case r >= 'A' && r <= 'Z': - fallthrough - case r >= '0' && r <= '9': - fallthrough - case r == '_': - return r - case r == '-': - return '_' - default: - return -1 +func ensureCueMod(fsys fs.FS, pdef plugindef.PluginDef) (fs.FS, error) { + if modf, err := fs.ReadFile(fsys, filepath.Join("cue.mod", "module.cue")); err != nil { + if !errors.Is(err, fs.ErrNotExist) { + return nil, err } - }, s) -} - -func ewrap(actual, is error) error { - return &errPassthrough{ - actual: actual, - is: is, + return merged_fs.NewMergedFS(fsys, fstest.MapFS{ + "cue.mod/module.cue": &fstest.MapFile{Data: []byte(fmt.Sprintf(`module: "grafana.com/grafana/plugins/%s"`, pdef.Id))}, + }), nil + } else if _, err := cuecontext.New().CompileBytes(modf).LookupPath(cue.MakePath(cue.Str("module"))).String(); err != nil { + return nil, fmt.Errorf("error reading cue module name: %w", err) } -} -type errPassthrough struct { - actual error - is error -} - -func (e *errPassthrough) Is(err error) bool { - return errors.Is(err, e.actual) || errors.Is(err, e.is) -} - -func (e *errPassthrough) Error() string { - return e.actual.Error() + return fsys, nil } diff --git a/pkg/plugins/pfs/pfs_test.go b/pkg/plugins/pfs/pfs_test.go index de08cb5c082..7f1dd59ecb2 100644 --- a/pkg/plugins/pfs/pfs_test.go +++ b/pkg/plugins/pfs/pfs_test.go @@ -13,7 +13,7 @@ import ( "github.com/stretchr/testify/require" ) -func TestParseTreeTestdata(t *testing.T) { +func TestParsePluginTestdata(t *testing.T) { type tt struct { tfs fs.FS // TODO could remove this by getting rid of inconsistent subdirs @@ -110,11 +110,8 @@ func TestParseTreeTestdata(t *testing.T) { "no-rootfile": { err: ErrNoRootFile, }, - "valid-model-panel": {}, - "valid-model-datasource": {}, - "wrong-slot-panel": { - err: ErrImplementedSlots, - }, + "valid-model-panel": {}, + "valid-model-datasource": {}, "missing-kind-datasource": {}, "panel-conflicting-joinschema": { err: ErrInvalidLineage, @@ -124,11 +121,8 @@ func TestParseTreeTestdata(t *testing.T) { err: ErrInvalidLineage, skip: "TODO implement BindOption in thema, SatisfiesJoinSchema, then use it here", }, - "name-id-mismatch": { - err: ErrLineageNameMismatch, - }, - "mismatch": { - err: ErrLineageNameMismatch, + "name-mismatch-panel": { + err: ErrInvalidGrafanaPluginInstance, }, "disallowed-cue-import": { err: ErrDisallowedCUEImport, @@ -170,11 +164,12 @@ func TestParseTreeTestdata(t *testing.T) { t.Skip(tst.skip) } - tree, err := ParsePluginFS(tst.tfs, lib) + pp, err := ParsePluginFS(tst.tfs, lib) if tst.err == nil { require.NoError(t, err, "unexpected error while parsing plugin tree") } else { require.Error(t, err) + t.Logf("%T %s", err, err) require.ErrorIs(t, err, tst.err, "unexpected error type while parsing plugin tree") return } @@ -183,8 +178,7 @@ func TestParseTreeTestdata(t *testing.T) { tst.rootid = name } - rootp := tree.RootPlugin() - require.Equal(t, tst.rootid, rootp.Meta().Id, "expected root plugin id and actual root plugin id differ") + require.Equal(t, tst.rootid, pp.Properties.Id, "expected plugin id and actual plugin id differ") }) } } @@ -270,11 +264,11 @@ func TestParseTreeZips(t *testing.T) { t.Skip(tst.skip) } - tree, err := ParsePluginFS(tst.tfs, lib) + pp, err := ParsePluginFS(tst.tfs, lib) if tst.err == nil { - require.NoError(t, err, "unexpected error while parsing plugin tree") + require.NoError(t, err, "unexpected error while parsing plugin fs") } else { - require.ErrorIs(t, err, tst.err, "unexpected error type while parsing plugin tree") + require.ErrorIs(t, err, tst.err, "unexpected error type while parsing plugin fs") return } @@ -282,8 +276,7 @@ func TestParseTreeZips(t *testing.T) { tst.rootid = name } - rootp := tree.RootPlugin() - require.Equal(t, tst.rootid, rootp.Meta().Id, "expected root plugin id and actual root plugin id differ") + require.Equal(t, tst.rootid, pp.Properties.Id, "expected plugin id and actual plugin id differ") }) } } diff --git a/pkg/plugins/pfs/plugin.go b/pkg/plugins/pfs/plugin.go new file mode 100644 index 00000000000..e28fcfa4ce3 --- /dev/null +++ b/pkg/plugins/pfs/plugin.go @@ -0,0 +1,48 @@ +package pfs + +import ( + "cuelang.org/go/cue/ast" + "github.com/grafana/grafana/pkg/kindsys" + "github.com/grafana/grafana/pkg/plugins/plugindef" +) + +// ParsedPlugin represents everything knowable about a single plugin from static +// analysis of its filesystem tree contents, as performed by [ParsePluginFS]. +// +// Guarantees described in the below comments only exist for instances of this +// struct returned from [ParsePluginFS]. +type ParsedPlugin struct { + // Properties contains the plugin's definition, as declared in plugin.json. + Properties plugindef.PluginDef + + // ComposableKinds is a map of all the composable kinds declared in this plugin. + // Keys are the name of the [kindsys.SchemaInterface] implemented by the value. + // + // Composable kind defs are only populated in this map by [ParsePluginFS] if + // they are implementations of a known schema interface, or are for + // an unknown schema interface. + ComposableKinds map[string]kindsys.Composable + + // CustomKinds is a map of all the custom kinds declared in this plugin. + // Keys are the machineName of the custom kind. + // CustomKinds map[string]kindsys.Custom + + // CUEImports lists the CUE import statements in the plugin's grafanaplugin CUE + // package, if any. + CUEImports []*ast.ImportSpec +} + +// TODO is this static approach worth using, akin to core generated registries? instead of the ParsedPlugins.ComposableKinds map? in addition to it? +// ComposableKinds represents all the possible composable kinds that may be +// defined in a Grafana plugin. +// +// The value of each field, if non-nil, is a standard [kindsys.Decl] +// representing a CUE definition of a composable kind that implements the +// schema interface corresponding to the field's name. (This invariant is +// only enforced in [ComposableKinds] returned from [ParsePluginFS].) +// +// type ComposableKinds struct { +// PanelCfg kindsys.Decl[kindsys.ComposableProperties] +// Queries kindsys.Decl[kindsys.ComposableProperties] +// DSCfg kindsys.Decl[kindsys.ComposableProperties] +// } diff --git a/pkg/plugins/pfs/plugin_test.go b/pkg/plugins/pfs/plugin_test.go new file mode 100644 index 00000000000..c75f2a220c1 --- /dev/null +++ b/pkg/plugins/pfs/plugin_test.go @@ -0,0 +1,31 @@ +package pfs + +import ( + "sort" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/grafana/grafana/pkg/kindsys" +) + +// This is a brick-dumb test that just ensures known schema interfaces are being +// loaded correctly from their declarations in .cue files. +// +// If this test fails, it's either because: +// - They're not being loaded correctly - there's a bug in kindsys or pfs somewhere, fix it +// - The set of schema interfaces has been modified - update the static list here +func TestSchemaInterfacesAreLoaded(t *testing.T) { + knownSI := []string{"PanelCfg", "DataQuery", "DataSourceCfg"} + all := kindsys.SchemaInterfaces(nil) + var loadedSI []string + for k := range all { + loadedSI = append(loadedSI, k) + } + + sort.Strings(knownSI) + sort.Strings(loadedSI) + + if diff := cmp.Diff(knownSI, loadedSI); diff != "" { + t.Fatalf("kindsys cue-declared schema interfaces differ from ComposableKinds go struct:\n%s", diff) + } +} diff --git a/pkg/plugins/plugindef/pascal_test.go b/pkg/plugins/plugindef/pascal_test.go new file mode 100644 index 00000000000..095384c5d03 --- /dev/null +++ b/pkg/plugins/plugindef/pascal_test.go @@ -0,0 +1,43 @@ +package plugindef + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestDerivePascal(t *testing.T) { + table := []struct { + id, name, out string + }{ + { + name: "-- Grafana --", + out: "Grafana", + }, + { + name: "A weird/Thing", + out: "AWeirdThing", + }, + { + name: "/", + out: "Empty", + }, + { + name: "some really Long thing WHY would38883 anyone do this i don't know but hey It seems like it this is just going on and", + out: "SomeReallyLongThingWHYWouldAnyoneDoThisIDonTKnowButHeyItSeemsLi", + }, + } + + for _, row := range table { + if row.id == "" { + row.id = "default-empty-panel" + } + + pd := PluginDef{ + Id: row.id, + Name: row.name, + } + + require.Equal(t, row.out, DerivePascalName(pd)) + } +} diff --git a/pkg/plugins/plugindef/plugindef.cue b/pkg/plugins/plugindef/plugindef.cue index a3a5b9e5ac6..307a5045f81 100644 --- a/pkg/plugins/plugindef/plugindef.cue +++ b/pkg/plugins/plugindef/plugindef.cue @@ -2,6 +2,7 @@ package plugindef import ( "strings" + "regexp" "github.com/grafana/thema" ) @@ -34,6 +35,18 @@ seqs: [ // the UI. name: string + // FIXME there appears to be a bug in thema that prevents this from working. Maybe it'd + // help to refer to it with an alias, but thema can't support using current list syntax. + // syntax (fixed by grafana/thema#82). Either way, for now, pascalName gets populated in Go. + let sani = (strings.ToTitle(regexp.ReplaceAllLiteral("[^a-zA-Z]+", name, ""))) + + // The PascalCase name for the plugin. Used for creating machine-friendly + // identifiers, typically in code generation. + // + // If not provided, defaults to name, but title-cased and sanitized (only + // alphabetical characters allowed). + pascalName: string & =~"^([A-Z][a-zA-Z]{1,62})$" | *sani + // Plugin category used on the Add data source page. category?: "tsdb" | "logging" | "cloud" | "tracing" | "sql" | "enterprise" | "profiling" | "other" diff --git a/pkg/plugins/plugindef/plugindef.go b/pkg/plugins/plugindef/plugindef.go index ae36dd64244..f49a73637cf 100644 --- a/pkg/plugins/plugindef/plugindef.go +++ b/pkg/plugins/plugindef/plugindef.go @@ -1,6 +1,7 @@ package plugindef import ( + "strings" "sync" "cuelang.org/go/cue/build" @@ -10,10 +11,6 @@ import ( //go:generate go run gen.go -// PkgName is the name of the CUE package that Grafana will load when looking -// for kind declarations by a Grafana plugin. -const PkgName = "grafanaplugin" - func loadInstanceForplugindef() (*build.Instance, error) { return cuectx.LoadGrafanaInstance("pkg/plugins/plugindef", "", nil) } @@ -36,3 +33,41 @@ func Lineage(rt *thema.Runtime, opts ...thema.BindOption) (thema.ConvergentLinea } return doLineage(rt, opts...) } + +// DerivePascalName derives a PascalCase name from a PluginDef. +// +// This function does not mutate the input PluginDef; as such, it ignores +// whether there exists any value for PluginDef.PascalName. +// +// FIXME this should be removable once CUE logic for it works/unmarshals correctly. +func DerivePascalName(pd PluginDef) string { + sani := func(s string) string { + ret := strings.Title(strings.Map(func(r rune) rune { + switch { + case r >= 'a' && r <= 'z': + return r + case r >= 'A' && r <= 'Z': + return r + default: + return -1 + } + }, strings.Title(strings.Map(func(r rune) rune { + switch r { + case '-', '_': + return ' ' + default: + return r + } + }, s)))) + if len(ret) > 63 { + return ret[:63] + } + return ret + } + + fromname := sani(pd.Name) + if len(fromname) != 0 { + return fromname + } + return sani(strings.Split(pd.Id, "-")[1]) +} diff --git a/pkg/plugins/plugindef/plugindef_types_gen.go b/pkg/plugins/plugindef/plugindef_types_gen.go index 2c26d1d9510..a38336c20a4 100644 --- a/pkg/plugins/plugindef/plugindef_types_gen.go +++ b/pkg/plugins/plugindef/plugindef_types_gen.go @@ -432,6 +432,13 @@ type PluginDef struct { // the UI. Name string `json:"name"` + // The PascalCase name for the plugin. Used for creating machine-friendly + // identifiers, typically in code generation. + // + // If not provided, defaults to name, but title-cased and sanitized (only + // alphabetical characters allowed). + PascalName string `json:"pascalName"` + // Initialize plugin on startup. By default, the plugin // initializes on first use. Preload *bool `json:"preload,omitempty"` diff --git a/public/app/plugins/gen.go b/public/app/plugins/gen.go index 543e0e43c06..376a79ec2c6 100644 --- a/public/app/plugins/gen.go +++ b/public/app/plugins/gen.go @@ -54,7 +54,7 @@ func main() { pluginKindGen.Append( codegen.PluginTreeListJenny(), - codegen.PluginGoTypesJenny("pkg/tsdb", adaptToPipeline(corecodegen.GoTypesJenny{})), + codegen.PluginGoTypesJenny("pkg/tsdb", adaptToPipeline(corecodegen.GoTypesJenny{ExpandReferences: true})), codegen.PluginTSTypesJenny("public/app/plugins", adaptToPipeline(corecodegen.TSTypesJenny{})), ) diff --git a/public/app/plugins/panel/annolist/models.cue b/public/app/plugins/panel/annolist/composable_panelcfg.cue similarity index 52% rename from public/app/plugins/panel/annolist/models.cue rename to public/app/plugins/panel/annolist/composable_panelcfg.cue index 12429df451c..446a4a4a798 100644 --- a/public/app/plugins/panel/annolist/models.cue +++ b/public/app/plugins/panel/annolist/composable_panelcfg.cue @@ -14,28 +14,29 @@ package grafanaplugin -import "github.com/grafana/thema" +composableKinds: PanelCfg: { + maturity: "experimental" -Panel: thema.#Lineage & { - name: "annolist" - seqs: [ - { - schemas: [ - { - PanelOptions: { - onlyFromThisDashboard: bool | *false - onlyInTimeRange: bool | *false - tags: [...string] - limit: uint32 | *10 - showUser: bool | *true - showTime: bool | *true - showTags: bool | *true - navigateToPanel: bool | *true - navigateBefore: string | *"10m" - navigateAfter: string | *"10m" - } @cuetsy(kind="interface") - }, - ] - }, - ] + lineage: { + seqs: [ + { + schemas: [ + { + PanelOptions: { + onlyFromThisDashboard: bool | *false + onlyInTimeRange: bool | *false + tags: [...string] + limit: uint32 | *10 + showUser: bool | *true + showTime: bool | *true + showTags: bool | *true + navigateToPanel: bool | *true + navigateBefore: string | *"10m" + navigateAfter: string | *"10m" + } @cuetsy(kind="interface") + }, + ] + }, + ] + } } diff --git a/public/app/plugins/panel/annolist/models.gen.ts b/public/app/plugins/panel/annolist/models.gen.ts index eb597b0ba57..479e5e0e46a 100644 --- a/public/app/plugins/panel/annolist/models.gen.ts +++ b/public/app/plugins/panel/annolist/models.gen.ts @@ -8,7 +8,7 @@ // // Run 'make gen-cue' from repository root to regenerate. -export const PanelModelVersion = Object.freeze([0, 0]); +export const PanelCfgModelVersion = Object.freeze([0, 0]); export interface PanelOptions { limit: number; diff --git a/public/app/plugins/panel/barchart/composable_panelcfg.cue b/public/app/plugins/panel/barchart/composable_panelcfg.cue new file mode 100644 index 00000000000..c41f9c0279e --- /dev/null +++ b/public/app/plugins/panel/barchart/composable_panelcfg.cue @@ -0,0 +1,81 @@ +// Copyright 2022 Grafana Labs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package grafanaplugin + +import ( + ui "github.com/grafana/grafana/packages/grafana-schema/src/common" +) + +composableKinds: PanelCfg: { + maturity: "experimental" + + lineage: { + seqs: [ + { + schemas: [ + // v0.0 + { + PanelOptions: { + ui.OptionsWithLegend + ui.OptionsWithTooltip + ui.OptionsWithTextFormatting + + // TODO docs + xField?: string + // TODO docs + colorByField?: string + // TODO docs + orientation: ui.VizOrientation | *"auto" + // TODO docs + barRadius?: float64 & >=0 & <=0.5 | *0 + // TODO docs + xTickLabelRotation: int32 & >=-90 & <=90 | *0 + // TODO docs + xTickLabelMaxLength: int32 & >=0 + // TODO docs + // negative values indicate backwards skipping behavior + xTickLabelSpacing?: int32 | *0 + // TODO docs + stacking: ui.StackingMode | *"none" + // This controls whether values are shown on top or to the left of bars. + showValue: ui.VisibilityMode | *"auto" + // Controls the width of bars. 1 = Max width, 0 = Min width. + barWidth: float64 & >=0 & <=1 | *0.97 + // Controls the width of groups. 1 = max with, 0 = min width. + groupWidth: float64 & >=0 & <=1 | *0.7 + // Enables mode which highlights the entire bar area and shows tooltip when cursor + // hovers over highlighted area + fullHighlight: bool | *false + } @cuetsy(kind="interface") + PanelFieldConfig: { + ui.AxisConfig + ui.HideableFieldConfig + + // Controls line width of the bars. + lineWidth?: int32 & >=0 & <=10 | *1 + // Controls the fill opacity of the bars. + fillOpacity?: int32 & >=0 & <=100 | *80 + // 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 | *"none" + // Threshold rendering + thresholdsStyle?: ui.GraphThresholdsStyleConfig + } @cuetsy(kind="interface") + }, + ] + }, + ] + } +} diff --git a/public/app/plugins/panel/barchart/models.cue b/public/app/plugins/panel/barchart/models.cue deleted file mode 100644 index 44ed67e98cb..00000000000 --- a/public/app/plugins/panel/barchart/models.cue +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright 2022 Grafana Labs -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package grafanaplugin - -import ( - "github.com/grafana/thema" - ui "github.com/grafana/grafana/packages/grafana-schema/src/common" -) - -Panel: thema.#Lineage & { - name: "barchart" - seqs: [ - { - schemas: [ - // v0.0 - { - PanelOptions: { - ui.OptionsWithLegend - ui.OptionsWithTooltip - ui.OptionsWithTextFormatting - // TODO docs - xField?: string - // TODO docs - colorByField?: string - // TODO docs - orientation: ui.VizOrientation | *"auto" - // TODO docs - barRadius?: float64 & >= 0 & <= 0.5 | *0 - // TODO docs - xTickLabelRotation: int32 & >= -90 & <= 90 | *0 - // TODO docs - xTickLabelMaxLength: int32 & >= 0 - // TODO docs - // negative values indicate backwards skipping behavior - xTickLabelSpacing?: int32 | *0 - // TODO docs - stacking: ui.StackingMode | *"none" - // This controls whether values are shown on top or to the left of bars. - showValue: ui.VisibilityMode | *"auto" - // Controls the width of bars. 1 = Max width, 0 = Min width. - barWidth: float64 & >= 0 & <= 1 | *0.97 - // Controls the width of groups. 1 = max with, 0 = min width. - groupWidth: float64 & >= 0 & <= 1 | *0.7 - // Enables mode which highlights the entire bar area and shows tooltip when cursor hovers over highlighted area - fullHighlight: bool | *false - } @cuetsy(kind="interface") - PanelFieldConfig: { - ui.AxisConfig - ui.HideableFieldConfig - // Controls line width of the bars. - lineWidth?: int32 & >= 0 & <= 10 | *1 - // Controls the fill opacity of the bars. - fillOpacity?: int32 & >= 0 & <= 100 | *80 - // 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 | *"none" - // Threshold rendering - thresholdsStyle?: ui.GraphThresholdsStyleConfig - } @cuetsy(kind="interface") - }, - ] - }, - ] -} diff --git a/public/app/plugins/panel/barchart/models.gen.ts b/public/app/plugins/panel/barchart/models.gen.ts index ed345d5583c..9f1648249b7 100644 --- a/public/app/plugins/panel/barchart/models.gen.ts +++ b/public/app/plugins/panel/barchart/models.gen.ts @@ -10,7 +10,7 @@ import * as ui from '@grafana/schema'; -export const PanelModelVersion = Object.freeze([0, 0]); +export const PanelCfgModelVersion = Object.freeze([0, 0]); export interface PanelOptions extends ui.OptionsWithLegend, ui.OptionsWithTooltip, ui.OptionsWithTextFormatting { /** @@ -26,7 +26,8 @@ export interface PanelOptions extends ui.OptionsWithLegend, ui.OptionsWithToolti */ colorByField?: string; /** - * Enables mode which highlights the entire bar area and shows tooltip when cursor hovers over highlighted area + * Enables mode which highlights the entire bar area and shows tooltip when cursor + * hovers over highlighted area */ fullHighlight: boolean; /** diff --git a/public/app/plugins/panel/bargauge/models.cue b/public/app/plugins/panel/bargauge/composable_panelcfg.cue similarity index 64% rename from public/app/plugins/panel/bargauge/models.cue rename to public/app/plugins/panel/bargauge/composable_panelcfg.cue index 35578b9ebbd..a2e70451ce0 100644 --- a/public/app/plugins/panel/bargauge/models.cue +++ b/public/app/plugins/panel/bargauge/composable_panelcfg.cue @@ -15,25 +15,27 @@ package grafanaplugin import ( - "github.com/grafana/thema" ui "github.com/grafana/grafana/packages/grafana-schema/src/common" ) -Panel: thema.#Lineage & { - name: "bargauge" - seqs: [ - { - schemas: [ - { - PanelOptions: { - ui.SingleStatBaseOptions - displayMode: ui.BarGaugeDisplayMode | *"gradient" - showUnfilled: bool | *true - minVizWidth: uint32 | *0 - minVizHeight: uint32 | *10 - } @cuetsy(kind="interface") - }, - ] - }, - ] +composableKinds: PanelCfg: { + maturity: "experimental" + + lineage: { + seqs: [ + { + schemas: [ + { + PanelOptions: { + ui.SingleStatBaseOptions + displayMode: ui.BarGaugeDisplayMode | *"gradient" + showUnfilled: bool | *true + minVizWidth: uint32 | *0 + minVizHeight: uint32 | *10 + } @cuetsy(kind="interface") + }, + ] + }, + ] + } } diff --git a/public/app/plugins/panel/bargauge/models.gen.ts b/public/app/plugins/panel/bargauge/models.gen.ts index c7613d0ebe4..60b8b623e9b 100644 --- a/public/app/plugins/panel/bargauge/models.gen.ts +++ b/public/app/plugins/panel/bargauge/models.gen.ts @@ -10,7 +10,7 @@ import * as ui from '@grafana/schema'; -export const PanelModelVersion = Object.freeze([0, 0]); +export const PanelCfgModelVersion = Object.freeze([0, 0]); export interface PanelOptions extends ui.SingleStatBaseOptions { displayMode: ui.BarGaugeDisplayMode; diff --git a/public/app/plugins/panel/news/models.cue b/public/app/plugins/panel/candlestick/composable_panelcfg.cue similarity index 67% rename from public/app/plugins/panel/news/models.cue rename to public/app/plugins/panel/candlestick/composable_panelcfg.cue index fff1277e77b..d548b45934f 100644 --- a/public/app/plugins/panel/news/models.cue +++ b/public/app/plugins/panel/candlestick/composable_panelcfg.cue @@ -14,21 +14,23 @@ package grafanaplugin -import "github.com/grafana/thema" - -Panel: thema.#Lineage & { - name: "news" - seqs: [ - { - schemas: [ - { - PanelOptions: { - // empty/missing will default to grafana blog - feedUrl?: string - showImage?: bool | *true - } @cuetsy(kind="interface") - }, - ] - }, - ] +composableKinds: PanelCfg: { + lineage: { + seqs: [ + { + schemas: [ + { + PanelOptions: { + // anything for now + ... + } @cuetsy(kind="interface") + PanelFieldConfig: { + // anything for now + ... + } @cuetsy(kind="interface") + }, + ] + }, + ] + } } diff --git a/public/app/plugins/panel/canvas/models.cue b/public/app/plugins/panel/canvas/composable_panelcfg.cue similarity index 73% rename from public/app/plugins/panel/canvas/models.cue rename to public/app/plugins/panel/canvas/composable_panelcfg.cue index 475775b727a..b547dea5f40 100644 --- a/public/app/plugins/panel/canvas/models.cue +++ b/public/app/plugins/panel/canvas/composable_panelcfg.cue @@ -14,20 +14,19 @@ package grafanaplugin -import "github.com/grafana/thema" - -Panel: thema.#Lineage & { - name: "canvas" - seqs: [ - { - schemas: [ - { - PanelOptions: { - // anything for now - ... - } @cuetsy(kind="interface") - }, - ] - }, - ] +composableKinds: PanelCfg: { + lineage: { + seqs: [ + { + schemas: [ + { + PanelOptions: { + // anything for now + ... + } @cuetsy(kind="interface") + }, + ] + }, + ] + } } diff --git a/public/app/plugins/panel/dashlist/models.cue b/public/app/plugins/panel/dashlist/composable_panelcfg.cue similarity index 50% rename from public/app/plugins/panel/dashlist/models.cue rename to public/app/plugins/panel/dashlist/composable_panelcfg.cue index 5338bf34bde..8d1c43adbdf 100644 --- a/public/app/plugins/panel/dashlist/models.cue +++ b/public/app/plugins/panel/dashlist/composable_panelcfg.cue @@ -14,28 +14,29 @@ package grafanaplugin -import "github.com/grafana/thema" +composableKinds: PanelCfg: { + maturity: "experimental" -Panel: thema.#Lineage & { - name: "dashlist" - seqs: [ - { - schemas: [ - { - PanelLayout: "list" | "previews" @cuetsy(kind="enum") - PanelOptions: { - layout?: PanelLayout | *"list" - showStarred: bool | *true - showRecentlyViewed: bool | *false - showSearch: bool | *false - showHeadings: bool | *true - maxItems: int | *10 - query: string | *"" - folderId?: int - tags: [...string] | *[] - } @cuetsy(kind="interface") - }, - ] - }, - ] + lineage: { + seqs: [ + { + schemas: [ + { + PanelLayout: "list" | "previews" @cuetsy(kind="enum") + PanelOptions: { + layout?: PanelLayout | *"list" + showStarred: bool | *true + showRecentlyViewed: bool | *false + showSearch: bool | *false + showHeadings: bool | *true + maxItems: int | *10 + query: string | *"" + folderId?: int + tags: [...string] | *[] + } @cuetsy(kind="interface") + }, + ] + }, + ] + } } diff --git a/public/app/plugins/panel/dashlist/models.gen.ts b/public/app/plugins/panel/dashlist/models.gen.ts index 91a909e92ed..0fdea561c83 100644 --- a/public/app/plugins/panel/dashlist/models.gen.ts +++ b/public/app/plugins/panel/dashlist/models.gen.ts @@ -8,7 +8,7 @@ // // Run 'make gen-cue' from repository root to regenerate. -export const PanelModelVersion = Object.freeze([0, 0]); +export const PanelCfgModelVersion = Object.freeze([0, 0]); export enum PanelLayout { List = 'list', diff --git a/public/app/plugins/panel/gauge/models.cue b/public/app/plugins/panel/gauge/composable_panelcfg.cue similarity index 69% rename from public/app/plugins/panel/gauge/models.cue rename to public/app/plugins/panel/gauge/composable_panelcfg.cue index 62e363ffdc4..9468211647c 100644 --- a/public/app/plugins/panel/gauge/models.cue +++ b/public/app/plugins/panel/gauge/composable_panelcfg.cue @@ -15,23 +15,25 @@ package grafanaplugin import ( - "github.com/grafana/thema" ui "github.com/grafana/grafana/packages/grafana-schema/src/common" ) -Panel: thema.#Lineage & { - name: "gauge" - seqs: [ - { - schemas: [ - { - PanelOptions: { - ui.SingleStatBaseOptions - showThresholdLabels: bool | *false - showThresholdMarkers: bool | *true - } @cuetsy(kind="interface") - }, - ] - }, - ] +composableKinds: PanelCfg: { + maturity: "experimental" + + lineage: { + seqs: [ + { + schemas: [ + { + PanelOptions: { + ui.SingleStatBaseOptions + showThresholdLabels: bool | *false + showThresholdMarkers: bool | *true + } @cuetsy(kind="interface") + }, + ] + }, + ] + } } diff --git a/public/app/plugins/panel/gauge/models.gen.ts b/public/app/plugins/panel/gauge/models.gen.ts index f3b53823c51..1da208ed498 100644 --- a/public/app/plugins/panel/gauge/models.gen.ts +++ b/public/app/plugins/panel/gauge/models.gen.ts @@ -10,7 +10,7 @@ import * as ui from '@grafana/schema'; -export const PanelModelVersion = Object.freeze([0, 0]); +export const PanelCfgModelVersion = Object.freeze([0, 0]); export interface PanelOptions extends ui.SingleStatBaseOptions { showThresholdLabels: boolean; diff --git a/public/app/plugins/panel/candlestick/models.cue b/public/app/plugins/panel/heatmap/composable_panelcfg.cue similarity index 67% rename from public/app/plugins/panel/candlestick/models.cue rename to public/app/plugins/panel/heatmap/composable_panelcfg.cue index db14a3ede1f..d548b45934f 100644 --- a/public/app/plugins/panel/candlestick/models.cue +++ b/public/app/plugins/panel/heatmap/composable_panelcfg.cue @@ -14,22 +14,23 @@ package grafanaplugin -Panel: thema.#Lineage & { - name: "candlestick" - seqs: [ - { - schemas: [ - { - PanelOptions: { - // anything for now - ... - } @cuetsy(kind="interface") - PanelFieldConfig: { - // anything for now - ... - } @cuetsy(kind="interface") - }, - ] - }, - ] +composableKinds: PanelCfg: { + lineage: { + seqs: [ + { + schemas: [ + { + PanelOptions: { + // anything for now + ... + } @cuetsy(kind="interface") + PanelFieldConfig: { + // anything for now + ... + } @cuetsy(kind="interface") + }, + ] + }, + ] + } } diff --git a/public/app/plugins/panel/histogram/composable_panelcfg.cue b/public/app/plugins/panel/histogram/composable_panelcfg.cue new file mode 100644 index 00000000000..27946f13f87 --- /dev/null +++ b/public/app/plugins/panel/histogram/composable_panelcfg.cue @@ -0,0 +1,58 @@ +// Copyright 2022 Grafana Labs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package grafanaplugin + +import ( + ui "github.com/grafana/grafana/packages/grafana-schema/src/common" +) + +composableKinds: PanelCfg: { + maturity: "experimental" + + lineage: { + seqs: [ + { + schemas: [ + { + PanelOptions: { + ui.OptionsWithLegend + ui.OptionsWithTooltip + + //Size of each bucket + bucketSize?: int32 + //Offset buckets by this amount + bucketOffset?: int32 | *0 + //Combines multiple series into a single histogram + combine?: bool + } @cuetsy(kind="interface") + + PanelFieldConfig: { + ui.AxisConfig + ui.HideableFieldConfig + + // Controls line width of the bars. + lineWidth?: uint32 & <=10 | *1 + // Controls the fill opacity of the bars. + fillOpacity?: uint32 & <=100 | *80 + // 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 | *"none" + } @cuetsy(kind="interface") + }, + ] + }, + ] + } +} diff --git a/public/app/plugins/panel/histogram/models.cue b/public/app/plugins/panel/histogram/models.cue deleted file mode 100644 index 5a453c7ae26..00000000000 --- a/public/app/plugins/panel/histogram/models.cue +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright 2022 Grafana Labs -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package grafanaplugin - -import ( - "github.com/grafana/thema" - ui "github.com/grafana/grafana/packages/grafana-schema/src/common" -) - -Panel: thema.#Lineage & { - name: "histogram" - seqs: [ - { - schemas: [ - { - PanelOptions: { - ui.OptionsWithLegend - ui.OptionsWithTooltip - //Size of each bucket - bucketSize?: int32 - //Offset buckets by this amount - bucketOffset?: int32 | *0 - //Combines multiple series into a single histogram - combine?: bool - } @cuetsy(kind="interface") - - PanelFieldConfig: { - ui.AxisConfig - ui.HideableFieldConfig - // Controls line width of the bars. - lineWidth?: uint32 & <= 10 | *1 - // Controls the fill opacity of the bars. - fillOpacity?: uint32 & <= 100 | *80 - // 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 | *"none" - } @cuetsy(kind="interface") - }, - ] - }, - ] -} diff --git a/public/app/plugins/panel/histogram/models.gen.ts b/public/app/plugins/panel/histogram/models.gen.ts index 8daa5395765..2b77ef7952d 100644 --- a/public/app/plugins/panel/histogram/models.gen.ts +++ b/public/app/plugins/panel/histogram/models.gen.ts @@ -10,7 +10,7 @@ import * as ui from '@grafana/schema'; -export const PanelModelVersion = Object.freeze([0, 0]); +export const PanelCfgModelVersion = Object.freeze([0, 0]); export interface PanelOptions extends ui.OptionsWithLegend, ui.OptionsWithTooltip { /** diff --git a/public/app/plugins/panel/heatmap/models.cue b/public/app/plugins/panel/news/composable_panelcfg.cue similarity index 66% rename from public/app/plugins/panel/heatmap/models.cue rename to public/app/plugins/panel/news/composable_panelcfg.cue index 06b5ff734ea..454ac9fc665 100644 --- a/public/app/plugins/panel/heatmap/models.cue +++ b/public/app/plugins/panel/news/composable_panelcfg.cue @@ -14,24 +14,22 @@ package grafanaplugin -import "github.com/grafana/thema" +composableKinds: PanelCfg: { + maturity: "experimental" -Panel: thema.#Lineage & { - name: "heatmap" - seqs: [ - { - schemas: [ - { - PanelOptions: { - // anything for now - ... - } @cuetsy(kind="interface") - PanelFieldConfig: { - // anything for now - ... - } @cuetsy(kind="interface") - }, - ] - }, - ] + lineage: { + seqs: [ + { + schemas: [ + { + PanelOptions: { + // empty/missing will default to grafana blog + feedUrl?: string + showImage?: bool | *true + } @cuetsy(kind="interface") + }, + ] + }, + ] + } } diff --git a/public/app/plugins/panel/news/models.gen.ts b/public/app/plugins/panel/news/models.gen.ts index 4ac44d16e7f..a5037c732d3 100644 --- a/public/app/plugins/panel/news/models.gen.ts +++ b/public/app/plugins/panel/news/models.gen.ts @@ -8,7 +8,7 @@ // // Run 'make gen-cue' from repository root to regenerate. -export const PanelModelVersion = Object.freeze([0, 0]); +export const PanelCfgModelVersion = Object.freeze([0, 0]); export interface PanelOptions { /** diff --git a/public/app/plugins/panel/piechart/composable_panelcfg.cue b/public/app/plugins/panel/piechart/composable_panelcfg.cue new file mode 100644 index 00000000000..4d5e0e80a87 --- /dev/null +++ b/public/app/plugins/panel/piechart/composable_panelcfg.cue @@ -0,0 +1,58 @@ +// Copyright 2022 Grafana Labs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package grafanaplugin + +import ( + ui "github.com/grafana/grafana/packages/grafana-schema/src/common" +) + +composableKinds: PanelCfg: { + maturity: "experimental" + + lineage: { + seqs: [ + { + schemas: [ + // v0.0 + { + // Select the pie chart display style. + PieChartType: "pie" | "donut" @cuetsy(kind="enum") + // 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. + PieChartLabels: "name" | "value" | "percent" @cuetsy(kind="enum") + // Select values to display in the legend. + // - Percent: The percentage of the whole. + // - Value: The raw numerical value. + PieChartLegendValues: "value" | "percent" @cuetsy(kind="enum") + PieChartLegendOptions: { + ui.VizLegendOptions + values: [...PieChartLegendValues] + } @cuetsy(kind="interface") + PanelOptions: { + ui.OptionsWithTooltip + ui.SingleStatBaseOptions + pieType: PieChartType + displayLabels: [...PieChartLabels] + legend: PieChartLegendOptions + } @cuetsy(kind="interface") + PanelFieldConfig: ui.HideableFieldConfig @cuetsy(kind="interface") + }, + ] + }, + ] + } +} diff --git a/public/app/plugins/panel/piechart/models.cue b/public/app/plugins/panel/piechart/models.cue deleted file mode 100644 index af19526d5e2..00000000000 --- a/public/app/plugins/panel/piechart/models.cue +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright 2022 Grafana Labs -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package grafanaplugin - -import ( - "github.com/grafana/thema" - ui "github.com/grafana/grafana/packages/grafana-schema/src/common" -) - -Panel: thema.#Lineage & { - name: "piechart" - seqs: [ - { - schemas: [ - // v0.0 - { - // Select the pie chart display style. - PieChartType: "pie" | "donut" @cuetsy(kind="enum") - // 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. - PieChartLabels: "name" | "value" | "percent" @cuetsy(kind="enum") - // Select values to display in the legend. - // - Percent: The percentage of the whole. - // - Value: The raw numerical value. - PieChartLegendValues: "value" | "percent" @cuetsy(kind="enum") - PieChartLegendOptions: { - ui.VizLegendOptions - values: [...PieChartLegendValues] - } @cuetsy(kind="interface") - PanelOptions: { - ui.OptionsWithTooltip - ui.SingleStatBaseOptions - pieType: PieChartType - displayLabels: [...PieChartLabels] - legend: PieChartLegendOptions - } @cuetsy(kind="interface") - PanelFieldConfig: ui.HideableFieldConfig @cuetsy(kind="interface") - }, - ] - }, - ] -} diff --git a/public/app/plugins/panel/piechart/models.gen.ts b/public/app/plugins/panel/piechart/models.gen.ts index c08f4984ab2..db2a2548334 100644 --- a/public/app/plugins/panel/piechart/models.gen.ts +++ b/public/app/plugins/panel/piechart/models.gen.ts @@ -10,7 +10,7 @@ import * as ui from '@grafana/schema'; -export const PanelModelVersion = Object.freeze([0, 0]); +export const PanelCfgModelVersion = Object.freeze([0, 0]); /** * Select the pie chart display style. diff --git a/public/app/plugins/panel/stat/models.cue b/public/app/plugins/panel/stat/composable_panelcfg.cue similarity index 61% rename from public/app/plugins/panel/stat/models.cue rename to public/app/plugins/panel/stat/composable_panelcfg.cue index 17207c6927c..8b06899418c 100644 --- a/public/app/plugins/panel/stat/models.cue +++ b/public/app/plugins/panel/stat/composable_panelcfg.cue @@ -15,25 +15,27 @@ package grafanaplugin import ( - "github.com/grafana/thema" ui "github.com/grafana/grafana/packages/grafana-schema/src/common" ) -Panel: thema.#Lineage & { - name: "stat" - seqs: [ - { - schemas: [ - { - PanelOptions: { - ui.SingleStatBaseOptions - graphMode: ui.BigValueGraphMode | *"area" - colorMode: ui.BigValueColorMode | *"value" - justifyMode: ui.BigValueJustifyMode | *"auto" - textMode: ui.BigValueTextMode | *"auto" - } @cuetsy(kind="interface") - }, - ] - }, - ] +composableKinds: PanelCfg: { + maturity: "experimental" + + lineage: { + seqs: [ + { + schemas: [ + { + PanelOptions: { + ui.SingleStatBaseOptions + graphMode: ui.BigValueGraphMode | *"area" + colorMode: ui.BigValueColorMode | *"value" + justifyMode: ui.BigValueJustifyMode | *"auto" + textMode: ui.BigValueTextMode | *"auto" + } @cuetsy(kind="interface") + }, + ] + }, + ] + } } diff --git a/public/app/plugins/panel/stat/models.gen.ts b/public/app/plugins/panel/stat/models.gen.ts index e1e0bd3a8f1..11a22602500 100644 --- a/public/app/plugins/panel/stat/models.gen.ts +++ b/public/app/plugins/panel/stat/models.gen.ts @@ -10,7 +10,7 @@ import * as ui from '@grafana/schema'; -export const PanelModelVersion = Object.freeze([0, 0]); +export const PanelCfgModelVersion = Object.freeze([0, 0]); export interface PanelOptions extends ui.SingleStatBaseOptions { colorMode: ui.BigValueColorMode; diff --git a/public/app/plugins/panel/state-timeline/composable_panelcfg.cue b/public/app/plugins/panel/state-timeline/composable_panelcfg.cue new file mode 100644 index 00000000000..6ee5b04192c --- /dev/null +++ b/public/app/plugins/panel/state-timeline/composable_panelcfg.cue @@ -0,0 +1,51 @@ +// Copyright 2021 Grafana Labs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package grafanaplugin + +import ( + ui "github.com/grafana/grafana/packages/grafana-schema/src/common" +) + +composableKinds: PanelCfg: { + lineage: { + seqs: [ + { + schemas: [ + { + TimelineMode: "changes" | "samples" @cuetsy(kind="enum") + TimelineValueAlignment: "center" | "left" | "right" @cuetsy(kind="type") + PanelOptions: { + // FIXME ts comments indicate this shouldn't be in the saved model, but currently is emitted + mode?: TimelineMode + ui.OptionsWithLegend + ui.OptionsWithTooltip + ui.OptionsWithTimezones + showValue: ui.VisibilityMode | *"auto" + rowHeight: number | *0.9 + colWidth?: number + mergeValues?: bool | *true + alignValue?: TimelineValueAlignment | *"left" + } @cuetsy(kind="interface") + PanelFieldConfig: { + ui.HideableFieldConfig + lineWidth?: number | *0 + fillOpacity?: number | *70 + } @cuetsy(kind="interface") + }, + ] + }, + ] + } +} diff --git a/public/app/plugins/panel/state-timeline/models.cue b/public/app/plugins/panel/state-timeline/models.cue deleted file mode 100644 index 8a4a21e7c21..00000000000 --- a/public/app/plugins/panel/state-timeline/models.cue +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright 2021 Grafana Labs -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package grafanaplugin - -import ( - "github.com/grafana/thema" - ui "github.com/grafana/grafana/packages/grafana-schema/src/common" -) - -Panel: thema.#Lineage & { - name: "state-timeline" - seqs: [ - { - schemas: [ - { - TimelineMode: "changes" | "samples" @cuetsy(kind="enum") - TimelineValueAlignment: "center" | "left" | "right" @cuetsy(kind="type") - PanelOptions: { - // FIXME ts comments indicate this shouldn't be in the saved model, but currently is emitted - mode?: TimelineMode - ui.OptionsWithLegend - ui.OptionsWithTooltip - ui.OptionsWithTimezones - showValue: ui.VisibilityMode | *"auto" - rowHeight: number | *0.9 - colWidth?: number - mergeValues?: bool | *true - alignValue?: TimelineValueAlignment | *"left" - } @cuetsy(kind="interface") - PanelFieldConfig: { - ui.HideableFieldConfig - lineWidth?: number | *0 - fillOpacity?: number | *70 - } @cuetsy(kind="interface") - }, - ] - }, - ] -} diff --git a/public/app/plugins/panel/status-history/composable_panelcfg.cue b/public/app/plugins/panel/status-history/composable_panelcfg.cue new file mode 100644 index 00000000000..74a31517efa --- /dev/null +++ b/public/app/plugins/panel/status-history/composable_panelcfg.cue @@ -0,0 +1,46 @@ +// Copyright 2021 Grafana Labs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package grafanaplugin + +import ( + ui "github.com/grafana/grafana/packages/grafana-schema/src/common" +) + +composableKinds: PanelCfg: { + lineage: { + seqs: [ + { + schemas: [ + { + PanelOptions: { + ui.OptionsWithLegend + ui.OptionsWithTooltip + ui.OptionsWithTimezones + showValue: ui.VisibilityMode + rowHeight: number + colWidth?: number + alignValue: "center" | *"left" | "right" + } @cuetsy(kind="interface") + PanelFieldConfig: { + ui.HideableFieldConfig + lineWidth?: number | *1 + fillOpacity?: number | *70 + } @cuetsy(kind="interface") + }, + ] + }, + ] + } +} diff --git a/public/app/plugins/panel/status-history/models.cue b/public/app/plugins/panel/status-history/models.cue deleted file mode 100644 index 42c47935ad5..00000000000 --- a/public/app/plugins/panel/status-history/models.cue +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright 2021 Grafana Labs -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package grafanaplugin - -import ( - "github.com/grafana/thema" - ui "github.com/grafana/grafana/packages/grafana-schema/src/common" -) - -Panel: thema.#Lineage & { - name: "status-history" - seqs: [ - { - schemas: [ - { - PanelOptions: { - ui.OptionsWithLegend - ui.OptionsWithTooltip - ui.OptionsWithTimezones - showValue: ui.VisibilityMode - rowHeight: number - colWidth?: number - alignValue: "center" | *"left" | "right" - } @cuetsy(kind="interface") - PanelFieldConfig: { - ui.HideableFieldConfig - lineWidth?: number | *1 - fillOpacity?: number | *70 - } @cuetsy(kind="interface") - }, - ] - }, - ] -} diff --git a/public/app/plugins/panel/table/models.cue b/public/app/plugins/panel/table/composable_panelcfg.cue similarity index 63% rename from public/app/plugins/panel/table/models.cue rename to public/app/plugins/panel/table/composable_panelcfg.cue index 9dcca3c149a..822fcabafe0 100644 --- a/public/app/plugins/panel/table/models.cue +++ b/public/app/plugins/panel/table/composable_panelcfg.cue @@ -15,25 +15,25 @@ package grafanaplugin import ( - "github.com/grafana/thema" ui "github.com/grafana/grafana/packages/grafana-schema/src/common" ) -Panel: thema.#Lineage & { - name: "table" - seqs: [ - { - schemas: [ - { - PanelOptions: { - frameIndex: number | *0 - showHeader: bool | *true - showTypeIcons: bool | *false - sortBy?: [...ui.TableSortByFieldState] - } @cuetsy(kind="interface") - PanelFieldConfig: ui.TableFieldOptions & {} @cuetsy(kind="interface") - }, - ] - }, - ] +composableKinds: PanelCfg: { + lineage: { + seqs: [ + { + schemas: [ + { + PanelOptions: { + frameIndex: number | *0 + showHeader: bool | *true + showTypeIcons: bool | *false + sortBy?: [...ui.TableSortByFieldState] + } @cuetsy(kind="interface") + PanelFieldConfig: ui.TableFieldOptions & {} @cuetsy(kind="interface") + }, + ] + }, + ] + } } diff --git a/public/app/plugins/panel/text/models.cue b/public/app/plugins/panel/text/composable_panelcfg.cue similarity index 51% rename from public/app/plugins/panel/text/models.cue rename to public/app/plugins/panel/text/composable_panelcfg.cue index d046b646452..3ffc1aca627 100644 --- a/public/app/plugins/panel/text/models.cue +++ b/public/app/plugins/panel/text/composable_panelcfg.cue @@ -14,36 +14,37 @@ package grafanaplugin -import "github.com/grafana/thema" +composableKinds: PanelCfg: { + maturity: "experimental" -Panel: thema.#Lineage & { - name: "text" - seqs: [ - { - schemas: [ - { - TextMode: "html" | "markdown" | "code" @cuetsy(kind="enum",memberNames="HTML|Markdown|Code") + lineage: { + seqs: [ + { + schemas: [ + { + TextMode: "html" | "markdown" | "code" @cuetsy(kind="enum",memberNames="HTML|Markdown|Code") - CodeLanguage: "json" | "yaml" | "xml" | "typescript" | "sql" | "go" | "markdown" | "html" | *"plaintext" @cuetsy(kind="enum") + CodeLanguage: "json" | "yaml" | "xml" | "typescript" | "sql" | "go" | "markdown" | "html" | *"plaintext" @cuetsy(kind="enum") - CodeOptions: { - // The language passed to monaco code editor - language: CodeLanguage - showLineNumbers: bool | *false - showMiniMap: bool | *false - } @cuetsy(kind="interface") + CodeOptions: { + // The language passed to monaco code editor + language: CodeLanguage + showLineNumbers: bool | *false + showMiniMap: bool | *false + } @cuetsy(kind="interface") - PanelOptions: { - mode: TextMode | *"markdown" - code?: CodeOptions - content: string | *""" + PanelOptions: { + mode: TextMode | *"markdown" + code?: CodeOptions + content: string | *""" # Title For markdown syntax help: [commonmark.org/help](https://commonmark.org/help/) """ - } @cuetsy(kind="interface") - }, - ] - }, - ] + } @cuetsy(kind="interface") + }, + ] + }, + ] + } } diff --git a/public/app/plugins/panel/text/models.gen.ts b/public/app/plugins/panel/text/models.gen.ts index 79928e0024b..8dd74fce95d 100644 --- a/public/app/plugins/panel/text/models.gen.ts +++ b/public/app/plugins/panel/text/models.gen.ts @@ -8,7 +8,7 @@ // // Run 'make gen-cue' from repository root to regenerate. -export const PanelModelVersion = Object.freeze([0, 0]); +export const PanelCfgModelVersion = Object.freeze([0, 0]); export enum TextMode { Code = 'code', diff --git a/public/app/plugins/panel/timeseries/models.cue b/public/app/plugins/panel/timeseries/composable_panelcfg.cue similarity index 68% rename from public/app/plugins/panel/timeseries/models.cue rename to public/app/plugins/panel/timeseries/composable_panelcfg.cue index 53f0d6ad4ba..963a4a13424 100644 --- a/public/app/plugins/panel/timeseries/models.cue +++ b/public/app/plugins/panel/timeseries/composable_panelcfg.cue @@ -15,23 +15,23 @@ package grafanaplugin import ( - "github.com/grafana/thema" ui "github.com/grafana/grafana/packages/grafana-schema/src/common" ) -Panel: thema.#Lineage & { - name: "timeseries" - seqs: [ - { - schemas: [ - { - PanelOptions: { - legend: ui.VizLegendOptions - tooltip: ui.VizTooltipOptions - } @cuetsy(kind="interface") - PanelFieldConfig: ui.GraphFieldConfig & {} @cuetsy(kind="interface") - }, - ] - }, - ] +composableKinds: PanelCfg: { + lineage: { + seqs: [ + { + schemas: [ + { + PanelOptions: { + legend: ui.VizLegendOptions + tooltip: ui.VizTooltipOptions + } @cuetsy(kind="interface") + PanelFieldConfig: ui.GraphFieldConfig & {} @cuetsy(kind="interface") + }, + ] + }, + ] + } }