From f74afa5e7f40a30222cfe31cced34d924598d643 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20Tolnai?= Date: Fri, 21 Jun 2024 08:54:12 +0200 Subject: [PATCH 01/21] Codeowners: Remove `@mikkancso` from codeowners (#89471) remove mikkancso from codeowners --- .github/CODEOWNERS | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index cf7bc7537ad..e26d5baa7a2 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -409,12 +409,12 @@ playwright.config.ts @grafana/plugins-platform-frontend /public/app/features/geo/ @grafana/dataviz-squad /public/app/features/visualization/data-hover/ @grafana/dataviz-squad /public/app/features/commandPalette/ @grafana/grafana-frontend-platform -/public/app/features/connections/ @grafana/plugins-platform-frontend @mikkancso +/public/app/features/connections/ @grafana/plugins-platform-frontend /public/app/features/correlations/ @grafana/explore-squad /public/app/features/dashboard/ @grafana/dashboards-squad /public/app/features/dashboard/components/TransformationsEditor/ @grafana/dataviz-squad /public/app/features/dashboard-scene/ @grafana/dashboards-squad -/public/app/features/datasources/ @grafana/plugins-platform-frontend @mikkancso +/public/app/features/datasources/ @grafana/plugins-platform-frontend /public/app/features/dimensions/ @grafana/dataviz-squad /public/app/features/dataframe-import/ @grafana/dataviz-squad /public/app/features/explore/ @grafana/explore-squad From 93ef90a1e411d6b4372f3d5e8a175525c8fcb466 Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Fri, 21 Jun 2024 10:23:08 +0300 Subject: [PATCH 02/21] K8s: remove (not great) example api (#89524) --- pkg/apis/example/v0alpha1/doc.go | 11 - pkg/apis/example/v0alpha1/register.go | 30 --- pkg/apis/example/v0alpha1/types.go | 48 ---- .../example/v0alpha1/zz_generated.deepcopy.go | 122 ---------- .../example/v0alpha1/zz_generated.defaults.go | 19 -- .../example/v0alpha1/zz_generated.openapi.go | 219 ----------------- ...enerated.openapi_violation_exceptions.list | 1 - pkg/registry/apis/apis.go | 2 - pkg/registry/apis/example/dummy_storage.go | 128 ---------- pkg/registry/apis/example/register.go | 226 ------------------ pkg/registry/apis/example/storage.go | 86 ------- pkg/registry/apis/example/subresource.go | 54 ----- pkg/registry/apis/wireset.go | 2 - pkg/services/apiserver/standalone/factory.go | 4 - pkg/tests/apis/example/example_test.go | 176 -------------- 15 files changed, 1128 deletions(-) delete mode 100644 pkg/apis/example/v0alpha1/doc.go delete mode 100644 pkg/apis/example/v0alpha1/register.go delete mode 100644 pkg/apis/example/v0alpha1/types.go delete mode 100644 pkg/apis/example/v0alpha1/zz_generated.deepcopy.go delete mode 100644 pkg/apis/example/v0alpha1/zz_generated.defaults.go delete mode 100644 pkg/apis/example/v0alpha1/zz_generated.openapi.go delete mode 100644 pkg/apis/example/v0alpha1/zz_generated.openapi_violation_exceptions.list delete mode 100644 pkg/registry/apis/example/dummy_storage.go delete mode 100644 pkg/registry/apis/example/register.go delete mode 100644 pkg/registry/apis/example/storage.go delete mode 100644 pkg/registry/apis/example/subresource.go delete mode 100644 pkg/tests/apis/example/example_test.go diff --git a/pkg/apis/example/v0alpha1/doc.go b/pkg/apis/example/v0alpha1/doc.go deleted file mode 100644 index 96849073b83..00000000000 --- a/pkg/apis/example/v0alpha1/doc.go +++ /dev/null @@ -1,11 +0,0 @@ -// +k8s:deepcopy-gen=package -// +k8s:openapi-gen=true -// +k8s:defaulter-gen=TypeMeta -// +groupName=example.grafana.app - -// The testing api is a dependency free service that we can use to experiment with -// api aggregation across multiple deployment models. Specifically: -// - standalone: running as part of the standard grafana build -// - aggregated: running as the target - -package v0alpha1 // import "github.com/grafana/grafana/pkg/apis/example/v0alpha1" diff --git a/pkg/apis/example/v0alpha1/register.go b/pkg/apis/example/v0alpha1/register.go deleted file mode 100644 index e254cad1d34..00000000000 --- a/pkg/apis/example/v0alpha1/register.go +++ /dev/null @@ -1,30 +0,0 @@ -package v0alpha1 - -import ( - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - - common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" -) - -const ( - GROUP = "example.grafana.app" - VERSION = "v0alpha1" - APIVERSION = GROUP + "/" + VERSION -) - -var RuntimeResourceInfo = common.NewResourceInfo(GROUP, VERSION, - "runtime", "runtime", "RuntimeInfo", - func() runtime.Object { return &RuntimeInfo{} }, - func() runtime.Object { return &RuntimeInfo{} }, -) -var DummyResourceInfo = common.NewResourceInfo(GROUP, VERSION, - "dummy", "dummy", "DummyResource", - func() runtime.Object { return &DummyResource{} }, - func() runtime.Object { return &DummyResourceList{} }, -) - -var ( - // SchemeGroupVersion is group version used to register these objects - SchemeGroupVersion = schema.GroupVersion{Group: GROUP, Version: VERSION} -) diff --git a/pkg/apis/example/v0alpha1/types.go b/pkg/apis/example/v0alpha1/types.go deleted file mode 100644 index a8fef3ffe6f..00000000000 --- a/pkg/apis/example/v0alpha1/types.go +++ /dev/null @@ -1,48 +0,0 @@ -package v0alpha1 - -import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" -) - -// Mirrors the info exposed in "github.com/grafana/grafana/pkg/setting" -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -type RuntimeInfo struct { - metav1.TypeMeta `json:",inline"` - - // Unix timestamp when the process started - StartupTime int64 `json:"startupTime,omitempty"` - - BuildVersion string `json:"buildVersion,omitempty"` - BuildCommit string `json:"buildCommit,omitempty"` - EnterpriseBuildCommit string `json:"enterpriseBuildCommit,omitempty"` - BuildBranch string `json:"buildBranch,omitempty"` - BuildStamp int64 `json:"buildStamp,omitempty"` - IsEnterprise bool `json:"enterprise,omitempty"` - Packaging string `json:"packaging,omitempty"` -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -type DummyResource struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - Spec common.Unstructured `json:"spec,omitempty"` -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -type DummyResourceList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - - Items []DummyResource `json:"items,omitempty"` -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -type DummySubresource struct { - metav1.TypeMeta `json:",inline"` - - // add subresource info here - Info string `json:"info,omitempty"` -} diff --git a/pkg/apis/example/v0alpha1/zz_generated.deepcopy.go b/pkg/apis/example/v0alpha1/zz_generated.deepcopy.go deleted file mode 100644 index 063ad395718..00000000000 --- a/pkg/apis/example/v0alpha1/zz_generated.deepcopy.go +++ /dev/null @@ -1,122 +0,0 @@ -//go:build !ignore_autogenerated -// +build !ignore_autogenerated - -// SPDX-License-Identifier: AGPL-3.0-only - -// Code generated by deepcopy-gen. DO NOT EDIT. - -package v0alpha1 - -import ( - runtime "k8s.io/apimachinery/pkg/runtime" -) - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *DummyResource) DeepCopyInto(out *DummyResource) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DummyResource. -func (in *DummyResource) DeepCopy() *DummyResource { - if in == nil { - return nil - } - out := new(DummyResource) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *DummyResource) 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 *DummyResourceList) DeepCopyInto(out *DummyResourceList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]DummyResource, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DummyResourceList. -func (in *DummyResourceList) DeepCopy() *DummyResourceList { - if in == nil { - return nil - } - out := new(DummyResourceList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *DummyResourceList) 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 *DummySubresource) DeepCopyInto(out *DummySubresource) { - *out = *in - out.TypeMeta = in.TypeMeta - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DummySubresource. -func (in *DummySubresource) DeepCopy() *DummySubresource { - if in == nil { - return nil - } - out := new(DummySubresource) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *DummySubresource) 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 *RuntimeInfo) DeepCopyInto(out *RuntimeInfo) { - *out = *in - out.TypeMeta = in.TypeMeta - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RuntimeInfo. -func (in *RuntimeInfo) DeepCopy() *RuntimeInfo { - if in == nil { - return nil - } - out := new(RuntimeInfo) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *RuntimeInfo) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} diff --git a/pkg/apis/example/v0alpha1/zz_generated.defaults.go b/pkg/apis/example/v0alpha1/zz_generated.defaults.go deleted file mode 100644 index 238fc2f4edc..00000000000 --- a/pkg/apis/example/v0alpha1/zz_generated.defaults.go +++ /dev/null @@ -1,19 +0,0 @@ -//go:build !ignore_autogenerated -// +build !ignore_autogenerated - -// SPDX-License-Identifier: AGPL-3.0-only - -// Code generated by defaulter-gen. DO NOT EDIT. - -package v0alpha1 - -import ( - runtime "k8s.io/apimachinery/pkg/runtime" -) - -// RegisterDefaults adds defaulters functions to the given scheme. -// Public to allow building arbitrary schemes. -// All generated defaulters are covering - they call all nested defaulters. -func RegisterDefaults(scheme *runtime.Scheme) error { - return nil -} diff --git a/pkg/apis/example/v0alpha1/zz_generated.openapi.go b/pkg/apis/example/v0alpha1/zz_generated.openapi.go deleted file mode 100644 index 8c441c55064..00000000000 --- a/pkg/apis/example/v0alpha1/zz_generated.openapi.go +++ /dev/null @@ -1,219 +0,0 @@ -//go:build !ignore_autogenerated -// +build !ignore_autogenerated - -// SPDX-License-Identifier: AGPL-3.0-only - -// Code generated by openapi-gen. DO NOT EDIT. - -// This file was autogenerated by openapi-gen. Do not edit it manually! - -package v0alpha1 - -import ( - common "k8s.io/kube-openapi/pkg/common" - spec "k8s.io/kube-openapi/pkg/validation/spec" -) - -func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition { - return map[string]common.OpenAPIDefinition{ - "github.com/grafana/grafana/pkg/apis/example/v0alpha1.DummyResource": schema_pkg_apis_example_v0alpha1_DummyResource(ref), - "github.com/grafana/grafana/pkg/apis/example/v0alpha1.DummyResourceList": schema_pkg_apis_example_v0alpha1_DummyResourceList(ref), - "github.com/grafana/grafana/pkg/apis/example/v0alpha1.DummySubresource": schema_pkg_apis_example_v0alpha1_DummySubresource(ref), - "github.com/grafana/grafana/pkg/apis/example/v0alpha1.RuntimeInfo": schema_pkg_apis_example_v0alpha1_RuntimeInfo(ref), - } -} - -func schema_pkg_apis_example_v0alpha1_DummyResource(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.ObjectMeta"), - }, - }, - "spec": { - SchemaProps: spec.SchemaProps{ - Ref: ref("github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1.Unstructured"), - }, - }, - }, - }, - }, - Dependencies: []string{ - "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1.Unstructured", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, - } -} - -func schema_pkg_apis_example_v0alpha1_DummyResourceList(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/example/v0alpha1.DummyResource"), - }, - }, - }, - }, - }, - }, - }, - }, - Dependencies: []string{ - "github.com/grafana/grafana/pkg/apis/example/v0alpha1.DummyResource", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, - } -} - -func schema_pkg_apis_example_v0alpha1_DummySubresource(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: "", - }, - }, - "info": { - SchemaProps: spec.SchemaProps{ - Description: "add subresource info here", - Type: []string{"string"}, - Format: "", - }, - }, - }, - }, - }, - } -} - -func schema_pkg_apis_example_v0alpha1_RuntimeInfo(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "Mirrors the info exposed in \"github.com/grafana/grafana/pkg/setting\"", - 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: "", - }, - }, - "startupTime": { - SchemaProps: spec.SchemaProps{ - Description: "Unix timestamp when the process started", - Type: []string{"integer"}, - Format: "int64", - }, - }, - "buildVersion": { - SchemaProps: spec.SchemaProps{ - Type: []string{"string"}, - Format: "", - }, - }, - "buildCommit": { - SchemaProps: spec.SchemaProps{ - Type: []string{"string"}, - Format: "", - }, - }, - "enterpriseBuildCommit": { - SchemaProps: spec.SchemaProps{ - Type: []string{"string"}, - Format: "", - }, - }, - "buildBranch": { - SchemaProps: spec.SchemaProps{ - Type: []string{"string"}, - Format: "", - }, - }, - "buildStamp": { - SchemaProps: spec.SchemaProps{ - Type: []string{"integer"}, - Format: "int64", - }, - }, - "enterprise": { - SchemaProps: spec.SchemaProps{ - Type: []string{"boolean"}, - Format: "", - }, - }, - "packaging": { - SchemaProps: spec.SchemaProps{ - Type: []string{"string"}, - Format: "", - }, - }, - }, - }, - }, - } -} diff --git a/pkg/apis/example/v0alpha1/zz_generated.openapi_violation_exceptions.list b/pkg/apis/example/v0alpha1/zz_generated.openapi_violation_exceptions.list deleted file mode 100644 index 75c16b7b44e..00000000000 --- a/pkg/apis/example/v0alpha1/zz_generated.openapi_violation_exceptions.list +++ /dev/null @@ -1 +0,0 @@ -API rule violation: names_match,github.com/grafana/grafana/pkg/apis/example/v0alpha1,RuntimeInfo,IsEnterprise diff --git a/pkg/registry/apis/apis.go b/pkg/registry/apis/apis.go index 3ccfae82528..a44271582af 100644 --- a/pkg/registry/apis/apis.go +++ b/pkg/registry/apis/apis.go @@ -8,7 +8,6 @@ import ( "github.com/grafana/grafana/pkg/registry/apis/dashboard" "github.com/grafana/grafana/pkg/registry/apis/dashboardsnapshot" "github.com/grafana/grafana/pkg/registry/apis/datasource" - "github.com/grafana/grafana/pkg/registry/apis/example" "github.com/grafana/grafana/pkg/registry/apis/featuretoggle" "github.com/grafana/grafana/pkg/registry/apis/folders" "github.com/grafana/grafana/pkg/registry/apis/peakq" @@ -28,7 +27,6 @@ type Service struct{} func ProvideRegistryServiceSink( _ *dashboard.DashboardsAPIBuilder, _ *playlist.PlaylistAPIBuilder, - _ *example.TestingAPIBuilder, _ *dashboardsnapshot.SnapshotsAPIBuilder, _ *featuretoggle.FeatureFlagAPIBuilder, _ *datasource.DataSourceAPIBuilder, diff --git a/pkg/registry/apis/example/dummy_storage.go b/pkg/registry/apis/example/dummy_storage.go deleted file mode 100644 index 574a87dd527..00000000000 --- a/pkg/registry/apis/example/dummy_storage.go +++ /dev/null @@ -1,128 +0,0 @@ -package example - -import ( - "context" - "fmt" - "slices" - - "k8s.io/apimachinery/pkg/apis/meta/internalversion" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - genericregistry "k8s.io/apiserver/pkg/registry/generic/registry" - "k8s.io/apiserver/pkg/registry/rest" - - common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" - example "github.com/grafana/grafana/pkg/apis/example/v0alpha1" - grafanaregistry "github.com/grafana/grafana/pkg/apiserver/registry/generic" - grafanarequest "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" -) - -var ( - _ rest.Storage = (*dummyStorage)(nil) - _ rest.Scoper = (*dummyStorage)(nil) - _ rest.SingularNameProvider = (*dummyStorage)(nil) - _ rest.Getter = (*dummyStorage)(nil) - _ rest.Lister = (*dummyStorage)(nil) -) - -type dummyStorage struct { - store *genericregistry.Store - names []string - creationTimestamp metav1.Time -} - -func newDummyStorage(gv schema.GroupVersion, scheme *runtime.Scheme, names ...string) *dummyStorage { - var resourceInfo = example.DummyResourceInfo - strategy := grafanaregistry.NewStrategy(scheme) - store := &genericregistry.Store{ - NewFunc: resourceInfo.NewFunc, - NewListFunc: resourceInfo.NewListFunc, - PredicateFunc: grafanaregistry.Matcher, - DefaultQualifiedResource: resourceInfo.GroupResource(), - SingularQualifiedResource: resourceInfo.SingularGroupResource(), - TableConvertor: rest.NewDefaultTableConvertor(resourceInfo.GroupResource()), - CreateStrategy: strategy, - UpdateStrategy: strategy, - DeleteStrategy: strategy, - } - - return &dummyStorage{ - store: store, - names: names, - creationTimestamp: metav1.Now(), - } -} - -func (s *dummyStorage) New() runtime.Object { - return s.store.New() -} - -func (s *dummyStorage) Destroy() {} - -func (s *dummyStorage) NamespaceScoped() bool { - return true -} - -func (s *dummyStorage) GetSingularName() string { - return example.DummyResourceInfo.GetSingularName() -} - -func (s *dummyStorage) NewList() runtime.Object { - return s.store.NewListFunc() -} - -func (s *dummyStorage) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) { - return s.store.TableConvertor.ConvertToTable(ctx, object, tableOptions) -} - -func (s *dummyStorage) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { - info, err := grafanarequest.NamespaceInfoFrom(ctx, true) - if err != nil { - return nil, err - } - - idx := slices.Index(s.names, name) - if idx < 0 { - return nil, fmt.Errorf("dummy not found") - } - - return &example.DummyResource{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: info.Value, - CreationTimestamp: s.creationTimestamp, - ResourceVersion: "1", - }, - Spec: common.Unstructured{ - Object: map[string]any{ - "Dummy": name, - }, - }, - }, nil -} - -func (s *dummyStorage) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) { - info, err := grafanarequest.NamespaceInfoFrom(ctx, true) - if err != nil { - return nil, err - } - - res := &example.DummyResourceList{} - for _, name := range s.names { - res.Items = append(res.Items, example.DummyResource{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: info.Value, - CreationTimestamp: s.creationTimestamp, - ResourceVersion: "1", - }, - Spec: common.Unstructured{ - Object: map[string]any{ - "Dummy": name, - }, - }, - }) - } - return res, nil -} diff --git a/pkg/registry/apis/example/register.go b/pkg/registry/apis/example/register.go deleted file mode 100644 index 6dc5644bffa..00000000000 --- a/pkg/registry/apis/example/register.go +++ /dev/null @@ -1,226 +0,0 @@ -package example - -import ( - "context" - "fmt" - "net/http" - - "github.com/prometheus/client_golang/prometheus" - apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/runtime/serializer" - "k8s.io/apiserver/pkg/authorization/authorizer" - "k8s.io/apiserver/pkg/endpoints/handlers/responsewriters" - "k8s.io/apiserver/pkg/endpoints/request" - "k8s.io/apiserver/pkg/registry/generic" - "k8s.io/apiserver/pkg/registry/rest" - genericapiserver "k8s.io/apiserver/pkg/server" - common "k8s.io/kube-openapi/pkg/common" - "k8s.io/kube-openapi/pkg/spec3" - "k8s.io/kube-openapi/pkg/validation/spec" - - "github.com/grafana/grafana/pkg/apimachinery/identity" - example "github.com/grafana/grafana/pkg/apis/example/v0alpha1" - "github.com/grafana/grafana/pkg/apiserver/builder" - grafanarest "github.com/grafana/grafana/pkg/apiserver/rest" - "github.com/grafana/grafana/pkg/services/featuremgmt" -) - -var _ builder.APIGroupBuilder = (*TestingAPIBuilder)(nil) - -// This is used just so wire has something unique to return -type TestingAPIBuilder struct { - codecs serializer.CodecFactory - gv schema.GroupVersion -} - -func NewTestingAPIBuilder() *TestingAPIBuilder { - return &TestingAPIBuilder{ - gv: schema.GroupVersion{Group: example.GROUP, Version: example.VERSION}, - } -} - -func RegisterAPIService(features featuremgmt.FeatureToggles, apiregistration builder.APIRegistrar, reg prometheus.Registerer) *TestingAPIBuilder { - if !features.IsEnabledGlobally(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs) { - return nil // skip registration unless opting into experimental apis - } - builder := NewTestingAPIBuilder() - apiregistration.RegisterAPI(builder) - return builder -} - -func (b *TestingAPIBuilder) GetGroupVersion() schema.GroupVersion { - return b.gv -} - -func (b *TestingAPIBuilder) GetDesiredDualWriterMode(dualWrite bool, modeMap map[string]grafanarest.DualWriterMode) grafanarest.DualWriterMode { - // Add required configuration support in order to enable other modes. For an example, see pkg/registry/apis/playlist/register.go - return grafanarest.Mode0 -} - -func addKnownTypes(scheme *runtime.Scheme, gv schema.GroupVersion) { - scheme.AddKnownTypes(gv, - &example.RuntimeInfo{}, - &example.DummyResource{}, - &example.DummyResourceList{}, - &example.DummySubresource{}, - ) -} - -func (b *TestingAPIBuilder) InstallSchema(scheme *runtime.Scheme) error { - addKnownTypes(scheme, b.gv) - - // Link this version to the internal representation. - // This is used for server-side-apply (PATCH), and avoids the error: - // "no kind is registered for the type" - addKnownTypes(scheme, schema.GroupVersion{ - Group: b.gv.Group, - Version: runtime.APIVersionInternal, - }) - - // If multiple versions exist, then register conversions from zz_generated.conversion.go - // if err := playlist.RegisterConversions(scheme); err != nil { - // return err - // } - metav1.AddToGroupVersion(scheme, b.gv) - return scheme.SetVersionPriority(b.gv) -} - -func (b *TestingAPIBuilder) GetAPIGroupInfo( - scheme *runtime.Scheme, - codecs serializer.CodecFactory, // pointer? - _ generic.RESTOptionsGetter, - _ grafanarest.DualWriterMode, - _ prometheus.Registerer, -) (*genericapiserver.APIGroupInfo, error) { - b.codecs = codecs - apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(b.gv.Group, scheme, metav1.ParameterCodec, codecs) - - storage := map[string]rest.Storage{} - storage[example.RuntimeResourceInfo.StoragePath()] = newDeploymentInfoStorage(b.gv, scheme) - storage[example.DummyResourceInfo.StoragePath()] = newDummyStorage(b.gv, scheme, "test1", "test2", "test3") - storage[example.DummyResourceInfo.StoragePath("sub")] = &dummySubresourceREST{} - apiGroupInfo.VersionedResourcesStorageMap[b.gv.Version] = storage - return &apiGroupInfo, nil -} - -func (b *TestingAPIBuilder) GetOpenAPIDefinitions() common.GetOpenAPIDefinitions { - return example.GetOpenAPIDefinitions -} - -// Register additional routes with the server -func (b *TestingAPIBuilder) GetAPIRoutes() *builder.APIRoutes { - return &builder.APIRoutes{ - Root: []builder.APIRouteHandler{ - { - Path: "aaa", - Spec: &spec3.PathProps{ - Summary: "an example at the root level", - Description: "longer description here?", - Get: &spec3.Operation{ - OperationProps: spec3.OperationProps{ - Parameters: []*spec3.Parameter{ - {ParameterProps: spec3.ParameterProps{ - Name: "a", - }}, - }, - Responses: &spec3.Responses{ - ResponsesProps: spec3.ResponsesProps{ - StatusCodeResponses: map[int]*spec3.Response{ - 200: { - ResponseProps: spec3.ResponseProps{ - Description: "OK", - Content: map[string]*spec3.MediaType{ - "text/plain": { - MediaTypeProps: spec3.MediaTypeProps{ - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"string"}, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - Handler: func(w http.ResponseWriter, r *http.Request) { - _, _ = w.Write([]byte("Root level handler (aaa)")) - }, - }, - { - Path: "bbb", - Spec: &spec3.PathProps{ - Summary: "an example at the root level", - Description: "longer description here?", - Get: &spec3.Operation{ - OperationProps: spec3.OperationProps{ - Parameters: []*spec3.Parameter{ - {ParameterProps: spec3.ParameterProps{ - Name: "b", - }}, - }, - }, - }, - }, - Handler: func(w http.ResponseWriter, r *http.Request) { - _, _ = w.Write([]byte("Root level handler (bbb)")) - }, - }, - }, - Namespace: []builder.APIRouteHandler{ - { - Path: "ccc", - Spec: &spec3.PathProps{ - Summary: "an example at the root level", - Description: "longer description here?", - Get: &spec3.Operation{ - OperationProps: spec3.OperationProps{ - Parameters: []*spec3.Parameter{ - {ParameterProps: spec3.ParameterProps{ - Name: "a", - }}, - }, - }, - }, - }, - Handler: func(w http.ResponseWriter, r *http.Request) { - info, ok := request.RequestInfoFrom(r.Context()) - if !ok { - responsewriters.ErrorNegotiated( - apierrors.NewInternalError(fmt.Errorf("no RequestInfo found in the context")), - b.codecs, schema.GroupVersion{}, w, r, - ) - return - } - - _, _ = w.Write([]byte("Custom namespace route ccc: " + info.Namespace)) - }, - }, - }, - } -} - -func (b *TestingAPIBuilder) GetAuthorizer() authorizer.Authorizer { - return authorizer.AuthorizerFunc( - func(ctx context.Context, attr authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) { - if !attr.IsResourceRequest() { - return authorizer.DecisionNoOpinion, "", nil - } - - // require a user - _, err = identity.GetRequester(ctx) - if err != nil { - return authorizer.DecisionDeny, "valid user is required", err - } - - return authorizer.DecisionNoOpinion, "", err // fallback to org/role logic - }) -} diff --git a/pkg/registry/apis/example/storage.go b/pkg/registry/apis/example/storage.go deleted file mode 100644 index 4e2c1858837..00000000000 --- a/pkg/registry/apis/example/storage.go +++ /dev/null @@ -1,86 +0,0 @@ -package example - -import ( - "context" - "time" - - "k8s.io/apimachinery/pkg/apis/meta/internalversion" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - genericregistry "k8s.io/apiserver/pkg/registry/generic/registry" - "k8s.io/apiserver/pkg/registry/rest" - - example "github.com/grafana/grafana/pkg/apis/example/v0alpha1" - grafanaregistry "github.com/grafana/grafana/pkg/apiserver/registry/generic" - "github.com/grafana/grafana/pkg/setting" -) - -var ( - _ rest.Storage = (*staticStorage)(nil) - _ rest.Scoper = (*staticStorage)(nil) - _ rest.SingularNameProvider = (*staticStorage)(nil) - _ rest.Lister = (*staticStorage)(nil) -) - -type staticStorage struct { - Store *genericregistry.Store - info example.RuntimeInfo -} - -func newDeploymentInfoStorage(gv schema.GroupVersion, scheme *runtime.Scheme) *staticStorage { - var resourceInfo = example.RuntimeResourceInfo - strategy := grafanaregistry.NewStrategy(scheme) - store := &genericregistry.Store{ - NewFunc: resourceInfo.NewFunc, - NewListFunc: resourceInfo.NewListFunc, - PredicateFunc: grafanaregistry.Matcher, - DefaultQualifiedResource: resourceInfo.GroupResource(), - SingularQualifiedResource: resourceInfo.SingularGroupResource(), - TableConvertor: rest.NewDefaultTableConvertor(resourceInfo.GroupResource()), - CreateStrategy: strategy, - UpdateStrategy: strategy, - DeleteStrategy: strategy, - } - - return &staticStorage{ - Store: store, - info: example.RuntimeInfo{ - TypeMeta: example.RuntimeResourceInfo.TypeMeta(), - BuildVersion: setting.BuildVersion, - BuildCommit: setting.BuildCommit, - BuildBranch: setting.BuildBranch, - EnterpriseBuildCommit: setting.EnterpriseBuildCommit, - BuildStamp: setting.BuildStamp, - IsEnterprise: setting.IsEnterprise, - Packaging: setting.Packaging, - StartupTime: time.Now().UnixMilli(), - }, - } -} - -func (s *staticStorage) New() runtime.Object { - return s.Store.New() -} - -func (s *staticStorage) Destroy() {} - -func (s *staticStorage) NamespaceScoped() bool { - return false -} - -func (s *staticStorage) GetSingularName() string { - return example.RuntimeResourceInfo.GetSingularName() -} - -func (s *staticStorage) NewList() runtime.Object { - return s.Store.NewListFunc() -} - -func (s *staticStorage) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) { - return s.Store.TableConvertor.ConvertToTable(ctx, object, tableOptions) -} - -func (s *staticStorage) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) { - return &s.info, nil -} diff --git a/pkg/registry/apis/example/subresource.go b/pkg/registry/apis/example/subresource.go deleted file mode 100644 index 3fa5807a9e9..00000000000 --- a/pkg/registry/apis/example/subresource.go +++ /dev/null @@ -1,54 +0,0 @@ -package example - -import ( - "context" - "fmt" - "net/http" - - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apiserver/pkg/registry/rest" - - "github.com/grafana/grafana/pkg/apimachinery/identity" - example "github.com/grafana/grafana/pkg/apis/example/v0alpha1" - "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" -) - -type dummySubresourceREST struct{} - -var _ = rest.Connecter(&dummySubresourceREST{}) - -func (r *dummySubresourceREST) New() runtime.Object { - return &example.DummySubresource{} -} - -func (r *dummySubresourceREST) Destroy() { -} - -func (r *dummySubresourceREST) ConnectMethods() []string { - return []string{"GET"} -} - -func (r *dummySubresourceREST) NewConnectOptions() (runtime.Object, bool, string) { - return nil, false, "" // true means you can use the trailing path as a variable -} - -func (r *dummySubresourceREST) Connect(ctx context.Context, name string, opts runtime.Object, responder rest.Responder) (http.Handler, error) { - info, err := request.NamespaceInfoFrom(ctx, true) - if err != nil { - return nil, err - } - - user, err := identity.GetRequester(ctx) - if err != nil { - return nil, err - } - - // This response object format is negotiated by k8s - dummy := &example.DummySubresource{ - Info: fmt.Sprintf("%s/%s", info.Value, user.GetLogin()), - } - - return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - responder.Object(http.StatusOK, dummy) - }), nil -} diff --git a/pkg/registry/apis/wireset.go b/pkg/registry/apis/wireset.go index d9782c44697..2ce96e4af0d 100644 --- a/pkg/registry/apis/wireset.go +++ b/pkg/registry/apis/wireset.go @@ -7,7 +7,6 @@ import ( "github.com/grafana/grafana/pkg/registry/apis/dashboard" "github.com/grafana/grafana/pkg/registry/apis/dashboardsnapshot" "github.com/grafana/grafana/pkg/registry/apis/datasource" - "github.com/grafana/grafana/pkg/registry/apis/example" "github.com/grafana/grafana/pkg/registry/apis/featuretoggle" "github.com/grafana/grafana/pkg/registry/apis/folders" "github.com/grafana/grafana/pkg/registry/apis/peakq" @@ -29,7 +28,6 @@ var WireSet = wire.NewSet( // Each must be added here *and* in the ServiceSink above playlist.RegisterAPIService, dashboard.RegisterAPIService, - example.RegisterAPIService, dashboardsnapshot.RegisterAPIService, featuretoggle.RegisterAPIService, datasource.RegisterAPIService, diff --git a/pkg/services/apiserver/standalone/factory.go b/pkg/services/apiserver/standalone/factory.go index 34ae2316813..a2c413d69a9 100644 --- a/pkg/services/apiserver/standalone/factory.go +++ b/pkg/services/apiserver/standalone/factory.go @@ -15,7 +15,6 @@ import ( "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/registry/apis/datasource" - "github.com/grafana/grafana/pkg/registry/apis/example" "github.com/grafana/grafana/pkg/registry/apis/featuretoggle" "github.com/grafana/grafana/pkg/registry/apis/query" "github.com/grafana/grafana/pkg/registry/apis/query/client" @@ -75,9 +74,6 @@ func (p *DummyAPIFactory) MakeAPIServer(_ context.Context, tracer tracing.Tracer } switch gv.Group { - case "example.grafana.app": - return example.NewTestingAPIBuilder(), nil - // Only works with testdata case "query.grafana.app": return query.NewQueryAPIBuilder( diff --git a/pkg/tests/apis/example/example_test.go b/pkg/tests/apis/example/example_test.go deleted file mode 100644 index c702402837f..00000000000 --- a/pkg/tests/apis/example/example_test.go +++ /dev/null @@ -1,176 +0,0 @@ -package playlist - -import ( - "context" - "encoding/json" - "testing" - "time" - - "github.com/stretchr/testify/require" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime/schema" - - "github.com/grafana/grafana/pkg/services/featuremgmt" - "github.com/grafana/grafana/pkg/tests/apis" - "github.com/grafana/grafana/pkg/tests/testinfra" - "github.com/grafana/grafana/pkg/tests/testsuite" -) - -func TestMain(m *testing.M) { - testsuite.Run(m) -} - -func TestIntegrationExampleApp(t *testing.T) { - if testing.Short() { - t.Skip("skipping integration test") - } - helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ - AppModeProduction: false, // required for experimental APIs - DisableAnonymous: true, - EnableFeatureToggles: []string{ - featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs, // Required to start the example service - }, - }) - - t.Run("Check runtime info resource", func(t *testing.T) { - // Resource is not namespaced! - client := helper.Org1.Admin.ResourceClient(t, schema.GroupVersionResource{ - Group: "example.grafana.app", - Version: "v0alpha1", - Resource: "runtime", - }) - rsp, err := client.List(context.Background(), metav1.ListOptions{}) - require.NoError(t, err) - - v, ok := rsp.Object["startupTime"].(int64) - require.True(t, ok) - require.Greater(t, v, time.Now().Add(-1*time.Hour).UnixMilli()) // should be within the last hour - }) - - t.Run("Check discovery client", func(t *testing.T) { - disco := helper.NewDiscoveryClient() - resources, err := disco.ServerResourcesForGroupVersion("example.grafana.app/v0alpha1") - require.NoError(t, err) - - v1Disco, err := json.MarshalIndent(resources, "", " ") - require.NoError(t, err) - //fmt.Printf("%s", string(v1Disco)) - - require.JSONEq(t, `{ - "kind": "APIResourceList", - "apiVersion": "v1", - "groupVersion": "example.grafana.app/v0alpha1", - "resources": [ - { - "name": "dummy", - "singularName": "dummy", - "namespaced": true, - "kind": "DummyResource", - "verbs": [ - "get", - "list" - ] - }, - { - "name": "dummy/sub", - "singularName": "", - "namespaced": true, - "kind": "DummySubresource", - "verbs": [ - "get" - ] - }, - { - "name": "runtime", - "singularName": "runtime", - "namespaced": false, - "kind": "RuntimeInfo", - "verbs": [ - "list" - ] - } - ] - }`, string(v1Disco)) - - //fmt.Printf("%s", string(v1Disco)) - require.JSONEq(t, `[ - { - "version": "v0alpha1", - "freshness": "Current", - "resources": [ - { - "resource": "dummy", - "responseKind": { - "group": "", - "kind": "DummyResource", - "version": "" - }, - "scope": "Namespaced", - "singularResource": "dummy", - "subresources": [ - { - "responseKind": { - "group": "", - "kind": "DummySubresource", - "version": "" - }, - "subresource": "sub", - "verbs": [ - "get" - ] - } - ], - "verbs": [ - "get", - "list" - ] - }, - { - "resource": "runtime", - "responseKind": { - "group": "", - "kind": "RuntimeInfo", - "version": "" - }, - "scope": "Cluster", - "singularResource": "runtime", - "verbs": [ - "list" - ] - } - ] - } - ]`, helper.GetGroupVersionInfoJSON("example.grafana.app")) - }) - - t.Run("Check dummy with subresource", func(t *testing.T) { - client := helper.Org1.Viewer.ResourceClient(t, schema.GroupVersionResource{ - Group: "example.grafana.app", - Version: "v0alpha1", - Resource: "dummy", - }).Namespace("default") - rsp, err := client.Get(context.Background(), "test2", metav1.GetOptions{}) - require.NoError(t, err) - - v, ok, err := unstructured.NestedString(rsp.Object, "spec", "Dummy") - require.NoError(t, err) - require.True(t, ok) - - require.Equal(t, "test2", v) - require.Equal(t, "DummyResource", rsp.GetObjectKind().GroupVersionKind().Kind) - - // Now a sub-resource - rsp, err = client.Get(context.Background(), "test2", metav1.GetOptions{}, "sub") - require.NoError(t, err) - - raw, err := json.MarshalIndent(rsp, "", " ") - require.NoError(t, err) - //fmt.Printf("%s", string(raw)) - require.JSONEq(t, `{ - "apiVersion": "example.grafana.app/v0alpha1", - "kind": "DummySubresource", - "info": "default/viewer-1" - }`, string(raw)) - }) -} From d972bdca085e12e93d733d23aa28438b92f7ca8b Mon Sep 17 00:00:00 2001 From: Jo Date: Fri, 21 Jun 2024 10:20:02 +0200 Subject: [PATCH 03/21] Docs: Add fixed role UUIDs to docs for terraform provisioning (#89457) * add role UUID to docs * add table description and terraform hyperlink --- .../index.md | 153 +++++++++--------- 1 file changed, 78 insertions(+), 75 deletions(-) diff --git a/docs/sources/administration/roles-and-permissions/access-control/rbac-fixed-basic-role-definitions/index.md b/docs/sources/administration/roles-and-permissions/access-control/rbac-fixed-basic-role-definitions/index.md index 3f75bca900a..1ec8a8a0b26 100644 --- a/docs/sources/administration/roles-and-permissions/access-control/rbac-fixed-basic-role-definitions/index.md +++ b/docs/sources/administration/roles-and-permissions/access-control/rbac-fixed-basic-role-definitions/index.md @@ -33,81 +33,84 @@ The following tables list permissions associated with basic and fixed roles. ## Fixed role definitions -| Fixed role | Permissions | Description | -| -------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `fixed:alerting.instances:writer` | All permissions from `fixed:alerting.instances:reader` and
`alert.instances:create`
`alert.instances:write` for organization scope
`alert.instances.external:write` for scope `datasources:*` | Create, update and expire all silences in the organization produced by Grafana, Mimir, and Loki.[\*](#alerting-roles) | -| `fixed:alerting.instances:reader` | `alert.instances:read` for organization scope
`alert.instances.external:read` for scope `datasources:*` | Read all alerts and silences in the organization produced by Grafana Alerts and Mimir and Loki alerts and silences.[\*](#alerting-roles) | -| `fixed:alerting.notifications:writer` | All permissions from `fixed:alerting.notifications:reader` and
`alert.notifications:write`for organization scope
`alert.notifications.external:read` for scope `datasources:*` | Create, update, and delete contact points, templates, mute timings and notification policies for Grafana and external Alertmanager.[\*](#alerting-roles) | -| `fixed:alerting.notifications:reader` | `alert.notifications:read` for organization scope
`alert.notifications.external:read` for scope `datasources:*` | Read all Grafana and Alertmanager contact points, templates, and notification policies.[\*](#alerting-roles) | -| `fixed:alerting.rules:writer` | All permissions from `fixed:alerting.rules:reader` and
`alert.rule:create`
`alert.rule:write`
`alert.rule:delete`
`alert.silences:create`
`alert.silences:write` for scope `folders:*`
`alert.rules.external:write` for scope `datasources:*` | Create, update, and delete all\* Grafana, Mimir, and Loki alert rules.[\*](#alerting-roles) and manage rule-specific silences | -| `fixed:alerting.rules:reader` | `alert.rule:read`, `alert.silences:read` for scope `folders:*`
`alert.rules.external:read` for scope `datasources:*`
`alert.notifications.time-intervals:read`
`alert.notifications.receivers:list` | Read all\* Grafana, Mimir, and Loki alert rules.[\*](#alerting-roles) and read rule-specific silences | -| `fixed:alerting:writer` | All permissions from `fixed:alerting.rules:writer`
`fixed:alerting.instances:writer`
`fixed:alerting.notifications:writer` | Create, update, and delete Grafana, Mimir, Loki and Alertmanager alert rules\*, silences, contact points, templates, mute timings, and notification policies.[\*](#alerting-roles) | -| `fixed:alerting:reader` | All permissions from `fixed:alerting.rules:reader`
`fixed:alerting.instances:reader`
`fixed:alerting.notifications:reader` | Read-only permissions for all Grafana, Mimir, Loki and Alertmanager alert rules\*, alerts, contact points, and notification policies.[\*](#alerting-roles) | -| `fixed:alerting.provisioning.secrets:reader` | `alert.provisioning:read` and `alert.provisioning.secrets:read` | Read-only permissions for Provisioning API and let export resources with decrypted secrets [\*](#alerting-roles) | -| `fixed:alerting.provisioning:writer` | `alert.provisioning:read` and `alert.provisioning:write` | Create, update and delete Grafana alert rules, notification policies, contact points, templates, etc via provisioning API. [\*](#alerting-roles) | -| `fixed:alerting.provisioning.status:writer` | `alert.provisioning.provenance:write` | Set provenance status to alert rules, notification policies, contact points, etc. Should be used together with regular writer roles. [\*](#alerting-roles) | -| `fixed:annotations.dashboard:writer` | `annotations:write`
`annotations.create`
`annotations:delete` for scope `annotations:type:dashboard` | Create, update and delete dashboard annotations and annotation tags. | -| `fixed:annotations:reader` | `annotations:read` for scopes `annotations:type:*` | Read all annotations and annotation tags. | -| `fixed:annotations:writer` | All permissions from `fixed:annotations:reader`
`annotations:write`
`annotations.create`
`annotations:delete` for scope `annotations:type:*` | Read, create, update and delete all annotations and annotation tags. | -| `fixed:apikeys:reader` | `apikeys:read` for scope `apikeys:*` | Read all api keys. | -| `fixed:apikeys:writer` | All permissions from `fixed:apikeys:reader` and
`apikeys:create`
`apikeys:delete` for scope `apikeys:*` | Read, create, delete all api keys. | -| `fixed:authentication.config:writer` | `settings:read` for scope `settings:auth.saml:*`
`settings:write` for scope `settings:auth.saml:*` | Read and update authentication and SAML settings. | -| `fixed:dashboards:creator` | `dashboards:create`
`folders:read` | Create dashboards. | -| `fixed:dashboards.insights:reader` | `dashboards.insights:read` | Read dashboard insights data and see presence indicators. | -| `fixed:dashboards.permissions:reader` | `dashboards.permissions:read` | Read all dashboard permissions. | -| `fixed:dashboards.permissions:writer` | All permissions from `fixed:dashboards.permissions:reader` and
`dashboards.permissions:write` | Read and update all dashboard permissions. | -| `fixed:dashboards.public:writer` | `dashboards.public:write` | Create, update, delete or pause a public dashboard. | -| `fixed:dashboards:reader` | `dashboards:read` | Read all dashboards. | -| `fixed:dashboards:writer` | All permissions from `fixed:dashboards:reader` and
`dashboards:write`
`dashboards:edit`
`dashboards:delete`
`dashboards:create`
`dashboards.permissions:read`
`dashboards.permissions:write` | Read, create, update, and delete all dashboards. | -| `fixed:datasources.caching:reader` | `datasources.caching:read` | Read data source query caching settings. | -| `fixed:datasources.caching:writer` | `datasources.caching:read`
`datasources.caching:write` | Enable, disable, or update query caching settings. | -| `fixed:datasources:explorer` | `datasources:explore` | Enable the Explore feature. Data source permissions still apply, you can only query data sources for which you have query permissions. | -| `fixed:datasources.id:reader` | `datasources.id:read` | Read the ID of a data source based on its name. | -| `fixed:datasources.insights:reader` | `datasources.insights:read` | Read data source insights data. | -| `fixed:datasources.permissions:reader` | `datasources.permissions:read` | Read data source permissions. | -| `fixed:datasources.permissions:writer` | All permissions from `fixed:datasources.permissions:reader` and
`datasources.permissions:write` | Create, read, or delete permissions of a data source. | -| `fixed:datasources:creator` | `datasources:create` | Create data sources. | -| `fixed:datasources:reader` | `datasources:read`
`datasources:query` | Read and query data sources. | -| `fixed:datasources:writer` | All permissions from `fixed:datasources:reader` and
`datasources:create`
`datasources:write`
`datasources:delete` | Read, query, create, delete, or update a data source. | -| `fixed:folders.permissions:reader` | `folders.permissions:read` | Read all folder permissions. | -| `fixed:folders.permissions:writer` | All permissions from `fixed:folders.permissions:reader` and
`folders.permissions:write` | Read and update all folder permissions. | -| `fixed:folders:creator` | `folders:create` | Create folders in the root level. If granted together with `folders:write` permission, also allows creating subfolders under all folders. | -| `fixed:folders:reader` | `folders:read`
`dashboards:read` | Read all folders and dashboards. | -| `fixed:folders:writer` | All permissions from `fixed:dashboards:writer` and
`folders:read`
`folders:write`
`folders:create`
`folders:delete`
`folders.permissions:read`
`folders.permissions:write` | Read, create, update, and delete all folders and dashboards. If granted together with `fixed:folders:creator`, allows creating subfolders under all folders. | -| `fixed:ldap:reader` | `ldap.user:read`
`ldap.status:read` | Read the LDAP configuration and LDAP status information. | -| `fixed:ldap:writer` | All permissions from `fixed:ldap:reader` and
`ldap.user:sync`
`ldap.config:reload` | Read and update the LDAP configuration, and read LDAP status information. | -| `fixed:library.panels:creator` | `library.panels:create`
`folders:read` | Create library panel at the root level. | -| `fixed:library.panels:reader` | `library.panels:read` | Read all library panels. | -| `fixed:library.panels:general.reader` | `library.panels:read` | Read all library panels at the root level. | -| `fixed:library.panels:writer` | All permissions from `fixed:library.panels:reader` plus
`library.panels:create`
`library.panels:delete`
`library.panels:write` | Create, read, write or delete all library panels and their permissions. | -| `fixed:library.panels:general.writer` | All permissions from `fixed:library.panels:general.reader` plus
`library.panels:create`
`library.panels:delete`
`library.panels:write` | Create, read, write or delete all library panels and their permissions at the root level. | -| `fixed:licensing:reader` | `licensing:read`
`licensing.reports:read` | Read licensing information and licensing reports. | -| `fixed:licensing:writer` | All permissions from `fixed:licensing:viewer` and
`licensing:write`
`licensing:delete` | Read licensing information and licensing reports, update and delete the license token. | -| `fixed:org.users:reader` | `org.users:read` | Read users within a single organization. | -| `fixed:org.users:writer` | All permissions from `fixed:org.users:reader` and
`org.users:add`
`org.users:remove`
`org.users:write` | Within a single organization, add a user, invite a new user, read information about a user and their role, remove a user from that organization, or change the role of a user. | -| `fixed:organization:maintainer` | All permissions from `fixed:organization:reader` and
`orgs:write`
`orgs:create`
`orgs:delete`
`orgs.quotas:write` | Create, read, write, or delete an organization. Read or write its quotas. This role needs to be assigned globally. | -| `fixed:organization:reader` | `orgs:read`
`orgs.quotas:read` | Read an organization and its quotas. | -| `fixed:organization:writer` | All permissions from `fixed:organization:reader` and
`orgs:write`
`orgs.preferences:read`
`orgs.preferences:write` | Read an organization, its quotas, or its preferences. Update organization properties, or its preferences. | -| `fixed:plugins.app:reader` | `plugins.app:access` | Access application plugins (still enforcing the organization role). | -| `fixed:plugins:maintainer` | `plugins:install` | Install and uninstall plugins. Needs to be assigned globally. | -| `fixed:plugins:writer` | `plugins:write` | Enable and disable plugins and edit plugins' settings. | -| `fixed:provisioning:writer` | `provisioning:reload` | Reload provisioning. | -| `fixed:reports:reader` | `reports:read`
`reports:send`
`reports.settings:read` | Read all reports and shared report settings. | -| `fixed:reports:writer` | All permissions from `fixed:reports:reader` and
`reports:create`
`reports:write`
`reports:delete`
`reports.settings:write` | Create, read, update, or delete all reports and shared report settings. | -| `fixed:roles:reader` | `roles:read`
`teams.roles:read`
`users.roles:read`
`users.permissions:read` | Read all access control roles, roles and permissions assigned to users, teams. | -| `fixed:roles:writer` | All permissions from `fixed:roles:reader` and
`roles:write`
`roles:delete`
`teams.roles:add`
`teams.roles:remove`
`users.roles:add`
`users.roles:remove` | Create, read, update, or delete all roles, assign or unassign roles to users, teams. | -| `fixed:roles:resetter` | `roles:write` with scope `permissions:type:escalate` | Reset basic roles to their default. | -| `fixed:serviceaccounts:reader` | `serviceaccounts:read` | Read Grafana service accounts. | -| `fixed:serviceaccounts:creator` | `serviceaccounts:create` | Create Grafana service accounts. | -| `fixed:serviceaccounts:writer` | `serviceaccounts:read`
`serviceaccounts:create`
`serviceaccounts:write`
`serviceaccounts:delete`
`serviceaccounts.permissions:read`
`serviceaccounts.permissions:write` | Create, update, read and delete all Grafana service accounts and manage service account permissions. | -| `fixed:settings:reader` | `settings:read` | Read Grafana instance settings. | -| `fixed:settings:writer` | All permissions from `fixed:settings:reader` and
`settings:write` | Read and update Grafana instance settings. | -| `fixed:stats:reader` | `server.stats:read` | Read Grafana instance statistics. | -| `fixed:teams:reader` | `teams:read` | List all teams. | -| `fixed:teams:creator` | `teams:create`
`org.users:read` | Create a team and list organization users (required to manage the created team). | -| `fixed:teams:writer` | `teams:create`
`teams:delete`
`teams:read`
`teams:write`
`teams.permissions:read`
`teams.permissions:write` | Create, read, update and delete teams and manage team memberships. | -| `fixed:users:reader` | `users:read`
`users.quotas:read`
`users.authtoken:read`
` | Read all users and their information, such as team memberships, authentication tokens, and quotas. | -| `fixed:users:writer` | All permissions from `fixed:users:reader` and
`users:write`
`users:create`
`users:delete`
`users:enable`
`users:disable`
`users.password:write`
`users.permissions:write`
`users:logout`
`users.authtoken:write`
`users.quotas:write` | Read and update all attributes and settings for all users in Grafana: update user information, read user information, create or enable or disable a user, make a user a Grafana administrator, sign out a user, update a user’s authentication token, or update quotas for all users. | +The following table has the existing built-in fixed role definitions. Other fixed roles might be added by plugins installed in Grafana. +The UUID presented here can be used as an identifier for [Terraform provisioning](../rbac-terraform-provisioning). + +| Fixed role | UUID | Permissions | Description | +| -------------------------------------------- | ----------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `fixed:alerting:reader` | `fixed_O2oP1_uBFozI2i93klAkcvEWR30` | All permissions from `fixed:alerting.rules:reader`
`fixed:alerting.instances:reader`
`fixed:alerting.notifications:reader` | Read-only permissions for all Grafana, Mimir, Loki and Alertmanager alert rules\*, alerts, contact points, and notification policies.[\*](#alerting-roles) | +| `fixed:alerting:writer` | `fixed_-PAZgSJsDlRD8NUg-PFSeH_BkJY` | All permissions from `fixed:alerting.rules:writer`
`fixed:alerting.instances:writer`
`fixed:alerting.notifications:writer` | Create, update, and delete Grafana, Mimir, Loki and Alertmanager alert rules\*, silences, contact points, templates, mute timings, and notification policies.[\*](#alerting-roles) | +| `fixed:alerting.instances:reader` | `fixed_ut5fVS-Ulh_ejFoskFhJT_rYg0Y` | `alert.instances:read` for organization scope
`alert.instances.external:read` for scope `datasources:*` | Read all alerts and silences in the organization produced by Grafana Alerts and Mimir and Loki alerts and silences.[\*](#alerting-roles) | +| `fixed:alerting.instances:writer` | `fixed_pKOBJE346uyqMLdgWbk1NsQfEl0` | All permissions from `fixed:alerting.instances:reader` and
`alert.instances:create`
`alert.instances:write` for organization scope
`alert.instances.external:write` for scope `datasources:*` | Create, update and expire all silences in the organization produced by Grafana, Mimir, and Loki.[\*](#alerting-roles) | +| `fixed:alerting.notifications:reader` | `fixed_hmBn0lX5h1RZXB9Vaot420EEdA0` | `alert.notifications:read` for organization scope
`alert.notifications.external:read` for scope `datasources:*` | Read all Grafana and Alertmanager contact points, templates, and notification policies.[\*](#alerting-roles) | +| `fixed:alerting.notifications:writer` | `fixed_XplK6HPNxf9AP5IGTdB5Iun4tJc` | All permissions from `fixed:alerting.notifications:reader` and
`alert.notifications:write`for organization scope
`alert.notifications.external:read` for scope `datasources:*` | Create, update, and delete contact points, templates, mute timings and notification policies for Grafana and external Alertmanager.[\*](#alerting-roles) | +| `fixed:alerting.provisioning:writer` | `fixed_y7pFjdEkxpx5ETdcxPvp0AgRuUo` | `alert.provisioning:read` and `alert.provisioning:write` | Create, update and delete Grafana alert rules, notification policies, contact points, templates, etc via provisioning API. [\*](#alerting-roles) | +| `fixed:alerting.provisioning.secrets:reader` | `fixed_9fmzXXZZG-Od0Amy2ofEG8Uk--c` | `alert.provisioning:read` and `alert.provisioning.secrets:read` | Read-only permissions for Provisioning API and let export resources with decrypted secrets [\*](#alerting-roles) | +| `fixed:alerting.provisioning.status:writer` | `fixed_eAxlzfkTuobvKEgXHveFMBZrOj8` | `alert.provisioning.provenance:write` | Set provenance status to alert rules, notification policies, contact points, etc. Should be used together with regular writer roles. [\*](#alerting-roles) | +| `fixed:alerting.rules:reader` | `fixed_fRGKL_vAqUsmUWq5EYKnOha9DcA` | `alert.rule:read`, `alert.silences:read` for scope `folders:*`
`alert.rules.external:read` for scope `datasources:*`
`alert.notifications.time-intervals:read`
`alert.notifications.receivers:list` | Read all\* Grafana, Mimir, and Loki alert rules.[\*](#alerting-roles) and read rule-specific silences | +| `fixed:alerting.rules:writer` | `fixed_YJJGwAalUwDZPrXSyFH8GfYBXAc` | All permissions from `fixed:alerting.rules:reader` and
`alert.rule:create`
`alert.rule:write`
`alert.rule:delete`
`alert.silences:create`
`alert.silences:write` for scope `folders:*`
`alert.rules.external:write` for scope `datasources:*` | Create, update, and delete all\* Grafana, Mimir, and Loki alert rules.[\*](#alerting-roles) and manage rule-specific silences | +| `fixed:annotations:reader` | `fixed_hpZnoizrfAJsrceNcNQqWYV-xNU` | `annotations:read` for scopes `annotations:type:*` | Read all annotations and annotation tags. | +| `fixed:annotations:writer` | `fixed_ZVW-Aa9Tzle6J4s2aUFcq1StKWE` | All permissions from `fixed:annotations:reader`
`annotations:write`
`annotations.create`
`annotations:delete` for scope `annotations:type:*` | Read, create, update and delete all annotations and annotation tags. | +| `fixed:annotations.dashboard:writer` | `fixed_8A775xenXeKaJk4Cr7bchP9yXOA` | `annotations:write`
`annotations.create`
`annotations:delete` for scope `annotations:type:dashboard` | Create, update and delete dashboard annotations and annotation tags. | +| `fixed:apikeys:reader` | `fixed_kYZ7UEkwEvGmCCjTrq07cFAVFws` | `apikeys:read` for scope `apikeys:*` | Read all api keys. | +| `fixed:apikeys:writer` | `fixed_anTrcpRkm21NBO1Q2CsX8y0fiCQ` | All permissions from `fixed:apikeys:reader` and
`apikeys:create`
`apikeys:delete` for scope `apikeys:*` | Read, create, delete all api keys. | +| `fixed:authentication.config:writer` | `fixed_0rYhZ2Qnzs8AdB1nX7gexk3fHDw` | `settings:read` for scope `settings:auth.saml:*`
`settings:write` for scope `settings:auth.saml:*` | Read and update authentication and SAML settings. | +| `fixed:dashboards:creator` | `fixed_ZorKUcEPCM01A1fPakEzGBUyU64` | `dashboards:create`
`folders:read` | Create dashboards. | +| `fixed:dashboards:reader` | `fixed_Sgr67JTOhjQGFlzYRahOe45TdWM` | `dashboards:read` | Read all dashboards. | +| `fixed:dashboards:writer` | `fixed_OK2YOQGIoI1G031hVzJB6rAJQAs` | All permissions from `fixed:dashboards:reader` and
`dashboards:write`
`dashboards:edit`
`dashboards:delete`
`dashboards:create`
`dashboards.permissions:read`
`dashboards.permissions:write` | Read, create, update, and delete all dashboards. | +| `fixed:dashboards.insights:reader` | `fixed_JlBJ2_gizP8zhgaeGE2rjyZe2Rs` | `dashboards.insights:read` | Read dashboard insights data and see presence indicators. | +| `fixed:dashboards.permissions:reader` | `fixed_f17oxuXW_58LL8mYJsm4T_mCeIw` | `dashboards.permissions:read` | Read all dashboard permissions. | +| `fixed:dashboards.permissions:writer` | `fixed_CcznxhWX_Yqn8uWMXMQ-b5iFW9k` | All permissions from `fixed:dashboards.permissions:reader` and
`dashboards.permissions:write` | Read and update all dashboard permissions. | +| `fixed:dashboards.public:writer` | `fixed_f_GHHRBciaqESXfGz2oCcooqHxs` | `dashboards.public:write` | Create, update, delete or pause a public dashboard. | +| `fixed:datasources:creator` | `fixed_XX8jHREgUt-wo1A-rPXIiFlX6Zw` | `datasources:create` | Create data sources. | +| `fixed:datasources:explorer` | `fixed_qDzW9mzx9yM91T5Bi8dHUM2muTw` | `datasources:explore` | Enable the Explore feature. Data source permissions still apply, you can only query data sources for which you have query permissions. | +| `fixed:datasources:reader` | `fixed_C2x8IxkiBc1KZVjyYH775T9jNMQ` | `datasources:read`
`datasources:query` | Read and query data sources. | +| `fixed:datasources:writer` | `fixed_q8HXq8kjjA5IlHHgBJlKlUyaNik` | All permissions from `fixed:datasources:reader` and
`datasources:create`
`datasources:write`
`datasources:delete` | Read, query, create, delete, or update a data source. | +| `fixed:datasources.caching:reader` | `fixed_D2ddpGxJYlw0mbsTS1ek9fj0kj4` | `datasources.caching:read` | Read data source query caching settings. | +| `fixed:datasources.caching:writer` | `fixed_JtFjHr7jd7hSqUYcktKvRvIOGRE` | `datasources.caching:read`
`datasources.caching:write` | Enable, disable, or update query caching settings. | +| `fixed:datasources.id:reader` | `fixed_entg--fHmDqWY2-69N0ocawK0Os` | `datasources.id:read` | Read the ID of a data source based on its name. | +| `fixed:datasources.insights:reader` | `fixed_EBZ3NwlfecNPp2p0XcZRC1nfEYk` | `datasources.insights:read` | Read data source insights data. | +| `fixed:datasources.permissions:reader` | `fixed_ErYA-cTN3yn4h4GxaVPcawRhiOY` | `datasources.permissions:read` | Read data source permissions. | +| `fixed:datasources.permissions:writer` | `fixed_aiQh9YDfLOKjQhYasF9_SFUjQiw` | All permissions from `fixed:datasources.permissions:reader` and
`datasources.permissions:write` | Create, read, or delete permissions of a data source. | +| `fixed:folders:creator` | `fixed_gGLRbZGAGB6n9uECqSh_W382RlQ` | `folders:create` | Create folders in the root level. If granted together with `folders:write` permission, also allows creating subfolders under all folders. | +| `fixed:folders:reader` | `fixed_yeW-5QPeo-i5PZUIUXMlAA97GnQ` | `folders:read`
`dashboards:read` | Read all folders and dashboards. | +| `fixed:folders:writer` | `fixed_wJXLoTzgE7jVuz90dryYoiogL0o` | All permissions from `fixed:dashboards:writer` and
`folders:read`
`folders:write`
`folders:create`
`folders:delete`
`folders.permissions:read`
`folders.permissions:write` | Read, create, update, and delete all folders and dashboards. If granted together with `fixed:folders:creator`, allows creating subfolders under all folders. | +| `fixed:folders.permissions:reader` | `fixed_E06l4cx0JFm47EeLBE4nmv3pnSo` | `folders.permissions:read` | Read all folder permissions. | +| `fixed:folders.permissions:writer` | `fixed_3GAgpQ_hWG8o7-lwNb86_VB37eI` | All permissions from `fixed:folders.permissions:reader` and
`folders.permissions:write` | Read and update all folder permissions. | +| `fixed:ldap:reader` | `fixed_lMcOPwSkxKY-qCK8NMJc5k6izLE` | `ldap.user:read`
`ldap.status:read` | Read the LDAP configuration and LDAP status information. | +| `fixed:ldap:writer` | `fixed_p6AvnU4GCQyIh7-hbwI-bk3GYnU` | All permissions from `fixed:ldap:reader` and
`ldap.user:sync`
`ldap.config:reload` | Read and update the LDAP configuration, and read LDAP status information. | +| `fixed:library.panels:creator` | `fixed_6eX6ItfegCIY5zLmPqTDW8ZV7KY` | `library.panels:create`
`folders:read` | Create library panel at the root level. | +| `fixed:library.panels:general.reader` | `fixed_ct0DghiBWR_2BiQm3EvNPDVmpio` | `library.panels:read` | Read all library panels at the root level. | +| `fixed:library.panels:general.writer` | `fixed_DgprkmqfN_1EhZ2v1_d1fYG8LzI` | All permissions from `fixed:library.panels:general.reader` plus
`library.panels:create`
`library.panels:delete`
`library.panels:write` | Create, read, write or delete all library panels and their permissions at the root level. | +| `fixed:library.panels:reader` | `fixed_tvTr9CnZ6La5vvUO_U_X1LPnhUs` | `library.panels:read` | Read all library panels. | +| `fixed:library.panels:writer` | `fixed_JTljAr21LWLTXCkgfBC4H0lhBC8` | All permissions from `fixed:library.panels:reader` plus
`library.panels:create`
`library.panels:delete`
`library.panels:write` | Create, read, write or delete all library panels and their permissions. | +| `fixed:licensing:reader` | `fixed_OADpuXvNEylO2Kelu3GIuBXEAYE` | `licensing:read`
`licensing.reports:read` | Read licensing information and licensing reports. | +| `fixed:licensing:writer` | `fixed_gzbz3rJpQMdaKHt-E4q0PVaKMoE` | All permissions from `fixed:licensing:viewer` and
`licensing:write`
`licensing:delete` | Read licensing information and licensing reports, update and delete the license token. | +| `fixed:org.users:reader` | `fixed_oCqNwlVHLOpw7-jAlwp4HzYqwGY` | `org.users:read` | Read users within a single organization. | +| `fixed:org.users:writer` | `fixed_VERj5nayasjgf_Yh0sWqqCkxWlw` | All permissions from `fixed:org.users:reader` and
`org.users:add`
`org.users:remove`
`org.users:write` | Within a single organization, add a user, invite a new user, read information about a user and their role, remove a user from that organization, or change the role of a user. | +| `fixed:organization:maintainer` | `fixed_CMm-uuBaPUBf4r8XG3jIvxo55bg` | All permissions from `fixed:organization:reader` and
`orgs:write`
`orgs:create`
`orgs:delete`
`orgs.quotas:write` | Create, read, write, or delete an organization. Read or write its quotas. This role needs to be assigned globally. | +| `fixed:organization:reader` | `fixed_0SZPJlTHdNEe8zO91zv7Zwiwa2w` | `orgs:read`
`orgs.quotas:read` | Read an organization and its quotas. | +| `fixed:organization:writer` | `fixed_Y4jGqDd8w1yCrPwlik8z5Iu8-3M` | All permissions from `fixed:organization:reader` and
`orgs:write`
`orgs.preferences:read`
`orgs.preferences:write` | Read an organization, its quotas, or its preferences. Update organization properties, or its preferences. | +| `fixed:plugins:maintainer` | `fixed_yEOKidBcWgbm74x-nTa3lW5lOyY` | `plugins:install` | Install and uninstall plugins. Needs to be assigned globally. | +| `fixed:plugins:writer` | `fixed_MRYpGk7kpNNwt2VoVOXFiPnQziE` | `plugins:write` | Enable and disable plugins and edit plugins' settings. | +| `fixed:plugins.app:reader` | `fixed_AcZRiNYx7NueYkUqzw1o2OGGUAA` | `plugins.app:access` | Access application plugins (still enforcing the organization role). | +| `fixed:provisioning:writer` | `fixed_bgk1FCyR6OEDwhgirZlQgu5LlCA` | `provisioning:reload` | Reload provisioning. | +| `fixed:reports:reader` | `fixed_72_8LU_0ukfm6BdblOw8Z9q-GQ8` | `reports:read`
`reports:send`
`reports.settings:read` | Read all reports and shared report settings. | +| `fixed:reports:writer` | `fixed_jBW3_7g1EWOjGVBYeVRwtFxhUNw` | All permissions from `fixed:reports:reader` and
`reports:create`
`reports:write`
`reports:delete`
`reports.settings:write` | Create, read, update, or delete all reports and shared report settings. | +| `fixed:roles:reader` | `fixed_GkfG-1NSwEGb4hpK3-E3qHyNltc` | `roles:read`
`teams.roles:read`
`users.roles:read`
`users.permissions:read` | Read all access control roles, roles and permissions assigned to users, teams. | +| `fixed:roles:resetter` | `fixed_WgPpC3qJRmVpVTJavFNwfS5RuzQ` | `roles:write` with scope `permissions:type:escalate` | Reset basic roles to their default. | +| `fixed:roles:writer` | `fixed_W5aFaw8isAM27x_eWfElBhZ0iOc` | All permissions from `fixed:roles:reader` and
`roles:write`
`roles:delete`
`teams.roles:add`
`teams.roles:remove`
`users.roles:add`
`users.roles:remove` | Create, read, update, or delete all roles, assign or unassign roles to users, teams. | +| `fixed:serviceaccounts:creator` | `fixed_Ikw60fckA0MyiiZ73BawSfOULy4` | `serviceaccounts:create` | Create Grafana service accounts. | +| `fixed:serviceaccounts:reader` | `fixed_QFjJAZ88iawMLInYOxPA1DB1w6I` | `serviceaccounts:read` | Read Grafana service accounts. | +| `fixed:serviceaccounts:writer` | `fixed_iBvUNUEZBZ7PUW0vdkN5iojc2sk` | `serviceaccounts:read`
`serviceaccounts:create`
`serviceaccounts:write`
`serviceaccounts:delete`
`serviceaccounts.permissions:read`
`serviceaccounts.permissions:write` | Create, update, read and delete all Grafana service accounts and manage service account permissions. | +| `fixed:settings:reader` | `fixed_0LaUt1x6PP8hsZzEBhqPQZFUd8Q` | `settings:read` | Read Grafana instance settings. | +| `fixed:settings:writer` | `fixed_joIHDgMrGg790hMhUufVzcU4j44` | All permissions from `fixed:settings:reader` and
`settings:write` | Read and update Grafana instance settings. | +| `fixed:stats:reader` | `fixed_OnRCXxZVINWpcKvTF5A1gecJ7pA` | `server.stats:read` | Read Grafana instance statistics. | +| `fixed:teams:creator` | `fixed_nzVQoNSDSn0fg1MDgO6XnZX2RZI` | `teams:create`
`org.users:read` | Create a team and list organization users (required to manage the created team). | +| `fixed:teams:reader` | `fixed_3SNL15gkRtJ7XeEKpMVJyQjYbjg` | `teams:read` | List all teams. | +| `fixed:teams:writer` | `fixed_xw1T0579h620MOYi4L96GUs7fZY` | `teams:create`
`teams:delete`
`teams:read`
`teams:write`
`teams.permissions:read`
`teams.permissions:write` | Create, read, update and delete teams and manage team memberships. | +| `fixed:users:reader` | `fixed_buZastUG3reWyQpPemcWjGqPAd0` | `users:read`
`users.quotas:read`
`users.authtoken:read`
` | Read all users and their information, such as team memberships, authentication tokens, and quotas. | +| `fixed:users:writer` | `fixed_wjzgHHo_Ux25DJuELn_oiAdB_yM` | All permissions from `fixed:users:reader` and
`users:write`
`users:create`
`users:delete`
`users:enable`
`users:disable`
`users.password:write`
`users.permissions:write`
`users:logout`
`users.authtoken:write`
`users.quotas:write` | Read and update all attributes and settings for all users in Grafana: update user information, read user information, create or enable or disable a user, make a user a Grafana administrator, sign out a user, update a user’s authentication token, or update quotas for all users. | ### Alerting roles From 5e75c0d17938e67f6b3fc33dfe1902eff658ae96 Mon Sep 17 00:00:00 2001 From: antonio <45235678+tonypowa@users.noreply.github.com> Date: Fri, 21 Jun 2024 10:40:49 +0200 Subject: [PATCH 04/21] tutorials:alerting:get-started-p2 (#89477) * tutorials:alerting:get-started-p2 * author,figures * link, numbering, note * fix,title * Update docs/sources/tutorials/alerting-get-started-pt2/index.md Co-authored-by: brendamuir <100768211+brendamuir@users.noreply.github.com> * Update docs/sources/tutorials/alerting-get-started-pt2/index.md Co-authored-by: brendamuir <100768211+brendamuir@users.noreply.github.com> * Update docs/sources/tutorials/alerting-get-started-pt2/index.md Co-authored-by: brendamuir <100768211+brendamuir@users.noreply.github.com> * alt text * Revert "alt text" This reverts commit 511a946158ff8b88313d48f6d44b70e4703de42a. * alt-text-pt2 * all pretty no pity --------- Co-authored-by: brendamuir <100768211+brendamuir@users.noreply.github.com> --- .../alerting-get-started-pt2/index.md | 172 ++++++++++++++++++ .../tutorials/alerting-get-started/index.md | 8 +- 2 files changed, 175 insertions(+), 5 deletions(-) create mode 100644 docs/sources/tutorials/alerting-get-started-pt2/index.md diff --git a/docs/sources/tutorials/alerting-get-started-pt2/index.md b/docs/sources/tutorials/alerting-get-started-pt2/index.md new file mode 100644 index 00000000000..5a1ae5709e4 --- /dev/null +++ b/docs/sources/tutorials/alerting-get-started-pt2/index.md @@ -0,0 +1,172 @@ +--- +Feedback Link: https://github.com/grafana/tutorials/issues/new +categories: + - alerting +description: This is part 2 of the Get started with Grafana Alerting tutorials. Learn how to leverage alert instances, and set up a notification policy that routes alert notifications based on labels to a specific contact point. +id: alerting-get-started-pt2 +labels: + products: + - enterprise + - oss + - cloud +tags: + - beginner +title: Get started with Grafana Alerting - Part 2 +weight: 50 +--- + +# Get started with Grafana Alerting - Part 2 + +## Introduction + +This is part 2 of the [Get Started with Grafana Alerting tutorial](http://grafana.com/tutorials/alerting-get-started/). + +In this guide, we dig into more complex yet equally fundamental elements of Grafana Alerting: **alert instances** and **notification policies**. + +After introducing each component, you will learn how to: + +- Configure an alert rule that returns more than one alert instance +- Create notification policies that route firing alert instances to different contact points +- Use labels to match alert instances and notification policies + +Learning about alert instances and notification policies is useful if you have more than one contact point in your organization, or if your alert rule returns a number of metrics that you want to handle separately by routing each alert instance to a specific contact point. The tutorial will introduce each concept, followed by how to apply both concepts in a real-world scenario. + +## Alert instances + +An [alert instance](https://grafana.com/docs/grafana/latest/alerting/fundamentals/#alert-instances) is an event that matches a metric returned by an alert rule query. + +Let's consider a scenario where you're monitoring website traffic using Grafana. You've set up an alert rule to trigger an alert instance if the number of page views exceeds a certain threshold (more than `1000` page views) within a specific time period, say, over the past `5` minutes. + +If the query returns more than one time-series, each time-series represents a different metric or aspect being monitored. In this case, the alert rule is applied individually to each time-series. + +{{< figure alt="Screenshot displaying alert instances in the context of an alert rule, highlighting the specific alerts triggered by the rule and their respective statuses" src="/media/docs/alerting/get-started-digram-instance-grey.png" max-width="1200px" caption="Alert Instances in the Context of an Alert Rule" >}} + +In this scenario, each time-series is evaluated independently against the alert rule. It results in the creation of an alert instance for each time-series. The time-series corresponding to the desktop page views meets the threshold and, therefore, results in an alert instance in **Firing** state for which an alert notification is sent. The mobile alert instance state remains **Normal**. + +## Notification policies + +[Notification policies](https://grafana.com/docs/grafana/latest/alerting/fundamentals/notifications/notification-policies/) route alerts to different communication channels, reducing alert noise and providing control over when and how alerts are sent. For example, you might use notification policies to ensure that critical alerts about server downtime are sent immediately to the on-call engineer. Another use case could be routing performance alerts to the development team for review and action. + +Key Characteristics: + +- Route alert notifications by matching alerts and policies with labels +- Manage when to send notifications + +{{< figure alt="Screenshot illustrating the routing of alerts with notification policies, including the configuration and flow of alerts through different notification channels" src="/media/docs/alerting/get-started-notification-policy-tree-combo.png" max-width="1200px" caption="Routing of alerts with notification policies" >}} + +In the above diagram, alert instances and notification policies are matched by labels. For instance, the label `team=operations` matches the alert instance “**Pod stuck in CrashLoop**” and “**Disk Usage -80%**” to child policies that send alert notifications to a particular contact point (operations@grafana.com). + +## Create notification policies + +Create a notification policy if you want to handle metrics returned by alert rules separately by routing each alert instance to a specific contact point. In Grafana, click on the icon at the top left corner of the screen to access the navigation menu. + +1. Navigate to **Alerts & IRM > Alerting > Notification policies**. +1. In the Default policy, click **+ New child policy**. +1. In the field **Label** enter `device`, and in the field **Value** enter `desktop`. +1. From the **Contact point** drop-down, choose **Webhook**. + {{< admonition type="note" >}} + If you don’t have any contact points, add a [Contact point](http://localhost:3002/docs/grafana/latest/tutorials/alerting-get-started/#create-a-contact-point). + {{}} +1. Click **Save Policy**. + + This new child policy routes alerts that match the label `device=desktop` to the Webhook contact point. + +1. **Repeat the steps above to create a second child policy** to match another alert instance. For labels use: `device=mobile`. Use the Webhook integration for the contact point. Alternatively, experiment by using a different Webhook endpoint or a [different integration](https://grafana.com/docs/grafana/latest/alerting/configure-notifications/manage-contact-points/#list-of-supported-integrations). + +## Create an alert rule that returns alert instances + +The alert rule that you are about to create is meant to monitor web traffic page views. The objective is to explore what an alert instance is and how to leverage routing individual alert instances by using label matchers and notification policies. + +### Add a data source + +Grafana includes a [test data source](https://grafana.com/docs/grafana/latest/datasources/testdata/) that creates simulated time series data. + +1. In Grafana navigate to **Connections > Add new connection**. +1. Search for **TestData**. +1. Click **Add new data source**. +1. Click **Save & test**. + + You should see a message confirming that the data source is working. + +### Create an alert rule + +1. Navigate to **Alerting > Alert rules**. +1. Click **New alert rule**. + +### Enter an alert rule name + +Make it short and descriptive as this will appear in your alert notification. For instance, `web-traffic`. + +### Define query and alert condition + +In this section, we define queries, expressions (used to manipulate the data), and the condition that must be met for the alert to be triggered. + +1. Select **TestData** data source from the drop-down menu. +1. From **Scenario** select **CSV Content**. +1. Copy in the following CSV data: + +``` +device,views +desktop,1200 +mobile,900 +``` + +The above CSV data simulates a data source returning multiple time series, each leading to the creation of an alert instance for that specific time series. Note that the data returned matches the example in the [Alert instance](#alert-instances) section. + +1. Remove the ‘B’ **Reduce expression** (click the bin icon). The Reduce expression is default, and in this case, is not required since the queried data is already reduced. Note that the Threshold expression is now your **Alert condition**. +1. In the ‘C’ **Threshold expression**: + - Change the **Input** to ‘**A**’ to select the data source. + - Enter `1000` as the threshold value. This is the value above which the alert rule should trigger. +1. Click **Preview** to run the queries. + +It should return two series.`desktop` in Firing state, and `mobile` in Normal state. The values `1`, and `0` mean that the condition is either `true` or `false`. + +{{< figure alt="Screenshot showing a preview of a query in Grafana that returns two alert instances, including the query results and relevant alert details" src="/media/docs/alerting/get-started-expression-instances.png" max-width="1200px" caption="Preview of a query returning two alert instances in Grafana." >}} + +### Set evaluation behavior + +In the [life cycle](http://grafana.com/docs/grafana/next/alerting/fundamentals/alert-rule-evaluation/) of alert instances, when an alert condition (threshold) is not met, the alert instance state is **Normal**. Similarly, when the condition is breached (for longer than the pending period, which in this tutorial will be 0), the alert instance state switches back to **Alerting**, which means that the alert rule state is **Firing**, and a notification is sent. + +To set up evaluation behavior: + +1. In **Folder**, click **+ New folder** and enter a name. For example: `web-traffic-alerts`. This folder will contain our alerts. +1. In the **Evaluation group**, repeat the above step to create a new evaluation group. We will name it `1m` (referring to “1 minute”). +1. Choose an Evaluation interval (how often the alert will be evaluated). Choose `1m`. +1. Set the pending period to `0s` (zero seconds), so the alert rule fires the moment the condition is met. + +### Configure labels and notifications + +In this section, you can select how you want to route your alert instances. Since we want to route by notification policy, we need to ensure that the labels match the alert instance. + +1. Choose **Use notification policy**. +1. Click **Preview routing**. Based on the existing labels, you should see a preview of what policies are matching with the alerts. There should be two alert instances matching the labels that were previously setup in each notification policy: `device=desktop`, `device=mobile`. + + These [types of labels](https://grafana.com/docs/grafana/latest/alerting/fundamentals/alert-rules/annotation-label/#label-types) are generated by the data source query and they can be leveraged to match our notification policies without needing to manually add them to the alert rule. + + {{< figure alt="Screenshot showing a routing preview of matched notification policies, detailing how alerts are matched and routed to specific notification channels" src="/media/docs/alerting/get-started-alert-instace-routing-prev.png" max-width="1200px" caption="Routing preview of matched notification policies" >}} + + {{< admonition type="note" >}} + Even if both labels match the policies, only the alert instance in Firing state produces an alert notification. + {{}} + +1. Click **Save rule and exit**. + +Now that we have set up the alert rule, it’s time to check the alert notification. + +## Receive alert notifications + +Now that the alert rule has been configured, you should receive alert [notifications](http://grafana.com/docs/grafana/next/alerting/fundamentals/alert-rule-evaluation/state-and-health/#notifications) in the contact point whenever the alert triggers and gets resolved. In our example, each alert instance should be routed separately as we configured labels to match notification policies. Once the evaluation interval has concluded (1m), you should receive an alert notification in the Webhook endpoint. + +{{< figure alt="Screenshot showing the exploration of alert notification details in a webhook endpoint, displaying the content and structure of the alert payload received by the endpoint" src="/media/docs/alerting/get-started-webhook-alert-isntance.png" max-width="1200px" caption="Exploring alert notification details in webhook endpoint" >}} + +The alert notification details show that the alert instance corresponding to the website views from desktop devices was correctly routed through the notification policy to the Webhook contact point. The notification also shows that the instance is in **Firing** state, as well as it includes the label `device=desktop`, which makes the routing of the alert instance possible. + +Feel free to change the CSV data in the alert rule to trigger the routing of the alert instance that matches the label `device=mobile`. + +## Summary + +In this tutorial, you have learned how Grafana Alerting can route individual alert instances using the labels generated by the data-source query and match these labels with notification policies, which in turn routes alert notifications to specific contact points. + +If you run into any problems, you are welcome to post questions in our [Grafana Community forum](https://community.grafana.com/). + +Enjoy your monitoring! diff --git a/docs/sources/tutorials/alerting-get-started/index.md b/docs/sources/tutorials/alerting-get-started/index.md index 18ac15cee5b..15357f0fa0a 100644 --- a/docs/sources/tutorials/alerting-get-started/index.md +++ b/docs/sources/tutorials/alerting-get-started/index.md @@ -1,11 +1,9 @@ --- Feedback Link: https://github.com/grafana/tutorials/issues/new -authors: - - antonio-calero-merello categories: - alerting description: Get started with Grafana Alerting by creating your first alert in just a few minutes. Learn how to set up an alert, send alert notifications to a public webhook, and generate sample data to observe your alert in action. -id: alerting-get-started +id: alerting-get-started-pt1 labels: products: - enterprise @@ -13,11 +11,11 @@ labels: - cloud tags: - beginner -title: Get started with Grafana Alerting +title: Get started with Grafana Alerting - Part 1 weight: 50 --- -# Get Started with Grafana Alerting +# Get Started with Grafana Alerting - Part 1 In this guide, we'll walk you through the process of setting up your first alert in just a few minutes. You'll witness your alert in action with real-time data, as well as sending alert notifications. From 88404715748e858c30bb457f6d9fa32eb368218b Mon Sep 17 00:00:00 2001 From: Ashley Harrison Date: Fri, 21 Jun 2024 09:48:40 +0100 Subject: [PATCH 05/21] Internationalisation: Update docs with nested variable examples (#89484) * update i18n docs to handle interpolating variables inside inner components * update with wrapping component example --- contribute/internationalization.md | 30 +++++++++++++++++++++++-- packages/grafana-eslint-rules/README.md | 21 +++++++++++------ 2 files changed, 42 insertions(+), 9 deletions(-) diff --git a/contribute/internationalization.md b/contribute/internationalization.md index 5ca730ab835..8729f8175f5 100644 --- a/contribute/internationalization.md +++ b/contribute/internationalization.md @@ -22,14 +22,40 @@ Grafana uses the [i18next](https://www.i18next.com/) framework for managing tran ```jsx import { Trans } from 'app/core/internationalization'; +const SearchTitle = ({ term }) => Results for {{ term }}; +``` + +Prefer using `` for JSX children, and `t()` for props and other JavaScript usage. + +There may be cases where you need to interpolate variables inside other components in the translation. + +If the nested component is displaying the variable only (e.g. to add emphasis or color), the best solution is to create a new wrapping component: + +```jsx +import { Trans } from 'app/core/internationalization'; +import { Text } from '@grafana/ui'; + +const SearchTerm = ({ term }) => {term}; + const SearchTitle = ({ term }) => ( - Results for {{ term }} + Results for ); ``` -Prefer using `` for JSX children, and `t()` for props and other JavaScript usage. +However there are also cases where the nested component might be displaying additional text which also needs to be translated. In this case, you can use the `values` prop to explicitly pass variables to the translation, and reference them as templated strings in the markup. For example: + +```jsx +import { Trans } from 'app/core/internationalization'; +import { Text } from '@grafana/ui'; + +const SearchTitle = ({ term }) => ( + + Results for {'{{ myVariable }}'} and this translated text is also in green + +); +``` When translating in `grafana-ui`, use a relative path to import `` and `t()` from `src/utils/i18n`. diff --git a/packages/grafana-eslint-rules/README.md b/packages/grafana-eslint-rules/README.md index 060db7211d9..2f73d46a529 100644 --- a/packages/grafana-eslint-rules/README.md +++ b/packages/grafana-eslint-rules/README.md @@ -133,16 +133,23 @@ Check if strings are marked for translation. ```tsx // Bad ❌ -const SearchTitle = ({ term }) => ( -
- Results for {term} -
-); +const SearchTitle = ({ term }) =>
Results for {term}
; -//Good ✅ +// Good ✅ +const SearchTitle = ({ term }) => Results for {{ term }}; + +// Good ✅ (if you need to interpolate variables inside nested components) +const SearchTerm = ({ term }) => {term}; const SearchTitle = ({ term }) => ( - Results for {{ term }} + Results for + +); + +// Good ✅ (if you need to interpolate variables and additional translated strings inside nested components) +const SearchTitle = ({ term }) => ( + + Results for {'{{ myVariable }}'} and this translated text is also in green ); ``` From be65aaf5dcc212b85db94969c0735eb0443b01b5 Mon Sep 17 00:00:00 2001 From: Ashley Harrison Date: Fri, 21 Jun 2024 10:03:57 +0100 Subject: [PATCH 06/21] Chore: Clean up some more old SCSS (#89404) * remove/migrate normalize.scss * fix typo * migrate _add_data_source styles * migrate panel-header styles * migrate slate styles to emotion globals * put back a couple of classes used by external plugins --- .../src/themes/GlobalStyles/GlobalStyles.tsx | 2 + .../src/themes/GlobalStyles/slate.ts | 145 +++++++++++++++ .../PanelEditor/PanelHeaderCorner.test.tsx | 4 +- .../PanelEditor/PanelHeaderCorner.tsx | 34 +++- .../components/DataSourceCategories.tsx | 26 ++- public/sass/_angular.scss | 71 ++++++++ public/sass/_grafana.scss | 3 - public/sass/components/_add_data_source.scss | 86 --------- public/sass/components/_panel_header.scss | 122 ------------- public/sass/components/_slate_editor.scss | 165 ------------------ 10 files changed, 269 insertions(+), 389 deletions(-) create mode 100644 packages/grafana-ui/src/themes/GlobalStyles/slate.ts delete mode 100644 public/sass/components/_add_data_source.scss delete mode 100644 public/sass/components/_panel_header.scss delete mode 100644 public/sass/components/_slate_editor.scss diff --git a/packages/grafana-ui/src/themes/GlobalStyles/GlobalStyles.tsx b/packages/grafana-ui/src/themes/GlobalStyles/GlobalStyles.tsx index e8880ad6f5e..5a437110ab5 100644 --- a/packages/grafana-ui/src/themes/GlobalStyles/GlobalStyles.tsx +++ b/packages/grafana-ui/src/themes/GlobalStyles/GlobalStyles.tsx @@ -17,6 +17,7 @@ import { getMarkdownStyles } from './markdownStyles'; import { getPageStyles } from './page'; import { getRcTimePickerStyles } from './rcTimePicker'; import { getSkeletonStyles } from './skeletonStyles'; +import { getSlateStyles } from './slate'; import { getUplotStyles } from './uPlot'; /** @internal */ @@ -38,6 +39,7 @@ export function GlobalStyles() { getAgularPanelStyles(theme), getMarkdownStyles(theme), getSkeletonStyles(theme), + getSlateStyles(theme), getRcTimePickerStyles(theme), getUplotStyles(theme), getLegacySelectStyles(theme), diff --git a/packages/grafana-ui/src/themes/GlobalStyles/slate.ts b/packages/grafana-ui/src/themes/GlobalStyles/slate.ts new file mode 100644 index 00000000000..e8fd858d456 --- /dev/null +++ b/packages/grafana-ui/src/themes/GlobalStyles/slate.ts @@ -0,0 +1,145 @@ +import { css } from '@emotion/react'; + +import { GrafanaTheme2 } from '@grafana/data'; + +export function getSlateStyles(theme: GrafanaTheme2) { + return css({ + '.slate-query-field': { + fontSize: theme.typography.fontSize, + fontFamily: theme.typography.fontFamilyMonospace, + height: 'auto', + wordBreak: 'break-word', + // Affects only placeholder in query field. Adds scrollbar only if content is cropped. + overflow: 'auto', + }, + + '.slate-query-field__wrapper': { + position: 'relative', + display: 'inline-block', + padding: '6px 8px', + minHeight: '32px', + width: '100%', + color: theme.colors.text.primary, + backgroundColor: theme.components.input.background, + backgroundImage: 'none', + border: `1px solid ${theme.components.input.borderColor}`, + borderRadius: theme.shape.radius.default, + transition: 'all 0.3s', + lineHeight: '18px', + }, + + '.slate-query-field__wrapper--disabled': { + backgroundColor: 'inherit', + cursor: 'not-allowed', + }, + + '.slate-typeahead': { + '.typeahead': { + position: 'relative', + zIndex: theme.zIndex.typeahead, + borderRadius: theme.shape.radius.default, + border: `1px solid ${theme.components.panel.borderColor}`, + maxHeight: '66vh', + overflowY: 'scroll', + overflowX: 'hidden', + outline: 'none', + listStyle: 'none', + background: theme.components.panel.background, + color: theme.colors.text.primary, + boxShadow: theme.shadows.z2, + }, + + '.typeahead-group__title': { + color: theme.colors.text.secondary, + fontSize: theme.typography.size.sm, + lineHeight: theme.typography.body.lineHeight, + padding: theme.spacing(1), + }, + + '.typeahead-item': { + height: 'auto', + fontFamily: theme.typography.fontFamilyMonospace, + padding: theme.spacing(1, 1, 1, 2), + fontSize: theme.typography.size.sm, + textOverflow: 'ellipsis', + overflow: 'hidden', + zIndex: 1, + display: 'block', + whiteSpace: 'nowrap', + cursor: 'pointer', + transition: + 'color 0.3s cubic-bezier(0.645, 0.045, 0.355, 1), border-color 0.3s cubic-bezier(0.645, 0.045, 0.355, 1), background 0.3s cubic-bezier(0.645, 0.045, 0.355, 1), padding 0.15s cubic-bezier(0.645, 0.045, 0.355, 1)', + }, + + '.typeahead-item__selected': { + backgroundColor: theme.isDark ? theme.v1.palette.dark9 : theme.v1.palette.gray6, + + '.typeahead-item-hint': { + fontSize: theme.typography.size.xs, + color: theme.colors.text.primary, + whiteSpace: 'normal', + }, + }, + + '.typeahead-match': { + color: theme.v1.palette.yellow, + borderBottom: `1px solid ${theme.v1.palette.yellow}`, + // Undoing mark styling + padding: 'inherit', + background: 'inherit', + }, + }, + + /* SYNTAX */ + + '.slate-query-field, .prism-syntax-highlight': { + '.token.comment, .token.block-comment, .token.prolog, .token.doctype, .token.cdata': { + color: theme.colors.text.secondary, + }, + + '.token.variable, .token.entity': { + color: theme.colors.text.primary, + }, + + '.token.property, .token.tag, .token.constant, .token.symbol, .token.deleted': { + color: theme.colors.error.text, + }, + + '.token.attr-value, .token.selector, .token.string, .token.char, .token.builtin, .token.inserted': { + color: theme.colors.success.text, + }, + + '.token.boolean, .token.number, .token.operator, .token.url': { + color: '#fe85fc', + }, + + '.token.function, .token.attr-name, .token.function-name, .token.atrule, .token.keyword, .token.class-name': { + color: theme.colors.primary.text, + }, + + '.token.punctuation, .token.regex, .token.important': { + color: theme.v1.palette.orange, + }, + + '.token.important': { + fontWeight: 'normal', + }, + + '.token.bold': { + fontWeight: 'bold', + }, + + '.token.italic': { + fontStyle: 'italic', + }, + + '.token.entity': { + cursor: 'help', + }, + + '.namespace': { + opacity: 0.7, + }, + }, + }); +} diff --git a/public/app/features/dashboard/components/PanelEditor/PanelHeaderCorner.test.tsx b/public/app/features/dashboard/components/PanelEditor/PanelHeaderCorner.test.tsx index c92baacc746..5e2067ce961 100644 --- a/public/app/features/dashboard/components/PanelEditor/PanelHeaderCorner.test.tsx +++ b/public/app/features/dashboard/components/PanelEditor/PanelHeaderCorner.test.tsx @@ -1,16 +1,18 @@ import { render, screen } from '@testing-library/react'; import React from 'react'; +import { createTheme } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; import { PanelModel } from '../../state'; -import PanelHeaderCorner, { Props } from './PanelHeaderCorner'; +import { PanelHeaderCorner, Props } from './PanelHeaderCorner'; const setup = () => { const testPanel = new PanelModel({ title: 'test', description: 'test panel' }); const props: Props = { panel: testPanel, + theme: createTheme(), }; return render(); }; diff --git a/public/app/features/dashboard/components/PanelEditor/PanelHeaderCorner.tsx b/public/app/features/dashboard/components/PanelEditor/PanelHeaderCorner.tsx index c761570fdc4..1d5c7a5a8e1 100644 --- a/public/app/features/dashboard/components/PanelEditor/PanelHeaderCorner.tsx +++ b/public/app/features/dashboard/components/PanelEditor/PanelHeaderCorner.tsx @@ -5,7 +5,7 @@ import { renderMarkdown, LinkModelSupplier, ScopedVars, IconName } from '@grafan import { GrafanaTheme2 } from '@grafana/data/'; import { selectors } from '@grafana/e2e-selectors'; import { locationService, getTemplateSrv } from '@grafana/runtime'; -import { Tooltip, PopoverContent, Icon } from '@grafana/ui'; +import { Tooltip, PopoverContent, Icon, Themeable2, withTheme2 } from '@grafana/ui'; import { useStyles2 } from '@grafana/ui/'; import { getTimeSrv, TimeSrv } from 'app/features/dashboard/services/TimeSrv'; import { PanelModel } from 'app/features/dashboard/state/PanelModel'; @@ -17,7 +17,7 @@ enum InfoMode { Links = 'Links', } -export interface Props { +export interface Props extends Themeable2 { panel: PanelModel; title?: string; description?: string; @@ -45,22 +45,23 @@ export class PanelHeaderCorner extends Component { }; getInfoContent = (): JSX.Element => { - const { panel } = this.props; + const { panel, theme } = this.props; const markdown = panel.description || ''; const interpolatedMarkdown = getTemplateSrv().replace(markdown, panel.scopedVars); const markedInterpolatedMarkdown = renderMarkdown(interpolatedMarkdown); const links = this.props.links && this.props.links.getLinks(panel.replaceVariables); + const styles = getContentStyles(theme); return ( -
+
{links && links.length > 0 && ( -