LibraryPanels: Expose library panels in dashboard apiserver (#92213)
This commit is contained in:
@@ -38,7 +38,35 @@ var DashboardResourceInfo = common.NewResourceInfo(GROUP, VERSION,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("expected dashboard or summary")
|
||||
return nil, fmt.Errorf("expected dashboard")
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
var LibraryPanelResourceInfo = common.NewResourceInfo(GROUP, VERSION,
|
||||
"librarypanels", "librarypanel", "LibraryPanel",
|
||||
func() runtime.Object { return &LibraryPanel{} },
|
||||
func() runtime.Object { return &LibraryPanelList{} },
|
||||
utils.TableColumns{
|
||||
Definition: []metav1.TableColumnDefinition{
|
||||
{Name: "Name", Type: "string", Format: "name"},
|
||||
{Name: "Title", Type: "string", Description: "The dashboard name"},
|
||||
{Name: "Type", Type: "string", Description: "the panel type"},
|
||||
{Name: "Created At", Type: "date"},
|
||||
},
|
||||
Reader: func(obj any) ([]interface{}, error) {
|
||||
dash, ok := obj.(*LibraryPanel)
|
||||
if ok {
|
||||
if dash != nil {
|
||||
return []interface{}{
|
||||
dash.Name,
|
||||
dash.Spec.Title,
|
||||
dash.Spec.Type,
|
||||
dash.CreationTimestamp.UTC().Format(time.RFC3339),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("expected library panel")
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
@@ -3,6 +3,7 @@ package v0alpha1
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
data "github.com/grafana/grafana-plugin-sdk-go/experimental/apis/data/v0alpha1"
|
||||
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
|
||||
)
|
||||
|
||||
@@ -66,6 +67,65 @@ type VersionsQueryOptions struct {
|
||||
Version int64 `json:"version,omitempty"`
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
type LibraryPanel struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
// Standard object's metadata
|
||||
// More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata
|
||||
// +optional
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
// Panel properties
|
||||
Spec LibraryPanelSpec `json:"spec"`
|
||||
|
||||
// Status will show errors
|
||||
Status *LibraryPanelStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
type LibraryPanelList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
// +optional
|
||||
metav1.ListMeta `json:"metadata,omitempty"`
|
||||
|
||||
Items []LibraryPanel `json:"items,omitempty"`
|
||||
}
|
||||
|
||||
type LibraryPanelSpec struct {
|
||||
// The panel type
|
||||
Type string `json:"type"`
|
||||
|
||||
// The panel type
|
||||
PluginVersion string `json:"pluginVersion,omitempty"`
|
||||
|
||||
// The panel title
|
||||
Title string `json:"title,omitempty"`
|
||||
|
||||
// Library panel description
|
||||
Description string `json:"description,omitempty"`
|
||||
|
||||
// The options schema depends on the panel type
|
||||
Options common.Unstructured `json:"options"`
|
||||
|
||||
// The fieldConfig schema depends on the panel type
|
||||
FieldConfig common.Unstructured `json:"fieldConfig"`
|
||||
|
||||
// The default datasource type
|
||||
Datasource *data.DataSourceRef `json:"datasource,omitempty"`
|
||||
|
||||
// The datasource queries
|
||||
// +listType=set
|
||||
Targets []data.DataQuery `json:"targets,omitempty"`
|
||||
}
|
||||
|
||||
type LibraryPanelStatus struct {
|
||||
// Translation warnings (mostly things that were in SQL columns but not found in the saved body)
|
||||
Warnings []string `json:"warnings,omitempty"`
|
||||
|
||||
// The properties previously stored in SQL that are not included in this model
|
||||
Missing common.Unstructured `json:"missing,omitempty"`
|
||||
}
|
||||
|
||||
// This is like the legacy DTO where access and metadata are all returned in a single call
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
type DashboardWithAccessInfo struct {
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
package v0alpha1
|
||||
|
||||
import (
|
||||
datav0alpha1 "github.com/grafana/grafana-plugin-sdk-go/experimental/apis/data/v0alpha1"
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
@@ -199,6 +200,123 @@ func (in *DashboardWithAccessInfo) DeepCopyObject() runtime.Object {
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *LibraryPanel) DeepCopyInto(out *LibraryPanel) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
in.Spec.DeepCopyInto(&out.Spec)
|
||||
if in.Status != nil {
|
||||
in, out := &in.Status, &out.Status
|
||||
*out = new(LibraryPanelStatus)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LibraryPanel.
|
||||
func (in *LibraryPanel) DeepCopy() *LibraryPanel {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(LibraryPanel)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *LibraryPanel) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *LibraryPanelList) DeepCopyInto(out *LibraryPanelList) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]LibraryPanel, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LibraryPanelList.
|
||||
func (in *LibraryPanelList) DeepCopy() *LibraryPanelList {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(LibraryPanelList)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *LibraryPanelList) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *LibraryPanelSpec) DeepCopyInto(out *LibraryPanelSpec) {
|
||||
*out = *in
|
||||
in.Options.DeepCopyInto(&out.Options)
|
||||
in.FieldConfig.DeepCopyInto(&out.FieldConfig)
|
||||
if in.Datasource != nil {
|
||||
in, out := &in.Datasource, &out.Datasource
|
||||
*out = new(datav0alpha1.DataSourceRef)
|
||||
**out = **in
|
||||
}
|
||||
if in.Targets != nil {
|
||||
in, out := &in.Targets, &out.Targets
|
||||
*out = make([]datav0alpha1.DataQuery, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LibraryPanelSpec.
|
||||
func (in *LibraryPanelSpec) DeepCopy() *LibraryPanelSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(LibraryPanelSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *LibraryPanelStatus) DeepCopyInto(out *LibraryPanelStatus) {
|
||||
*out = *in
|
||||
if in.Warnings != nil {
|
||||
in, out := &in.Warnings, &out.Warnings
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
in.Missing.DeepCopyInto(&out.Missing)
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LibraryPanelStatus.
|
||||
func (in *LibraryPanelStatus) DeepCopy() *LibraryPanelStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(LibraryPanelStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *VersionsQueryOptions) DeepCopyInto(out *VersionsQueryOptions) {
|
||||
*out = *in
|
||||
|
||||
@@ -22,6 +22,10 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA
|
||||
"github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1.DashboardVersionInfo": schema_pkg_apis_dashboard_v0alpha1_DashboardVersionInfo(ref),
|
||||
"github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1.DashboardVersionList": schema_pkg_apis_dashboard_v0alpha1_DashboardVersionList(ref),
|
||||
"github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1.DashboardWithAccessInfo": schema_pkg_apis_dashboard_v0alpha1_DashboardWithAccessInfo(ref),
|
||||
"github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1.LibraryPanel": schema_pkg_apis_dashboard_v0alpha1_LibraryPanel(ref),
|
||||
"github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1.LibraryPanelList": schema_pkg_apis_dashboard_v0alpha1_LibraryPanelList(ref),
|
||||
"github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1.LibraryPanelSpec": schema_pkg_apis_dashboard_v0alpha1_LibraryPanelSpec(ref),
|
||||
"github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1.LibraryPanelStatus": schema_pkg_apis_dashboard_v0alpha1_LibraryPanelStatus(ref),
|
||||
"github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1.VersionsQueryOptions": schema_pkg_apis_dashboard_v0alpha1_VersionsQueryOptions(ref),
|
||||
}
|
||||
}
|
||||
@@ -392,6 +396,217 @@ func schema_pkg_apis_dashboard_v0alpha1_DashboardWithAccessInfo(ref common.Refer
|
||||
}
|
||||
}
|
||||
|
||||
func schema_pkg_apis_dashboard_v0alpha1_LibraryPanel(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"object"},
|
||||
Properties: map[string]spec.Schema{
|
||||
"kind": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"apiVersion": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"metadata": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Standard object's metadata More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata",
|
||||
Default: map[string]interface{}{},
|
||||
Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"),
|
||||
},
|
||||
},
|
||||
"spec": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Panel properties",
|
||||
Default: map[string]interface{}{},
|
||||
Ref: ref("github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1.LibraryPanelSpec"),
|
||||
},
|
||||
},
|
||||
"status": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Status will show errors",
|
||||
Ref: ref("github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1.LibraryPanelStatus"),
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: []string{"spec"},
|
||||
},
|
||||
},
|
||||
Dependencies: []string{
|
||||
"github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1.LibraryPanelSpec", "github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1.LibraryPanelStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"},
|
||||
}
|
||||
}
|
||||
|
||||
func schema_pkg_apis_dashboard_v0alpha1_LibraryPanelList(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"object"},
|
||||
Properties: map[string]spec.Schema{
|
||||
"kind": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"apiVersion": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"metadata": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Default: map[string]interface{}{},
|
||||
Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"),
|
||||
},
|
||||
},
|
||||
"items": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"array"},
|
||||
Items: &spec.SchemaOrArray{
|
||||
Schema: &spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Default: map[string]interface{}{},
|
||||
Ref: ref("github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1.LibraryPanel"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Dependencies: []string{
|
||||
"github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1.LibraryPanel", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"},
|
||||
}
|
||||
}
|
||||
|
||||
func schema_pkg_apis_dashboard_v0alpha1_LibraryPanelSpec(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"object"},
|
||||
Properties: map[string]spec.Schema{
|
||||
"type": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "The panel type",
|
||||
Default: "",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"pluginVersion": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "The panel type",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"title": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "The panel title",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"description": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Library panel description",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"options": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "The options schema depends on the panel type",
|
||||
Ref: ref("github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1.Unstructured"),
|
||||
},
|
||||
},
|
||||
"fieldConfig": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "The fieldConfig schema depends on the panel type",
|
||||
Ref: ref("github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1.Unstructured"),
|
||||
},
|
||||
},
|
||||
"datasource": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "The default datasource type",
|
||||
Ref: ref("github.com/grafana/grafana-plugin-sdk-go/experimental/apis/data/v0alpha1.DataSourceRef"),
|
||||
},
|
||||
},
|
||||
"targets": {
|
||||
VendorExtensible: spec.VendorExtensible{
|
||||
Extensions: spec.Extensions{
|
||||
"x-kubernetes-list-type": "set",
|
||||
},
|
||||
},
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "The datasource queries",
|
||||
Type: []string{"array"},
|
||||
Items: &spec.SchemaOrArray{
|
||||
Schema: &spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Ref: ref("github.com/grafana/grafana-plugin-sdk-go/experimental/apis/data/v0alpha1.DataQuery"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: []string{"type", "options", "fieldConfig"},
|
||||
},
|
||||
},
|
||||
Dependencies: []string{
|
||||
"github.com/grafana/grafana-plugin-sdk-go/experimental/apis/data/v0alpha1.DataQuery", "github.com/grafana/grafana-plugin-sdk-go/experimental/apis/data/v0alpha1.DataSourceRef", "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1.Unstructured"},
|
||||
}
|
||||
}
|
||||
|
||||
func schema_pkg_apis_dashboard_v0alpha1_LibraryPanelStatus(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"object"},
|
||||
Properties: map[string]spec.Schema{
|
||||
"warnings": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Translation warnings (mostly things that were in SQL columns but not found in the saved body)",
|
||||
Type: []string{"array"},
|
||||
Items: &spec.SchemaOrArray{
|
||||
Schema: &spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Default: "",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"missing": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "The properties previously stored in SQL that are not included in this model",
|
||||
Ref: ref("github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1.Unstructured"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Dependencies: []string{
|
||||
"github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1.Unstructured"},
|
||||
}
|
||||
}
|
||||
|
||||
func schema_pkg_apis_dashboard_v0alpha1_VersionsQueryOptions(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1,LibraryPanelStatus,Warnings
|
||||
@@ -27,6 +27,7 @@ func mustTemplate(filename string) *template.Template {
|
||||
// Templates.
|
||||
var (
|
||||
sqlQueryDashboards = mustTemplate("query_dashboards.sql")
|
||||
sqlQueryPanels = mustTemplate("query_panels.sql")
|
||||
)
|
||||
|
||||
type sqlQuery struct {
|
||||
@@ -54,3 +55,25 @@ func newQueryReq(sql *legacysql.LegacyDatabaseHelper, query *DashboardQuery) sql
|
||||
UserTable: sql.Table("user"),
|
||||
}
|
||||
}
|
||||
|
||||
type sqlLibraryQuery struct {
|
||||
sqltemplate.SQLTemplate
|
||||
Query *LibraryPanelQuery
|
||||
|
||||
LibraryElementTable string
|
||||
UserTable string
|
||||
}
|
||||
|
||||
func (r sqlLibraryQuery) Validate() error {
|
||||
return nil // TODO
|
||||
}
|
||||
|
||||
func newLibraryQueryReq(sql *legacysql.LegacyDatabaseHelper, query *LibraryPanelQuery) sqlLibraryQuery {
|
||||
return sqlLibraryQuery{
|
||||
SQLTemplate: sqltemplate.New(sql.DialectForDriver()),
|
||||
Query: query,
|
||||
|
||||
LibraryElementTable: sql.Table("library_element"),
|
||||
UserTable: sql.Table("user"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,12 @@ func TestQueries(t *testing.T) {
|
||||
return &v
|
||||
}
|
||||
|
||||
getLibraryQuery := func(q *LibraryPanelQuery) sqltemplate.SQLTemplate {
|
||||
v := newLibraryQueryReq(nodb, q)
|
||||
v.SQLTemplate = mocks.NewTestingSQLTemplate()
|
||||
return &v
|
||||
}
|
||||
|
||||
mocks.CheckQuerySnapshots(t, mocks.TemplateTestSetup{
|
||||
RootDir: "testdata",
|
||||
Templates: map[*template.Template][]mocks.TemplateTestCase{
|
||||
@@ -64,6 +70,29 @@ func TestQueries(t *testing.T) {
|
||||
}),
|
||||
},
|
||||
},
|
||||
sqlQueryPanels: {
|
||||
{
|
||||
Name: "list",
|
||||
Data: getLibraryQuery(&LibraryPanelQuery{
|
||||
OrgID: 1,
|
||||
Limit: 5,
|
||||
}),
|
||||
},
|
||||
{
|
||||
Name: "list_page_two",
|
||||
Data: getLibraryQuery(&LibraryPanelQuery{
|
||||
OrgID: 1,
|
||||
LastID: 4,
|
||||
}),
|
||||
},
|
||||
{
|
||||
Name: "get_uid",
|
||||
Data: getLibraryQuery(&LibraryPanelQuery{
|
||||
OrgID: 1,
|
||||
UID: "xyz",
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
SELECT p.id, p.uid, p.folder_uid,
|
||||
p.created, created_user.uid as created_by,
|
||||
p.updated, updated_user.uid as updated_by,
|
||||
p.name, p.type, p.description, p.model
|
||||
FROM {{ .Ident .LibraryElementTable }} as p
|
||||
LEFT OUTER JOIN {{ .Ident .UserTable }} AS created_user ON p.created_by = created_user.id
|
||||
LEFT OUTER JOIN {{ .Ident .UserTable }} AS updated_user ON p.updated_by = updated_user.id
|
||||
WHERE p.org_id = {{ .Arg .Query.OrgID }}
|
||||
{{ if .Query.LastID }}
|
||||
AND p.id > {{ .Arg .Query.LastID }}
|
||||
{{ end }}
|
||||
{{ if .Query.UID }}
|
||||
AND p.uid = {{ .Arg .Query.UID }}
|
||||
{{ end }}
|
||||
ORDER BY p.id DESC
|
||||
{{ if .Query.Limit }}
|
||||
LIMIT {{ .Arg .Query.Limit }}
|
||||
{{ end }}
|
||||
@@ -6,10 +6,12 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/authlib/claims"
|
||||
"github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
|
||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||
dashboardsV0 "github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1"
|
||||
@@ -50,7 +52,8 @@ type dashboardSqlAccess struct {
|
||||
provisioning provisioning.ProvisioningService
|
||||
|
||||
// Use for writing (not reading)
|
||||
dashStore dashboards.Store
|
||||
dashStore dashboards.Store
|
||||
softDelete bool
|
||||
|
||||
// Typically one... the server wrapper
|
||||
subscribers []chan *resource.WrittenEvent
|
||||
@@ -61,12 +64,14 @@ func NewDashboardAccess(sql legacysql.LegacyDatabaseProvider,
|
||||
namespacer request.NamespaceMapper,
|
||||
dashStore dashboards.Store,
|
||||
provisioning provisioning.ProvisioningService,
|
||||
softDelete bool,
|
||||
) DashboardAccess {
|
||||
return &dashboardSqlAccess{
|
||||
sql: sql,
|
||||
namespacer: namespacer,
|
||||
dashStore: dashStore,
|
||||
provisioning: provisioning,
|
||||
softDelete: softDelete,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -314,6 +319,16 @@ func (a *dashboardSqlAccess) DeleteDashboard(ctx context.Context, orgId int64, u
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
if a.softDelete {
|
||||
err = a.dashStore.SoftDeleteDashboard(ctx, orgId, uid)
|
||||
if err == nil && dash != nil {
|
||||
now := metav1.NewTime(time.Now())
|
||||
dash.DeletionTimestamp = &now
|
||||
return dash, true, err
|
||||
}
|
||||
return dash, false, err
|
||||
}
|
||||
|
||||
id := dash.Spec.GetNestedInt64("id")
|
||||
if id == 0 {
|
||||
return nil, false, fmt.Errorf("could not find id in saved body")
|
||||
@@ -383,3 +398,128 @@ func (a *dashboardSqlAccess) SaveDashboard(ctx context.Context, orgId int64, das
|
||||
dash, _, err = a.GetDashboard(ctx, orgId, out.UID, 0)
|
||||
return dash, created, err
|
||||
}
|
||||
|
||||
func (a *dashboardSqlAccess) GetLibraryPanels(ctx context.Context, query LibraryPanelQuery) (*dashboardsV0.LibraryPanelList, error) {
|
||||
limit := int(query.Limit)
|
||||
query.Limit += 1 // for continue
|
||||
if query.OrgID == 0 {
|
||||
return nil, fmt.Errorf("expected non zero orgID")
|
||||
}
|
||||
|
||||
sql, err := a.sql(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req := newLibraryQueryReq(sql, &query)
|
||||
rawQuery, err := sqltemplate.Execute(sqlQueryPanels, req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("execute template %q: %w", sqlQueryPanels.Name(), err)
|
||||
}
|
||||
q := rawQuery
|
||||
|
||||
res := &dashboardsV0.LibraryPanelList{}
|
||||
rows, err := sql.DB.GetSqlxSession().Query(ctx, q, req.GetArgs()...)
|
||||
defer func() {
|
||||
if rows != nil {
|
||||
_ = rows.Close()
|
||||
}
|
||||
}()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
type panel struct {
|
||||
ID int64
|
||||
UID string
|
||||
FolderUID string
|
||||
|
||||
Created time.Time
|
||||
CreatedBy string
|
||||
|
||||
Updated time.Time
|
||||
UpdatedBy string
|
||||
|
||||
Name string
|
||||
Type string
|
||||
Description string
|
||||
Model []byte
|
||||
}
|
||||
|
||||
var lastID int64
|
||||
for rows.Next() {
|
||||
p := panel{}
|
||||
err = rows.Scan(&p.ID, &p.UID, &p.FolderUID,
|
||||
&p.Created, &p.CreatedBy,
|
||||
&p.Updated, &p.UpdatedBy,
|
||||
&p.Name, &p.Type, &p.Description, &p.Model,
|
||||
)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
lastID = p.ID
|
||||
|
||||
item := dashboardsV0.LibraryPanel{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: p.UID,
|
||||
CreationTimestamp: metav1.NewTime(p.Created),
|
||||
ResourceVersion: strconv.FormatInt(p.Updated.UnixMilli(), 10),
|
||||
},
|
||||
Spec: dashboardsV0.LibraryPanelSpec{},
|
||||
}
|
||||
|
||||
status := &dashboardsV0.LibraryPanelStatus{
|
||||
Missing: v0alpha1.Unstructured{},
|
||||
}
|
||||
err = json.Unmarshal(p.Model, &item.Spec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = json.Unmarshal(p.Model, &status.Missing.Object)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if item.Spec.Title != p.Name {
|
||||
status.Warnings = append(item.Status.Warnings, fmt.Sprintf("title mismatch (expected: %s)", p.Name))
|
||||
}
|
||||
if item.Spec.Description != p.Description {
|
||||
status.Warnings = append(item.Status.Warnings, fmt.Sprintf("description mismatch (expected: %s)", p.Description))
|
||||
}
|
||||
if item.Spec.Type != p.Type {
|
||||
status.Warnings = append(item.Status.Warnings, fmt.Sprintf("type mismatch (expected: %s)", p.Type))
|
||||
}
|
||||
item.Status = status
|
||||
|
||||
// Remove the properties we are already showing
|
||||
for _, k := range []string{"type", "pluginVersion", "title", "description", "options", "fieldConfig", "datasource", "targets", "libraryPanel"} {
|
||||
delete(status.Missing.Object, k)
|
||||
}
|
||||
|
||||
meta, err := utils.MetaAccessor(&item)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
meta.SetFolder(p.FolderUID)
|
||||
meta.SetCreatedBy(p.CreatedBy)
|
||||
meta.SetUpdatedBy(p.UpdatedBy)
|
||||
meta.SetUpdatedTimestamp(&p.Updated)
|
||||
meta.SetOriginInfo(&utils.ResourceOriginInfo{
|
||||
Name: "SQL",
|
||||
Path: strconv.FormatInt(p.ID, 10),
|
||||
})
|
||||
|
||||
res.Items = append(res.Items, item)
|
||||
if len(res.Items) > limit {
|
||||
res.Continue = strconv.FormatInt(lastID, 10)
|
||||
break
|
||||
}
|
||||
}
|
||||
if query.UID == "" {
|
||||
rv, err := sql.GetResourceVersion(ctx, "library_element", "updated")
|
||||
if err == nil {
|
||||
res.ResourceVersion = strconv.FormatInt(rv, 10)
|
||||
}
|
||||
}
|
||||
return res, err
|
||||
}
|
||||
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
SELECT p.id, p.uid, p.folder_uid,
|
||||
p.created, created_user.uid as created_by,
|
||||
p.updated, updated_user.uid as updated_by,
|
||||
p.name, p.type, p.description, p.model
|
||||
FROM "grafana.library_element" as p
|
||||
LEFT OUTER JOIN "grafana.user" AS created_user ON p.created_by = created_user.id
|
||||
LEFT OUTER JOIN "grafana.user" AS updated_user ON p.updated_by = updated_user.id
|
||||
WHERE p.org_id = 1
|
||||
AND p.uid = 'xyz'
|
||||
ORDER BY p.id DESC
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
SELECT p.id, p.uid, p.folder_uid,
|
||||
p.created, created_user.uid as created_by,
|
||||
p.updated, updated_user.uid as updated_by,
|
||||
p.name, p.type, p.description, p.model
|
||||
FROM "grafana.library_element" as p
|
||||
LEFT OUTER JOIN "grafana.user" AS created_user ON p.created_by = created_user.id
|
||||
LEFT OUTER JOIN "grafana.user" AS updated_user ON p.updated_by = updated_user.id
|
||||
WHERE p.org_id = 1
|
||||
ORDER BY p.id DESC
|
||||
LIMIT 5
|
||||
Vendored
Executable
+10
@@ -0,0 +1,10 @@
|
||||
SELECT p.id, p.uid, p.folder_uid,
|
||||
p.created, created_user.uid as created_by,
|
||||
p.updated, updated_user.uid as updated_by,
|
||||
p.name, p.type, p.description, p.model
|
||||
FROM "grafana.library_element" as p
|
||||
LEFT OUTER JOIN "grafana.user" AS created_user ON p.created_by = created_user.id
|
||||
LEFT OUTER JOIN "grafana.user" AS updated_user ON p.updated_by = updated_user.id
|
||||
WHERE p.org_id = 1
|
||||
AND p.id > 4
|
||||
ORDER BY p.id DESC
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
SELECT p.id, p.uid, p.folder_uid,
|
||||
p.created, created_user.uid as created_by,
|
||||
p.updated, updated_user.uid as updated_by,
|
||||
p.name, p.type, p.description, p.model
|
||||
FROM "grafana.library_element" as p
|
||||
LEFT OUTER JOIN "grafana.user" AS created_user ON p.created_by = created_user.id
|
||||
LEFT OUTER JOIN "grafana.user" AS updated_user ON p.updated_by = updated_user.id
|
||||
WHERE p.org_id = 1
|
||||
AND p.uid = 'xyz'
|
||||
ORDER BY p.id DESC
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
SELECT p.id, p.uid, p.folder_uid,
|
||||
p.created, created_user.uid as created_by,
|
||||
p.updated, updated_user.uid as updated_by,
|
||||
p.name, p.type, p.description, p.model
|
||||
FROM "grafana.library_element" as p
|
||||
LEFT OUTER JOIN "grafana.user" AS created_user ON p.created_by = created_user.id
|
||||
LEFT OUTER JOIN "grafana.user" AS updated_user ON p.updated_by = updated_user.id
|
||||
WHERE p.org_id = 1
|
||||
ORDER BY p.id DESC
|
||||
LIMIT 5
|
||||
Vendored
Executable
+10
@@ -0,0 +1,10 @@
|
||||
SELECT p.id, p.uid, p.folder_uid,
|
||||
p.created, created_user.uid as created_by,
|
||||
p.updated, updated_user.uid as updated_by,
|
||||
p.name, p.type, p.description, p.model
|
||||
FROM "grafana.library_element" as p
|
||||
LEFT OUTER JOIN "grafana.user" AS created_user ON p.created_by = created_user.id
|
||||
LEFT OUTER JOIN "grafana.user" AS updated_user ON p.updated_by = updated_user.id
|
||||
WHERE p.org_id = 1
|
||||
AND p.id > 4
|
||||
ORDER BY p.id DESC
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
SELECT p.id, p.uid, p.folder_uid,
|
||||
p.created, created_user.uid as created_by,
|
||||
p.updated, updated_user.uid as updated_by,
|
||||
p.name, p.type, p.description, p.model
|
||||
FROM "grafana.library_element" as p
|
||||
LEFT OUTER JOIN "grafana.user" AS created_user ON p.created_by = created_user.id
|
||||
LEFT OUTER JOIN "grafana.user" AS updated_user ON p.updated_by = updated_user.id
|
||||
WHERE p.org_id = 1
|
||||
AND p.uid = 'xyz'
|
||||
ORDER BY p.id DESC
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
SELECT p.id, p.uid, p.folder_uid,
|
||||
p.created, created_user.uid as created_by,
|
||||
p.updated, updated_user.uid as updated_by,
|
||||
p.name, p.type, p.description, p.model
|
||||
FROM "grafana.library_element" as p
|
||||
LEFT OUTER JOIN "grafana.user" AS created_user ON p.created_by = created_user.id
|
||||
LEFT OUTER JOIN "grafana.user" AS updated_user ON p.updated_by = updated_user.id
|
||||
WHERE p.org_id = 1
|
||||
ORDER BY p.id DESC
|
||||
LIMIT 5
|
||||
Vendored
Executable
+10
@@ -0,0 +1,10 @@
|
||||
SELECT p.id, p.uid, p.folder_uid,
|
||||
p.created, created_user.uid as created_by,
|
||||
p.updated, updated_user.uid as updated_by,
|
||||
p.name, p.type, p.description, p.model
|
||||
FROM "grafana.library_element" as p
|
||||
LEFT OUTER JOIN "grafana.user" AS created_user ON p.created_by = created_user.id
|
||||
LEFT OUTER JOIN "grafana.user" AS updated_user ON p.updated_by = updated_user.id
|
||||
WHERE p.org_id = 1
|
||||
AND p.id > 4
|
||||
ORDER BY p.id DESC
|
||||
@@ -33,6 +33,16 @@ func (r *DashboardQuery) UseHistoryTable() bool {
|
||||
return r.GetHistory || r.Version > 0
|
||||
}
|
||||
|
||||
type LibraryPanelQuery struct {
|
||||
OrgID int64
|
||||
UID string // to select a single dashboard
|
||||
Limit int64
|
||||
|
||||
// Included in the continue token
|
||||
// This is the ID from the last dashboard sent in the previous page
|
||||
LastID int64
|
||||
}
|
||||
|
||||
type DashboardAccess interface {
|
||||
resource.StorageBackend
|
||||
resource.ResourceIndexServer
|
||||
@@ -40,4 +50,7 @@ type DashboardAccess interface {
|
||||
GetDashboard(ctx context.Context, orgId int64, uid string, version int64) (*dashboardsV0.Dashboard, int64, error)
|
||||
SaveDashboard(ctx context.Context, orgId int64, dash *dashboardsV0.Dashboard) (*dashboardsV0.Dashboard, bool, error)
|
||||
DeleteDashboard(ctx context.Context, orgId int64, uid string) (*dashboardsV0.Dashboard, bool, error)
|
||||
|
||||
// Get a typed list
|
||||
GetLibraryPanels(ctx context.Context, query LibraryPanelQuery) (*dashboardsV0.LibraryPanelList, error)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
package dashboard
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"k8s.io/apimachinery/pkg/apis/meta/internalversion"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
|
||||
dashboard "github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/dashboard/legacy"
|
||||
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
|
||||
)
|
||||
|
||||
var (
|
||||
_ rest.Scoper = (*libraryPanelStore)(nil)
|
||||
_ rest.SingularNameProvider = (*libraryPanelStore)(nil)
|
||||
_ rest.Getter = (*libraryPanelStore)(nil)
|
||||
_ rest.Lister = (*libraryPanelStore)(nil)
|
||||
_ rest.Storage = (*libraryPanelStore)(nil)
|
||||
)
|
||||
|
||||
var lpr = dashboard.LibraryPanelResourceInfo
|
||||
|
||||
type libraryPanelStore struct {
|
||||
access legacy.DashboardAccess
|
||||
}
|
||||
|
||||
func (s *libraryPanelStore) New() runtime.Object {
|
||||
return lpr.NewFunc()
|
||||
}
|
||||
|
||||
func (s *libraryPanelStore) Destroy() {}
|
||||
|
||||
func (s *libraryPanelStore) NamespaceScoped() bool {
|
||||
return true // namespace == org
|
||||
}
|
||||
|
||||
func (s *libraryPanelStore) GetSingularName() string {
|
||||
return lpr.GetSingularName()
|
||||
}
|
||||
|
||||
func (s *libraryPanelStore) NewList() runtime.Object {
|
||||
return lpr.NewListFunc()
|
||||
}
|
||||
|
||||
func (s *libraryPanelStore) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) {
|
||||
return lpr.TableConverter().ConvertToTable(ctx, object, tableOptions)
|
||||
}
|
||||
|
||||
func (s *libraryPanelStore) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) {
|
||||
ns, err := request.NamespaceInfoFrom(ctx, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
query := legacy.LibraryPanelQuery{
|
||||
OrgID: ns.OrgID,
|
||||
Limit: options.Limit,
|
||||
}
|
||||
if options.Continue != "" {
|
||||
query.LastID, err = strconv.ParseInt(options.Continue, 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid continue token")
|
||||
}
|
||||
}
|
||||
if query.Limit < 1 {
|
||||
query.Limit = 25
|
||||
}
|
||||
return s.access.GetLibraryPanels(ctx, query)
|
||||
}
|
||||
|
||||
func (s *libraryPanelStore) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {
|
||||
ns, err := request.NamespaceInfoFrom(ctx, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
query := legacy.LibraryPanelQuery{
|
||||
OrgID: ns.OrgID,
|
||||
UID: name,
|
||||
Limit: 1,
|
||||
}
|
||||
found, err := s.access.GetLibraryPanels(ctx, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(found.Items) == 1 {
|
||||
return &found.Items[0], nil
|
||||
}
|
||||
return nil, lpr.NewNotFound(name)
|
||||
}
|
||||
@@ -56,6 +56,7 @@ func RegisterAPIService(cfg *setting.Cfg, features featuremgmt.FeatureToggles,
|
||||
return nil // skip registration unless opting into experimental apis
|
||||
}
|
||||
|
||||
softDelete := features.IsEnabledGlobally(featuremgmt.FlagDashboardRestore)
|
||||
dbp := legacysql.NewDatabaseProvider(sql)
|
||||
namespacer := request.GetNamespaceMapper(cfg)
|
||||
builder := &DashboardsAPIBuilder{
|
||||
@@ -66,7 +67,7 @@ func RegisterAPIService(cfg *setting.Cfg, features featuremgmt.FeatureToggles,
|
||||
|
||||
legacy: &dashboardStorage{
|
||||
resource: dashboard.DashboardResourceInfo,
|
||||
access: legacy.NewDashboardAccess(dbp, namespacer, dashStore, provisioning),
|
||||
access: legacy.NewDashboardAccess(dbp, namespacer, dashStore, provisioning, softDelete),
|
||||
tableConverter: dashboard.DashboardResourceInfo.TableConverter(),
|
||||
},
|
||||
}
|
||||
@@ -90,6 +91,8 @@ func addKnownTypes(scheme *runtime.Scheme, gv schema.GroupVersion) {
|
||||
&dashboard.DashboardWithAccessInfo{},
|
||||
&dashboard.DashboardVersionList{},
|
||||
&dashboard.VersionsQueryOptions{},
|
||||
&dashboard.LibraryPanel{},
|
||||
&dashboard.LibraryPanelList{},
|
||||
&metav1.PartialObjectMetadata{},
|
||||
&metav1.PartialObjectMetadataList{},
|
||||
)
|
||||
@@ -156,6 +159,11 @@ func (b *DashboardsAPIBuilder) GetAPIGroupInfo(
|
||||
}
|
||||
}
|
||||
|
||||
// Expose read only library panels
|
||||
storage[dashboard.LibraryPanelResourceInfo.StoragePath()] = &libraryPanelStore{
|
||||
access: b.legacy.access,
|
||||
}
|
||||
|
||||
apiGroupInfo.VersionedResourcesStorageMap[dashboard.VERSION] = storage
|
||||
return &apiGroupInfo, nil
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
genericregistry "k8s.io/apiserver/pkg/registry/generic/registry"
|
||||
|
||||
"github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1"
|
||||
dashboard "github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1"
|
||||
grafanaregistry "github.com/grafana/grafana/pkg/apiserver/registry/generic"
|
||||
grafanarest "github.com/grafana/grafana/pkg/apiserver/rest"
|
||||
)
|
||||
@@ -17,7 +17,7 @@ type storage struct {
|
||||
|
||||
func newStorage(scheme *runtime.Scheme) (*storage, error) {
|
||||
strategy := grafanaregistry.NewStrategy(scheme)
|
||||
resourceInfo := v0alpha1.DashboardResourceInfo
|
||||
resourceInfo := dashboard.DashboardResourceInfo
|
||||
store := &genericregistry.Store{
|
||||
NewFunc: resourceInfo.NewFunc,
|
||||
NewListFunc: resourceInfo.NewListFunc,
|
||||
|
||||
@@ -17,6 +17,17 @@ func GetOpenAPIDefinitions(builders []APIGroupBuilder) common.GetOpenAPIDefiniti
|
||||
return func(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition {
|
||||
defs := v0alpha1.GetOpenAPIDefinitions(ref) // common grafana apis
|
||||
maps.Copy(defs, data.GetOpenAPIDefinitions(ref))
|
||||
// TODO: remove when https://github.com/grafana/grafana-plugin-sdk-go/pull/1062 is merged
|
||||
maps.Copy(defs, map[string]common.OpenAPIDefinition{
|
||||
"github.com/grafana/grafana-plugin-sdk-go/experimental/apis/data/v0alpha1.DataSourceRef": {
|
||||
Schema: spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"object"},
|
||||
AdditionalProperties: &spec.SchemaOrBool{Allows: true},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
for _, b := range builders {
|
||||
g := b.GetOpenAPIDefinitions()
|
||||
if g != nil {
|
||||
|
||||
@@ -159,6 +159,20 @@ func TestIntegrationDashboardsApp(t *testing.T) {
|
||||
"update",
|
||||
"watch"
|
||||
]
|
||||
},
|
||||
{
|
||||
"resource": "librarypanels",
|
||||
"responseKind": {
|
||||
"group": "",
|
||||
"kind": "LibraryPanel",
|
||||
"version": ""
|
||||
},
|
||||
"scope": "Namespaced",
|
||||
"singularResource": "librarypanel",
|
||||
"verbs": [
|
||||
"get",
|
||||
"list"
|
||||
]
|
||||
}
|
||||
],
|
||||
"version": "v0alpha1"
|
||||
|
||||
Reference in New Issue
Block a user