From 157cab192c58c9e8c31ca72bceef9c7f3e82a85c Mon Sep 17 00:00:00 2001 From: Todd Treece <360020+toddtreece@users.noreply.github.com> Date: Wed, 3 Dec 2025 11:01:55 -0500 Subject: [PATCH 001/110] Plugins API: Add authlib authorizer (#114773) --- apps/plugins/pkg/app/app.go | 51 +++++++++++++++++++++++++--- pkg/apimachinery/identity/context.go | 1 + 2 files changed, 47 insertions(+), 5 deletions(-) diff --git a/apps/plugins/pkg/app/app.go b/apps/plugins/pkg/app/app.go index ec0f59df3fd..3d17cc34221 100644 --- a/apps/plugins/pkg/app/app.go +++ b/apps/plugins/pkg/app/app.go @@ -10,11 +10,13 @@ import ( "github.com/grafana/grafana-app-sdk/operator" "github.com/grafana/grafana-app-sdk/simple" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apiserver/pkg/authorization/authorizer" "k8s.io/apiserver/pkg/registry/generic" "k8s.io/apiserver/pkg/registry/rest" restclient "k8s.io/client-go/rest" "k8s.io/klog/v2" + authlib "github.com/grafana/authlib/types" pluginsappapis "github.com/grafana/grafana/apps/plugins/pkg/apis" pluginsv0alpha1 "github.com/grafana/grafana/apps/plugins/pkg/apis/plugins/v0alpha1" "github.com/grafana/grafana/apps/plugins/pkg/app/meta" @@ -68,7 +70,7 @@ type PluginAppConfig struct { func ProvideAppInstaller( metaProviderManager *meta.ProviderManager, -) (appsdkapiserver.AppInstaller, error) { +) (*PluginAppInstaller, error) { specificConfig := &PluginAppConfig{ MetaProviderManager: metaProviderManager, } @@ -83,7 +85,7 @@ func ProvideAppInstaller( return nil, err } - appInstaller := &pluginAppInstaller{ + appInstaller := &PluginAppInstaller{ AppInstaller: defaultInstaller, metaManager: metaProviderManager, ready: make(chan struct{}), @@ -91,16 +93,22 @@ func ProvideAppInstaller( return appInstaller, nil } -type pluginAppInstaller struct { +func (p *PluginAppInstaller) WithAccessChecker(access authlib.AccessChecker) *PluginAppInstaller { + p.access = access + return p +} + +type PluginAppInstaller struct { appsdkapiserver.AppInstaller metaManager *meta.ProviderManager + access authlib.AccessChecker // restConfig is set during InitializeApp and used by the client factory restConfig *restclient.Config ready chan struct{} } -func (p *pluginAppInstaller) InitializeApp(restConfig restclient.Config) error { +func (p *PluginAppInstaller) InitializeApp(restConfig restclient.Config) error { if p.restConfig == nil { p.restConfig = &restConfig close(p.ready) @@ -108,7 +116,7 @@ func (p *pluginAppInstaller) InitializeApp(restConfig restclient.Config) error { return p.AppInstaller.InitializeApp(restConfig) } -func (p *pluginAppInstaller) InstallAPIs( +func (p *PluginAppInstaller) InstallAPIs( server appsdkapiserver.GenericAPIServer, restOptsGetter generic.RESTOptionsGetter, ) error { @@ -139,3 +147,36 @@ func (p *pluginAppInstaller) InstallAPIs( } return p.AppInstaller.InstallAPIs(wrappedServer, restOptsGetter) } + +func (p *PluginAppInstaller) GetAuthorizer() authorizer.Authorizer { + if p.access == nil { + return nil + } + + return authorizer.AuthorizerFunc( + func(ctx context.Context, a authorizer.Attributes) (decision authorizer.Decision, reason string, err error) { + info, ok := authlib.AuthInfoFrom(ctx) + if !ok { + return authorizer.DecisionDeny, "failed to get auth info", nil + } + + res, err := p.access.Check(ctx, info, authlib.CheckRequest{ + Verb: a.GetVerb(), + Group: a.GetAPIGroup(), + Resource: a.GetResource(), + Name: a.GetName(), + Namespace: a.GetNamespace(), + Subresource: a.GetSubresource(), + Path: a.GetPath(), + }, "") + if err != nil { + return authorizer.DecisionDeny, "failed to perform authorization", err + } + + if !res.Allowed { + return authorizer.DecisionDeny, "permission denied", nil + } + + return authorizer.DecisionAllow, "", nil + }) +} diff --git a/pkg/apimachinery/identity/context.go b/pkg/apimachinery/identity/context.go index 6b39c1af046..984a9b24831 100644 --- a/pkg/apimachinery/identity/context.go +++ b/pkg/apimachinery/identity/context.go @@ -160,6 +160,7 @@ var serviceIdentityTokenPermissions = []string{ "iam.grafana.app:*", "preferences.grafana.app:*", // user, team, and org preferences "collections.grafana.app:*", // user stars + "plugins.grafana.app:*", // Secrets Manager uses a custom verb for secret decryption, and its authorizer does not allow wildcard permissions. "secret.grafana.app/securevalues:decrypt", From c847f1fa4bcd87cf19f29b37926e81577e1a45f2 Mon Sep 17 00:00:00 2001 From: Liza Detrick <114438185+L2D2Grafana@users.noreply.github.com> Date: Wed, 3 Dec 2025 08:03:36 -0800 Subject: [PATCH 002/110] Logs in Explore: Persist table sorting in the url (#114060) --- packages/grafana-data/src/types/explore.ts | 3 + public/app/features/explore/Logs/Logs.tsx | 6 ++ .../features/explore/Logs/LogsTable.test.tsx | 63 ++++++++++++++++++- .../app/features/explore/Logs/LogsTable.tsx | 31 +++++++-- .../features/explore/Logs/LogsTableWrap.tsx | 22 +++++++ 5 files changed, 119 insertions(+), 6 deletions(-) diff --git a/packages/grafana-data/src/types/explore.ts b/packages/grafana-data/src/types/explore.ts index 71fd7eae35d..1eb255aa7c3 100644 --- a/packages/grafana-data/src/types/explore.ts +++ b/packages/grafana-data/src/types/explore.ts @@ -85,6 +85,9 @@ export interface ExploreLogsPanelState { refId?: string; displayedFields?: string[]; sortOrder?: LogsSortOrder; + // Column sort state for table view. Persists between query changes. + tableSortBy?: string; + tableSortDir?: 'asc' | 'desc'; } export interface SplitOpenOptions { diff --git a/public/app/features/explore/Logs/Logs.tsx b/public/app/features/explore/Logs/Logs.tsx index 7fed4aa6cf2..ac07e2cacd5 100644 --- a/public/app/features/explore/Logs/Logs.tsx +++ b/public/app/features/explore/Logs/Logs.tsx @@ -308,6 +308,8 @@ const UnthemedLogs: React.FunctionComponent = (props: Props) => { refId: undefined, displayedFields: undefined, sortOrder: undefined, + tableSortBy: undefined, + tableSortDir: undefined, }) ); }); @@ -324,6 +326,8 @@ const UnthemedLogs: React.FunctionComponent = (props: Props) => { labelFieldName: logsPanelState.labelFieldName, refId: logsPanelState.refId ?? panelState?.logs?.refId, displayedFields: logsPanelState.displayedFields ?? panelState?.logs?.displayedFields, + tableSortBy: logsPanelState.tableSortBy ?? panelState?.logs?.tableSortBy, + tableSortDir: logsPanelState.tableSortDir ?? panelState?.logs?.tableSortDir, }) ); } @@ -334,6 +338,8 @@ const UnthemedLogs: React.FunctionComponent = (props: Props) => { panelState?.logs?.columns, panelState?.logs?.displayedFields, panelState?.logs?.refId, + panelState?.logs?.tableSortBy, + panelState?.logs?.tableSortDir, visualisationType, ] ); diff --git a/public/app/features/explore/Logs/LogsTable.test.tsx b/public/app/features/explore/Logs/LogsTable.test.tsx index 2c9cdedb95a..ea203456008 100644 --- a/public/app/features/explore/Logs/LogsTable.test.tsx +++ b/public/app/features/explore/Logs/LogsTable.test.tsx @@ -1,4 +1,4 @@ -import { render, screen, waitFor } from '@testing-library/react'; +import { render, screen, waitFor, fireEvent } from '@testing-library/react'; import { ComponentProps } from 'react'; import { DataFrame, FieldType, LogsSortOrder, toUtc } from '@grafana/data'; @@ -301,4 +301,65 @@ describe('LogsTable', () => { }); }); }); + + describe('Sort persistence', () => { + it('should update URL with sort parameters when sort changes', async () => { + // Mock onSortByChange to update URL (simulating parent Explore component behavior) + const onSortByChange = jest.fn((sortBy) => { + const mockUrl = new URL(window.location.href); + if (sortBy && sortBy.length > 0) { + mockUrl.searchParams.set('tableSortBy', sortBy[0].displayName); + mockUrl.searchParams.set('tableSortDir', sortBy[0].desc ? 'desc' : 'asc'); + } else { + // Remove sort params if no sort is applied + mockUrl.searchParams.delete('tableSortBy'); + mockUrl.searchParams.delete('tableSortDir'); + } + window.history.replaceState({}, '', mockUrl.toString()); + }); + + setup({ + tableSortBy: 'Time', + tableSortDir: 'desc', + onSortByChange, + columnsWithMeta: { + Time: { active: true, percentOfLinesWithLabel: 3, index: 0 }, + line: { active: true, percentOfLinesWithLabel: 3, index: 1 }, + }, + }); + + await waitFor(() => { + const rows = screen.getAllByRole('row'); + expect(rows.length).toBe(4); + }); + + // Verify the Time column has the sort indicator (arrow down for descending) + const timeColumnHeader = screen.getByRole('columnheader', { name: /Time/i }); + const sortButton = timeColumnHeader.querySelector('button[title="Toggle SortBy"]'); + expect(sortButton).toBeTruthy(); + + // Click to toggle sort (desc -> asc) + if (sortButton) { + fireEvent.click(sortButton); + } + + await waitFor(() => { + expect(onSortByChange).toHaveBeenCalled(); + }); + + // Verify URL was updated (callback was called and URL reflects the new sort state) + const currentUrl = new URL(window.location.href); + const tableSortBy = currentUrl.searchParams.get('tableSortBy'); + const tableSortDir = currentUrl.searchParams.get('tableSortDir'); + + expect(onSortByChange).toHaveBeenCalled(); + + // Verify sort parameters are in URL after clicking + // The mock simulates parent component updating URL with sort state + if (tableSortBy && tableSortDir) { + expect(tableSortBy).toBe('Time'); + expect(tableSortDir).toBe('desc'); + } + }); + }); }); diff --git a/public/app/features/explore/Logs/LogsTable.tsx b/public/app/features/explore/Logs/LogsTable.tsx index 7583a3c9b0b..adc17844bb3 100644 --- a/public/app/features/explore/Logs/LogsTable.tsx +++ b/public/app/features/explore/Logs/LogsTable.tsx @@ -18,7 +18,7 @@ import { ValueLinkConfig, } from '@grafana/data'; import { config } from '@grafana/runtime'; -import { AdHocFilterItem, Table } from '@grafana/ui'; +import { AdHocFilterItem, Table, TableSortByFieldState } from '@grafana/ui'; import { FILTER_FOR_OPERATOR, FILTER_OUT_OPERATOR } from '@grafana/ui/internal'; import { LogsFrame } from 'app/features/logs/logsFrame'; @@ -38,10 +38,25 @@ interface Props { onClickFilterLabel?: (key: string, value: string, frame?: DataFrame) => void; onClickFilterOutLabel?: (key: string, value: string, frame?: DataFrame) => void; logsFrame: LogsFrame | null; + tableSortBy?: string; + tableSortDir?: 'asc' | 'desc'; + onSortByChange?: (sortBy: TableSortByFieldState[]) => void; } export function LogsTable(props: Props) { - const { timeZone, splitOpen, range, logsSortOrder, width, dataFrame, columnsWithMeta, logsFrame } = props; + const { + timeZone, + splitOpen, + range, + logsSortOrder, + width, + dataFrame, + columnsWithMeta, + logsFrame, + tableSortBy, + tableSortDir, + onSortByChange, + } = props; const [tableFrame, setTableFrame] = useState(undefined); const timeIndex = logsFrame?.timeField.index; @@ -167,6 +182,13 @@ export function LogsTable(props: Props) { } }; + // Use persisted sortBy if available, otherwise default to time field based on logsSortOrder + const defaultSortBy: TableSortByFieldState[] = [ + { displayName: logsFrame?.timeField.name || '', desc: logsSortOrder === LogsSortOrder.Descending }, + ]; + const initialSortBy: TableSortByFieldState[] = + tableSortBy && tableSortDir ? [{ displayName: tableSortBy, desc: tableSortDir === 'desc' }] : defaultSortBy; + return ( ); } diff --git a/public/app/features/explore/Logs/LogsTableWrap.tsx b/public/app/features/explore/Logs/LogsTableWrap.tsx index 67829081b31..0148c1a06df 100644 --- a/public/app/features/explore/Logs/LogsTableWrap.tsx +++ b/public/app/features/explore/Logs/LogsTableWrap.tsx @@ -278,6 +278,25 @@ export function LogsTableWrap(props: Props) { const styles = useStyles2(getStyles, height, sidebarWidth); + const onSortByChange = useCallback( + (sortBy: Array<{ displayName: string; desc?: boolean }>) => { + // Transform from Table format to URL format - only store the first sort column + if (sortBy.length > 0) { + // Defer the Redux dispatch to avoid updating ExploreActions during Table's render cycle + // Even though this is called from an event handler, the synchronous Redux dispatch + // can cause ExploreActions (which subscribes to panes state) to re-render while + // Table is still rendering, triggering the React warning + setTimeout(() => { + updatePanelState({ + tableSortBy: sortBy[0].displayName, + tableSortDir: sortBy[0].desc ? 'desc' : 'asc', + }); + }, 0); + } + }, + [updatePanelState] + ); + if (!columnsWithMeta) { return null; } @@ -512,6 +531,9 @@ export function LogsTableWrap(props: Props) { dataFrame={currentDataFrame} columnsWithMeta={columnsWithMeta} height={height} + tableSortBy={panelState?.tableSortBy} + tableSortDir={panelState?.tableSortDir} + onSortByChange={onSortByChange} /> From 0f698d08d30941d87fc3001ab36b798e74055c9f Mon Sep 17 00:00:00 2001 From: Austin Pond Date: Wed, 3 Dec 2025 11:05:00 -0500 Subject: [PATCH 003/110] appinstaller: Use grafana-app-sdk `apiserver.KubernetesGenericAPIServer` to wrap the generic API server (#114654) [App Platform] Use the app-sdk's apiserver.KubernetesGenericAPIServer in the serverWrapper to ensure that any extra logic for handling WebServices is used. --- pkg/services/apiserver/appinstaller/installer.go | 2 +- pkg/services/apiserver/appinstaller/server.go | 9 +++------ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/pkg/services/apiserver/appinstaller/installer.go b/pkg/services/apiserver/appinstaller/installer.go index 6397262d614..b6e9145ff15 100644 --- a/pkg/services/apiserver/appinstaller/installer.go +++ b/pkg/services/apiserver/appinstaller/installer.go @@ -147,7 +147,7 @@ func InstallAPIs( logger.Debug("Installing APIs for app installer", "app", installer.ManifestData().AppName) wrapper := &serverWrapper{ ctx: ctx, - GenericAPIServer: server, + GenericAPIServer: appsdkapiserver.NewKubernetesGenericAPIServer(server), installer: installer, storageOpts: storageOpts, restOptionsGetter: restOpsGetter, diff --git a/pkg/services/apiserver/appinstaller/server.go b/pkg/services/apiserver/appinstaller/server.go index ab688a46fb9..1967fee9712 100644 --- a/pkg/services/apiserver/appinstaller/server.go +++ b/pkg/services/apiserver/appinstaller/server.go @@ -26,8 +26,8 @@ import ( var _ appsdkapiserver.GenericAPIServer = (*serverWrapper)(nil) type serverWrapper struct { - ctx context.Context - *genericapiserver.GenericAPIServer + ctx context.Context + GenericAPIServer appsdkapiserver.GenericAPIServer installer appsdkapiserver.AppInstaller restOptionsGetter generic.RESTOptionsGetter storageOpts *grafanaapiserveroptions.StorageOptions @@ -143,8 +143,5 @@ func (s *serverWrapper) configureStorage(gr schema.GroupResource, dualWriteSuppo } func (s *serverWrapper) RegisteredWebServices() []*restful.WebService { - if s.Handler != nil && s.Handler.GoRestfulContainer != nil { - return s.Handler.GoRestfulContainer.RegisteredWebServices() - } - return nil + return s.GenericAPIServer.RegisteredWebServices() } From 8998b1fde495b1b0da10e1e2fd7d0a0a069b17f7 Mon Sep 17 00:00:00 2001 From: Gabriel MABILLE Date: Wed, 3 Dec 2025 17:06:26 +0100 Subject: [PATCH 004/110] `grafana-iam`: Implement api level user authorization (#114498) * OnGoing comment * WIP on the wrapper * Get before Delete * WIP: add an unimplemented storage authorizer * WIP implementing the resource permission authorize * Implement beforeCreate * Create, Delete, Update * List * Use a resource permissions wrapper * Switch the main authorizer to service * Add namespace * Use compile for list * Comment * Remove unecessary comments * fix bug with folder permissions * Implement tests for List * Test get * List test small refactor * Delete test * Reorganize code * imports * Start splitting the tests * test AfterDelete * actually test beforeWrite * Implement tests for wrapper create * Test delete * Test List and Get * Fix List * Remaining tests * simplify * Remove comments * Reorder * Change authorizer to allow access --- pkg/registry/apis/iam/authorizer.go | 15 +- .../iam/authorizer/resource_permissions.go | 163 +++++++ .../authorizer/resource_permissions_test.go | 216 ++++++++++ pkg/registry/apis/iam/register.go | 12 +- .../auth/authorizer/storewrapper/wrapper.go | 191 +++++++++ .../authorizer/storewrapper/wrapper_test.go | 399 ++++++++++++++++++ pkg/services/authz/rbac/service.go | 2 +- 7 files changed, 995 insertions(+), 3 deletions(-) create mode 100644 pkg/registry/apis/iam/authorizer/resource_permissions.go create mode 100644 pkg/registry/apis/iam/authorizer/resource_permissions_test.go create mode 100644 pkg/services/apiserver/auth/authorizer/storewrapper/wrapper.go create mode 100644 pkg/services/apiserver/auth/authorizer/storewrapper/wrapper_test.go diff --git a/pkg/registry/apis/iam/authorizer.go b/pkg/registry/apis/iam/authorizer.go index 89f3f5ff2fa..0ec018d86de 100644 --- a/pkg/registry/apis/iam/authorizer.go +++ b/pkg/registry/apis/iam/authorizer.go @@ -22,6 +22,19 @@ type iamAuthorizer struct { func newIAMAuthorizer(accessClient authlib.AccessClient, legacyAccessClient authlib.AccessClient) authorizer.Authorizer { resourceAuthorizer := make(map[string]authorizer.Authorizer) + // Authorizer that allows any authenticated user + // To be used when authorization is handled at the storage layer + allowAuthorizer := authorizer.AuthorizerFunc(func( + ctx context.Context, attr authorizer.Attributes, + ) (authorized authorizer.Decision, reason string, err error) { + if !attr.IsResourceRequest() { + return authorizer.DecisionNoOpinion, "", nil + } + + // Any authenticated user can access the API + return authorizer.DecisionAllow, "", nil + }) + // Identity specific resources legacyAuthorizer := gfauthorizer.NewResourceAuthorizer(legacyAccessClient) resourceAuthorizer[iamv0.TeamBindingResourceInfo.GetName()] = legacyAuthorizer @@ -31,7 +44,7 @@ func newIAMAuthorizer(accessClient authlib.AccessClient, legacyAccessClient auth authorizer := gfauthorizer.NewResourceAuthorizer(accessClient) resourceAuthorizer[iamv0.CoreRoleInfo.GetName()] = iamauthorizer.NewCoreRoleAuthorizer(accessClient) resourceAuthorizer[iamv0.RoleInfo.GetName()] = authorizer - resourceAuthorizer[iamv0.ResourcePermissionInfo.GetName()] = authorizer + resourceAuthorizer[iamv0.ResourcePermissionInfo.GetName()] = allowAuthorizer // Handled at storage layer resourceAuthorizer[iamv0.RoleBindingInfo.GetName()] = authorizer resourceAuthorizer[iamv0.ServiceAccountResourceInfo.GetName()] = authorizer resourceAuthorizer[iamv0.UserResourceInfo.GetName()] = authorizer diff --git a/pkg/registry/apis/iam/authorizer/resource_permissions.go b/pkg/registry/apis/iam/authorizer/resource_permissions.go new file mode 100644 index 00000000000..237739c78f1 --- /dev/null +++ b/pkg/registry/apis/iam/authorizer/resource_permissions.go @@ -0,0 +1,163 @@ +package authorizer + +import ( + "context" + "fmt" + + "github.com/grafana/authlib/types" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + + iamv0 "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1" + "github.com/grafana/grafana/pkg/apimachinery/utils" + "github.com/grafana/grafana/pkg/services/apiserver/auth/authorizer/storewrapper" +) + +// TODO: Logs, Metrics, Traces? + +// ResourcePermissionsAuthorizer +type ResourcePermissionsAuthorizer struct { + accessClient types.AccessClient +} + +var _ storewrapper.ResourceStorageAuthorizer = (*ResourcePermissionsAuthorizer)(nil) + +func NewResourcePermissionsAuthorizer(accessClient types.AccessClient) *ResourcePermissionsAuthorizer { + return &ResourcePermissionsAuthorizer{ + accessClient: accessClient, + } +} + +// AfterGet implements ResourceStorageAuthorizer. +func (r *ResourcePermissionsAuthorizer) AfterGet(ctx context.Context, obj runtime.Object) error { + authInfo, ok := types.AuthInfoFrom(ctx) + if !ok { + return storewrapper.ErrUnauthenticated + } + switch o := obj.(type) { + case *iamv0.ResourcePermission: + target := o.Spec.Resource + + // TODO: Fetch the resource to retrieve its parent folder. + parent := "" + + checkReq := types.CheckRequest{ + Namespace: o.Namespace, + Group: target.ApiGroup, + Resource: target.Resource, + Verb: utils.VerbGetPermissions, + Name: target.Name, + } + res, err := r.accessClient.Check(ctx, authInfo, checkReq, parent) + if err != nil { + return err + } + if !res.Allowed { + return storewrapper.ErrUnauthorized + } + return nil + default: + return fmt.Errorf("expected ResourcePermission, got %T: %w", o, storewrapper.ErrUnexpectedType) + } +} + +func (r *ResourcePermissionsAuthorizer) beforeWrite(ctx context.Context, obj runtime.Object) error { + authInfo, ok := types.AuthInfoFrom(ctx) + if !ok { + return storewrapper.ErrUnauthenticated + } + switch o := obj.(type) { + case *iamv0.ResourcePermission: + target := o.Spec.Resource + + // TODO: Fetch the resource to retrieve its parent folder. + parent := "" + + checkReq := types.CheckRequest{ + Namespace: o.Namespace, + Group: target.ApiGroup, + Resource: target.Resource, + Verb: utils.VerbSetPermissions, + Name: target.Name, + } + res, err := r.accessClient.Check(ctx, authInfo, checkReq, parent) + if err != nil { + return err + } + if !res.Allowed { + return storewrapper.ErrUnauthorized + } + return nil + default: + return fmt.Errorf("expected ResourcePermission, got %T: %w", o, storewrapper.ErrUnexpectedType) + } +} + +// BeforeCreate implements ResourceStorageAuthorizer. +func (r *ResourcePermissionsAuthorizer) BeforeCreate(ctx context.Context, obj runtime.Object) error { + return r.beforeWrite(ctx, obj) +} + +// BeforeDelete implements ResourceStorageAuthorizer. +func (r *ResourcePermissionsAuthorizer) BeforeDelete(ctx context.Context, obj runtime.Object) error { + return r.beforeWrite(ctx, obj) +} + +// BeforeUpdate implements ResourceStorageAuthorizer. +func (r *ResourcePermissionsAuthorizer) BeforeUpdate(ctx context.Context, obj runtime.Object) error { + return r.beforeWrite(ctx, obj) +} + +// FilterList implements ResourceStorageAuthorizer. +func (r *ResourcePermissionsAuthorizer) FilterList(ctx context.Context, list runtime.Object) (runtime.Object, error) { + authInfo, ok := types.AuthInfoFrom(ctx) + if !ok { + return nil, storewrapper.ErrUnauthenticated + } + + switch l := list.(type) { + case *iamv0.ResourcePermissionList: + var ( + filteredItems []iamv0.ResourcePermission + err error + canViewFuncs = map[schema.GroupResource]types.ItemChecker{} + ) + for _, item := range l.Items { + gr := schema.GroupResource{ + Group: item.Spec.Resource.ApiGroup, + Resource: item.Spec.Resource.Resource, + } + + // Reuse the same canView for items with the same resource + canView, found := canViewFuncs[gr] + + if !found { + listReq := types.ListRequest{ + Namespace: item.Namespace, + Group: item.Spec.Resource.ApiGroup, + Resource: item.Spec.Resource.Resource, + Verb: utils.VerbGetPermissions, + } + + canView, _, err = r.accessClient.Compile(ctx, authInfo, listReq) + if err != nil { + return nil, err + } + + canViewFuncs[gr] = canView + } + + // TODO : Fetch the resource to retrieve its parent folder. + parent := "" + + allowed := canView(item.Spec.Resource.Name, parent) + if allowed { + filteredItems = append(filteredItems, item) + } + } + l.Items = filteredItems + return l, nil + default: + return nil, fmt.Errorf("expected ResourcePermissionList, got %T: %w", l, storewrapper.ErrUnexpectedType) + } +} diff --git a/pkg/registry/apis/iam/authorizer/resource_permissions_test.go b/pkg/registry/apis/iam/authorizer/resource_permissions_test.go new file mode 100644 index 00000000000..9f1762e365f --- /dev/null +++ b/pkg/registry/apis/iam/authorizer/resource_permissions_test.go @@ -0,0 +1,216 @@ +package authorizer + +import ( + "context" + "testing" + + "github.com/go-jose/go-jose/v4/jwt" + "github.com/grafana/authlib/authn" + "github.com/grafana/authlib/types" + iamv0 "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1" + "github.com/grafana/grafana/pkg/apimachinery/identity" + "github.com/grafana/grafana/pkg/apimachinery/utils" + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +var ( + user = authn.NewIDTokenAuthInfo( + authn.Claims[authn.AccessTokenClaims]{ + Claims: jwt.Claims{Issuer: "grafana", + Subject: types.NewTypeID(types.TypeAccessPolicy, "grafana"), Audience: []string{"iam.grafana.app"}}, + Rest: authn.AccessTokenClaims{ + Namespace: "*", + Permissions: identity.ServiceIdentityClaims.Rest.Permissions, + DelegatedPermissions: identity.ServiceIdentityClaims.Rest.DelegatedPermissions, + }, + }, &authn.Claims[authn.IDTokenClaims]{ + Claims: jwt.Claims{Subject: types.NewTypeID(types.TypeUser, "u001")}, + Rest: authn.IDTokenClaims{Namespace: "org-2", Identifier: "u001", Type: types.TypeUser}, + }, + ) +) + +func newResourcePermission(apiGroup, resource, name string) *iamv0.ResourcePermission { + return &iamv0.ResourcePermission{ + ObjectMeta: metav1.ObjectMeta{Namespace: "org-2"}, + Spec: iamv0.ResourcePermissionSpec{ + Resource: iamv0.ResourcePermissionspecResource{ + ApiGroup: apiGroup, + Resource: resource, + Name: name, + }, + }, + } +} + +func TestResourcePermissions_AfterGet(t *testing.T) { + // In this test, we verify that AfterGet calls accessClient.Check with the correct parameters + fold1 := newResourcePermission("folder.grafana.app", "folders", "fold-1") + + tests := []struct { + name string + shouldAllow bool + }{ + { + name: "allow access", + shouldAllow: true, + }, + { + name: "deny access", + shouldAllow: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + checkFunc := func(id types.AuthInfo, req *types.CheckRequest, folder string) (types.CheckResponse, error) { + require.NotNil(t, id) + // Check is called with the user's identity + require.Equal(t, "user:u001", id.GetUID()) + require.Equal(t, "org-2", id.GetNamespace()) + // Check the request values + require.Equal(t, "org-2", req.Namespace) + require.Equal(t, fold1.Spec.Resource.ApiGroup, req.Group) + require.Equal(t, fold1.Spec.Resource.Resource, req.Resource) + require.Equal(t, fold1.Spec.Resource.Name, req.Name) + require.Equal(t, utils.VerbGetPermissions, req.Verb) + + return types.CheckResponse{Allowed: tt.shouldAllow}, nil + } + + accessClient := &fakeAccessClient{checkFunc: checkFunc} + resPermAuthz := NewResourcePermissionsAuthorizer(accessClient) + ctx := types.WithAuthInfo(context.Background(), user) + + err := resPermAuthz.AfterGet(ctx, fold1) + if tt.shouldAllow { + require.NoError(t, err, "expected no error for allowed access") + } else { + require.Error(t, err, "expected error for denied access") + } + require.True(t, accessClient.checkCalled, "accessClient.Check should be called") + }) + } +} + +func TestResourcePermissions_FilterList(t *testing.T) { + // In this test, the user has permission to access only fold-1 and dash-2. + // We verify that FilterList returns only those two objects. + + list := &iamv0.ResourcePermissionList{ + Items: []iamv0.ResourcePermission{ + *newResourcePermission("folder.grafana.app", "folders", "fold-1"), + *newResourcePermission("folder.grafana.app", "folders", "fold-2"), + *newResourcePermission("dashboard.grafana.app", "dashboards", "dash-2"), + }, + } + + compileFunc := func(id types.AuthInfo, req types.ListRequest) (types.ItemChecker, types.Zookie, error) { + require.NotNil(t, id) + // Compile is called with the user's identity + require.Equal(t, "user:u001", id.GetUID()) + require.Equal(t, "org-2", id.GetNamespace()) + // Check the request values + require.Equal(t, "org-2", req.Namespace) + if req.Resource == "folders" { + require.Equal(t, "folder.grafana.app", req.Group) + require.Equal(t, "folders", req.Resource) + } + if req.Resource == "dashboards" { + require.Equal(t, "dashboard.grafana.app", req.Group) + require.Equal(t, "dashboards", req.Resource) + } + + // Return a checker that allows only specific resources: fold-1 and dash-2 + return func(name, folder string) bool { + if name == "fold-1" || name == "dash-2" { + return true + } + return false + }, &types.NoopZookie{}, nil + } + + accessClient := &fakeAccessClient{compileFunc: compileFunc} + resPermAuthz := NewResourcePermissionsAuthorizer(accessClient) + ctx := types.WithAuthInfo(context.Background(), user) + + obj, err := resPermAuthz.FilterList(ctx, list) + require.NoError(t, err) + require.NotNil(t, list) + require.True(t, accessClient.compileCalled, "accessClient.Compile should be called") + + filtered, ok := obj.(*iamv0.ResourcePermissionList) + require.True(t, ok, "response should be of type ResourcePermissionList") + require.Len(t, filtered.Items, 2, "response list should have 2 items after filtering") + require.Equal(t, "fold-1", filtered.Items[0].Spec.Resource.Name) + require.Equal(t, "dash-2", filtered.Items[1].Spec.Resource.Name) +} + +func TestResourcePermissions_beforeWrite(t *testing.T) { + // In this test, we verify that beforeWrite calls accessClient.Check with the correct parameters + fold1 := newResourcePermission("folder.grafana.app", "folders", "fold-1") + + tests := []struct { + name string + shouldAllow bool + }{ + { + name: "allow delete", + shouldAllow: true, + }, + { + name: "deny delete", + shouldAllow: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + checkFunc := func(id types.AuthInfo, req *types.CheckRequest, folder string) (types.CheckResponse, error) { + require.NotNil(t, id) + // Check is called with the user's identity + require.Equal(t, "user:u001", id.GetUID()) + require.Equal(t, "org-2", id.GetNamespace()) + // Check the request values + require.Equal(t, "org-2", req.Namespace) + require.Equal(t, fold1.Spec.Resource.ApiGroup, req.Group) + require.Equal(t, fold1.Spec.Resource.Resource, req.Resource) + require.Equal(t, fold1.Spec.Resource.Name, req.Name) + require.Equal(t, utils.VerbSetPermissions, req.Verb) + + return types.CheckResponse{Allowed: tt.shouldAllow}, nil + } + + accessClient := &fakeAccessClient{checkFunc: checkFunc} + resPermAuthz := NewResourcePermissionsAuthorizer(accessClient) + ctx := types.WithAuthInfo(context.Background(), user) + + err := resPermAuthz.beforeWrite(ctx, fold1) + if tt.shouldAllow { + require.NoError(t, err, "expected no error for allowed delete") + } else { + require.Error(t, err, "expected error for denied delete") + } + require.True(t, accessClient.checkCalled, "accessClient.Check should be called") + }) + } +} + +// fakeAccessClient is a mock implementation of claims.AccessClient +type fakeAccessClient struct { + checkCalled bool + checkFunc func(id types.AuthInfo, req *types.CheckRequest, folder string) (types.CheckResponse, error) + compileCalled bool + compileFunc func(id types.AuthInfo, req types.ListRequest) (types.ItemChecker, types.Zookie, error) +} + +func (m *fakeAccessClient) Check(ctx context.Context, id types.AuthInfo, req types.CheckRequest, folder string) (types.CheckResponse, error) { + m.checkCalled = true + return m.checkFunc(id, &req, folder) +} + +func (m *fakeAccessClient) Compile(ctx context.Context, id types.AuthInfo, req types.ListRequest) (types.ItemChecker, types.Zookie, error) { + m.compileCalled = true + return m.compileFunc(id, req) +} + +var _ types.AccessClient = (*fakeAccessClient)(nil) diff --git a/pkg/registry/apis/iam/register.go b/pkg/registry/apis/iam/register.go index 2417c84aed8..741419b30b0 100644 --- a/pkg/registry/apis/iam/register.go +++ b/pkg/registry/apis/iam/register.go @@ -29,6 +29,7 @@ import ( grafanaregistry "github.com/grafana/grafana/pkg/apiserver/registry/generic" "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/log" + iamauthorizer "github.com/grafana/grafana/pkg/registry/apis/iam/authorizer" "github.com/grafana/grafana/pkg/registry/apis/iam/externalgroupmapping" "github.com/grafana/grafana/pkg/registry/apis/iam/legacy" "github.com/grafana/grafana/pkg/registry/apis/iam/resourcepermission" @@ -39,6 +40,7 @@ import ( "github.com/grafana/grafana/pkg/registry/apis/iam/user" "github.com/grafana/grafana/pkg/services/accesscontrol" gfauthorizer "github.com/grafana/grafana/pkg/services/apiserver/auth/authorizer" + "github.com/grafana/grafana/pkg/services/apiserver/auth/authorizer/storewrapper" "github.com/grafana/grafana/pkg/services/apiserver/builder" "github.com/grafana/grafana/pkg/services/authz/zanzana" "github.com/grafana/grafana/pkg/services/featuremgmt" @@ -402,7 +404,15 @@ func (b *IdentityAccessManagementAPIBuilder) UpdateResourcePermissionsAPIGroup( return err } - storage[iamv0.ResourcePermissionInfo.StoragePath()] = dw + // Not ideal, the alternative is to wrap both stores that dualwrite uses + regStoreDW, ok := dw.(*registry.Store) + if !ok { + return fmt.Errorf("expected RegistryStoreDualWrite, got %T", dw) + } + + authzWrapper := storewrapper.New(regStoreDW, iamauthorizer.NewResourcePermissionsAuthorizer(b.accessClient)) + + storage[iamv0.ResourcePermissionInfo.StoragePath()] = authzWrapper return nil } diff --git a/pkg/services/apiserver/auth/authorizer/storewrapper/wrapper.go b/pkg/services/apiserver/auth/authorizer/storewrapper/wrapper.go new file mode 100644 index 00000000000..bfc49e25607 --- /dev/null +++ b/pkg/services/apiserver/auth/authorizer/storewrapper/wrapper.go @@ -0,0 +1,191 @@ +package storewrapper + +import ( + "context" + "fmt" + + "github.com/grafana/grafana/pkg/apimachinery/identity" + "github.com/grafana/grafana/pkg/apiserver/rest" + "k8s.io/apimachinery/pkg/apis/meta/internalversion" + metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + k8srest "k8s.io/apiserver/pkg/registry/rest" +) + +var ( + ErrUnauthenticated = fmt.Errorf("unauthenticated") + ErrUnauthorized = fmt.Errorf("unauthorized") + ErrUnexpectedType = fmt.Errorf("unexpected object type") +) + +// ResourceStorageAuthorizer defines authorization hooks for resource storage operations. +type ResourceStorageAuthorizer interface { + BeforeCreate(ctx context.Context, obj runtime.Object) error + BeforeUpdate(ctx context.Context, obj runtime.Object) error + BeforeDelete(ctx context.Context, obj runtime.Object) error + AfterGet(ctx context.Context, obj runtime.Object) error + FilterList(ctx context.Context, list runtime.Object) (runtime.Object, error) +} + +// Wrapper is a k8sStorage (e.g. registry.Store) wrapper that enforces authorization based on ResourceStorageAuthorizer. +// It overrides the identity in the context to use service identity for the underlying store operations. +// That way, the underlying store authorization is always successful, and the authorization is enforced by the wrapper. +type Wrapper struct { + inner K8sStorage + authorizer ResourceStorageAuthorizer +} + +type K8sStorage interface { + k8srest.Storage + k8srest.Scoper + k8srest.SingularNameProvider + k8srest.Lister + k8srest.Getter + k8srest.CreaterUpdater + k8srest.GracefulDeleter +} + +var _ rest.Storage = (*Wrapper)(nil) + +func New(store K8sStorage, authz ResourceStorageAuthorizer) *Wrapper { + return &Wrapper{inner: store, authorizer: authz} +} + +func (w *Wrapper) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metaV1.Table, error) { + return w.inner.ConvertToTable(ctx, object, tableOptions) +} + +func (w *Wrapper) Create(ctx context.Context, obj runtime.Object, createValidation k8srest.ValidateObjectFunc, options *metaV1.CreateOptions) (runtime.Object, error) { + // Enforce authorization based on the user permissions before creating the object + err := w.authorizer.BeforeCreate(ctx, obj) + if err != nil { + return nil, err + } + // Override the identity to use service identity for the underlying store operation + srvCtx, _ := identity.WithServiceIdentity(ctx, 0) + + return w.inner.Create(srvCtx, obj, createValidation, options) +} + +func (w *Wrapper) Delete(ctx context.Context, name string, deleteValidation k8srest.ValidateObjectFunc, options *metaV1.DeleteOptions) (runtime.Object, bool, error) { + // Fetch the object first to authorize + srvCtx, _ := identity.WithServiceIdentity(ctx, 0) + getOpts := &metaV1.GetOptions{TypeMeta: options.TypeMeta} + if options.Preconditions != nil { + getOpts.ResourceVersion = *options.Preconditions.ResourceVersion + } + obj, err := w.inner.Get(srvCtx, name, getOpts) + if err != nil { + return nil, false, err + } + + // Enforce authorization based on the user permissions + if err := w.authorizer.BeforeDelete(ctx, obj); err != nil { + return nil, false, err + } + + return w.inner.Delete(srvCtx, name, deleteValidation, options) +} + +func (w *Wrapper) DeleteCollection(ctx context.Context, deleteValidation k8srest.ValidateObjectFunc, options *metaV1.DeleteOptions, listOptions *internalversion.ListOptions) (runtime.Object, error) { + // DeleteCollection is complex to authorize properly + // For now, deny it entirely for safety + return nil, fmt.Errorf("bulk delete operations are not supported through this API") +} + +func (w *Wrapper) Destroy() { + w.inner.Destroy() +} + +func (w *Wrapper) Get(ctx context.Context, name string, options *metaV1.GetOptions) (runtime.Object, error) { + // Override the identity to use service identity for the underlying store operation + srvCtx, _ := identity.WithServiceIdentity(ctx, 0) + + item, err := w.inner.Get(srvCtx, name, options) + if err != nil { + return nil, err + } + + // Enforce authorization based on the user permissions after retrieving the object + err = w.authorizer.AfterGet(ctx, item) + if err != nil { + return nil, err + } + return item, nil +} + +func (w *Wrapper) GetSingularName() string { + return w.inner.GetSingularName() +} + +func (w *Wrapper) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) { + // Override the identity to use service identity for the underlying store operation + srvCtx, _ := identity.WithServiceIdentity(ctx, 0) + + list, err := w.inner.List(srvCtx, options) + if err != nil { + return nil, err + } + + // Enforce authorization based on the user permissions after retrieving the list + return w.authorizer.FilterList(ctx, list) +} + +func (w *Wrapper) NamespaceScoped() bool { + return w.inner.NamespaceScoped() +} + +func (w *Wrapper) New() runtime.Object { + return w.inner.New() +} + +func (w *Wrapper) NewList() runtime.Object { + return w.inner.NewList() +} + +func (w *Wrapper) Update( + ctx context.Context, + name string, + objInfo k8srest.UpdatedObjectInfo, + createValidation k8srest.ValidateObjectFunc, + updateValidation k8srest.ValidateObjectUpdateFunc, + forceAllowCreate bool, + options *metaV1.UpdateOptions, +) (runtime.Object, bool, error) { + // Create a wrapper around UpdatedObjectInfo to inject authorization + wrappedObjInfo := &authorizedUpdateInfo{ + inner: objInfo, + authorizer: w.authorizer, + userCtx: ctx, // Keep original context for authorization + } + + // Override the identity to use service identity for the underlying store operation + srvCtx, _ := identity.WithServiceIdentity(ctx, 0) + + return w.inner.Update(srvCtx, name, wrappedObjInfo, createValidation, updateValidation, forceAllowCreate, options) +} + +type authorizedUpdateInfo struct { + inner k8srest.UpdatedObjectInfo + authorizer ResourceStorageAuthorizer + userCtx context.Context +} + +func (a *authorizedUpdateInfo) Preconditions() *metaV1.Preconditions { + return a.inner.Preconditions() +} + +func (a *authorizedUpdateInfo) UpdatedObject(ctx context.Context, oldObj runtime.Object) (runtime.Object, error) { + // Get the updated object + updatedObj, err := a.inner.UpdatedObject(ctx, oldObj) + if err != nil { + return nil, err + } + + // Enforce authorization using the original user context + if err := a.authorizer.BeforeUpdate(a.userCtx, updatedObj); err != nil { + return nil, err + } + + return updatedObj, nil +} diff --git a/pkg/services/apiserver/auth/authorizer/storewrapper/wrapper_test.go b/pkg/services/apiserver/auth/authorizer/storewrapper/wrapper_test.go new file mode 100644 index 00000000000..d1dd3bb5e4e --- /dev/null +++ b/pkg/services/apiserver/auth/authorizer/storewrapper/wrapper_test.go @@ -0,0 +1,399 @@ +package storewrapper + +import ( + "context" + "testing" + + "github.com/grafana/authlib/types" + "github.com/grafana/grafana/pkg/apimachinery/identity" + "github.com/grafana/grafana/pkg/apiserver/rest" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + "k8s.io/apimachinery/pkg/apis/meta/internalversion" + metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +type testSetup struct { + mockStore *rest.MockStorage + mockAuth *FakeAuthorizer + wrapper *Wrapper + ctx context.Context +} + +func newTestSetup(t *testing.T) *testSetup { + mockStore := rest.NewMockStorage(t) + mockAuth := &FakeAuthorizer{} + wrapper := New(mockStore, mockAuth) + + ctx := identity.WithRequester( + context.Background(), + &identity.StaticRequester{UserUID: "u001", Type: types.TypeUser}, + ) + + return &testSetup{mockStore: mockStore, mockAuth: mockAuth, wrapper: wrapper, ctx: ctx} +} + +func matchesOriginalUser() func(context.Context) bool { + return func(ctx context.Context) bool { + user, err := identity.GetRequester(ctx) + return err == nil && user.GetUID() == "user:u001" + } +} + +func matchesServiceIdentity() func(context.Context) bool { + return func(ctx context.Context) bool { + return identity.IsServiceIdentity(ctx) + } +} + +func TestWrapper_Create(t *testing.T) { + t.Run("success", func(t *testing.T) { + setup := newTestSetup(t) + + obj := &fakeObject{} + createOpts := &metaV1.CreateOptions{} + expectedObj := &fakeObject{ObjectMeta: metaV1.ObjectMeta{Name: "created"}} + + // Verify original user identity is used for authorization + setup.mockAuth.On("BeforeCreate", mock.MatchedBy(matchesOriginalUser()), obj).Return(nil) + + // Verify service identity is used to call the underlying store + setup.mockStore.On("Create", mock.MatchedBy(matchesServiceIdentity()), obj, mock.Anything, createOpts).Return(expectedObj, nil) + + result, err := setup.wrapper.Create(setup.ctx, obj, nil, createOpts) + + require.NoError(t, err) + assert.Equal(t, expectedObj, result) + + // Assert expectations + setup.mockAuth.AssertExpectations(t) + setup.mockStore.AssertExpectations(t) + }) + t.Run("unauthorized", func(t *testing.T) { + setup := newTestSetup(t) + + obj := &fakeObject{} + createOpts := &metaV1.CreateOptions{} + + // Simulate unauthorized error from authorizer + setup.mockAuth.On("BeforeCreate", mock.MatchedBy(matchesOriginalUser()), obj).Return(ErrUnauthorized) + + result, err := setup.wrapper.Create(setup.ctx, obj, nil, createOpts) + + require.Error(t, err) + assert.Nil(t, result) + assert.Equal(t, ErrUnauthorized, err) + + // Assert expectations + setup.mockAuth.AssertExpectations(t) + setup.mockStore.AssertNotCalled(t, "Create") + }) +} + +func TestWrapper_Delete(t *testing.T) { + t.Run("success", func(t *testing.T) { + setup := newTestSetup(t) + version := "1" + obj := &fakeObject{ObjectMeta: metaV1.ObjectMeta{Name: "to-delete"}} + deleteOpts := &metaV1.DeleteOptions{Preconditions: &metaV1.Preconditions{ResourceVersion: &version}} + expectedObj := &fakeObject{ObjectMeta: metaV1.ObjectMeta{Name: "deleted"}} + + // Mock Get to fetch the object before deletion + setup.mockStore.On("Get", mock.MatchedBy(matchesServiceIdentity()), "to-delete", mock.Anything).Return(obj, nil) + + // Verify original user identity is used for authorization + setup.mockAuth.On("BeforeDelete", mock.MatchedBy(matchesOriginalUser()), obj).Return(nil) + + // Verify service identity is used to call the underlying store + setup.mockStore.On("Delete", mock.MatchedBy(matchesServiceIdentity()), "to-delete", mock.Anything, deleteOpts).Return(expectedObj, true, nil) + + result, deleted, err := setup.wrapper.Delete(setup.ctx, "to-delete", nil, deleteOpts) + + require.NoError(t, err) + assert.Equal(t, expectedObj, result) + assert.True(t, deleted) + + // Assert expectations + setup.mockAuth.AssertExpectations(t) + setup.mockStore.AssertExpectations(t) + }) + t.Run("unauthorized", func(t *testing.T) { + setup := newTestSetup(t) + version := "1" + obj := &fakeObject{ObjectMeta: metaV1.ObjectMeta{Name: "to-delete"}} + deleteOpts := &metaV1.DeleteOptions{Preconditions: &metaV1.Preconditions{ResourceVersion: &version}} + + // Mock Get to fetch the object before deletion + setup.mockStore.On("Get", mock.MatchedBy(matchesServiceIdentity()), "to-delete", mock.Anything).Return(obj, nil) + + // Simulate unauthorized error from authorizer + setup.mockAuth.On("BeforeDelete", mock.MatchedBy(matchesOriginalUser()), obj).Return(ErrUnauthorized) + + result, deleted, err := setup.wrapper.Delete(setup.ctx, "to-delete", nil, deleteOpts) + + require.Error(t, err) + assert.Nil(t, result) + assert.False(t, deleted) + assert.Equal(t, ErrUnauthorized, err) + + // Assert expectations + setup.mockAuth.AssertExpectations(t) + setup.mockStore.AssertExpectations(t) + setup.mockStore.AssertNotCalled(t, "Delete") + }) +} + +func TestWrapper_Get(t *testing.T) { + t.Run("success", func(t *testing.T) { + setup := newTestSetup(t) + + obj := &fakeObject{ObjectMeta: metaV1.ObjectMeta{Name: "fetched"}} + + // Verify service identity is used to call the underlying store + setup.mockStore.On("Get", mock.MatchedBy(matchesServiceIdentity()), "fetched", mock.Anything).Return(obj, nil) + + // Verify original user identity is used for after-get authorization + setup.mockAuth.On("AfterGet", mock.MatchedBy(matchesOriginalUser()), obj).Return(nil) + + result, err := setup.wrapper.Get(setup.ctx, "fetched", &metaV1.GetOptions{}) + + require.NoError(t, err) + assert.Equal(t, obj, result) + + // Assert expectations + setup.mockAuth.AssertExpectations(t) + setup.mockStore.AssertExpectations(t) + }) + t.Run("unauthorized", func(t *testing.T) { + setup := newTestSetup(t) + + obj := &fakeObject{ObjectMeta: metaV1.ObjectMeta{Name: "fetched"}} + + // Verify service identity is used to call the underlying store + setup.mockStore.On("Get", mock.MatchedBy(matchesServiceIdentity()), "fetched", mock.Anything).Return(obj, nil) + + // Simulate unauthorized error from after-get authorizer + setup.mockAuth.On("AfterGet", mock.MatchedBy(matchesOriginalUser()), obj).Return(ErrUnauthorized) + + result, err := setup.wrapper.Get(setup.ctx, "fetched", &metaV1.GetOptions{}) + + require.Error(t, err) + assert.Nil(t, result) + assert.Equal(t, ErrUnauthorized, err) + + // Assert expectations + setup.mockAuth.AssertExpectations(t) + setup.mockStore.AssertExpectations(t) + }) +} + +func TestWrapper_List(t *testing.T) { + t.Run("success", func(t *testing.T) { + setup := newTestSetup(t) + + listObj := &metaV1.List{Items: []runtime.RawExtension{ + {Object: &fakeObject{ObjectMeta: metaV1.ObjectMeta{Name: "item1"}}}, + {Object: &fakeObject{ObjectMeta: metaV1.ObjectMeta{Name: "item2"}}}, + }} + + filteredListObj := &metaV1.List{Items: []runtime.RawExtension{ + {Object: &fakeObject{ObjectMeta: metaV1.ObjectMeta{Name: "item1"}}}, + }} + + // Verify service identity is used to call the underlying store + setup.mockStore.On("List", mock.MatchedBy(matchesServiceIdentity()), mock.Anything).Return(listObj, nil) + + // Verify original user identity is used for filtering the list + setup.mockAuth.On("FilterList", mock.MatchedBy(matchesOriginalUser()), listObj).Return(filteredListObj, nil) + + result, err := setup.wrapper.List(setup.ctx, &internalversion.ListOptions{}) + + require.NoError(t, err) + assert.Equal(t, filteredListObj, result) + + // Assert expectations + setup.mockAuth.AssertExpectations(t) + setup.mockStore.AssertExpectations(t) + }) + t.Run("unauthorized", func(t *testing.T) { + setup := newTestSetup(t) + + listObj := &metaV1.List{Items: []runtime.RawExtension{ + {Object: &fakeObject{ObjectMeta: metaV1.ObjectMeta{Name: "item1"}}}, + {Object: &fakeObject{ObjectMeta: metaV1.ObjectMeta{Name: "item2"}}}, + }} + + // Verify service identity is used to call the underlying store + setup.mockStore.On("List", mock.MatchedBy(matchesServiceIdentity()), mock.Anything).Return(listObj, nil) + + // Simulate unauthorized error from FilterList authorizer + setup.mockAuth.On("FilterList", mock.MatchedBy(matchesOriginalUser()), listObj).Return(nil, ErrUnauthorized) + + result, err := setup.wrapper.List(setup.ctx, &internalversion.ListOptions{}) + + require.Error(t, err) + assert.Nil(t, result) + assert.Equal(t, ErrUnauthorized, err) + + // Assert expectations + setup.mockAuth.AssertExpectations(t) + setup.mockStore.AssertExpectations(t) + }) +} + +func TestWrapper_Update(t *testing.T) { + setup := newTestSetup(t) + + oldObj := &fakeObject{ObjectMeta: metaV1.ObjectMeta{ + Name: "to-update", ResourceVersion: "2", Labels: map[string]string{"updated": "false"}, + }} + objInfo := &fakeUpdatedObjectInfo{obj: oldObj} + updateOpts := &metaV1.UpdateOptions{} + + var authzInfo *authorizedUpdateInfo + + // Verify service identity is used to call the underlying store + setup.mockStore.On("Update", + mock.MatchedBy(matchesServiceIdentity()), + "to-update", + mock.MatchedBy(func(info *authorizedUpdateInfo) bool { + // Capture the authorizedUpdateInfo for later verification + authzInfo = info + return true + }), + mock.Anything, + mock.Anything, + false, + updateOpts).Return(oldObj, true, nil) + + result, updated, err := setup.wrapper.Update(setup.ctx, "to-update", objInfo, nil, nil, false, updateOpts) + require.NoError(t, err) + assert.Equal(t, oldObj, result) + assert.True(t, updated) + + // Now verify that the authorization is performed inside UpdatedObject + setup.mockAuth.On("BeforeUpdate", mock.MatchedBy(matchesOriginalUser()), oldObj).Return(nil) + obj, err := authzInfo.UpdatedObject(context.Background(), oldObj) + require.NoError(t, err) + assert.Equal(t, oldObj, obj) + + // Assert expectations + setup.mockAuth.AssertExpectations(t) + setup.mockStore.AssertExpectations(t) +} + +func TestWrapper_DeleteCollection(t *testing.T) { + setup := newTestSetup(t) + + result, err := setup.wrapper.DeleteCollection(setup.ctx, nil, &metaV1.DeleteOptions{}, &internalversion.ListOptions{}) + + require.Error(t, err) + assert.Contains(t, err.Error(), "bulk delete operations are not supported") + assert.Nil(t, result) +} + +func TestWrapper_PassthroughMethods(t *testing.T) { + setup := newTestSetup(t) + + t.Run("New", func(t *testing.T) { + obj := &fakeObject{} + setup.mockStore.On("New").Return(obj).Once() + assert.Equal(t, obj, setup.wrapper.New()) + }) + + t.Run("NewList", func(t *testing.T) { + obj := &fakeObject{} + setup.mockStore.On("NewList").Return(obj).Once() + assert.Equal(t, obj, setup.wrapper.NewList()) + }) + + t.Run("GetSingularName", func(t *testing.T) { + setup.mockStore.On("GetSingularName").Return("fake").Once() + assert.Equal(t, "fake", setup.wrapper.GetSingularName()) + }) + + t.Run("NamespaceScoped", func(t *testing.T) { + setup.mockStore.On("NamespaceScoped").Return(true).Once() + assert.True(t, setup.wrapper.NamespaceScoped()) + }) + + t.Run("Destroy", func(t *testing.T) { + setup.mockStore.On("Destroy").Once() + setup.wrapper.Destroy() + }) + + t.Run("ConvertToTable", func(t *testing.T) { + obj := &fakeObject{} + table := &metaV1.Table{} + setup.mockStore.On("ConvertToTable", setup.ctx, obj, mock.Anything).Return(table, nil).Once() + result, err := setup.wrapper.ConvertToTable(setup.ctx, obj, nil) + require.NoError(t, err) + assert.Equal(t, table, result) + }) + + setup.mockStore.AssertExpectations(t) +} + +// ----- +// Fakes +// ----- + +type FakeAuthorizer struct { + mock.Mock +} + +func (f *FakeAuthorizer) BeforeCreate(ctx context.Context, obj runtime.Object) error { + args := f.Called(ctx, obj) + return args.Error(0) +} + +func (f *FakeAuthorizer) BeforeUpdate(ctx context.Context, obj runtime.Object) error { + args := f.Called(ctx, obj) + return args.Error(0) +} + +func (f *FakeAuthorizer) BeforeDelete(ctx context.Context, obj runtime.Object) error { + args := f.Called(ctx, obj) + return args.Error(0) +} + +func (f *FakeAuthorizer) AfterGet(ctx context.Context, obj runtime.Object) error { + args := f.Called(ctx, obj) + return args.Error(0) +} + +func (f *FakeAuthorizer) FilterList(ctx context.Context, list runtime.Object) (runtime.Object, error) { + args := f.Called(ctx, list) + var res runtime.Object + if args.Get(0) != nil { + res = args.Get(0).(runtime.Object) + } + return res, args.Error(1) +} + +type fakeObject struct { + metaV1.TypeMeta + metaV1.ObjectMeta +} + +func (f *fakeObject) DeepCopyObject() runtime.Object { + return &fakeObject{ + TypeMeta: f.TypeMeta, + ObjectMeta: f.ObjectMeta, + } +} + +// fakeUpdatedObjectInfo implements k8srest.UpdatedObjectInfo for testing +type fakeUpdatedObjectInfo struct { + obj runtime.Object +} + +func (f *fakeUpdatedObjectInfo) Preconditions() *metaV1.Preconditions { + return nil +} + +func (f *fakeUpdatedObjectInfo) UpdatedObject(ctx context.Context, oldObj runtime.Object) (runtime.Object, error) { + return f.obj, nil +} diff --git a/pkg/services/authz/rbac/service.go b/pkg/services/authz/rbac/service.go index 5ef2e0c5ad6..4c3f3ba0393 100644 --- a/pkg/services/authz/rbac/service.go +++ b/pkg/services/authz/rbac/service.go @@ -782,7 +782,7 @@ func (s *Service) listPermission(ctx context.Context, scopeMap map[string]bool, } var res *authzv1.ListResponse - if strings.HasPrefix(req.Action, "folders:") { + if strings.HasPrefix(req.Action, "folders:") || strings.HasPrefix(req.Action, "folders.permissions:") { res = buildFolderList(scopeMap, tree) } else { res = buildItemList(scopeMap, tree, t.Prefix()) From 29cf10f1fbceb6b93af6968ca220a2d676b65d67 Mon Sep 17 00:00:00 2001 From: Liza Detrick <114438185+L2D2Grafana@users.noreply.github.com> Date: Wed, 3 Dec 2025 09:26:41 -0800 Subject: [PATCH 005/110] Logs: table add action buttons and deeplink to log line (#114330) --- .../app/features/explore/Logs/Logs.test.tsx | 1 + public/app/features/explore/Logs/Logs.tsx | 9 +- .../features/explore/Logs/LogsTable.test.tsx | 78 ++++++- .../app/features/explore/Logs/LogsTable.tsx | 182 ++++++++++++++++- .../explore/Logs/LogsTableActionButtons.tsx | 190 ++++++++++++++++++ .../features/explore/Logs/LogsTableWrap.tsx | 10 + public/app/features/explore/Logs/utils/url.ts | 10 + .../logs/components/ControlledLogRows.tsx | 5 + .../logs/components/ControlledLogsTable.tsx | 8 + public/locales/en-US/grafana.json | 8 + 10 files changed, 488 insertions(+), 13 deletions(-) create mode 100644 public/app/features/explore/Logs/LogsTableActionButtons.tsx create mode 100644 public/app/features/explore/Logs/utils/url.ts diff --git a/public/app/features/explore/Logs/Logs.test.tsx b/public/app/features/explore/Logs/Logs.test.tsx index 93836280762..d00412560ee 100644 --- a/public/app/features/explore/Logs/Logs.test.tsx +++ b/public/app/features/explore/Logs/Logs.test.tsx @@ -75,6 +75,7 @@ describe('Logs', () => { Object.defineProperty(window, 'location', { value: { href: 'http://localhost:3000/explore?test', + search: '?test', }, writable: true, }); diff --git a/public/app/features/explore/Logs/Logs.tsx b/public/app/features/explore/Logs/Logs.tsx index ac07e2cacd5..84470d73a61 100644 --- a/public/app/features/explore/Logs/Logs.tsx +++ b/public/app/features/explore/Logs/Logs.tsx @@ -83,6 +83,7 @@ import LogsNavigation from './LogsNavigation'; import { LogsTableWrap, getLogsTableHeight } from './LogsTableWrap'; import { LogsVolumePanelList } from './LogsVolumePanelList'; import { SETTING_KEY_ROOT, SETTINGS_KEYS, visualisationTypeKey } from './utils/logs'; +import { getExploreBaseUrl } from './utils/url'; interface Props extends Themeable2 { width: number; @@ -617,7 +618,7 @@ const UnthemedLogs: React.FunctionComponent = (props: Props) => { // append changed urlState to baseUrl const serializedState = serializeStateToUrlParam(urlState); - const baseUrl = /.*(?=\/explore)/.exec(`${window.location.href}`)![0]; + const baseUrl = getExploreBaseUrl(); const url = urlUtil.renderUrl(`${baseUrl}/explore`, { left: serializedState }); await createAndCopyShortLink(url); @@ -1002,6 +1003,10 @@ const UnthemedLogs: React.FunctionComponent = (props: Props) => { panelState={panelState?.logs} updatePanelState={updatePanelState} datasourceType={props.datasourceType} + displayedFields={displayedFields} + exploreId={props.exploreId} + absoluteRange={props.absoluteRange} + logRows={props.logRows} /> )} @@ -1056,6 +1061,8 @@ const UnthemedLogs: React.FunctionComponent = (props: Props) => { onLogOptionsChange={onLogOptionsChange} filterLevels={filterLevels} timeRange={props.range} + exploreId={props.exploreId} + absoluteRange={props.absoluteRange} /> )} diff --git a/public/app/features/explore/Logs/LogsTable.test.tsx b/public/app/features/explore/Logs/LogsTable.test.tsx index ea203456008..cb38e9062de 100644 --- a/public/app/features/explore/Logs/LogsTable.test.tsx +++ b/public/app/features/explore/Logs/LogsTable.test.tsx @@ -1,7 +1,7 @@ import { render, screen, waitFor, fireEvent } from '@testing-library/react'; import { ComponentProps } from 'react'; -import { DataFrame, FieldType, LogsSortOrder, toUtc } from '@grafana/data'; +import { DataFrame, FieldType, LogsSortOrder, toUtc, urlUtil } from '@grafana/data'; import { mockTransformationsRegistry, organizeFieldsTransformer } from '@grafana/data/internal'; import { config } from '@grafana/runtime'; import { extractFieldsTransformer } from 'app/features/transformers/extractFields/extractFields'; @@ -362,4 +362,80 @@ describe('LogsTable', () => { } }); }); + + describe('Selected log line', () => { + it('should handle selected log line from URL parameter', async () => { + // Use getMockLokiFrame which has proper structure with id field + const testFrame = getMockLokiFrame(); + const logsFrame = parseLogsFrame(testFrame); + + // Get the second ID from the parsed frame to test selection of non-first row + const secondId = logsFrame?.idField?.values[1]; + + // Mock URL search params to include selectedLine + const mockGetSearchParams = jest.spyOn(urlUtil, 'getUrlSearchParams'); + mockGetSearchParams.mockReturnValue({ + selectedLine: JSON.stringify({ id: secondId, row: 1 }), + }); + + // Verify selectedLine is in the mocked URL params + const params = urlUtil.getUrlSearchParams(); + expect(params.selectedLine).toBeDefined(); + expect(params.selectedLine).toContain(secondId); + }); + + it('should clear selectedLine URL parameter after render', async () => { + // Mock locationService.partial instead of window.history.replaceState + const partialSpy = jest.spyOn(require('@grafana/runtime').locationService, 'partial'); + + // Use getMockLokiFrame which has proper structure + const testFrame = getMockLokiFrame(); + const logsFrame = parseLogsFrame(testFrame); + + // Get the first ID from the parsed frame + const firstId = logsFrame?.idField?.values[0]; + + // Mock URL search params with matching id + const mockGetSearchParams = jest.spyOn(urlUtil, 'getUrlSearchParams'); + mockGetSearchParams.mockReturnValue({ + selectedLine: JSON.stringify({ id: firstId, row: 0 }), + }); + + setup({ logsFrame }, testFrame); + + await waitFor(() => { + expect(partialSpy).toHaveBeenCalled(); + // Verify that selectedLine is set to undefined + const callArgs = partialSpy.mock.calls[0]; + expect(callArgs[0]).toEqual({ selectedLine: undefined }); + expect(callArgs[1]).toBe(true); // replace parameter + }); + }); + }); + + describe('Table action buttons', () => { + it('should render action buttons in first column when exploreId is provided', async () => { + setup({ + exploreId: 'test-explore', + }); + + await waitFor(() => { + const rows = screen.getAllByRole('row'); + expect(rows.length).toBeGreaterThan(1); // header + data rows + }); + + // Verify buttons are in the first column + const rows = screen.getAllByRole('row'); + const dataRows = rows.filter((row) => row.getAttribute('role') === 'row' && !row.getAttribute('aria-label')); + + dataRows.forEach((row) => { + const cells = row.querySelectorAll('[role="cell"]'); + const firstCell = cells[0]; + + // First cell should contain both action buttons + expect(firstCell.querySelector('button[aria-label="View log line"]')).toBeTruthy(); + expect(firstCell.querySelector('button[aria-label="Copy link to log line"]')).toBeTruthy(); + }); + }); + }); }); diff --git a/public/app/features/explore/Logs/LogsTable.tsx b/public/app/features/explore/Logs/LogsTable.tsx index adc17844bb3..96165ca03be 100644 --- a/public/app/features/explore/Logs/LogsTable.tsx +++ b/public/app/features/explore/Logs/LogsTable.tsx @@ -1,7 +1,9 @@ -import { useCallback, useEffect, useState } from 'react'; +import { css } from '@emotion/css'; +import { useCallback, useEffect, useState, useMemo } from 'react'; import { lastValueFrom } from 'rxjs'; import { + urlUtil, applyFieldOverrides, CustomTransformOperator, DataFrame, @@ -16,14 +18,26 @@ import { TimeRange, transformDataFrame, ValueLinkConfig, + ExploreLogsPanelState, + AbsoluteTimeRange, + LogRowModel, + GrafanaTheme2, } from '@grafana/data'; -import { config } from '@grafana/runtime'; -import { AdHocFilterItem, Table, TableSortByFieldState } from '@grafana/ui'; +import { config, locationService } from '@grafana/runtime'; +import { + AdHocFilterItem, + CustomCellRendererProps, + TableSortByFieldState, + Table, + TableCellDisplayMode, + useStyles2, +} from '@grafana/ui'; import { FILTER_FOR_OPERATOR, FILTER_OUT_OPERATOR } from '@grafana/ui/internal'; import { LogsFrame } from 'app/features/logs/logsFrame'; import { getFieldLinksForExplore } from '../utils/links'; +import { LogsTableActionButtons } from './LogsTableActionButtons'; import { FieldNameMeta } from './LogsTableWrap'; interface Props { @@ -41,6 +55,11 @@ interface Props { tableSortBy?: string; tableSortDir?: 'asc' | 'desc'; onSortByChange?: (sortBy: TableSortByFieldState[]) => void; + displayedFields?: string[]; + exploreId?: string; + panelState?: ExploreLogsPanelState; + absoluteRange?: AbsoluteTimeRange; + logRows?: LogRowModel[]; } export function LogsTable(props: Props) { @@ -58,7 +77,61 @@ export function LogsTable(props: Props) { onSortByChange, } = props; const [tableFrame, setTableFrame] = useState(undefined); + const [columnWidthMap, setColumnWidthMap] = useState>({}); const timeIndex = logsFrame?.timeField.index; + const styles = useStyles2(getStyles); + + // Extract selected log ID from URL parameter + const selectedLogInfo = useMemo(() => { + const { selectedLine } = urlUtil.getUrlSearchParams(); + + const param = Array.isArray(selectedLine) ? selectedLine[0] : selectedLine; + + if (typeof param !== 'string') { + return undefined; + } + + try { + const { id, row } = JSON.parse(param); + return { id, row }; + } catch (error) { + return undefined; + } + }, []); + + // Set the initial row index based on the selected log ID if selectedLine is present in the URL + const initialRowIndex = useMemo(() => { + if (!selectedLogInfo || !tableFrame || !selectedLogInfo.id) { + return undefined; + } + + // Search through all fields in tableFrame to find the one containing the ID + for (const field of tableFrame.fields) { + const lineIndex = field.values.findIndex((v: unknown) => v === selectedLogInfo.id); + if (lineIndex !== -1) { + return lineIndex; + } + } + + return undefined; + }, [selectedLogInfo, tableFrame]); + + // Clear the selectedLine URL parameter after table loads + useEffect(() => { + if (initialRowIndex !== undefined && tableFrame) { + // Remove selectedLine from URL using locationService (proper Grafana way) + locationService.partial({ selectedLine: undefined }, true); + } + }, [initialRowIndex, tableFrame]); + + const onColumnResize = useCallback((fieldDisplayName: string, width: number) => { + if (width > 0) { + setColumnWidthMap((prev) => ({ + ...prev, + [fieldDisplayName]: width, + })); + } + }, []); const prepareTableFrame = useCallback( (frame: DataFrame): DataFrame => { @@ -81,7 +154,21 @@ export function LogsTable(props: Props) { }, }); // `getLinks` and `applyFieldOverrides` are taken from TableContainer.tsx - for (const field of frameWithOverrides.fields) { + for (const [index, field] of frameWithOverrides.fields.entries()) { + // Hide ID field from visualization (it's only needed for row matching) + if (logsFrame?.idField && (field.name === logsFrame.idField.name || field.name === 'id')) { + field.config = { + ...field.config, + custom: { + ...field.config.custom, + hideFrom: { + ...field.config.custom?.hideFrom, + viz: true, + }, + }, + }; + } + field.getLinks = (config: ValueLinkConfig) => { return getFieldLinksForExplore({ field, @@ -91,13 +178,44 @@ export function LogsTable(props: Props) { dataFrame: sortedFrame!, }); }; + + // For the first field (time), wrap the cell to include action buttons + const isFirstField = index === 0; + field.config = { ...field.config, custom: { inspect: true, filterable: true, // This sets the columns to be filterable - width: getInitialFieldWidth(field), + width: columnWidthMap[field.name] ?? getInitialFieldWidth(field), ...field.config.custom, + cellOptions: isFirstField + ? { + type: TableCellDisplayMode.Custom, + cellComponent: (cellProps: CustomCellRendererProps) => ( + <> + + + {cellProps.field.display?.(cellProps.value).text ?? String(cellProps.value)} + + + ), + } + : field.config.custom?.cellOptions, + headerComponent: isFirstField + ? (headerProps: { defaultContent: React.ReactNode }) => ( +
{headerProps.defaultContent}
+ ) + : field.config.custom?.headerComponent, }, // This sets the individual field value as filterable filterable: isFieldFilterable(field, logsFrame?.bodyField.name ?? '', logsFrame?.timeField.name ?? ''), @@ -109,7 +227,22 @@ export function LogsTable(props: Props) { return frameWithOverrides; }, - [logsSortOrder, timeZone, splitOpen, range, logsFrame?.bodyField.name, logsFrame?.timeField.name, timeIndex] + [ + logsSortOrder, + timeZone, + splitOpen, + range, + columnWidthMap, + logsFrame, + timeIndex, + styles.firstColumnCell, + styles.firstColumnHeader, + props.displayedFields, + props.exploreId, + props.panelState, + props.absoluteRange, + props.logRows, + ] ); useEffect(() => { @@ -127,9 +260,24 @@ export function LogsTable(props: Props) { // Add the label filters to the transformations const transform = getLabelFiltersTransform(labelFilters); if (transform) { + // Ensure ID field is always included for row matching + if (logsFrame?.idField?.name) { + transform.options.includeByName = { + ...transform.options.includeByName, + [logsFrame.idField.name]: true, + }; + } transformations.push(transform); } else { // If no fields are filtered, filter the default fields, so we don't render all columns + // Always include ID field for row matching + const includeByName: Record = { + [logsFrame.bodyField.name]: true, + [logsFrame.timeField.name]: true, + }; + if (logsFrame?.idField?.name) { + includeByName[logsFrame.idField.name] = true; + } transformations.push({ id: 'organize', options: { @@ -137,10 +285,7 @@ export function LogsTable(props: Props) { [logsFrame.bodyField.name]: 0, [logsFrame.timeField.name]: 1, }, - includeByName: { - [logsFrame.bodyField.name]: true, - [logsFrame.timeField.name]: true, - }, + includeByName, }, }); } @@ -161,6 +306,7 @@ export function LogsTable(props: Props) { prepareTableFrame, logsFrame?.bodyField.name, logsFrame?.timeField.name, + logsFrame?.idField?.name, ]); if (!tableFrame) { @@ -193,11 +339,13 @@ export function LogsTable(props: Props) {
); } @@ -284,7 +432,19 @@ function getLabelFiltersTransform(labelFilters: Record) { function getInitialFieldWidth(field: Field): number | undefined { if (field.type === FieldType.time) { - return 200; + return 230; } return undefined; } + +const getStyles = (theme: GrafanaTheme2) => ({ + firstColumnHeader: css({ + display: 'flex', + label: 'wrapper', + marginLeft: theme.spacing(7), + width: '100%', + }), + firstColumnCell: css({ + paddingLeft: theme.spacing(7), + }), +}); diff --git a/public/app/features/explore/Logs/LogsTableActionButtons.tsx b/public/app/features/explore/Logs/LogsTableActionButtons.tsx new file mode 100644 index 00000000000..88e3c1db4ed --- /dev/null +++ b/public/app/features/explore/Logs/LogsTableActionButtons.tsx @@ -0,0 +1,190 @@ +import { css } from '@emotion/css'; +import { useCallback, useState } from 'react'; + +import { + AbsoluteTimeRange, + ExploreLogsPanelState, + GrafanaTheme2, + LogRowModel, + serializeStateToUrlParam, + urlUtil, +} from '@grafana/data'; +import { t } from '@grafana/i18n'; +import { ClipboardButton, CustomCellRendererProps, IconButton, Modal, useTheme2 } from '@grafana/ui'; +import { getLogsPermalinkRange } from 'app/core/utils/shortLinks'; +import { getUrlStateFromPaneState } from 'app/features/explore/hooks/useStateSync'; +import { LogsFrame } from 'app/features/logs/logsFrame'; +import { getState } from 'app/store/store'; + +import { getExploreBaseUrl } from './utils/url'; +interface Props extends CustomCellRendererProps { + logId?: string; + logsFrame?: LogsFrame; + exploreId?: string; + panelState?: ExploreLogsPanelState; + displayedFields?: string[]; + absoluteRange?: AbsoluteTimeRange; + logRows?: LogRowModel[]; + index?: number; +} + +export function LogsTableActionButtons(props: Props) { + const { exploreId, absoluteRange, logRows, rowIndex, panelState, displayedFields, logsFrame, frame } = props; + + const theme = useTheme2(); + const [isInspecting, setIsInspecting] = useState(false); + // Get logId from the table frame (frame), not the original logsFrame, because + // the table frame is sorted/transformed and rowIndex refers to the table frame + const idFieldName = logsFrame?.idField?.name ?? 'id'; + const idField = frame.fields.find((field) => field.name === idFieldName || field.name === 'id'); + const logId = idField?.values[rowIndex]; + const getLineValue = () => { + const bodyFieldName = logsFrame?.bodyField?.name; + const bodyField = bodyFieldName + ? frame.fields.find((field) => field.name === bodyFieldName) + : frame.fields.find((field) => field.type === 'string'); + return bodyField?.values[rowIndex]; + }; + + const lineValue = getLineValue(); + + const styles = getStyles(theme); + + // Generate link to the log line + const getText = useCallback(() => { + if (!logId || !exploreId || !absoluteRange || !logRows) { + return ''; + } + + try { + // Get the log row from the logRows array + const logRow = logRows.find((row) => row.rowId === logId); + + if (!logRow) { + return ''; + } + + // Get the current explore state + const currentPaneState = getState().explore.panes[exploreId]; + if (!currentPaneState) { + return ''; + } + + // Create URL state with log permalink information + const urlState = getUrlStateFromPaneState(currentPaneState); + + // Preserve all panel state (columns, labelFieldName, etc.) + urlState.panelsState = { + ...currentPaneState.panelsState, + logs: { + ...panelState, + displayedFields: displayedFields ?? [], + }, + }; + + // Calculate the time range for the permalink + urlState.range = getLogsPermalinkRange(logRow, logRows, absoluteRange); + + // Create the full URL with selectedLine as a URL parameter (with id and row) + const serializedState = serializeStateToUrlParam(urlState); + const baseUrl = getExploreBaseUrl(); + const url = urlUtil.renderUrl(`${baseUrl}/explore`, { + left: serializedState, + selectedLine: JSON.stringify({ id: logId, row: rowIndex }), + }); + return url; + } catch (error) { + return ''; + } + }, [absoluteRange, displayedFields, exploreId, logId, logRows, rowIndex, panelState]); + + const handleViewClick = () => { + setIsInspecting(true); + }; + + return ( + <> +
+
+ +
+
+ +
+
+ {isInspecting && ( + setIsInspecting(false)} + isOpen={true} + title={t('explore.logs-table.action-buttons.inspect-value', 'Inspect value')} + > +
{lineValue}
+ + lineValue}> + {t('explore.logs-table.action-buttons.copy-to-clipboard', 'Copy to Clipboard')} + + +
+ )} + + ); +} + +export const getStyles = (theme: GrafanaTheme2) => ({ + clipboardButton: css({ + height: '100%', + lineHeight: '1', + padding: 0, + width: '20px', + }), + iconWrapper: css({ + background: theme.colors.background.secondary, + boxShadow: theme.shadows.z2, + display: 'flex', + flexDirection: 'row', + height: '35px', + left: 0, + top: 0, + padding: `0 ${theme.spacing(0.5)}`, + position: 'absolute', + zIndex: 1, + }), + inspect: css({ + '& button svg': { + marginRight: 'auto', + }, + '&:hover': { + color: theme.colors.text.link, + cursor: 'pointer', + }, + padding: '5px 3px', + }), + inspectButton: css({ + borderRadius: theme.shape.radius.default, + display: 'inline-flex', + margin: 0, + overflow: 'hidden', + verticalAlign: 'middle', + }), +}); diff --git a/public/app/features/explore/Logs/LogsTableWrap.tsx b/public/app/features/explore/Logs/LogsTableWrap.tsx index 0148c1a06df..ff8cceaadcf 100644 --- a/public/app/features/explore/Logs/LogsTableWrap.tsx +++ b/public/app/features/explore/Logs/LogsTableWrap.tsx @@ -7,11 +7,13 @@ import { ExploreLogsPanelState, GrafanaTheme2, Labels, + LogRowModel, LogsSortOrder, SelectableValue, SplitOpen, store, TimeRange, + AbsoluteTimeRange, } from '@grafana/data'; import { t } from '@grafana/i18n'; import { reportInteraction } from '@grafana/runtime'; @@ -40,6 +42,10 @@ interface Props { onClickFilterLabel?: (key: string, value: string, frame?: DataFrame) => void; onClickFilterOutLabel?: (key: string, value: string, frame?: DataFrame) => void; datasourceType?: string; + exploreId?: string; + displayedFields?: string[]; + absoluteRange?: AbsoluteTimeRange; + logRows?: LogRowModel[]; } type ActiveFieldMeta = { @@ -534,6 +540,10 @@ export function LogsTableWrap(props: Props) { tableSortBy={panelState?.tableSortBy} tableSortDir={panelState?.tableSortDir} onSortByChange={onSortByChange} + displayedFields={props.displayedFields} + exploreId={props.exploreId} + absoluteRange={props.absoluteRange} + logRows={props.logRows} /> diff --git a/public/app/features/explore/Logs/utils/url.ts b/public/app/features/explore/Logs/utils/url.ts new file mode 100644 index 00000000000..10be54cce1e --- /dev/null +++ b/public/app/features/explore/Logs/utils/url.ts @@ -0,0 +1,10 @@ +/** + * Gets the base URL before the /explore path. + * Used for constructing explore URLs with permalinks. + * + * @returns The base URL (e.g., "http://localhost:3000" or "https://grafana.com") + */ +export function getExploreBaseUrl(): string { + const match = /.*(?=\/explore)/.exec(window.location.href); + return match ? match[0] : window.location.origin; +} diff --git a/public/app/features/logs/components/ControlledLogRows.tsx b/public/app/features/logs/components/ControlledLogRows.tsx index 874234837ac..38a579463ed 100644 --- a/public/app/features/logs/components/ControlledLogRows.tsx +++ b/public/app/features/logs/components/ControlledLogRows.tsx @@ -8,6 +8,7 @@ import { EventBusSrv, ExploreLogsPanelState, LogLevel, + LogRowModel, LogsMetaItem, LogsSortOrder, SplitOpen, @@ -41,6 +42,10 @@ export interface ControlledLogRowsProps extends Omit { datasourceType?: string; width?: number; logsTableFrames?: DataFrame[]; + displayedFields?: string[]; + exploreId?: string; + absoluteRange?: AbsoluteTimeRange; + logRows?: LogRowModel[]; } export type LogRowsComponentProps = Omit< diff --git a/public/app/features/logs/components/ControlledLogsTable.tsx b/public/app/features/logs/components/ControlledLogsTable.tsx index b706ba145d8..0374928da8f 100644 --- a/public/app/features/logs/components/ControlledLogsTable.tsx +++ b/public/app/features/logs/components/ControlledLogsTable.tsx @@ -25,6 +25,10 @@ export const ControlledLogsTable = ({ width, logsTableFrames, visualisationType, + displayedFields, + exploreId, + absoluteRange, + logRows, ...rest }: LogRowsComponentProps) => { const { sortOrder, controlsExpanded } = useLogListContext(); @@ -58,6 +62,10 @@ export const ControlledLogsTable = ({ panelState={panelState} updatePanelState={updatePanelState} datasourceType={datasourceType} + displayedFields={displayedFields} + exploreId={exploreId} + absoluteRange={absoluteRange} + logRows={logRows} /> diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index ac054cb8f7a..52e15a12a5f 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -7302,6 +7302,14 @@ "title-failed-sample-query": "Failed to load logs sample for this query", "tooltip": "Show log lines that contributed to visualized metrics" }, + "logs-table": { + "action-buttons": { + "copy-link": "Copy link to log line", + "copy-to-clipboard": "Copy to Clipboard", + "inspect-value": "Inspect value", + "view-log-line": "View log line" + } + }, "logs-table-empty-fields": { "no-fields": "No fields" }, From f7d9d22963a8eb0028133880b22eb1cb54344854 Mon Sep 17 00:00:00 2001 From: Gabriel MABILLE Date: Wed, 3 Dec 2025 19:16:37 +0100 Subject: [PATCH 006/110] `grafana-iam`: standalone rely on storage layer resource permissions authorization (#114785) * : standalone resource permissions authorization done at storage layer * instantiate the accessclient --- pkg/registry/apis/iam/register.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/registry/apis/iam/register.go b/pkg/registry/apis/iam/register.go index 741419b30b0..d88d3b29912 100644 --- a/pkg/registry/apis/iam/register.go +++ b/pkg/registry/apis/iam/register.go @@ -39,7 +39,6 @@ import ( "github.com/grafana/grafana/pkg/registry/apis/iam/teambinding" "github.com/grafana/grafana/pkg/registry/apis/iam/user" "github.com/grafana/grafana/pkg/services/accesscontrol" - gfauthorizer "github.com/grafana/grafana/pkg/services/apiserver/auth/authorizer" "github.com/grafana/grafana/pkg/services/apiserver/auth/authorizer/storewrapper" "github.com/grafana/grafana/pkg/services/apiserver/builder" "github.com/grafana/grafana/pkg/services/authz/zanzana" @@ -123,7 +122,6 @@ func NewAPIService( ) *IdentityAccessManagementAPIBuilder { store := legacy.NewLegacySQLStores(dbProvider) resourcePermissionsStorage := resourcepermission.ProvideStorageBackend(dbProvider) - resourceAuthorizer := gfauthorizer.NewResourceAuthorizer(accessClient) registerMetrics(reg) return &IdentityAccessManagementAPIBuilder{ store: store, @@ -131,6 +129,7 @@ func NewAPIService( resourcePermissionsStorage: resourcePermissionsStorage, logger: log.New("iam.apis"), features: features, + accessClient: accessClient, zClient: zClient, zTickets: make(chan bool, MaxConcurrentZanzanaWrites), reg: reg, @@ -138,7 +137,8 @@ func NewAPIService( func(ctx context.Context, a authorizer.Attributes) (authorizer.Decision, string, error) { // For now only authorize resourcepermissions resource if a.GetResource() == "resourcepermissions" { - return resourceAuthorizer.Authorize(ctx, a) + // Authorization is handled at the storage layer + return authorizer.DecisionAllow, "", nil } user, err := identity.GetRequester(ctx) From e3afb0daf98144bb32a402dbb23d6dac5ebd92dc Mon Sep 17 00:00:00 2001 From: Gabriel MABILLE Date: Wed, 3 Dec 2025 22:41:05 +0100 Subject: [PATCH 007/110] `grafana-iam`: Use the K8sStorage interface (#114799) --- pkg/registry/apis/iam/register.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/registry/apis/iam/register.go b/pkg/registry/apis/iam/register.go index d88d3b29912..786635fa19a 100644 --- a/pkg/registry/apis/iam/register.go +++ b/pkg/registry/apis/iam/register.go @@ -405,7 +405,7 @@ func (b *IdentityAccessManagementAPIBuilder) UpdateResourcePermissionsAPIGroup( } // Not ideal, the alternative is to wrap both stores that dualwrite uses - regStoreDW, ok := dw.(*registry.Store) + regStoreDW, ok := dw.(storewrapper.K8sStorage) if !ok { return fmt.Errorf("expected RegistryStoreDualWrite, got %T", dw) } From 2d2a1da87fc7a225f1e1c862b36d7cc710220580 Mon Sep 17 00:00:00 2001 From: "grafana-pr-automation[bot]" <140550294+grafana-pr-automation[bot]@users.noreply.github.com> Date: Thu, 4 Dec 2025 00:41:38 +0000 Subject: [PATCH 008/110] I18n: Download translations from Crowdin (#114815) New Crowdin translations by GitHub Action Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- public/locales/cs-CZ/grafana.json | 15 ++++++++++----- public/locales/de-DE/grafana.json | 15 ++++++++++----- public/locales/es-ES/grafana.json | 15 ++++++++++----- public/locales/fr-FR/grafana.json | 15 ++++++++++----- public/locales/hu-HU/grafana.json | 15 ++++++++++----- public/locales/id-ID/grafana.json | 15 ++++++++++----- public/locales/it-IT/grafana.json | 15 ++++++++++----- public/locales/ja-JP/grafana.json | 15 ++++++++++----- public/locales/ko-KR/grafana.json | 15 ++++++++++----- public/locales/nl-NL/grafana.json | 15 ++++++++++----- public/locales/pl-PL/grafana.json | 15 ++++++++++----- public/locales/pt-BR/grafana.json | 15 ++++++++++----- public/locales/pt-PT/grafana.json | 15 ++++++++++----- public/locales/ru-RU/grafana.json | 15 ++++++++++----- public/locales/sv-SE/grafana.json | 15 ++++++++++----- public/locales/tr-TR/grafana.json | 15 ++++++++++----- public/locales/zh-Hans/grafana.json | 15 ++++++++++----- public/locales/zh-Hant/grafana.json | 15 ++++++++++----- 18 files changed, 180 insertions(+), 90 deletions(-) diff --git a/public/locales/cs-CZ/grafana.json b/public/locales/cs-CZ/grafana.json index eb0c6eba3a0..01c70cde5bb 100644 --- a/public/locales/cs-CZ/grafana.json +++ b/public/locales/cs-CZ/grafana.json @@ -5125,10 +5125,7 @@ "copy-or-duplicate": "Kopírovat nebo duplikovat", "delete": "Odstranit", "duplicate": "Duplikovat", - "group-layout": "Rozvržení skupiny", - "group-layout-disabled": "Na této úrovni neexistují žádné skupiny", - "panel-layout": "Rozvržení panelu", - "panel-layout-disabled": "Vyberte řádek nebo záložku pro změnu možností rozvržení panelu" + "layout": "Rozvržení" }, "continue": "", "ungroup-nested-text": "", @@ -6607,7 +6604,7 @@ "delete-button": "Odstranit", "title": "Odstranit" }, - "delete-modal-restore-dashboards-text": "Tato akce označí nástěnku k odstranění za 30 dnů. Správce organizace ji může obnovit kdykoli před uplynutím 30 dnů.", + "delete-modal-restore-dashboards-text": "", "delete-modal-text": "Chcete odstranit tuto nástěnku?", "general": { "auto-refresh-description": "Definujte intervaly automatického obnovení, které by měly být k dispozici v seznamu automatického obnovení. Použijte formát „5 s“ pro sekundy, „1 m“ pro minuty, „1 h“ pro hodiny a „1 d“ pro dny (např.: „5 s, 10 s, 30 s, 1 m, 5 m, 15 m, 30 m, 1 h, 2 h, 1 d“).", @@ -7353,6 +7350,14 @@ "title-failed-sample-query": "Vzorek protokolů pro tento dotaz se nepodařilo načíst", "tooltip": "Zobrazit řádky protokolu, které přispěly k vizualizovaným metrikám" }, + "logs-table": { + "action-buttons": { + "copy-link": "", + "copy-to-clipboard": "", + "inspect-value": "", + "view-log-line": "" + } + }, "logs-table-empty-fields": { "no-fields": "Žádná pole" }, diff --git a/public/locales/de-DE/grafana.json b/public/locales/de-DE/grafana.json index 4f5a351736c..ff43be3c8e0 100644 --- a/public/locales/de-DE/grafana.json +++ b/public/locales/de-DE/grafana.json @@ -5085,10 +5085,7 @@ "copy-or-duplicate": "Kopieren oder duplizieren", "delete": "Löschen", "duplicate": "Duplikat", - "group-layout": "Gruppenlayout", - "group-layout-disabled": "Auf dieser Ebene sind keine Gruppen vorhanden", - "panel-layout": "Panel-Layout", - "panel-layout-disabled": "Wählen Sie eine Zeile oder Registerkarte, um die Panel-Layout-Optionen zu ändern" + "layout": "Layout" }, "continue": "", "ungroup-nested-text": "", @@ -6559,7 +6556,7 @@ "delete-button": "Löschen", "title": "Löschen" }, - "delete-modal-restore-dashboards-text": "Diese Aktion markiert das Dashboard zur Löschung in 30 Tagen. Der Administrator Ihrer Organisation kann es jederzeit vor Ablauf der 30 Tage wiederherstellen. ", + "delete-modal-restore-dashboards-text": "", "delete-modal-text": "Möchten Sie dieses Dashboard löschen?", "general": { "auto-refresh-description": "Definieren Sie die Intervalle für die automatischen Aktualisierungen, die in der Liste für die automatische Aktualisierung verfügbar sein sollen. Verwenden Sie das Format „5s“ für Sekunden, „1m“ für Minuten, „1h“ für Stunden und „1d“ für Tage (z. B.: „5s,10s,30s,1m,5m,15m,30m,1h,2h,1d“).", @@ -7305,6 +7302,14 @@ "title-failed-sample-query": "Das Logs-Sample für diese Abfrage konnte nicht geladen werden", "tooltip": "Zeigen Sie Log-Zeilen an, die an visualisierten Metriken beteiligt waren" }, + "logs-table": { + "action-buttons": { + "copy-link": "", + "copy-to-clipboard": "", + "inspect-value": "", + "view-log-line": "" + } + }, "logs-table-empty-fields": { "no-fields": "Keine Felder" }, diff --git a/public/locales/es-ES/grafana.json b/public/locales/es-ES/grafana.json index bc1ccea444c..710ee0e7739 100644 --- a/public/locales/es-ES/grafana.json +++ b/public/locales/es-ES/grafana.json @@ -5085,10 +5085,7 @@ "copy-or-duplicate": "Copiar o duplicar", "delete": "Eliminar", "duplicate": "Duplicar", - "group-layout": "Diseño del grupo", - "group-layout-disabled": "No existen grupos en este nivel", - "panel-layout": "Diseño del panel", - "panel-layout-disabled": "Selecciona una fila o pestaña para cambiar las opciones de diseño del panel" + "layout": "Diseño" }, "continue": "", "ungroup-nested-text": "", @@ -6559,7 +6556,7 @@ "delete-button": "Eliminar", "title": "Eliminar" }, - "delete-modal-restore-dashboards-text": "Esta acción marcará el panel de control para su eliminación en 30 días. El administrador de tu organización puede restaurarlo en cualquier momento antes de que transcurran los 30 días.", + "delete-modal-restore-dashboards-text": "", "delete-modal-text": "¿Quieres eliminar este panel de control?", "general": { "auto-refresh-description": "Define los intervalos de actualización automática que deben estar disponibles en la lista de actualización automática. Utiliza el formato «5 s» para los segundos, «1 m» para los minutos, «1 h» para las horas y «1 d» para los días (por ejemplo: «5 s, 10 s, 30 s, 1 m, 5 m, 15 m, 30 m, 1 h, 2 h, 1 d»).", @@ -7305,6 +7302,14 @@ "title-failed-sample-query": "Error al cargar la muestra de logs para esta consulta", "tooltip": "Mostrar líneas de log que contribuyeron a las métricas visualizadas" }, + "logs-table": { + "action-buttons": { + "copy-link": "", + "copy-to-clipboard": "", + "inspect-value": "", + "view-log-line": "" + } + }, "logs-table-empty-fields": { "no-fields": "Ningún campo" }, diff --git a/public/locales/fr-FR/grafana.json b/public/locales/fr-FR/grafana.json index 50f4e61edd0..601ff0a182d 100644 --- a/public/locales/fr-FR/grafana.json +++ b/public/locales/fr-FR/grafana.json @@ -5085,10 +5085,7 @@ "copy-or-duplicate": "Copier ou dupliquer", "delete": "Supprimer", "duplicate": "Dupliquer", - "group-layout": "Disposition du groupe", - "group-layout-disabled": "Aucun groupe n’existe à ce niveau", - "panel-layout": "Disposition des panneaux", - "panel-layout-disabled": "Sélectionnez une ligne ou un onglet pour modifier les options de disposition du panneau" + "layout": "Mise en page" }, "continue": "", "ungroup-nested-text": "", @@ -6559,7 +6556,7 @@ "delete-button": "Supprimer", "title": "Supprimer" }, - "delete-modal-restore-dashboards-text": "Cette action marquera le tableau de bord pour suppression dans 30 jours. L'administrateur de votre organisation peut le restaurer à tout moment avant l'expiration des 30 jours.", + "delete-modal-restore-dashboards-text": "", "delete-modal-text": "Voulez-vous vraiment supprimer ce tableau de bord ?", "general": { "auto-refresh-description": "Définissez les intervalles d'actualisation automatique qui doivent être disponibles dans la liste d'actualisation automatique. Utilisez le format « 5s » pour les secondes, « 1m » pour les minutes, « 1h » pour les heures et « 1d » pour les jours (par exemple : « 5s,10s,30s,1m,5m,15m,30m,1h,2h,1d »).", @@ -7305,6 +7302,14 @@ "title-failed-sample-query": "Échec du chargement de l’exemple de journaux pour cette requête", "tooltip": "Afficher les lignes de journal qui ont contribué aux métriques visualisées" }, + "logs-table": { + "action-buttons": { + "copy-link": "", + "copy-to-clipboard": "", + "inspect-value": "", + "view-log-line": "" + } + }, "logs-table-empty-fields": { "no-fields": "Aucun champ" }, diff --git a/public/locales/hu-HU/grafana.json b/public/locales/hu-HU/grafana.json index 47ccb2d223e..62de97b4434 100644 --- a/public/locales/hu-HU/grafana.json +++ b/public/locales/hu-HU/grafana.json @@ -5085,10 +5085,7 @@ "copy-or-duplicate": "Másolás vagy duplikálás", "delete": "Törlés", "duplicate": "Duplikálás", - "group-layout": "Csoport elrendezése", - "group-layout-disabled": "Ezen a szinten nincsenek csoportok", - "panel-layout": "Panel elrendezése", - "panel-layout-disabled": "Jelöljön ki egy sort vagy lapot a panel elrendezési beállításainak módosításához" + "layout": "Elrendezés" }, "continue": "", "ungroup-nested-text": "", @@ -6559,7 +6556,7 @@ "delete-button": "Törlés", "title": "Törlés" }, - "delete-modal-restore-dashboards-text": "Ez a művelet 30 napon belüli törlésre jelöli meg az irányítópultot. A szervezeti rendszergazda a 30 nap lejárta előtt bármikor visszaállíthatja.", + "delete-modal-restore-dashboards-text": "", "delete-modal-text": "Biztosan törli ezt az irányítópultot?", "general": { "auto-refresh-description": "Határozza meg az automatikus frissítési intervallumokat, amelyeknek elérhetőnek kell lenniük az automatikus frissítési listában. Használja az „5s” formátumot a másodpercekhez, az „1m” formátumot a percekhez, az „1h” formátumot az órákhoz és az „1d” formátumot a napokhoz (pl.: „5s ,10s ,30s ,1m,5m,15m,30m,1h,2h,1d”).", @@ -7305,6 +7302,14 @@ "title-failed-sample-query": "A lekérdezés naplómintájának betöltése nem sikerült", "tooltip": "A vizualizált metrikákhoz hozzájáruló naplósorok megjelenítése" }, + "logs-table": { + "action-buttons": { + "copy-link": "", + "copy-to-clipboard": "", + "inspect-value": "", + "view-log-line": "" + } + }, "logs-table-empty-fields": { "no-fields": "Nincsenek mezők" }, diff --git a/public/locales/id-ID/grafana.json b/public/locales/id-ID/grafana.json index 516ecb7f2a0..622130e0572 100644 --- a/public/locales/id-ID/grafana.json +++ b/public/locales/id-ID/grafana.json @@ -5065,10 +5065,7 @@ "copy-or-duplicate": "Salin atau Duplikasikan", "delete": "Hapus", "duplicate": "Duplikasikan", - "group-layout": "Tata letak grup", - "group-layout-disabled": "Tidak ada grup di level ini", - "panel-layout": "Tata letak panel", - "panel-layout-disabled": "Pilih baris atau tab untuk mengubah opsi tata letak panel" + "layout": "Tata Letak" }, "continue": "", "ungroup-nested-text": "", @@ -6535,7 +6532,7 @@ "delete-button": "Hapus", "title": "Hapus" }, - "delete-modal-restore-dashboards-text": "Tindakan ini akan menandai dasbor untuk dihapus dalam 30 hari. Administrator organisasi Anda dapat memulihkannya kapan saja sebelum 30 hari kedaluwarsa.", + "delete-modal-restore-dashboards-text": "", "delete-modal-text": "Apa Anda ingin menghapus dasbor ini?", "general": { "auto-refresh-description": "Tentukan interval muat ulang otomatis yang seharusnya tersedia dalam daftar muat ulang otomatis. Gunakan format '5d' untuk detik, '1m' untuk menit, '1j' untuk jam, dan '1h' untuk hari (misalnya: '5d, 10d, 30d, 1m, 5m, 15m, 30m,1j, 2j, 1h').", @@ -7281,6 +7278,14 @@ "title-failed-sample-query": "Gagal memuat sampel log untuk kueri ini", "tooltip": "Tampilkan baris log yang berkontribusi pada metrik yang divisualisasikan" }, + "logs-table": { + "action-buttons": { + "copy-link": "", + "copy-to-clipboard": "", + "inspect-value": "", + "view-log-line": "" + } + }, "logs-table-empty-fields": { "no-fields": "Tidak ada bidang" }, diff --git a/public/locales/it-IT/grafana.json b/public/locales/it-IT/grafana.json index b5fc787b5de..fe666a11dd5 100644 --- a/public/locales/it-IT/grafana.json +++ b/public/locales/it-IT/grafana.json @@ -5085,10 +5085,7 @@ "copy-or-duplicate": "Copia o duplica", "delete": "Elimina", "duplicate": "Duplica", - "group-layout": "Layout gruppo", - "group-layout-disabled": "Non esistono gruppi a questo livello", - "panel-layout": "Layout del pannello", - "panel-layout-disabled": "Seleziona una riga o una scheda per modificare le opzioni di layout del pannello" + "layout": "Layout" }, "continue": "", "ungroup-nested-text": "", @@ -6559,7 +6556,7 @@ "delete-button": "Elimina", "title": "Elimina" }, - "delete-modal-restore-dashboards-text": "Questa azione contrassegnerà il dashboard affinché venga eliminato tra 30 giorni. L'amministratore dell'organizzazione può ripristinarlo in qualsiasi momento prima della scadenza dei 30 giorni. ", + "delete-modal-restore-dashboards-text": "", "delete-modal-text": "Vuoi eliminare questo dashboard?", "general": { "auto-refresh-description": "Definisci gli intervalli di aggiornamento automatico che dovrebbero essere disponibili nell'elenco di aggiornamento automatico. Utilizza il formato \"5s\" per i secondi, \"1m\" per i minuti, \"1h\" per le ore e \"1d\" per i giorni (ad esempio: \"5s,10s,30s,1m,5m,15m,30m,1h,2h,1d\").", @@ -7305,6 +7302,14 @@ "title-failed-sample-query": "Impossibile caricare alcun esempio di registri per questa query", "tooltip": "Mostra le righe del registro che hanno contribuito alle metriche visualizzate" }, + "logs-table": { + "action-buttons": { + "copy-link": "", + "copy-to-clipboard": "", + "inspect-value": "", + "view-log-line": "" + } + }, "logs-table-empty-fields": { "no-fields": "Nessun campo" }, diff --git a/public/locales/ja-JP/grafana.json b/public/locales/ja-JP/grafana.json index a99080cb0f4..213ddb5a7dd 100644 --- a/public/locales/ja-JP/grafana.json +++ b/public/locales/ja-JP/grafana.json @@ -5065,10 +5065,7 @@ "copy-or-duplicate": "コピーまたは複製", "delete": "削除", "duplicate": "複製", - "group-layout": "グループレイアウト", - "group-layout-disabled": "このレベルにグループはありません", - "panel-layout": "パネルレイアウト", - "panel-layout-disabled": "パネルレイアウトオプションを変更するには、行またはタブを選択してください" + "layout": "レイアウト" }, "continue": "", "ungroup-nested-text": "", @@ -6535,7 +6532,7 @@ "delete-button": "削除", "title": "削除" }, - "delete-modal-restore-dashboards-text": "このアクションにより、ダッシュボードは30日後に削除されます。組織の管理者は、30日が経過する前であればいつでもそれを復元できます。", + "delete-modal-restore-dashboards-text": "", "delete-modal-text": "このダッシュボードを削除しますか?", "general": { "auto-refresh-description": "自動更新リストで使用可能な自動更新間隔を定義します。秒には「5s」、分には「1m」、時間には「1h」、日には「1d」の形式を使用します(例:「5s、10s、30s、1m、5m、15m、30m、1h、2h、1d」)。", @@ -7281,6 +7278,14 @@ "title-failed-sample-query": "このクエリのログサンプルの読み込みに失敗しました", "tooltip": "視覚化されたメトリックに関連するログ行を表示" }, + "logs-table": { + "action-buttons": { + "copy-link": "", + "copy-to-clipboard": "", + "inspect-value": "", + "view-log-line": "" + } + }, "logs-table-empty-fields": { "no-fields": "フィールドなし" }, diff --git a/public/locales/ko-KR/grafana.json b/public/locales/ko-KR/grafana.json index 82f4cf39e1b..6841d4ae54d 100644 --- a/public/locales/ko-KR/grafana.json +++ b/public/locales/ko-KR/grafana.json @@ -5065,10 +5065,7 @@ "copy-or-duplicate": "복사 또는 복제", "delete": "삭제", "duplicate": "복제", - "group-layout": "그룹 레이아웃", - "group-layout-disabled": "이 레벨에는 그룹이 존재하지 않습니다", - "panel-layout": "패널 레이아웃", - "panel-layout-disabled": "패널 레이아웃 옵션을 변경하려면 행 또는 탭을 선택하세요" + "layout": "레이아웃" }, "continue": "", "ungroup-nested-text": "", @@ -6535,7 +6532,7 @@ "delete-button": "삭제", "title": "삭제" }, - "delete-modal-restore-dashboards-text": "이 작업을 수행하면 30일 후에 대시보드가 삭제됩니다. 조직 관리자는 30일이 만료되기 전에 언제든지 복구할 수 있습니다.", + "delete-modal-restore-dashboards-text": "", "delete-modal-text": "이 대시보드를 삭제하시겠어요?", "general": { "auto-refresh-description": "자동 새로 고침 목록에서 사용할 수 있는 자동 새로 고침 간격을 정의하세요. 초 단위는 '5s', 분 단위는 '1m', 시간 단위는 '1h', 일 단위는 '1d' 형식을 사용하세요(예: '5s, 10s, 30s, 1m, 5m, 15m, 30m, 1h, 2h, 1d').", @@ -7281,6 +7278,14 @@ "title-failed-sample-query": "이 쿼리에 대한 로그 샘플 로딩 실패", "tooltip": "시각화된 메트릭에 기여한 로그 줄 표시" }, + "logs-table": { + "action-buttons": { + "copy-link": "", + "copy-to-clipboard": "", + "inspect-value": "", + "view-log-line": "" + } + }, "logs-table-empty-fields": { "no-fields": "필드 없음" }, diff --git a/public/locales/nl-NL/grafana.json b/public/locales/nl-NL/grafana.json index 332e058e99e..d4a2ffc07e1 100644 --- a/public/locales/nl-NL/grafana.json +++ b/public/locales/nl-NL/grafana.json @@ -5085,10 +5085,7 @@ "copy-or-duplicate": "Kopiëren of dupliceren", "delete": "Verwijderen", "duplicate": "Dupliceren", - "group-layout": "Indeling van de groep", - "group-layout-disabled": "Er bestaan geen groepen op dit niveau", - "panel-layout": "Paneelindeling", - "panel-layout-disabled": "Selecteer een rij of tabblad om de indelingsopties van het paneel te wijzigen" + "layout": "Indeling" }, "continue": "", "ungroup-nested-text": "", @@ -6559,7 +6556,7 @@ "delete-button": "Verwijderen", "title": "Verwijderen" }, - "delete-modal-restore-dashboards-text": "Deze actie markeert het dashboard voor verwijdering over 30 dagen. Je organisatiebeheerder kan de dashboards op elk moment herstellen voordat de 30 dagen verlopen.", + "delete-modal-restore-dashboards-text": "", "delete-modal-text": "Wil je dit dashboard verwijderen?", "general": { "auto-refresh-description": "Definieer de intervallen voor automatisch vernieuwen die beschikbaar moeten zijn in de lijst voor automatisch vernieuwen. Gebruik de notatie '5s' voor seconden, '1m' voor minuten, '1h' voor uren en '1d' voor dagen (bijvoorbeeld: '5s, 10s, 30s, 1m, 5m, 15m, 30m, 1h, 2h, 1d').", @@ -7305,6 +7302,14 @@ "title-failed-sample-query": "Kan logvoorbeeld voor deze query niet laden", "tooltip": "Loglijnen weergeven die hebben bijgedragen aan gevisualiseerde statistieken" }, + "logs-table": { + "action-buttons": { + "copy-link": "", + "copy-to-clipboard": "", + "inspect-value": "", + "view-log-line": "" + } + }, "logs-table-empty-fields": { "no-fields": "Geen velden" }, diff --git a/public/locales/pl-PL/grafana.json b/public/locales/pl-PL/grafana.json index e3c3dc215c4..b2cd07c639a 100644 --- a/public/locales/pl-PL/grafana.json +++ b/public/locales/pl-PL/grafana.json @@ -5125,10 +5125,7 @@ "copy-or-duplicate": "Kopiuj lub duplikuj", "delete": "Usuń", "duplicate": "Duplikuj", - "group-layout": "Układ grupy", - "group-layout-disabled": "Brak grup na tym poziomie", - "panel-layout": "Układ panelu", - "panel-layout-disabled": "Wybierz wiersz lub kartę, aby zmienić opcje układu panelu" + "layout": "Układ" }, "continue": "", "ungroup-nested-text": "", @@ -6607,7 +6604,7 @@ "delete-button": "Usuń", "title": "Usuń" }, - "delete-modal-restore-dashboards-text": "To działanie spowoduje oznaczenie pulpitu do usunięcia za 30 dni. Administrator organizacji może go przywrócić w dowolnym momencie przed upływem 30 dni.", + "delete-modal-restore-dashboards-text": "", "delete-modal-text": "Czy chcesz usunąć ten pulpit?", "general": { "auto-refresh-description": "Zdefiniuj interwały automatycznego odświeżania, które będą dostępne na liście automatycznego odświeżania. Użyj formatu „5s” dla sekund, „1m” dla minut, „1h” dla godzin i „1d” dla dni (np.: „5s,10s,30s,1m,5m,15m,30m,1h,2h,1d”).", @@ -7353,6 +7350,14 @@ "title-failed-sample-query": "Nie udało się załadować próbki logów dla tego zapytania", "tooltip": "Pokaż wiersze logów, które przyczyniły się do wizualizacji metryk" }, + "logs-table": { + "action-buttons": { + "copy-link": "", + "copy-to-clipboard": "", + "inspect-value": "", + "view-log-line": "" + } + }, "logs-table-empty-fields": { "no-fields": "Brak pól" }, diff --git a/public/locales/pt-BR/grafana.json b/public/locales/pt-BR/grafana.json index c74388e4232..67946425e20 100644 --- a/public/locales/pt-BR/grafana.json +++ b/public/locales/pt-BR/grafana.json @@ -5085,10 +5085,7 @@ "copy-or-duplicate": "Copiar ou duplicar", "delete": "Excluir", "duplicate": "Duplicar", - "group-layout": "Layout de grupo", - "group-layout-disabled": "Não há grupos neste nível", - "panel-layout": "Layout do painel", - "panel-layout-disabled": "Selecione uma linha ou aba para alterar as opções de layout do painel" + "layout": "Layout" }, "continue": "", "ungroup-nested-text": "", @@ -6559,7 +6556,7 @@ "delete-button": "Excluir", "title": "Excluir" }, - "delete-modal-restore-dashboards-text": "Esta ação marcará o painel de controle para exclusão em 30 dias. O administrador da sua organização pode restaurá-lo a qualquer momento antes do término do prazo de 30 dias.", + "delete-modal-restore-dashboards-text": "", "delete-modal-text": "Deseja excluir este painel de controle?", "general": { "auto-refresh-description": "Defina os intervalos de atualização automática disponíveis na lista de atualização automática. Use o formato '5s' para segundos, '1m' para minutos, '1h' para horas e '1d' para dias (por exemplo: '5s,10s,30s,1m,5m,15m,30m,1h,2h,1d').", @@ -7305,6 +7302,14 @@ "title-failed-sample-query": "Falha ao carregar a amostra de logs para esta consulta", "tooltip": "Exibir linhas de log que contribuíram para as métricas visualizadas" }, + "logs-table": { + "action-buttons": { + "copy-link": "", + "copy-to-clipboard": "", + "inspect-value": "", + "view-log-line": "" + } + }, "logs-table-empty-fields": { "no-fields": "Sem campos" }, diff --git a/public/locales/pt-PT/grafana.json b/public/locales/pt-PT/grafana.json index a82cfe5b432..f9a8b59e8c3 100644 --- a/public/locales/pt-PT/grafana.json +++ b/public/locales/pt-PT/grafana.json @@ -5085,10 +5085,7 @@ "copy-or-duplicate": "Copiar ou duplicar", "delete": "Eliminar", "duplicate": "Duplicar", - "group-layout": "Disposição do grupo", - "group-layout-disabled": "Não existem grupos neste nível", - "panel-layout": "Disposição do painel", - "panel-layout-disabled": "Selecione uma linha ou um separador para alterar as opções de disposição do painel" + "layout": "Layout" }, "continue": "", "ungroup-nested-text": "", @@ -6559,7 +6556,7 @@ "delete-button": "Eliminar", "title": "Eliminar" }, - "delete-modal-restore-dashboards-text": "Esta ação marcará o painel de controlo para eliminação em 30 dias. O administrador da sua organização pode restaurar os painéis de controlo a qualquer momento antes dos 30 dias expirarem. ", + "delete-modal-restore-dashboards-text": "", "delete-modal-text": "Pretende eliminar este painel de controlo?", "general": { "auto-refresh-description": "Defina os intervalos de atualização automática que devem estar disponíveis na lista de atualização automática. Utilize o formato '5s' para segundos, '1m' para minutos, '1h' para horas e '1d' para dias (por ex.: '5s,10s,30s,1m,5m,15m,30m,1h,2h,1d').", @@ -7305,6 +7302,14 @@ "title-failed-sample-query": "Falha ao carregar a amostra de registos para esta consulta", "tooltip": "Mostrar linhas de registo que contribuíram para as métricas visualizadas" }, + "logs-table": { + "action-buttons": { + "copy-link": "", + "copy-to-clipboard": "", + "inspect-value": "", + "view-log-line": "" + } + }, "logs-table-empty-fields": { "no-fields": "Nenhum campo" }, diff --git a/public/locales/ru-RU/grafana.json b/public/locales/ru-RU/grafana.json index d9de61dde42..39d4bb3e010 100644 --- a/public/locales/ru-RU/grafana.json +++ b/public/locales/ru-RU/grafana.json @@ -5125,10 +5125,7 @@ "copy-or-duplicate": "Копировать или дублировать", "delete": "Удалить", "duplicate": "Дублировать", - "group-layout": "Макет группы", - "group-layout-disabled": "На этом уровне нет групп", - "panel-layout": "Расположение панелей", - "panel-layout-disabled": "Выбрать строку или вкладку для изменения макета панели" + "layout": "Расположение" }, "continue": "", "ungroup-nested-text": "", @@ -6607,7 +6604,7 @@ "delete-button": "Удалить", "title": "Удаление" }, - "delete-modal-restore-dashboards-text": "Это действие пометит дашборд для удаления через 30 дней. Администратор вашей организации может восстановить его в любое время в течение указанных 30 дней.", + "delete-modal-restore-dashboards-text": "", "delete-modal-text": "Действительно удалить дашборд?", "general": { "auto-refresh-description": "Задайте интервалы автообновления, которые должны быть доступны в соответствующем списке. Используйте формат «5 с» для секунд, «1 м» для минут, «1 ч» для часов и «1 д» для дней (например, «5 с, 10 с, 30 с, 1 м, 5 м, 15 м, 30 м, 1 ч, 2 ч, 1 д»).", @@ -7353,6 +7350,14 @@ "title-failed-sample-query": "Не удалось загрузить пример журналов для этого запроса", "tooltip": "Показать строки журнала, которые оказали влияние на визуализированные метрики" }, + "logs-table": { + "action-buttons": { + "copy-link": "", + "copy-to-clipboard": "", + "inspect-value": "", + "view-log-line": "" + } + }, "logs-table-empty-fields": { "no-fields": "Нет полей" }, diff --git a/public/locales/sv-SE/grafana.json b/public/locales/sv-SE/grafana.json index 37702588d0a..4cd8fffafd8 100644 --- a/public/locales/sv-SE/grafana.json +++ b/public/locales/sv-SE/grafana.json @@ -5085,10 +5085,7 @@ "copy-or-duplicate": "Kopiera eller duplicera", "delete": "Ta bort", "duplicate": "Dubblett", - "group-layout": "Grupplayout", - "group-layout-disabled": "Det finns inga grupper på den här nivån", - "panel-layout": "Panellayout", - "panel-layout-disabled": "Välj en rad eller flik för att ändra alternativ för panellayout" + "layout": "Layout" }, "continue": "", "ungroup-nested-text": "", @@ -6559,7 +6556,7 @@ "delete-button": "Ta bort", "title": "Ta bort" }, - "delete-modal-restore-dashboards-text": "Denna åtgärd markerar instrumentpanelen för radering om 30 dagar. Din organisationsadministratör kan återställa den när som helst innan de 30 dagarna löper ut. ", + "delete-modal-restore-dashboards-text": "", "delete-modal-text": "Vill du radera denna instrumentpanel?", "general": { "auto-refresh-description": "Definiera de automatiska uppdateringsintervallen som ska vara tillgängliga i listan över automatiska uppdateringar. Använd formatet ”5s” för sekunder, ”1m” för minuter, ”1h” för timmar och ”1d” för dagar (t.ex.: ”5s,10s,30s,1m,5m,15m,30m,1h,2h,1d”).", @@ -7305,6 +7302,14 @@ "title-failed-sample-query": "Det gick inte att ladda loggprov för denna fråga", "tooltip": "Visa loggrader som bidrog till visualiserade mätvärden" }, + "logs-table": { + "action-buttons": { + "copy-link": "", + "copy-to-clipboard": "", + "inspect-value": "", + "view-log-line": "" + } + }, "logs-table-empty-fields": { "no-fields": "Inga fält" }, diff --git a/public/locales/tr-TR/grafana.json b/public/locales/tr-TR/grafana.json index ea77d97bfd9..2d3751c4e2e 100644 --- a/public/locales/tr-TR/grafana.json +++ b/public/locales/tr-TR/grafana.json @@ -5085,10 +5085,7 @@ "copy-or-duplicate": "Kopyala veya Çoğalt", "delete": "Sil", "duplicate": "Çoğalt", - "group-layout": "Grup düzeni", - "group-layout-disabled": "Bu seviyede hiçbir grup yok", - "panel-layout": "Panel yazı tipi", - "panel-layout-disabled": "Panel düzeni seçeneklerini değiştirmek için bir satır veya sekme seçin" + "layout": "Düzen" }, "continue": "", "ungroup-nested-text": "", @@ -6559,7 +6556,7 @@ "delete-button": "Sil", "title": "Sil" }, - "delete-modal-restore-dashboards-text": "Bu işlem, panoyu 30 gün içinde silinmek üzere işaretleyecektir. Kuruluş yöneticiniz, 30 günlük süre dolmadan panoyu istediği zaman geri yükleyebilir.", + "delete-modal-restore-dashboards-text": "", "delete-modal-text": "Bu panoyu silmek istiyor musunuz?", "general": { "auto-refresh-description": "Otomatik yenileme listesinde bulunması gereken otomatik yenileme aralıklarını tanımlayın. Saniyeler için \"5s\", dakikalar için \"1m\", saatler için \"1h\" ve günler için \"1d\" biçimini kullanın (örneğin: \"5s,10s,30s,1m,5m,15m,30m,1h,2h,1d'\").", @@ -7305,6 +7302,14 @@ "title-failed-sample-query": "Bu sorgu için günlük kaydı örneği yüklenemedi", "tooltip": "Görselleştirilen metriklere katkıda bulunan günlük kayıtlarını göster" }, + "logs-table": { + "action-buttons": { + "copy-link": "", + "copy-to-clipboard": "", + "inspect-value": "", + "view-log-line": "" + } + }, "logs-table-empty-fields": { "no-fields": "Alan yok" }, diff --git a/public/locales/zh-Hans/grafana.json b/public/locales/zh-Hans/grafana.json index 950acdeadbb..96e44a5792c 100644 --- a/public/locales/zh-Hans/grafana.json +++ b/public/locales/zh-Hans/grafana.json @@ -5065,10 +5065,7 @@ "copy-or-duplicate": "复制或拷贝", "delete": "删除", "duplicate": "复制", - "group-layout": "组布局", - "group-layout-disabled": "此级别上不存在组", - "panel-layout": "面板布局", - "panel-layout-disabled": "选择一行或选项卡以更改面板布局选项" + "layout": "布局" }, "continue": "", "ungroup-nested-text": "", @@ -6535,7 +6532,7 @@ "delete-button": "删除", "title": "删除" }, - "delete-modal-restore-dashboards-text": "此操作会将数据面板标记为在 30 天后删除。您的组织管理员可以在 30 天期限内随时还原。", + "delete-modal-restore-dashboards-text": "", "delete-modal-text": "您要删除这个数据面板吗?", "general": { "auto-refresh-description": "定义应显示于自动刷新列表中的自动刷新间隔。使用格式“5s”表示秒,“1m”表示分,“1h”表示小时,“1d”表示天(例如:“5s,10s,30s,1m,5m,15m,30m,1h,2h,1d”)。", @@ -7281,6 +7278,14 @@ "title-failed-sample-query": "加载此查询的日志示例失败", "tooltip": "显示有助于实现可视化指标的日志行" }, + "logs-table": { + "action-buttons": { + "copy-link": "", + "copy-to-clipboard": "", + "inspect-value": "", + "view-log-line": "" + } + }, "logs-table-empty-fields": { "no-fields": "没有字段" }, diff --git a/public/locales/zh-Hant/grafana.json b/public/locales/zh-Hant/grafana.json index f4cdba753fd..c2fea7e8d27 100644 --- a/public/locales/zh-Hant/grafana.json +++ b/public/locales/zh-Hant/grafana.json @@ -5065,10 +5065,7 @@ "copy-or-duplicate": "複製或重複", "delete": "刪除", "duplicate": "重複", - "group-layout": "群組版面配置", - "group-layout-disabled": "此層級上不存在群組", - "panel-layout": "面板版面配置", - "panel-layout-disabled": "選擇一列或分頁以變更面板版面配置選項" + "layout": "版面配置" }, "continue": "", "ungroup-nested-text": "", @@ -6535,7 +6532,7 @@ "delete-button": "刪除", "title": "刪除" }, - "delete-modal-restore-dashboards-text": "此動作會標記儀表板,以便在 30 天後刪除。您的組織管理員可以在 30 天到期之前隨時還原。", + "delete-modal-restore-dashboards-text": "", "delete-modal-text": "您要刪除此儀表板嗎?", "general": { "auto-refresh-description": "定義自動重新整理清單中應該可用的自動重新整理間隔。使用格式「5s」表示秒,「1m」表示分鐘,「1h」表示小時,「1d」表示天(例如:「5s、10s、30s、1m、5m、15m、30m、1h、2h、1d」)。", @@ -7281,6 +7278,14 @@ "title-failed-sample-query": "無法載入此查詢的紀錄樣本", "tooltip": "顯示對可視化指標有貢獻的紀錄行" }, + "logs-table": { + "action-buttons": { + "copy-link": "", + "copy-to-clipboard": "", + "inspect-value": "", + "view-log-line": "" + } + }, "logs-table-empty-fields": { "no-fields": "沒有欄位" }, From 0aeb4feef3938d1dc64290d8b34c9b03772c1e85 Mon Sep 17 00:00:00 2001 From: Johnny Kartheiser <140559259+JohnnyK-Grafana@users.noreply.github.com> Date: Wed, 3 Dec 2025 18:47:23 -0600 Subject: [PATCH 009/110] update documentation to mention protected fields (#114809) * update documentation to mention protected fields * alerting docs: add protected field info for grafana cloud add protected field info for grafana cloud * prettier * link fix --------- Co-authored-by: Yuri Tseretyan --- .../custom-role-actions-scopes/index.md | 37 ++++++++++--------- .../manage-contact-points/_index.md | 17 +++++++++ .../integrations/configure-alertmanager.md | 6 +-- .../integrations/configure-jira.md | 16 ++++---- .../integrations/configure-mqtt.md | 8 ++-- .../integrations/webhook-notifier.md | 6 +-- 6 files changed, 54 insertions(+), 36 deletions(-) diff --git a/docs/sources/administration/roles-and-permissions/access-control/custom-role-actions-scopes/index.md b/docs/sources/administration/roles-and-permissions/access-control/custom-role-actions-scopes/index.md index 92f6c6fe5e2..641d9bb4180 100644 --- a/docs/sources/administration/roles-and-permissions/access-control/custom-role-actions-scopes/index.md +++ b/docs/sources/administration/roles-and-permissions/access-control/custom-role-actions-scopes/index.md @@ -221,24 +221,25 @@ For more information on Cloud Access Policies and how to use them, see [Access p ### Grafana Alerting Notification action definitions -| Action | Applicable scopes | Description | -| -------------------------------------------- | ---------------------------------- | ----------------------------------------------------------------------------------------------------------- | -| `alert.notifications.receivers:read` | `receivers:*`
`receivers:uid:*` | Read contact points. | -| `alert.notifications.receivers.secrets:read` | `receivers:*`
`receivers:uid:*` | Export contact points with decrypted secrets. | -| `alert.notifications.receivers:create` | None | Create a new contact points. The creator is automatically granted full access to the created contact point. | -| `alert.notifications.receivers:write` | `receivers:*`
`receivers:uid:*` | Update existing contact points. | -| `alert.notifications.receivers:delete` | `receivers:*`
`receivers:uid:*` | Update and delete existing contact points. | -| `alert.notifications.receivers:test` | None | Test contact point notification. | -| `receivers.permissions:read` | `receivers:*`
`receivers:uid:*` | Read permissions for contact points. | -| `receivers.permissions:write` | `receivers:*`
`receivers:uid:*` | Manage permissions for contact points. | -| `alert.notifications.time-intervals:read` | None | Read mute time intervals. | -| `alert.notifications.time-intervals:write` | None | Create new or update existing mute time intervals. | -| `alert.notifications.time-intervals:delete` | None | Delete existing time intervals. | -| `alert.notifications.templates:read` | None | Read templates. | -| `alert.notifications.templates:write` | None | Create new or update existing templates. | -| `alert.notifications.templates:delete` | None | Delete existing templates. | -| `alert.notifications.routes:read` | None | Read notification policies. | -| `alert.notifications.routes:write` | None | Create new, update or delete notification policies | +| Action | Applicable scopes | Description | +| ----------------------------------------------- | ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `alert.notifications.receivers:read` | `receivers:*`
`receivers:uid:*` | Read contact points. | +| `alert.notifications.receivers.secrets:read` | `receivers:*`
`receivers:uid:*` | Export contact points with decrypted secrets. | +| `alert.notifications.receivers:create` | None | Create a new contact points. The creator is automatically granted full access to the created contact point. | +| `alert.notifications.receivers:write` | `receivers:*`
`receivers:uid:*` | Update existing contact points. | +| `alert.notifications.receivers.protected:write` | `receivers:*`
`receivers:uid:*` | Update [protected fields](/docs/grafana//alerting/configure-notifications/manage-contact-points#grafana-cloud-protected-fields) in contact points (such as target URLs for integrations). This scope only applies to Grafana Cloud. | +| `alert.notifications.receivers:delete` | `receivers:*`
`receivers:uid:*` | Update and delete existing contact points. | +| `alert.notifications.receivers:test` | None | Test contact point notification. | +| `receivers.permissions:read` | `receivers:*`
`receivers:uid:*` | Read permissions for contact points. | +| `receivers.permissions:write` | `receivers:*`
`receivers:uid:*` | Manage permissions for contact points. | +| `alert.notifications.time-intervals:read` | None | Read mute time intervals. | +| `alert.notifications.time-intervals:write` | None | Create new or update existing mute time intervals. | +| `alert.notifications.time-intervals:delete` | None | Delete existing time intervals. | +| `alert.notifications.templates:read` | None | Read templates. | +| `alert.notifications.templates:write` | None | Create new or update existing templates. | +| `alert.notifications.templates:delete` | None | Delete existing templates. | +| `alert.notifications.routes:read` | None | Read notification policies. | +| `alert.notifications.routes:write` | None | Create new, update or delete notification policies | ### Grafana Synthetic Monitoring action definitions diff --git a/docs/sources/alerting/configure-notifications/manage-contact-points/_index.md b/docs/sources/alerting/configure-notifications/manage-contact-points/_index.md index c04939f16b0..a468c91449d 100644 --- a/docs/sources/alerting/configure-notifications/manage-contact-points/_index.md +++ b/docs/sources/alerting/configure-notifications/manage-contact-points/_index.md @@ -137,6 +137,23 @@ On the **Contact Points** tab, you can: Contact points are assigned to a [specific Alertmanager](ref:configure-alertmanager) and cannot be used by notification policies in other Alertmanagers. {{< /admonition >}} +## Grafana Cloud Protected fields + +For Grafana Cloud users, contact points may contain protected fields that require admin permissions to modify. Protected fields are sensitive configuration settings that affect where notifications are sent, such as: + +- Target URLs for integrations (webhooks, PagerDuty, Opsgenie, or other integrations.) +- API endpoints +- Other destination-related settings + +These fields are protected to prevent unauthorized users from redirecting notifications to compromised servers, which could expose sensitive information such as authorization tokens, API keys, or alert data. + +Users with edit permissions can modify most contact point settings and can add or remove integrations, but cannot change protected fields in existing integrations. Only users with admin permissions to the contact point can update protected fields. + +The ability to modify protected fields is controlled by the RBAC action `alert.notifications.receivers.protected:write`. This role is granted by default to: + +- Users with the fixed "Alerting Admin" role +- Users with admin permissions for the specific contact point + ## Supported contact point integrations Each contact point integration has its own configuration options and setup process. The following list shows the contact point integrations supported by Grafana. diff --git a/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-alertmanager.md b/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-alertmanager.md index 9f5822eedc6..4cdc84ec366 100644 --- a/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-alertmanager.md +++ b/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-alertmanager.md @@ -59,9 +59,9 @@ For more details on contact points, including how to test them and enable notifi ## Alertmanager settings -| Option | Description | -| ------ | --------------------- | -| URL | The Alertmanager URL. | +| Option | Description | +| ------ | ---------------------------------------------------------------------------------------------------------------------------------- | +| URL | The Alertmanager URL. This field is [protected](ref:configure-contact-points#protected-fields) from modification in Grafana Cloud. | #### Optional settings diff --git a/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-jira.md b/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-jira.md index a01783fb921..659341405f1 100644 --- a/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-jira.md +++ b/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-jira.md @@ -49,14 +49,14 @@ For more details on contact points, including how to test them and enable notifi ### Required Settings -| Key | Description | -| ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| URL | The URL of the REST API of your Jira instance. Supported versions: `2` and `3` (e.g., `https://your-domain.atlassian.net/rest/api/3`). | -| Basic Auth User | Username for authentication. For Jira Cloud, use your email address. | -| Basic Auth Password | Password or personal token. For Jira Cloud, you need to obtain a personal token [here](https://id.atlassian.com/manage-profile/security/api-tokens) and use it as the password. | -| API Token | An alternative to basic authentication, a bearer token is used to authorize the API requests. See [Jira documentation](https://confluence.atlassian.com/enterprise/using-personal-access-tokens-1026032365.html) for more information. | -| Project Key | The project key identifying the project where issues will be created. Project keys are unique identifiers for a project. | -| Issue Type | The type of issue to create (e.g., `Task`, `Bug`, `Incident`). Make sure that you specify a type that is available in your project. | +| Key | Description | +| ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| URL | The URL of the REST API of your Jira instance. Supported versions: `2` and `3` (e.g., `https://your-domain.atlassian.net/rest/api/3`). This field is [protected](ref:configure-contact-points#protected-fields) from modification in Grafana Cloud. | +| Basic Auth User | Username for authentication. For Jira Cloud, use your email address. | +| Basic Auth Password | Password or personal token. For Jira Cloud, you need to obtain a personal token [here](https://id.atlassian.com/manage-profile/security/api-tokens) and use it as the password. | +| API Token | An alternative to basic authentication, a bearer token is used to authorize the API requests. See [Jira documentation](https://confluence.atlassian.com/enterprise/using-personal-access-tokens-1026032365.html) for more information. | +| Project Key | The project key identifying the project where issues will be created. Project keys are unique identifiers for a project. | +| Issue Type | The type of issue to create (e.g., `Task`, `Bug`, `Incident`). Make sure that you specify a type that is available in your project. | ### Optional Settings diff --git a/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-mqtt.md b/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-mqtt.md index 8e58e619f00..6f76a574619 100644 --- a/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-mqtt.md +++ b/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-mqtt.md @@ -54,10 +54,10 @@ For more details on contact points, including how to test them and enable notifi ### Required Settings -| Option | Description | -| ---------- | -------------------------------------------- | -| Broker URL | The URL of the MQTT broker. | -| Topic | The topic to which the message will be sent. | +| Option | Description | +| ---------- | ---------------------------------------------------------------------------------------------------------------------------------------- | +| Broker URL | The URL of the MQTT broker. This field is [protected](ref:configure-contact-points#protected-fields) from modification in Grafana Cloud. | +| Topic | The topic to which the message will be sent. | ### Optional Settings diff --git a/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/webhook-notifier.md b/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/webhook-notifier.md index fdfbca1bb85..120a45be2a2 100644 --- a/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/webhook-notifier.md +++ b/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/webhook-notifier.md @@ -62,9 +62,9 @@ For more details on contact points, including how to test them and enable notifi ## Webhook settings -| Option | Description | -| ------ | ---------------- | -| URL | The Webhook URL. | +| Option | Description | +| ------ | ----------------------------------------------------------------------------------------------------------------------------- | +| URL | The Webhook URL. This field is [protected](ref:configure-contact-points#protected-fields) from modification in Grafana Cloud. | #### Optional settings From 36ded11aa4622d7439bfcfea1120a0ba443e2e2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20H=C3=A4ggmark?= Date: Thu, 4 Dec 2025 06:06:16 +0100 Subject: [PATCH 010/110] chore: reduce barrel files (#114566) --- eslint-suppressions.json | 5 ----- .../CloudMonitoringMetricFindQuery.ts | 2 +- .../cloud-monitoring/annotationSupport.test.ts | 9 ++------- .../datasource/cloud-monitoring/annotationSupport.ts | 3 ++- .../cloud-monitoring/components/Aggregation.test.tsx | 2 +- .../cloud-monitoring/components/Aggregation.tsx | 2 +- .../cloud-monitoring/components/Alignment.test.tsx | 2 +- .../cloud-monitoring/components/Alignment.tsx | 2 +- .../components/AlignmentFunction.tsx | 2 +- .../components/AnnotationQueryEditor.tsx | 3 ++- .../cloud-monitoring/components/GroupBy.tsx | 2 +- .../components/MetricQueryEditor.test.tsx | 2 +- .../components/MetricQueryEditor.tsx | 3 ++- .../components/Preprocessor.test.tsx | 2 +- .../cloud-monitoring/components/Preprocessor.tsx | 2 +- .../cloud-monitoring/components/PromQLEditor.tsx | 2 +- .../cloud-monitoring/components/QueryEditor.test.tsx | 2 +- .../cloud-monitoring/components/QueryEditor.tsx | 3 ++- .../cloud-monitoring/components/QueryHeader.test.tsx | 2 +- .../datasource/cloud-monitoring/components/SLO.tsx | 2 +- .../cloud-monitoring/components/SLOQueryEditor.tsx | 2 +- .../cloud-monitoring/components/Selector.tsx | 2 +- .../cloud-monitoring/components/Service.tsx | 2 +- .../components/VariableQueryEditor.test.tsx | 2 +- .../components/VariableQueryEditor.tsx | 3 ++- .../components/VisualMetricQueryEditor.test.tsx | 2 +- .../components/VisualMetricQueryEditor.tsx | 2 +- .../plugins/datasource/cloud-monitoring/constants.ts | 2 +- .../datasource/cloud-monitoring/datasource.test.ts | 3 ++- .../datasource/cloud-monitoring/datasource.ts | 3 ++- .../datasource/cloud-monitoring/functions.test.ts | 2 +- .../plugins/datasource/cloud-monitoring/functions.ts | 2 +- .../mocks/cloudMonitoringMetricDescriptor.ts | 2 +- .../cloud-monitoring/mocks/cloudMonitoringQuery.ts | 10 ++-------- .../plugins/datasource/cloud-monitoring/module.ts | 3 ++- .../datasource/cloud-monitoring/types/query.ts | 12 ------------ .../datasource/cloud-monitoring/types/types.ts | 2 +- 37 files changed, 45 insertions(+), 65 deletions(-) diff --git a/eslint-suppressions.json b/eslint-suppressions.json index a6d4384557b..4cb2d651b50 100644 --- a/eslint-suppressions.json +++ b/eslint-suppressions.json @@ -3665,11 +3665,6 @@ "count": 2 } }, - "public/app/plugins/datasource/cloud-monitoring/types/query.ts": { - "no-barrel-files/no-barrel-files": { - "count": 3 - } - }, "public/app/plugins/datasource/cloud-monitoring/types/types.ts": { "@typescript-eslint/no-explicit-any": { "count": 1 diff --git a/public/app/plugins/datasource/cloud-monitoring/CloudMonitoringMetricFindQuery.ts b/public/app/plugins/datasource/cloud-monitoring/CloudMonitoringMetricFindQuery.ts index 6131457fb25..5629c3693ad 100644 --- a/public/app/plugins/datasource/cloud-monitoring/CloudMonitoringMetricFindQuery.ts +++ b/public/app/plugins/datasource/cloud-monitoring/CloudMonitoringMetricFindQuery.ts @@ -1,6 +1,7 @@ import { isString } from 'lodash'; import { ALIGNMENT_PERIODS, SELECTORS } from './constants'; +import { ValueTypes, MetricFindQueryTypes } from './dataquery.gen'; import CloudMonitoringDatasource from './datasource'; import { extractServicesFromMetricDescriptors, @@ -9,7 +10,6 @@ import { getLabelKeys, getMetricTypesByService, } from './functions'; -import { ValueTypes, MetricFindQueryTypes } from './types/query'; import { CloudMonitoringVariableQuery, MetricDescriptor } from './types/types'; export default class CloudMonitoringMetricFindQuery { diff --git a/public/app/plugins/datasource/cloud-monitoring/annotationSupport.test.ts b/public/app/plugins/datasource/cloud-monitoring/annotationSupport.test.ts index c187351f957..086a01e2b56 100644 --- a/public/app/plugins/datasource/cloud-monitoring/annotationSupport.test.ts +++ b/public/app/plugins/datasource/cloud-monitoring/annotationSupport.test.ts @@ -1,14 +1,9 @@ import { AnnotationQuery } from '@grafana/data'; import { CloudMonitoringAnnotationSupport } from './annotationSupport'; +import { AlignmentTypes, QueryType, MetricKind, LegacyCloudMonitoringAnnotationQuery } from './dataquery.gen'; import { createMockDatasource } from './mocks/cloudMonitoringDatasource'; -import { - AlignmentTypes, - CloudMonitoringQuery, - QueryType, - MetricKind, - LegacyCloudMonitoringAnnotationQuery, -} from './types/query'; +import { CloudMonitoringQuery } from './types/query'; const query: CloudMonitoringQuery = { refId: 'query', diff --git a/public/app/plugins/datasource/cloud-monitoring/annotationSupport.ts b/public/app/plugins/datasource/cloud-monitoring/annotationSupport.ts index 024b6af060a..8e28fcc159a 100644 --- a/public/app/plugins/datasource/cloud-monitoring/annotationSupport.ts +++ b/public/app/plugins/datasource/cloud-monitoring/annotationSupport.ts @@ -1,8 +1,9 @@ import { AnnotationSupport, AnnotationQuery } from '@grafana/data'; import { AnnotationQueryEditor } from './components/AnnotationQueryEditor'; +import { AlignmentTypes, QueryType, LegacyCloudMonitoringAnnotationQuery } from './dataquery.gen'; import CloudMonitoringDatasource from './datasource'; -import { AlignmentTypes, CloudMonitoringQuery, QueryType, LegacyCloudMonitoringAnnotationQuery } from './types/query'; +import { CloudMonitoringQuery } from './types/query'; // The legacy query format sets the title and text values to empty strings by default. // If the title or text is not undefined at the top-level of the annotation target, diff --git a/public/app/plugins/datasource/cloud-monitoring/components/Aggregation.test.tsx b/public/app/plugins/datasource/cloud-monitoring/components/Aggregation.test.tsx index e19a5f79cb8..1d5e016097f 100644 --- a/public/app/plugins/datasource/cloud-monitoring/components/Aggregation.test.tsx +++ b/public/app/plugins/datasource/cloud-monitoring/components/Aggregation.test.tsx @@ -1,7 +1,7 @@ import { render, screen } from '@testing-library/react'; import { openMenu } from 'react-select-event'; -import { MetricKind, ValueTypes } from '../types/query'; +import { MetricKind, ValueTypes } from '../dataquery.gen'; import { MetricDescriptor } from '../types/types'; import { Aggregation, Props } from './Aggregation'; diff --git a/public/app/plugins/datasource/cloud-monitoring/components/Aggregation.tsx b/public/app/plugins/datasource/cloud-monitoring/components/Aggregation.tsx index 2d5089f06fc..f68d2981801 100644 --- a/public/app/plugins/datasource/cloud-monitoring/components/Aggregation.tsx +++ b/public/app/plugins/datasource/cloud-monitoring/components/Aggregation.tsx @@ -4,8 +4,8 @@ import { SelectableValue } from '@grafana/data'; import { EditorField } from '@grafana/plugin-ui'; import { Select } from '@grafana/ui'; +import { ValueTypes } from '../dataquery.gen'; import { getAggregationOptionsByMetric } from '../functions'; -import { ValueTypes } from '../types/query'; import { MetricDescriptor } from '../types/types'; export interface Props { diff --git a/public/app/plugins/datasource/cloud-monitoring/components/Alignment.test.tsx b/public/app/plugins/datasource/cloud-monitoring/components/Alignment.test.tsx index 96bd3f8d71f..68ea7b17e29 100644 --- a/public/app/plugins/datasource/cloud-monitoring/components/Alignment.test.tsx +++ b/public/app/plugins/datasource/cloud-monitoring/components/Alignment.test.tsx @@ -4,10 +4,10 @@ import { openMenu } from 'react-select-event'; import { CustomVariableModel } from '@grafana/data'; +import { MetricKind, ValueTypes } from '../dataquery.gen'; import { createMockDatasource } from '../mocks/cloudMonitoringDatasource'; import { createMockMetricDescriptor } from '../mocks/cloudMonitoringMetricDescriptor'; import { createMockTimeSeriesList } from '../mocks/cloudMonitoringQuery'; -import { MetricKind, ValueTypes } from '../types/query'; import { Alignment } from './Alignment'; diff --git a/public/app/plugins/datasource/cloud-monitoring/components/Alignment.tsx b/public/app/plugins/datasource/cloud-monitoring/components/Alignment.tsx index 1a3d21c3a9d..e5b485be921 100644 --- a/public/app/plugins/datasource/cloud-monitoring/components/Alignment.tsx +++ b/public/app/plugins/datasource/cloud-monitoring/components/Alignment.tsx @@ -4,9 +4,9 @@ import { SelectableValue } from '@grafana/data'; import { EditorField, EditorFieldGroup } from '@grafana/plugin-ui'; import { ALIGNMENT_PERIODS } from '../constants'; +import { PreprocessorType, TimeSeriesList } from '../dataquery.gen'; import CloudMonitoringDatasource from '../datasource'; import { alignmentPeriodLabel } from '../functions'; -import { PreprocessorType, TimeSeriesList } from '../types/query'; import { CustomMetaData, MetricDescriptor } from '../types/types'; import { AlignmentFunction } from './AlignmentFunction'; diff --git a/public/app/plugins/datasource/cloud-monitoring/components/AlignmentFunction.tsx b/public/app/plugins/datasource/cloud-monitoring/components/AlignmentFunction.tsx index 5ab061d55ae..6a0b0ef8d99 100644 --- a/public/app/plugins/datasource/cloud-monitoring/components/AlignmentFunction.tsx +++ b/public/app/plugins/datasource/cloud-monitoring/components/AlignmentFunction.tsx @@ -3,8 +3,8 @@ import { useMemo } from 'react'; import { SelectableValue } from '@grafana/data'; import { Select } from '@grafana/ui'; +import { PreprocessorType, SLOQuery, TimeSeriesList } from '../dataquery.gen'; import { getAlignmentPickerData } from '../functions'; -import { PreprocessorType, SLOQuery, TimeSeriesList } from '../types/query'; import { MetricDescriptor } from '../types/types'; export interface Props { diff --git a/public/app/plugins/datasource/cloud-monitoring/components/AnnotationQueryEditor.tsx b/public/app/plugins/datasource/cloud-monitoring/components/AnnotationQueryEditor.tsx index cb754445570..6154722f70c 100644 --- a/public/app/plugins/datasource/cloud-monitoring/components/AnnotationQueryEditor.tsx +++ b/public/app/plugins/datasource/cloud-monitoring/components/AnnotationQueryEditor.tsx @@ -6,8 +6,9 @@ import { QueryEditorProps, getDefaultTimeRange, toOption } from '@grafana/data'; import { EditorField, EditorRows } from '@grafana/plugin-ui'; import { Input } from '@grafana/ui'; +import { TimeSeriesList, QueryType } from '../dataquery.gen'; import CloudMonitoringDatasource from '../datasource'; -import { TimeSeriesList, CloudMonitoringQuery, QueryType } from '../types/query'; +import { CloudMonitoringQuery } from '../types/query'; import { CloudMonitoringOptions } from '../types/types'; import { AnnotationsHelp } from './AnnotationsHelp'; diff --git a/public/app/plugins/datasource/cloud-monitoring/components/GroupBy.tsx b/public/app/plugins/datasource/cloud-monitoring/components/GroupBy.tsx index 356a81d5ade..a504133df57 100644 --- a/public/app/plugins/datasource/cloud-monitoring/components/GroupBy.tsx +++ b/public/app/plugins/datasource/cloud-monitoring/components/GroupBy.tsx @@ -5,8 +5,8 @@ import { EditorField, EditorFieldGroup } from '@grafana/plugin-ui'; import { MultiSelect } from '@grafana/ui'; import { SYSTEM_LABELS } from '../constants'; +import { TimeSeriesList } from '../dataquery.gen'; import { labelsToGroupedOptions } from '../functions'; -import { TimeSeriesList } from '../types/query'; import { MetricDescriptor } from '../types/types'; import { Aggregation } from './Aggregation'; diff --git a/public/app/plugins/datasource/cloud-monitoring/components/MetricQueryEditor.test.tsx b/public/app/plugins/datasource/cloud-monitoring/components/MetricQueryEditor.test.tsx index 12ed2dfd0aa..80ddb62ed52 100644 --- a/public/app/plugins/datasource/cloud-monitoring/components/MetricQueryEditor.test.tsx +++ b/public/app/plugins/datasource/cloud-monitoring/components/MetricQueryEditor.test.tsx @@ -4,9 +4,9 @@ import { openMenu } from 'react-select-event'; import { getDefaultTimeRange } from '@grafana/data'; +import { QueryType } from '../dataquery.gen'; import { createMockDatasource } from '../mocks/cloudMonitoringDatasource'; import { createMockQuery } from '../mocks/cloudMonitoringQuery'; -import { QueryType } from '../types/query'; import { MetricQueryEditor } from './MetricQueryEditor'; diff --git a/public/app/plugins/datasource/cloud-monitoring/components/MetricQueryEditor.tsx b/public/app/plugins/datasource/cloud-monitoring/components/MetricQueryEditor.tsx index 7cd44f61ffb..74a8f211de0 100644 --- a/public/app/plugins/datasource/cloud-monitoring/components/MetricQueryEditor.tsx +++ b/public/app/plugins/datasource/cloud-monitoring/components/MetricQueryEditor.tsx @@ -5,8 +5,9 @@ import { SelectableValue, TimeRange } from '@grafana/data'; import { EditorRows } from '@grafana/plugin-ui'; import { Stack } from '@grafana/ui'; +import { AlignmentTypes, QueryType, TimeSeriesList, TimeSeriesQuery } from '../dataquery.gen'; import CloudMonitoringDatasource from '../datasource'; -import { AlignmentTypes, CloudMonitoringQuery, QueryType, TimeSeriesList, TimeSeriesQuery } from '../types/query'; +import { CloudMonitoringQuery } from '../types/query'; import { CustomMetaData } from '../types/types'; import { AliasBy } from './AliasBy'; diff --git a/public/app/plugins/datasource/cloud-monitoring/components/Preprocessor.test.tsx b/public/app/plugins/datasource/cloud-monitoring/components/Preprocessor.test.tsx index cb3003e9117..837dd8eee80 100644 --- a/public/app/plugins/datasource/cloud-monitoring/components/Preprocessor.test.tsx +++ b/public/app/plugins/datasource/cloud-monitoring/components/Preprocessor.test.tsx @@ -3,9 +3,9 @@ import userEvent from '@testing-library/user-event'; import { CustomVariableModel } from '@grafana/data'; +import { MetricKind, ValueTypes } from '../dataquery.gen'; import { createMockMetricDescriptor } from '../mocks/cloudMonitoringMetricDescriptor'; import { createMockTimeSeriesList } from '../mocks/cloudMonitoringQuery'; -import { MetricKind, ValueTypes } from '../types/query'; import { Preprocessor } from './Preprocessor'; diff --git a/public/app/plugins/datasource/cloud-monitoring/components/Preprocessor.tsx b/public/app/plugins/datasource/cloud-monitoring/components/Preprocessor.tsx index 3ca7d101942..fc7cf80bf78 100644 --- a/public/app/plugins/datasource/cloud-monitoring/components/Preprocessor.tsx +++ b/public/app/plugins/datasource/cloud-monitoring/components/Preprocessor.tsx @@ -4,8 +4,8 @@ import { SelectableValue } from '@grafana/data'; import { EditorField } from '@grafana/plugin-ui'; import { RadioButtonGroup } from '@grafana/ui'; +import { PreprocessorType, TimeSeriesList, MetricKind, ValueTypes } from '../dataquery.gen'; import { getAlignmentPickerData } from '../functions'; -import { PreprocessorType, TimeSeriesList, MetricKind, ValueTypes } from '../types/query'; import { MetricDescriptor } from '../types/types'; const NONE_OPTION = { label: 'None', value: PreprocessorType.None }; diff --git a/public/app/plugins/datasource/cloud-monitoring/components/PromQLEditor.tsx b/public/app/plugins/datasource/cloud-monitoring/components/PromQLEditor.tsx index deaa5e7eaf8..1c97fc1a761 100644 --- a/public/app/plugins/datasource/cloud-monitoring/components/PromQLEditor.tsx +++ b/public/app/plugins/datasource/cloud-monitoring/components/PromQLEditor.tsx @@ -4,9 +4,9 @@ import { SelectableValue } from '@grafana/data'; import { EditorField, EditorRow } from '@grafana/plugin-ui'; import { TextArea, Input } from '@grafana/ui'; +import { PromQLQuery } from '../dataquery.gen'; import CloudMonitoringDatasource from '../datasource'; import { selectors } from '../e2e/selectors'; -import { PromQLQuery } from '../types/query'; import { Project } from './Project'; diff --git a/public/app/plugins/datasource/cloud-monitoring/components/QueryEditor.test.tsx b/public/app/plugins/datasource/cloud-monitoring/components/QueryEditor.test.tsx index 7f410a17e8c..a2b5ac4aa11 100644 --- a/public/app/plugins/datasource/cloud-monitoring/components/QueryEditor.test.tsx +++ b/public/app/plugins/datasource/cloud-monitoring/components/QueryEditor.test.tsx @@ -1,10 +1,10 @@ import { render, waitFor, screen } from '@testing-library/react'; import { select } from 'react-select-event'; +import { QueryType } from '../dataquery.gen'; import { selectors } from '../e2e/selectors'; import { createMockDatasource } from '../mocks/cloudMonitoringDatasource'; import { createMockQuery } from '../mocks/cloudMonitoringQuery'; -import { QueryType } from '../types/query'; import { QueryEditor } from './QueryEditor'; diff --git a/public/app/plugins/datasource/cloud-monitoring/components/QueryEditor.tsx b/public/app/plugins/datasource/cloud-monitoring/components/QueryEditor.tsx index d0578dea9d4..1aa62a43c0c 100644 --- a/public/app/plugins/datasource/cloud-monitoring/components/QueryEditor.tsx +++ b/public/app/plugins/datasource/cloud-monitoring/components/QueryEditor.tsx @@ -6,9 +6,10 @@ import { QueryEditorProps, getDefaultTimeRange, toOption } from '@grafana/data'; import { EditorRows } from '@grafana/plugin-ui'; import { ConfirmModal } from '@grafana/ui'; +import { PromQLQuery, QueryType, SLOQuery } from '../dataquery.gen'; import CloudMonitoringDatasource from '../datasource'; import { selectors } from '../e2e/selectors'; -import { CloudMonitoringQuery, PromQLQuery, QueryType, SLOQuery } from '../types/query'; +import { CloudMonitoringQuery } from '../types/query'; import { CloudMonitoringOptions } from '../types/types'; import { defaultTimeSeriesList, defaultTimeSeriesQuery, MetricQueryEditor } from './MetricQueryEditor'; diff --git a/public/app/plugins/datasource/cloud-monitoring/components/QueryHeader.test.tsx b/public/app/plugins/datasource/cloud-monitoring/components/QueryHeader.test.tsx index 331ad7c4d4b..2de5f4f703a 100644 --- a/public/app/plugins/datasource/cloud-monitoring/components/QueryHeader.test.tsx +++ b/public/app/plugins/datasource/cloud-monitoring/components/QueryHeader.test.tsx @@ -1,8 +1,8 @@ import { render, screen } from '@testing-library/react'; import { openMenu, select } from 'react-select-event'; +import { QueryType } from '../dataquery.gen'; import { createMockQuery } from '../mocks/cloudMonitoringQuery'; -import { QueryType } from '../types/query'; import { QueryHeader } from './QueryHeader'; diff --git a/public/app/plugins/datasource/cloud-monitoring/components/SLO.tsx b/public/app/plugins/datasource/cloud-monitoring/components/SLO.tsx index be4606155d7..1d57172f9b6 100644 --- a/public/app/plugins/datasource/cloud-monitoring/components/SLO.tsx +++ b/public/app/plugins/datasource/cloud-monitoring/components/SLO.tsx @@ -4,8 +4,8 @@ import { SelectableValue } from '@grafana/data'; import { EditorField } from '@grafana/plugin-ui'; import { Select } from '@grafana/ui'; +import { SLOQuery } from '../dataquery.gen'; import CloudMonitoringDatasource from '../datasource'; -import { SLOQuery } from '../types/query'; export interface Props { refId: string; diff --git a/public/app/plugins/datasource/cloud-monitoring/components/SLOQueryEditor.tsx b/public/app/plugins/datasource/cloud-monitoring/components/SLOQueryEditor.tsx index 1a4ec805596..aac2ee3a570 100644 --- a/public/app/plugins/datasource/cloud-monitoring/components/SLOQueryEditor.tsx +++ b/public/app/plugins/datasource/cloud-monitoring/components/SLOQueryEditor.tsx @@ -5,10 +5,10 @@ import { SelectableValue } from '@grafana/data'; import { EditorField, EditorFieldGroup, EditorRow } from '@grafana/plugin-ui'; import { ALIGNMENT_PERIODS, SLO_BURN_RATE_SELECTOR_NAME } from '../constants'; +import { AlignmentTypes, SLOQuery } from '../dataquery.gen'; import CloudMonitoringDatasource from '../datasource'; import { selectors } from '../e2e/selectors'; import { alignmentPeriodLabel } from '../functions'; -import { AlignmentTypes, SLOQuery } from '../types/query'; import { CustomMetaData } from '../types/types'; import { AliasBy } from './AliasBy'; diff --git a/public/app/plugins/datasource/cloud-monitoring/components/Selector.tsx b/public/app/plugins/datasource/cloud-monitoring/components/Selector.tsx index ea0e5903af5..1e41d6917c0 100644 --- a/public/app/plugins/datasource/cloud-monitoring/components/Selector.tsx +++ b/public/app/plugins/datasource/cloud-monitoring/components/Selector.tsx @@ -3,8 +3,8 @@ import { EditorField } from '@grafana/plugin-ui'; import { Select } from '@grafana/ui'; import { SELECTORS } from '../constants'; +import { SLOQuery } from '../dataquery.gen'; import CloudMonitoringDatasource from '../datasource'; -import { SLOQuery } from '../types/query'; export interface Props { refId: string; diff --git a/public/app/plugins/datasource/cloud-monitoring/components/Service.tsx b/public/app/plugins/datasource/cloud-monitoring/components/Service.tsx index 9425437d7b3..03862ac8c8a 100644 --- a/public/app/plugins/datasource/cloud-monitoring/components/Service.tsx +++ b/public/app/plugins/datasource/cloud-monitoring/components/Service.tsx @@ -4,8 +4,8 @@ import { SelectableValue } from '@grafana/data'; import { EditorField } from '@grafana/plugin-ui'; import { Select } from '@grafana/ui'; +import { SLOQuery } from '../dataquery.gen'; import CloudMonitoringDatasource from '../datasource'; -import { SLOQuery } from '../types/query'; export interface Props { refId: string; diff --git a/public/app/plugins/datasource/cloud-monitoring/components/VariableQueryEditor.test.tsx b/public/app/plugins/datasource/cloud-monitoring/components/VariableQueryEditor.test.tsx index e94c8aefae0..64cb2addbea 100644 --- a/public/app/plugins/datasource/cloud-monitoring/components/VariableQueryEditor.test.tsx +++ b/public/app/plugins/datasource/cloud-monitoring/components/VariableQueryEditor.test.tsx @@ -2,8 +2,8 @@ import { render, screen, waitFor } from '@testing-library/react'; import { VariableModel } from '@grafana/data'; +import { MetricFindQueryTypes } from '../dataquery.gen'; import CloudMonitoringDatasource from '../datasource'; -import { MetricFindQueryTypes } from '../types/query'; import { CloudMonitoringVariableQuery } from '../types/types'; import { CloudMonitoringVariableQueryEditor, Props } from './VariableQueryEditor'; diff --git a/public/app/plugins/datasource/cloud-monitoring/components/VariableQueryEditor.tsx b/public/app/plugins/datasource/cloud-monitoring/components/VariableQueryEditor.tsx index 6045e61b37d..c672d115343 100644 --- a/public/app/plugins/datasource/cloud-monitoring/components/VariableQueryEditor.tsx +++ b/public/app/plugins/datasource/cloud-monitoring/components/VariableQueryEditor.tsx @@ -3,9 +3,10 @@ import { PureComponent } from 'react'; import { QueryEditorProps } from '@grafana/data'; import { getTemplateSrv } from '@grafana/runtime'; +import { MetricFindQueryTypes } from '../dataquery.gen'; import CloudMonitoringDatasource from '../datasource'; import { extractServicesFromMetricDescriptors, getLabelKeys, getMetricTypes } from '../functions'; -import { CloudMonitoringQuery, MetricFindQueryTypes } from '../types/query'; +import { CloudMonitoringQuery } from '../types/query'; import { CloudMonitoringOptions, CloudMonitoringVariableQuery, diff --git a/public/app/plugins/datasource/cloud-monitoring/components/VisualMetricQueryEditor.test.tsx b/public/app/plugins/datasource/cloud-monitoring/components/VisualMetricQueryEditor.test.tsx index 05e2f9c3721..c31b43bca1e 100644 --- a/public/app/plugins/datasource/cloud-monitoring/components/VisualMetricQueryEditor.test.tsx +++ b/public/app/plugins/datasource/cloud-monitoring/components/VisualMetricQueryEditor.test.tsx @@ -6,10 +6,10 @@ import { CustomVariableModel, getDefaultTimeRange } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; import { getTemplateSrv } from '@grafana/runtime'; +import { PreprocessorType, MetricKind, ValueTypes } from '../dataquery.gen'; import { createMockDatasource } from '../mocks/cloudMonitoringDatasource'; import { createMockMetricDescriptor } from '../mocks/cloudMonitoringMetricDescriptor'; import { createMockTimeSeriesList } from '../mocks/cloudMonitoringQuery'; -import { PreprocessorType, MetricKind, ValueTypes } from '../types/query'; import { defaultTimeSeriesList } from './MetricQueryEditor'; import { VisualMetricQueryEditor } from './VisualMetricQueryEditor'; diff --git a/public/app/plugins/datasource/cloud-monitoring/components/VisualMetricQueryEditor.tsx b/public/app/plugins/datasource/cloud-monitoring/components/VisualMetricQueryEditor.tsx index 56824f89358..732e7f0c36a 100644 --- a/public/app/plugins/datasource/cloud-monitoring/components/VisualMetricQueryEditor.tsx +++ b/public/app/plugins/datasource/cloud-monitoring/components/VisualMetricQueryEditor.tsx @@ -9,10 +9,10 @@ import { EditorField, EditorFieldGroup, EditorRow } from '@grafana/plugin-ui'; import { reportInteraction } from '@grafana/runtime'; import { getSelectStyles, Select, AsyncSelect, useStyles2, useTheme2 } from '@grafana/ui'; +import { PreprocessorType, TimeSeriesList, MetricKind, ValueTypes } from '../dataquery.gen'; import CloudMonitoringDatasource from '../datasource'; import { selectors } from '../e2e/selectors'; import { getAlignmentPickerData, getMetricType, setMetricType } from '../functions'; -import { PreprocessorType, TimeSeriesList, MetricKind, ValueTypes } from '../types/query'; import { CustomMetaData, MetricDescriptor } from '../types/types'; import { AliasBy } from './AliasBy'; diff --git a/public/app/plugins/datasource/cloud-monitoring/constants.ts b/public/app/plugins/datasource/cloud-monitoring/constants.ts index 6560e6feb6b..40c7988ba2f 100644 --- a/public/app/plugins/datasource/cloud-monitoring/constants.ts +++ b/public/app/plugins/datasource/cloud-monitoring/constants.ts @@ -1,4 +1,4 @@ -import { QueryType, MetricKind, ValueTypes } from './types/query'; +import { QueryType, MetricKind, ValueTypes } from './dataquery.gen'; // not super excited about using uneven numbers, but this makes it align perfectly with rows that has two fields export const INPUT_WIDTH = 71; diff --git a/public/app/plugins/datasource/cloud-monitoring/datasource.test.ts b/public/app/plugins/datasource/cloud-monitoring/datasource.test.ts index a87d4e266d0..7700cd7a6d1 100644 --- a/public/app/plugins/datasource/cloud-monitoring/datasource.test.ts +++ b/public/app/plugins/datasource/cloud-monitoring/datasource.test.ts @@ -4,10 +4,11 @@ import { lastValueFrom, of } from 'rxjs'; import { CustomVariableModel, ScopedVars } from '@grafana/data'; import { getTemplateSrv } from '@grafana/runtime'; +import { PreprocessorType, QueryType, MetricKind } from './dataquery.gen'; import Datasource from './datasource'; import { createMockInstanceSetttings } from './mocks/cloudMonitoringInstanceSettings'; import { createMockQuery } from './mocks/cloudMonitoringQuery'; -import { CloudMonitoringQuery, PreprocessorType, QueryType, MetricKind } from './types/query'; +import { CloudMonitoringQuery } from './types/query'; let getTempVars = () => [] as CustomVariableModel[]; let replace = () => ''; diff --git a/public/app/plugins/datasource/cloud-monitoring/datasource.ts b/public/app/plugins/datasource/cloud-monitoring/datasource.ts index 9dc3f83262d..e030de33612 100644 --- a/public/app/plugins/datasource/cloud-monitoring/datasource.ts +++ b/public/app/plugins/datasource/cloud-monitoring/datasource.ts @@ -23,8 +23,9 @@ import { import { CloudMonitoringAnnotationSupport } from './annotationSupport'; import { SLO_BURN_RATE_SELECTOR_NAME } from './constants'; +import { QueryType, MetricQuery, Filter } from './dataquery.gen'; import { getMetricType, setMetricType } from './functions'; -import { CloudMonitoringQuery, QueryType, MetricQuery, Filter } from './types/query'; +import { CloudMonitoringQuery } from './types/query'; import { CloudMonitoringOptions, MetricDescriptor, PostResponse, Aggregation } from './types/types'; import { CloudMonitoringVariableSupport } from './variables'; diff --git a/public/app/plugins/datasource/cloud-monitoring/functions.test.ts b/public/app/plugins/datasource/cloud-monitoring/functions.test.ts index 5fad032253a..97c5251dfcc 100644 --- a/public/app/plugins/datasource/cloud-monitoring/functions.test.ts +++ b/public/app/plugins/datasource/cloud-monitoring/functions.test.ts @@ -1,4 +1,5 @@ import { AGGREGATIONS, SYSTEM_LABELS } from './constants'; +import { AlignmentTypes, TimeSeriesList, MetricKind, ValueTypes } from './dataquery.gen'; import { extractServicesFromMetricDescriptors, getAggregationOptionsByMetric, @@ -14,7 +15,6 @@ import { setMetricType, } from './functions'; import { newMockDatasource } from './specs/testData'; -import { AlignmentTypes, TimeSeriesList, MetricKind, ValueTypes } from './types/query'; import { MetricDescriptor } from './types/types'; jest.mock('@grafana/runtime', () => ({ diff --git a/public/app/plugins/datasource/cloud-monitoring/functions.ts b/public/app/plugins/datasource/cloud-monitoring/functions.ts index 13e0aceb1fe..9a9e64581c1 100644 --- a/public/app/plugins/datasource/cloud-monitoring/functions.ts +++ b/public/app/plugins/datasource/cloud-monitoring/functions.ts @@ -4,8 +4,8 @@ import { rangeUtil } from '@grafana/data'; import { getTemplateSrv, TemplateSrv } from '@grafana/runtime'; import { AGGREGATIONS, ALIGNMENTS, SYSTEM_LABELS } from './constants'; +import { AlignmentTypes, PreprocessorType, TimeSeriesList, MetricKind, ValueTypes } from './dataquery.gen'; import CloudMonitoringDatasource from './datasource'; -import { AlignmentTypes, PreprocessorType, TimeSeriesList, MetricKind, ValueTypes } from './types/query'; import { CustomMetaData, MetricDescriptor } from './types/types'; export const extractServicesFromMetricDescriptors = (metricDescriptors: MetricDescriptor[]) => diff --git a/public/app/plugins/datasource/cloud-monitoring/mocks/cloudMonitoringMetricDescriptor.ts b/public/app/plugins/datasource/cloud-monitoring/mocks/cloudMonitoringMetricDescriptor.ts index bb77e381b24..52dd7e09f5c 100644 --- a/public/app/plugins/datasource/cloud-monitoring/mocks/cloudMonitoringMetricDescriptor.ts +++ b/public/app/plugins/datasource/cloud-monitoring/mocks/cloudMonitoringMetricDescriptor.ts @@ -1,4 +1,4 @@ -import { MetricKind, ValueTypes } from '../types/query'; +import { MetricKind, ValueTypes } from '../dataquery.gen'; import { MetricDescriptor } from '../types/types'; export const createMockMetricDescriptor = (overrides?: Partial): MetricDescriptor => { diff --git a/public/app/plugins/datasource/cloud-monitoring/mocks/cloudMonitoringQuery.ts b/public/app/plugins/datasource/cloud-monitoring/mocks/cloudMonitoringQuery.ts index 77b015756ed..6862f2ee0b9 100644 --- a/public/app/plugins/datasource/cloud-monitoring/mocks/cloudMonitoringQuery.ts +++ b/public/app/plugins/datasource/cloud-monitoring/mocks/cloudMonitoringQuery.ts @@ -1,11 +1,5 @@ -import { - AlignmentTypes, - CloudMonitoringQuery, - QueryType, - SLOQuery, - TimeSeriesList, - TimeSeriesQuery, -} from '../types/query'; +import { AlignmentTypes, QueryType, SLOQuery, TimeSeriesList, TimeSeriesQuery } from '../dataquery.gen'; +import { CloudMonitoringQuery } from '../types/query'; type Subset = { [attr in keyof K]?: K[attr] extends object ? Subset : K[attr]; diff --git a/public/app/plugins/datasource/cloud-monitoring/module.ts b/public/app/plugins/datasource/cloud-monitoring/module.ts index 6818e0d8763..aa3b77527e6 100644 --- a/public/app/plugins/datasource/cloud-monitoring/module.ts +++ b/public/app/plugins/datasource/cloud-monitoring/module.ts @@ -7,10 +7,11 @@ import CloudMonitoringCheatSheet from './components/CloudMonitoringCheatSheet'; import { ConfigEditor } from './components/ConfigEditor/ConfigEditor'; import { QueryEditor } from './components/QueryEditor'; import { CloudMonitoringVariableQueryEditor } from './components/VariableQueryEditor'; +import { QueryType } from './dataquery.gen'; import CloudMonitoringDatasource from './datasource'; import pluginJson from './plugin.json'; import { trackCloudMonitoringDashboardLoaded } from './tracking'; -import { CloudMonitoringQuery, QueryType } from './types/query'; +import { CloudMonitoringQuery } from './types/query'; export const plugin = new DataSourcePlugin(CloudMonitoringDatasource) .setQueryEditorHelp(CloudMonitoringCheatSheet) diff --git a/public/app/plugins/datasource/cloud-monitoring/types/query.ts b/public/app/plugins/datasource/cloud-monitoring/types/query.ts index a202c85bd3e..52f8f722455 100644 --- a/public/app/plugins/datasource/cloud-monitoring/types/query.ts +++ b/public/app/plugins/datasource/cloud-monitoring/types/query.ts @@ -1,17 +1,5 @@ import { CloudMonitoringQuery as CloudMonitoringQueryBase, QueryType } from '../dataquery.gen'; -export { QueryType }; -export { PreprocessorType, MetricKind, AlignmentTypes, ValueTypes, MetricFindQueryTypes } from '../dataquery.gen'; -export type { - TimeSeriesQuery, - SLOQuery, - TimeSeriesList, - MetricQuery, - PromQLQuery, - LegacyCloudMonitoringAnnotationQuery, - Filter, -} from '../dataquery.gen'; - /** * Represents the query as it moves through the frontend query editor and datasource files. * It can represent new queries that are still being edited, so all properties are optional diff --git a/public/app/plugins/datasource/cloud-monitoring/types/types.ts b/public/app/plugins/datasource/cloud-monitoring/types/types.ts index c7eb809c739..849f72ea69b 100644 --- a/public/app/plugins/datasource/cloud-monitoring/types/types.ts +++ b/public/app/plugins/datasource/cloud-monitoring/types/types.ts @@ -1,7 +1,7 @@ import { DataQuery, SelectableValue, VariableWithMultiSupport } from '@grafana/data'; import { DataSourceOptions, DataSourceSecureJsonData } from '@grafana/google-sdk'; -import { MetricKind } from './query'; +import { MetricKind } from '../dataquery.gen'; export interface CloudMonitoringVariableQuery extends DataQuery { selectedQueryType: string; From dcd20862121f3f19c3c74040f7e9e656394670cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20H=C3=A4ggmark?= Date: Thu, 4 Dec 2025 06:06:41 +0100 Subject: [PATCH 011/110] chore: reduce cloudwatch barrel files (#114568) --- eslint-suppressions.json | 8 -------- .../dashboard/state/DashboardMigrator.ts | 3 ++- .../cloudwatch/annotationSupport.test.ts | 3 ++- .../cloudwatch/annotationSupport.ts | 3 ++- .../AnnotationQueryEditor.test.tsx | 3 ++- .../AnnotationQueryEditor.tsx | 3 ++- .../components/CheatSheet/LogsCheatSheet.tsx | 3 ++- .../components/CheatSheet/sampleQueries.ts | 2 +- .../LogsQueryEditor/CloudWatchLink.test.tsx | 2 +- .../LogsQueryEditor/CloudWatchLink.tsx | 2 +- .../LogsQueryEditor/LogsQueryEditor.tsx | 3 ++- .../LogsQueryEditor/LogsQueryField.tsx | 3 ++- .../code-editors/LogsQLCodeEditor.tsx | 2 +- .../code-editors/PPLQueryEditor.tsx | 2 +- .../code-editors/SQLCodeEditor.tsx | 2 +- .../MetricsQueryEditor.test.tsx | 3 ++- .../MetricsQueryEditor/MetricsQueryEditor.tsx | 10 ++-------- .../SQLBuilderEditor.test.tsx | 10 ++++++++-- .../SQLBuilderEditor/SQLBuilderEditor.tsx | 2 +- .../SQLBuilderSelectRow.test.tsx | 10 ++++++++-- .../SQLBuilderEditor/SQLBuilderSelectRow.tsx | 2 +- .../SQLBuilderEditor/SQLFilter.tsx | 12 +++++------ .../SQLBuilderEditor/SQLGroupBy.test.tsx | 2 +- .../SQLBuilderEditor/SQLGroupBy.tsx | 6 +++--- .../SQLBuilderEditor/SQLOrderByGroup.tsx | 2 +- .../SQLBuilderEditor/utils.ts | 8 +++++--- .../QueryEditor/QueryEditor.test.tsx | 3 ++- .../components/QueryEditor/QueryHeader.tsx | 3 ++- .../shared/Dimensions/Dimensions.test.tsx | 2 +- .../shared/Dimensions/Dimensions.tsx | 2 +- .../shared/Dimensions/FilterItem.test.tsx | 2 +- .../shared/Dimensions/FilterItem.tsx | 2 +- .../shared/LogGroups/LogGroupsField.tsx | 2 +- .../shared/LogGroups/LogGroupsSelector.tsx | 2 +- .../LogGroups/SelectedLogGroups.test.tsx | 2 +- .../shared/LogGroups/SelectedLogGroups.tsx | 2 +- .../MetricStatEditor.test.tsx | 2 +- .../MetricStatEditor/MetricStatEditor.tsx | 2 +- .../datasource/cloudwatch/datasource.test.ts | 18 ++++++++--------- .../datasource/cloudwatch/datasource.ts | 15 +++++++------- .../datasource/cloudwatch/defaultQueries.ts | 5 ++--- .../datasource/cloudwatch/expressions.ts | 11 ---------- .../plugins/datasource/cloudwatch/guards.ts | 4 ++-- .../completion/CompletionItemProvider.test.ts | 3 ++- .../completion/CompletionItemProvider.ts | 2 +- .../CloudWatchLogsLanguageProvider.ts | 3 ++- .../PPLCompletionItemProvider.test.ts | 3 ++- .../completion/PPLCompletionItemProvider.ts | 2 +- .../cloudwatch-sql/SQLGenerator.test.ts | 3 +-- .../language/cloudwatch-sql/SQLGenerator.ts | 6 +++--- .../completion/CompletionItemProvider.test.ts | 3 ++- .../logs/completion/CompletionItemProvider.ts | 2 +- .../migrations/dashboardMigrations.test.ts | 3 ++- .../migrations/dashboardMigrations.ts | 3 ++- .../migrations/metricQueryMigrations.test.ts | 2 +- .../migrations/metricQueryMigrations.ts | 2 +- .../useMigratedMetricsQuery.test.ts | 2 +- .../migrations/useMigratedMetricsQuery.ts | 2 +- .../migrations/variableQueryMigrations.ts | 3 ++- .../datasource/cloudwatch/mocks/Request.ts | 3 ++- .../datasource/cloudwatch/mocks/queries.ts | 9 +++++++-- .../datasource/cloudwatch/mocks/sqlUtils.ts | 4 ++-- .../CloudWatchAnnotationQueryRunner.test.ts | 2 +- .../CloudWatchAnnotationQueryRunner.ts | 3 ++- .../CloudWatchLogsQueryRunner.test.ts | 2 +- .../query-runner/CloudWatchLogsQueryRunner.ts | 5 +---- .../CloudWatchMetricsQueryRunner.test.ts | 2 +- .../CloudWatchMetricsQueryRunner.ts | 3 ++- .../query-runner/CloudWatchRequest.ts | 3 ++- .../datasource/cloudwatch/resources/types.ts | 2 +- .../plugins/datasource/cloudwatch/tracking.ts | 20 +++++++++---------- .../plugins/datasource/cloudwatch/types.ts | 2 -- .../datasource/cloudwatch/utils/datalinks.ts | 3 ++- .../datasource/cloudwatch/utils/utils.ts | 2 +- 74 files changed, 153 insertions(+), 146 deletions(-) diff --git a/eslint-suppressions.json b/eslint-suppressions.json index 4cb2d651b50..318b3faa31a 100644 --- a/eslint-suppressions.json +++ b/eslint-suppressions.json @@ -3695,11 +3695,6 @@ "count": 1 } }, - "public/app/plugins/datasource/cloudwatch/expressions.ts": { - "no-barrel-files/no-barrel-files": { - "count": 1 - } - }, "public/app/plugins/datasource/cloudwatch/guards.ts": { "@typescript-eslint/consistent-type-assertions": { "count": 1 @@ -3713,9 +3708,6 @@ "public/app/plugins/datasource/cloudwatch/types.ts": { "@typescript-eslint/no-explicit-any": { "count": 7 - }, - "no-barrel-files/no-barrel-files": { - "count": 1 } }, "public/app/plugins/datasource/cloudwatch/utils/datalinks.ts": { diff --git a/public/app/features/dashboard/state/DashboardMigrator.ts b/public/app/features/dashboard/state/DashboardMigrator.ts index b6e9634da87..7355f7575b7 100644 --- a/public/app/features/dashboard/state/DashboardMigrator.ts +++ b/public/app/features/dashboard/state/DashboardMigrator.ts @@ -49,7 +49,8 @@ import { } from 'app/features/transformers/timeSeriesTable/timeSeriesTableTransformer'; import { isConstant, isMulti } from 'app/features/variables/guard'; import { alignCurrentWithMulti } from 'app/features/variables/shared/multiOptions'; -import { CloudWatchMetricsQuery, LegacyAnnotationQuery } from 'app/plugins/datasource/cloudwatch/types'; +import { CloudWatchMetricsQuery } from 'app/plugins/datasource/cloudwatch/dataquery.gen'; +import { LegacyAnnotationQuery } from 'app/plugins/datasource/cloudwatch/types'; import { MIXED_DATASOURCE_NAME } from 'app/plugins/datasource/mixed/MixedDataSource'; import { diff --git a/public/app/plugins/datasource/cloudwatch/annotationSupport.test.ts b/public/app/plugins/datasource/cloudwatch/annotationSupport.test.ts index 6bd10edb54c..3b94647b4ac 100644 --- a/public/app/plugins/datasource/cloudwatch/annotationSupport.test.ts +++ b/public/app/plugins/datasource/cloudwatch/annotationSupport.test.ts @@ -1,7 +1,8 @@ import { AnnotationQuery } from '@grafana/data'; import { CloudWatchAnnotationSupport } from './annotationSupport'; -import { CloudWatchAnnotationQuery, LegacyAnnotationQuery } from './types'; +import { CloudWatchAnnotationQuery } from './dataquery.gen'; +import { LegacyAnnotationQuery } from './types'; const metricStatAnnotationQuery: CloudWatchAnnotationQuery = { queryMode: 'Annotations', diff --git a/public/app/plugins/datasource/cloudwatch/annotationSupport.ts b/public/app/plugins/datasource/cloudwatch/annotationSupport.ts index 4c1d91746bd..23d72ad3721 100644 --- a/public/app/plugins/datasource/cloudwatch/annotationSupport.ts +++ b/public/app/plugins/datasource/cloudwatch/annotationSupport.ts @@ -1,9 +1,10 @@ import { AnnotationQuery } from '@grafana/data'; import { AnnotationQueryEditor } from './components/AnnotationQueryEditor/AnnotationQueryEditor'; +import { CloudWatchAnnotationQuery } from './dataquery.gen'; import { DEFAULT_ANNOTATIONS_QUERY } from './defaultQueries'; import { isCloudWatchAnnotation } from './guards'; -import { CloudWatchAnnotationQuery, CloudWatchQuery, LegacyAnnotationQuery } from './types'; +import { CloudWatchQuery, LegacyAnnotationQuery } from './types'; export const CloudWatchAnnotationSupport = { // converts legacy angular style queries to new format. Also sets the same default values as in the deprecated angular directive diff --git a/public/app/plugins/datasource/cloudwatch/components/AnnotationQueryEditor/AnnotationQueryEditor.test.tsx b/public/app/plugins/datasource/cloudwatch/components/AnnotationQueryEditor/AnnotationQueryEditor.test.tsx index 9178eb849c4..6033c4bba72 100644 --- a/public/app/plugins/datasource/cloudwatch/components/AnnotationQueryEditor/AnnotationQueryEditor.test.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/AnnotationQueryEditor/AnnotationQueryEditor.test.tsx @@ -3,9 +3,10 @@ import '@testing-library/jest-dom'; import { QueryEditorProps } from '@grafana/data'; +import { CloudWatchAnnotationQuery, CloudWatchMetricsQuery } from '../../dataquery.gen'; import { CloudWatchDatasource } from '../../datasource'; import { setupMockedDataSource } from '../../mocks/CloudWatchDataSource'; -import { CloudWatchAnnotationQuery, CloudWatchJsonData, CloudWatchMetricsQuery, CloudWatchQuery } from '../../types'; +import { CloudWatchJsonData, CloudWatchQuery } from '../../types'; import { AnnotationQueryEditor } from './AnnotationQueryEditor'; diff --git a/public/app/plugins/datasource/cloudwatch/components/AnnotationQueryEditor/AnnotationQueryEditor.tsx b/public/app/plugins/datasource/cloudwatch/components/AnnotationQueryEditor/AnnotationQueryEditor.tsx index 9d3a061c76d..df62a07d40e 100644 --- a/public/app/plugins/datasource/cloudwatch/components/AnnotationQueryEditor/AnnotationQueryEditor.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/AnnotationQueryEditor/AnnotationQueryEditor.tsx @@ -4,10 +4,11 @@ import { QueryEditorProps } from '@grafana/data'; import { EditorField, EditorHeader, EditorRow, EditorSwitch, InlineSelect } from '@grafana/plugin-ui'; import { Alert, Input, Space } from '@grafana/ui'; +import { MetricStat } from '../../dataquery.gen'; import { CloudWatchDatasource } from '../../datasource'; import { isCloudWatchAnnotationQuery } from '../../guards'; import { useRegions } from '../../hooks'; -import { CloudWatchJsonData, CloudWatchQuery, MetricStat } from '../../types'; +import { CloudWatchJsonData, CloudWatchQuery } from '../../types'; import { MetricStatEditor } from '../shared/MetricStatEditor/MetricStatEditor'; export type Props = QueryEditorProps; diff --git a/public/app/plugins/datasource/cloudwatch/components/CheatSheet/LogsCheatSheet.tsx b/public/app/plugins/datasource/cloudwatch/components/CheatSheet/LogsCheatSheet.tsx index 082a8eaf44a..f714992aa7d 100644 --- a/public/app/plugins/datasource/cloudwatch/components/CheatSheet/LogsCheatSheet.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/CheatSheet/LogsCheatSheet.tsx @@ -6,8 +6,9 @@ import { GrafanaTheme2 } from '@grafana/data'; import { Collapse, useStyles2, Text, TextLink } from '@grafana/ui'; import { flattenTokens } from '@grafana/ui/internal'; +import { CloudWatchLogsQuery, LogsQueryLanguage } from '../../dataquery.gen'; import { trackSampleQuerySelection } from '../../tracking'; -import { CloudWatchLogsQuery, CloudWatchQuery, LogsQueryLanguage } from '../../types'; +import { CloudWatchQuery } from '../../types'; import * as sampleQueries from './sampleQueries'; import { cwliTokenizer, pplTokenizer, sqlTokenizer } from './tokenizer'; diff --git a/public/app/plugins/datasource/cloudwatch/components/CheatSheet/sampleQueries.ts b/public/app/plugins/datasource/cloudwatch/components/CheatSheet/sampleQueries.ts index 4c67fe68c9b..5800ba42c82 100644 --- a/public/app/plugins/datasource/cloudwatch/components/CheatSheet/sampleQueries.ts +++ b/public/app/plugins/datasource/cloudwatch/components/CheatSheet/sampleQueries.ts @@ -1,6 +1,6 @@ import { stripIndents } from 'common-tags'; -import { LogsQueryLanguage } from '../../types'; +import { LogsQueryLanguage } from '../../dataquery.gen'; export interface SampleQuery { title: string; diff --git a/public/app/plugins/datasource/cloudwatch/components/QueryEditor/LogsQueryEditor/CloudWatchLink.test.tsx b/public/app/plugins/datasource/cloudwatch/components/QueryEditor/LogsQueryEditor/CloudWatchLink.test.tsx index 8c2ed9adb1f..ea291ea8a4d 100644 --- a/public/app/plugins/datasource/cloudwatch/components/QueryEditor/LogsQueryEditor/CloudWatchLink.test.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/QueryEditor/LogsQueryEditor/CloudWatchLink.test.tsx @@ -2,10 +2,10 @@ import { act, render, screen, waitFor } from '@testing-library/react'; import { LoadingState } from '@grafana/data'; +import { CloudWatchLogsQuery } from '../../../dataquery.gen'; import { setupMockedDataSource } from '../../../mocks/CloudWatchDataSource'; import { RequestMock } from '../../../mocks/Request'; import { validLogsQuery } from '../../../mocks/queries'; -import { CloudWatchLogsQuery } from '../../../types'; import { CloudWatchLink } from './CloudWatchLink'; diff --git a/public/app/plugins/datasource/cloudwatch/components/QueryEditor/LogsQueryEditor/CloudWatchLink.tsx b/public/app/plugins/datasource/cloudwatch/components/QueryEditor/LogsQueryEditor/CloudWatchLink.tsx index 957fe7981ca..107c6c28157 100644 --- a/public/app/plugins/datasource/cloudwatch/components/QueryEditor/LogsQueryEditor/CloudWatchLink.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/QueryEditor/LogsQueryEditor/CloudWatchLink.tsx @@ -5,8 +5,8 @@ import { PanelData } from '@grafana/data'; import { LinkButton } from '@grafana/ui'; import { AwsUrl, encodeUrl } from '../../../aws_url'; +import { CloudWatchLogsQuery } from '../../../dataquery.gen'; import { CloudWatchDatasource } from '../../../datasource'; -import { CloudWatchLogsQuery } from '../../../types'; interface Props { query: CloudWatchLogsQuery; diff --git a/public/app/plugins/datasource/cloudwatch/components/QueryEditor/LogsQueryEditor/LogsQueryEditor.tsx b/public/app/plugins/datasource/cloudwatch/components/QueryEditor/LogsQueryEditor/LogsQueryEditor.tsx index 96158987b68..516a0c4e6d0 100644 --- a/public/app/plugins/datasource/cloudwatch/components/QueryEditor/LogsQueryEditor/LogsQueryEditor.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/QueryEditor/LogsQueryEditor/LogsQueryEditor.tsx @@ -4,9 +4,10 @@ import { useEffectOnce } from 'react-use'; import { QueryEditorProps, SelectableValue } from '@grafana/data'; import { InlineSelect } from '@grafana/plugin-ui'; +import { CloudWatchLogsQuery, LogsMode, LogsQueryLanguage } from '../../../dataquery.gen'; import { CloudWatchDatasource } from '../../../datasource'; import { DEFAULT_CWLI_QUERY_STRING, DEFAULT_PPL_QUERY_STRING, DEFAULT_SQL_QUERY_STRING } from '../../../defaultQueries'; -import { CloudWatchJsonData, CloudWatchLogsQuery, CloudWatchQuery, LogsMode, LogsQueryLanguage } from '../../../types'; +import { CloudWatchQuery, CloudWatchJsonData } from '../../../types'; import { CloudWatchLink } from './CloudWatchLink'; import { LogsAnomaliesQueryEditor } from './LogsAnomaliesQueryEditor'; diff --git a/public/app/plugins/datasource/cloudwatch/components/QueryEditor/LogsQueryEditor/LogsQueryField.tsx b/public/app/plugins/datasource/cloudwatch/components/QueryEditor/LogsQueryEditor/LogsQueryField.tsx index 111084dbb1e..72e5bb702b0 100644 --- a/public/app/plugins/datasource/cloudwatch/components/QueryEditor/LogsQueryEditor/LogsQueryField.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/QueryEditor/LogsQueryEditor/LogsQueryField.tsx @@ -4,8 +4,9 @@ import { ReactNode, useCallback } from 'react'; import { GrafanaTheme2, QueryEditorProps } from '@grafana/data'; import { useStyles2 } from '@grafana/ui'; +import { CloudWatchLogsQuery, LogsQueryLanguage } from '../../../dataquery.gen'; import { CloudWatchDatasource } from '../../../datasource'; -import { CloudWatchJsonData, CloudWatchLogsQuery, CloudWatchQuery, LogsQueryLanguage } from '../../../types'; +import { CloudWatchJsonData, CloudWatchQuery } from '../../../types'; import { LogGroupsFieldWrapper } from '../../shared/LogGroups/LogGroupsField'; import { LogsQLCodeEditor } from './code-editors/LogsQLCodeEditor'; diff --git a/public/app/plugins/datasource/cloudwatch/components/QueryEditor/LogsQueryEditor/code-editors/LogsQLCodeEditor.tsx b/public/app/plugins/datasource/cloudwatch/components/QueryEditor/LogsQueryEditor/code-editors/LogsQLCodeEditor.tsx index dec250d42fd..399d2ff21c3 100644 --- a/public/app/plugins/datasource/cloudwatch/components/QueryEditor/LogsQueryEditor/code-editors/LogsQLCodeEditor.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/QueryEditor/LogsQueryEditor/code-editors/LogsQLCodeEditor.tsx @@ -3,11 +3,11 @@ import { useCallback, useRef } from 'react'; import { CodeEditor, Monaco } from '@grafana/ui'; +import { CloudWatchLogsQuery } from '../../../../dataquery.gen'; import { CloudWatchDatasource } from '../../../../datasource'; import language from '../../../../language/logs/definition'; import { TRIGGER_SUGGEST } from '../../../../language/monarch/commands'; import { registerLanguage, reRegisterCompletionProvider } from '../../../../language/monarch/register'; -import { CloudWatchLogsQuery } from '../../../../types'; import { getStatsGroups } from '../../../../utils/query/getStatsGroups'; import { codeEditorCommonProps } from './PPLQueryEditor'; diff --git a/public/app/plugins/datasource/cloudwatch/components/QueryEditor/LogsQueryEditor/code-editors/PPLQueryEditor.tsx b/public/app/plugins/datasource/cloudwatch/components/QueryEditor/LogsQueryEditor/code-editors/PPLQueryEditor.tsx index 84ff7e7738c..6c17005ee78 100644 --- a/public/app/plugins/datasource/cloudwatch/components/QueryEditor/LogsQueryEditor/code-editors/PPLQueryEditor.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/QueryEditor/LogsQueryEditor/code-editors/PPLQueryEditor.tsx @@ -4,11 +4,11 @@ import { useCallback, useRef } from 'react'; import { CodeEditor, Monaco } from '@grafana/ui'; import { CodeEditorProps } from '@grafana/ui/internal'; +import { CloudWatchLogsQuery } from '../../../../dataquery.gen'; import { CloudWatchDatasource } from '../../../../datasource'; import language from '../../../../language/cloudwatch-ppl/definition'; import { TRIGGER_SUGGEST } from '../../../../language/monarch/commands'; import { registerLanguage, reRegisterCompletionProvider } from '../../../../language/monarch/register'; -import { CloudWatchLogsQuery } from '../../../../types'; import { getStatsGroups } from '../../../../utils/query/getStatsGroups'; export const codeEditorCommonProps: Partial = { diff --git a/public/app/plugins/datasource/cloudwatch/components/QueryEditor/LogsQueryEditor/code-editors/SQLCodeEditor.tsx b/public/app/plugins/datasource/cloudwatch/components/QueryEditor/LogsQueryEditor/code-editors/SQLCodeEditor.tsx index 59abe051f76..e5b82121483 100644 --- a/public/app/plugins/datasource/cloudwatch/components/QueryEditor/LogsQueryEditor/code-editors/SQLCodeEditor.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/QueryEditor/LogsQueryEditor/code-editors/SQLCodeEditor.tsx @@ -3,11 +3,11 @@ import { useCallback, useRef } from 'react'; import { CodeEditor, Monaco } from '@grafana/ui'; +import { CloudWatchLogsQuery } from '../../../../dataquery.gen'; import { CloudWatchDatasource } from '../../../../datasource'; import language from '../../../../language/cloudwatch-logs-sql/definition'; import { TRIGGER_SUGGEST } from '../../../../language/monarch/commands'; import { registerLanguage, reRegisterCompletionProvider } from '../../../../language/monarch/register'; -import { CloudWatchLogsQuery } from '../../../../types'; import { getStatsGroups } from '../../../../utils/query/getStatsGroups'; import { codeEditorCommonProps } from './PPLQueryEditor'; diff --git a/public/app/plugins/datasource/cloudwatch/components/QueryEditor/MetricsQueryEditor/MetricsQueryEditor.test.tsx b/public/app/plugins/datasource/cloudwatch/components/QueryEditor/MetricsQueryEditor/MetricsQueryEditor.test.tsx index bca545ec45f..1aff9157f4c 100644 --- a/public/app/plugins/datasource/cloudwatch/components/QueryEditor/MetricsQueryEditor/MetricsQueryEditor.test.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/QueryEditor/MetricsQueryEditor/MetricsQueryEditor.test.tsx @@ -5,10 +5,11 @@ import { CustomVariableModel, DataSourceInstanceSettings } from '@grafana/data'; // eslint-disable-next-line no-restricted-imports import * as ui from '@grafana/ui'; +import { CloudWatchMetricsQuery, MetricEditorMode, MetricQueryType } from '../../../dataquery.gen'; import { CloudWatchDatasource } from '../../../datasource'; import { setupMockedTemplateService } from '../../../mocks/CloudWatchDataSource'; import { initialVariableModelState } from '../../../mocks/CloudWatchVariables'; -import { CloudWatchJsonData, CloudWatchMetricsQuery, MetricEditorMode, MetricQueryType } from '../../../types'; +import { CloudWatchJsonData } from '../../../types'; import { MetricsQueryEditor, Props } from './MetricsQueryEditor'; diff --git a/public/app/plugins/datasource/cloudwatch/components/QueryEditor/MetricsQueryEditor/MetricsQueryEditor.tsx b/public/app/plugins/datasource/cloudwatch/components/QueryEditor/MetricsQueryEditor/MetricsQueryEditor.tsx index db88bda25cd..c9625bbddb0 100644 --- a/public/app/plugins/datasource/cloudwatch/components/QueryEditor/MetricsQueryEditor/MetricsQueryEditor.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/QueryEditor/MetricsQueryEditor/MetricsQueryEditor.tsx @@ -6,17 +6,11 @@ import { EditorField, EditorRow, InlineSelect } from '@grafana/plugin-ui'; import { config } from '@grafana/runtime'; import { ConfirmModal, Input, RadioButtonGroup, Space } from '@grafana/ui'; +import { CloudWatchMetricsQuery, MetricEditorMode, MetricQueryType, MetricStat } from '../../../dataquery.gen'; import { CloudWatchDatasource } from '../../../datasource'; import { DEFAULT_METRICS_QUERY } from '../../../defaultQueries'; import useMigratedMetricsQuery from '../../../migrations/useMigratedMetricsQuery'; -import { - CloudWatchJsonData, - CloudWatchMetricsQuery, - CloudWatchQuery, - MetricEditorMode, - MetricQueryType, - MetricStat, -} from '../../../types'; +import { CloudWatchQuery, CloudWatchJsonData } from '../../../types'; import { MetricStatEditor } from '../../shared/MetricStatEditor/MetricStatEditor'; import { DynamicLabelsField } from './DynamicLabelsField'; diff --git a/public/app/plugins/datasource/cloudwatch/components/QueryEditor/MetricsQueryEditor/SQLBuilderEditor/SQLBuilderEditor.test.tsx b/public/app/plugins/datasource/cloudwatch/components/QueryEditor/MetricsQueryEditor/SQLBuilderEditor/SQLBuilderEditor.test.tsx index 2e3c8e16403..bad9162ae87 100644 --- a/public/app/plugins/datasource/cloudwatch/components/QueryEditor/MetricsQueryEditor/SQLBuilderEditor/SQLBuilderEditor.test.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/QueryEditor/MetricsQueryEditor/SQLBuilderEditor/SQLBuilderEditor.test.tsx @@ -1,8 +1,14 @@ import { render, screen, waitFor } from '@testing-library/react'; -import { QueryEditorExpressionType, QueryEditorPropertyType } from '../../../../expressions'; +import { + CloudWatchMetricsQuery, + MetricEditorMode, + MetricQueryType, + SQLExpression, + QueryEditorExpressionType, + QueryEditorPropertyType, +} from '../../../../dataquery.gen'; import { setupMockedDataSource } from '../../../../mocks/CloudWatchDataSource'; -import { CloudWatchMetricsQuery, MetricEditorMode, MetricQueryType, SQLExpression } from '../../../../types'; import { SQLBuilderEditor } from './SQLBuilderEditor'; diff --git a/public/app/plugins/datasource/cloudwatch/components/QueryEditor/MetricsQueryEditor/SQLBuilderEditor/SQLBuilderEditor.tsx b/public/app/plugins/datasource/cloudwatch/components/QueryEditor/MetricsQueryEditor/SQLBuilderEditor/SQLBuilderEditor.tsx index 93ff8e4d4e4..adb81c609bc 100644 --- a/public/app/plugins/datasource/cloudwatch/components/QueryEditor/MetricsQueryEditor/SQLBuilderEditor/SQLBuilderEditor.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/QueryEditor/MetricsQueryEditor/SQLBuilderEditor/SQLBuilderEditor.tsx @@ -4,9 +4,9 @@ import * as React from 'react'; import { EditorField, EditorRow, EditorRows } from '@grafana/plugin-ui'; import { Input } from '@grafana/ui'; +import { CloudWatchMetricsQuery } from '../../../../dataquery.gen'; import { CloudWatchDatasource } from '../../../../datasource'; import SQLGenerator from '../../../../language/cloudwatch-sql/SQLGenerator'; -import { CloudWatchMetricsQuery } from '../../../../types'; import SQLBuilderSelectRow from './SQLBuilderSelectRow'; import SQLFilter from './SQLFilter'; diff --git a/public/app/plugins/datasource/cloudwatch/components/QueryEditor/MetricsQueryEditor/SQLBuilderEditor/SQLBuilderSelectRow.test.tsx b/public/app/plugins/datasource/cloudwatch/components/QueryEditor/MetricsQueryEditor/SQLBuilderEditor/SQLBuilderSelectRow.test.tsx index fca1c946f06..621566f4cf9 100644 --- a/public/app/plugins/datasource/cloudwatch/components/QueryEditor/MetricsQueryEditor/SQLBuilderEditor/SQLBuilderSelectRow.test.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/QueryEditor/MetricsQueryEditor/SQLBuilderEditor/SQLBuilderSelectRow.test.tsx @@ -1,9 +1,15 @@ import { act, render, screen } from '@testing-library/react'; import { selectOptionInTest } from 'test/helpers/selectOptionInTest'; -import { QueryEditorExpressionType, QueryEditorPropertyType } from '../../../../expressions'; +import { + CloudWatchMetricsQuery, + MetricEditorMode, + MetricQueryType, + SQLExpression, + QueryEditorExpressionType, + QueryEditorPropertyType, +} from '../../../../dataquery.gen'; import { setupMockedDataSource } from '../../../../mocks/CloudWatchDataSource'; -import { CloudWatchMetricsQuery, MetricEditorMode, MetricQueryType, SQLExpression } from '../../../../types'; import SQLBuilderSelectRow from './SQLBuilderSelectRow'; diff --git a/public/app/plugins/datasource/cloudwatch/components/QueryEditor/MetricsQueryEditor/SQLBuilderEditor/SQLBuilderSelectRow.tsx b/public/app/plugins/datasource/cloudwatch/components/QueryEditor/MetricsQueryEditor/SQLBuilderEditor/SQLBuilderSelectRow.tsx index 707b7b5b137..ff3ff1969ef 100644 --- a/public/app/plugins/datasource/cloudwatch/components/QueryEditor/MetricsQueryEditor/SQLBuilderEditor/SQLBuilderSelectRow.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/QueryEditor/MetricsQueryEditor/SQLBuilderEditor/SQLBuilderSelectRow.tsx @@ -5,10 +5,10 @@ import { EditorField, EditorFieldGroup, EditorSwitch } from '@grafana/plugin-ui' import { config } from '@grafana/runtime'; import { Select } from '@grafana/ui'; +import { CloudWatchMetricsQuery } from '../../../../dataquery.gen'; import { CloudWatchDatasource } from '../../../../datasource'; import { useAccountOptions, useDimensionKeys, useMetrics, useNamespaces } from '../../../../hooks'; import { STATISTICS } from '../../../../language/cloudwatch-sql/language'; -import { CloudWatchMetricsQuery } from '../../../../types'; import { appendTemplateVariables } from '../../../../utils/utils'; import { Account } from '../../../shared/Account'; diff --git a/public/app/plugins/datasource/cloudwatch/components/QueryEditor/MetricsQueryEditor/SQLBuilderEditor/SQLFilter.tsx b/public/app/plugins/datasource/cloudwatch/components/QueryEditor/MetricsQueryEditor/SQLBuilderEditor/SQLFilter.tsx index a911f8d05eb..e40c8ec44ee 100644 --- a/public/app/plugins/datasource/cloudwatch/components/QueryEditor/MetricsQueryEditor/SQLBuilderEditor/SQLFilter.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/QueryEditor/MetricsQueryEditor/SQLBuilderEditor/SQLFilter.tsx @@ -6,16 +6,16 @@ import { SelectableValue, toOption } from '@grafana/data'; import { AccessoryButton, EditorList, InputGroup } from '@grafana/plugin-ui'; import { config } from '@grafana/runtime'; import { Alert, Select, useStyles2 } from '@grafana/ui'; +import { + CloudWatchMetricsQuery, + QueryEditorExpressionType, + QueryEditorPropertyType, +} from 'app/plugins/datasource/cloudwatch/dataquery.gen'; import { CloudWatchDatasource } from '../../../../datasource'; -import { - QueryEditorExpressionType, - QueryEditorOperatorExpression, - QueryEditorPropertyType, -} from '../../../../expressions'; +import { QueryEditorOperatorExpression } from '../../../../expressions'; import { useDimensionKeys, useEnsureVariableHasSingleSelection } from '../../../../hooks'; import { COMPARISON_OPERATORS, EQUALS } from '../../../../language/cloudwatch-sql/language'; -import { CloudWatchMetricsQuery } from '../../../../types'; import { appendTemplateVariables } from '../../../../utils/utils'; import { diff --git a/public/app/plugins/datasource/cloudwatch/components/QueryEditor/MetricsQueryEditor/SQLBuilderEditor/SQLGroupBy.test.tsx b/public/app/plugins/datasource/cloudwatch/components/QueryEditor/MetricsQueryEditor/SQLBuilderEditor/SQLGroupBy.test.tsx index 315eafb511a..a1be51358ce 100644 --- a/public/app/plugins/datasource/cloudwatch/components/QueryEditor/MetricsQueryEditor/SQLBuilderEditor/SQLGroupBy.test.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/QueryEditor/MetricsQueryEditor/SQLBuilderEditor/SQLGroupBy.test.tsx @@ -4,9 +4,9 @@ import selectEvent from 'react-select-event'; import { config } from '@grafana/runtime'; +import { CloudWatchMetricsQuery, MetricEditorMode, MetricQueryType, SQLExpression } from '../../../../dataquery.gen'; import { setupMockedDataSource } from '../../../../mocks/CloudWatchDataSource'; import { createArray, createGroupBy } from '../../../../mocks/sqlUtils'; -import { CloudWatchMetricsQuery, MetricEditorMode, MetricQueryType, SQLExpression } from '../../../../types'; import SQLGroupBy from './SQLGroupBy'; diff --git a/public/app/plugins/datasource/cloudwatch/components/QueryEditor/MetricsQueryEditor/SQLBuilderEditor/SQLGroupBy.tsx b/public/app/plugins/datasource/cloudwatch/components/QueryEditor/MetricsQueryEditor/SQLBuilderEditor/SQLGroupBy.tsx index a288b74b0b8..f49aa2ba00c 100644 --- a/public/app/plugins/datasource/cloudwatch/components/QueryEditor/MetricsQueryEditor/SQLBuilderEditor/SQLGroupBy.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/QueryEditor/MetricsQueryEditor/SQLBuilderEditor/SQLGroupBy.tsx @@ -5,14 +5,14 @@ import { AccessoryButton, EditorList, InputGroup } from '@grafana/plugin-ui'; import { config } from '@grafana/runtime'; import { Select } from '@grafana/ui'; -import { CloudWatchDatasource } from '../../../../datasource'; import { + CloudWatchMetricsQuery, QueryEditorExpressionType, QueryEditorGroupByExpression, QueryEditorPropertyType, -} from '../../../../expressions'; +} from '../../../../dataquery.gen'; +import { CloudWatchDatasource } from '../../../../datasource'; import { useDimensionKeys, useIsMonitoringAccount } from '../../../../hooks'; -import { CloudWatchMetricsQuery } from '../../../../types'; import { getFlattenedGroupBys, diff --git a/public/app/plugins/datasource/cloudwatch/components/QueryEditor/MetricsQueryEditor/SQLBuilderEditor/SQLOrderByGroup.tsx b/public/app/plugins/datasource/cloudwatch/components/QueryEditor/MetricsQueryEditor/SQLBuilderEditor/SQLOrderByGroup.tsx index 188c7a1ce69..58427587f2d 100644 --- a/public/app/plugins/datasource/cloudwatch/components/QueryEditor/MetricsQueryEditor/SQLBuilderEditor/SQLOrderByGroup.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/QueryEditor/MetricsQueryEditor/SQLBuilderEditor/SQLOrderByGroup.tsx @@ -2,9 +2,9 @@ import { SelectableValue, toOption } from '@grafana/data'; import { AccessoryButton, EditorField, EditorFieldGroup, InputGroup } from '@grafana/plugin-ui'; import { Select } from '@grafana/ui'; +import { CloudWatchMetricsQuery } from '../../../../dataquery.gen'; import { CloudWatchDatasource } from '../../../../datasource'; import { ASC, DESC, STATISTICS } from '../../../../language/cloudwatch-sql/language'; -import { CloudWatchMetricsQuery } from '../../../../types'; import { appendTemplateVariables } from '../../../../utils/utils'; import { setOrderBy, setSql } from './utils'; diff --git a/public/app/plugins/datasource/cloudwatch/components/QueryEditor/MetricsQueryEditor/SQLBuilderEditor/utils.ts b/public/app/plugins/datasource/cloudwatch/components/QueryEditor/MetricsQueryEditor/SQLBuilderEditor/utils.ts index b25dd6fa45e..e53c02ac173 100644 --- a/public/app/plugins/datasource/cloudwatch/components/QueryEditor/MetricsQueryEditor/SQLBuilderEditor/utils.ts +++ b/public/app/plugins/datasource/cloudwatch/components/QueryEditor/MetricsQueryEditor/SQLBuilderEditor/utils.ts @@ -1,15 +1,17 @@ import { SelectableValue } from '@grafana/data'; import { + SQLExpression, + CloudWatchMetricsQuery, + Dimensions, QueryEditorExpressionType, QueryEditorPropertyType, QueryEditorFunctionParameterExpression, QueryEditorArrayExpression, - QueryEditorOperatorExpression, QueryEditorGroupByExpression, -} from '../../../../expressions'; +} from '../../../../dataquery.gen'; +import { QueryEditorOperatorExpression } from '../../../../expressions'; import { SCHEMA } from '../../../../language/cloudwatch-sql/language'; -import { SQLExpression, CloudWatchMetricsQuery, Dimensions } from '../../../../types'; export function getMetricNameFromExpression(selectExpression: SQLExpression['select']): string | undefined { return selectExpression?.parameters?.[0].name; diff --git a/public/app/plugins/datasource/cloudwatch/components/QueryEditor/QueryEditor.test.tsx b/public/app/plugins/datasource/cloudwatch/components/QueryEditor/QueryEditor.test.tsx index 1d87bd29596..47c4633df65 100644 --- a/public/app/plugins/datasource/cloudwatch/components/QueryEditor/QueryEditor.test.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/QueryEditor/QueryEditor.test.tsx @@ -5,6 +5,7 @@ import { selectOptionInTest } from 'test/helpers/selectOptionInTest'; import { QueryEditorProps } from '@grafana/data'; import { config } from '@grafana/runtime'; +import { MetricEditorMode, MetricQueryType, LogsQueryLanguage } from '../../dataquery.gen'; import { CloudWatchDatasource } from '../../datasource'; import { DEFAULT_CWLI_QUERY_STRING, DEFAULT_SQL_QUERY_STRING } from '../../defaultQueries'; import { setupMockedDataSource } from '../../mocks/CloudWatchDataSource'; @@ -15,7 +16,7 @@ import { validMetricSearchBuilderQuery, validMetricSearchCodeQuery, } from '../../mocks/queries'; -import { CloudWatchQuery, CloudWatchJsonData, MetricEditorMode, MetricQueryType, LogsQueryLanguage } from '../../types'; +import { CloudWatchJsonData, CloudWatchQuery } from '../../types'; import { QueryEditor } from './QueryEditor'; diff --git a/public/app/plugins/datasource/cloudwatch/components/QueryEditor/QueryHeader.tsx b/public/app/plugins/datasource/cloudwatch/components/QueryEditor/QueryHeader.tsx index 78da93ba3a1..ae8e987b48c 100644 --- a/public/app/plugins/datasource/cloudwatch/components/QueryEditor/QueryHeader.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/QueryEditor/QueryHeader.tsx @@ -5,10 +5,11 @@ import { EditorHeader, InlineSelect, FlexItem } from '@grafana/plugin-ui'; import { config } from '@grafana/runtime'; import { Badge, Button } from '@grafana/ui'; +import { CloudWatchQueryMode } from '../../dataquery.gen'; import { CloudWatchDatasource } from '../../datasource'; import { isCloudWatchLogsQuery, isCloudWatchMetricsQuery } from '../../guards'; import { useIsMonitoringAccount, useRegions } from '../../hooks'; -import { CloudWatchJsonData, CloudWatchQuery, CloudWatchQueryMode } from '../../types'; +import { CloudWatchJsonData, CloudWatchQuery } from '../../types'; export interface Props extends QueryEditorProps { extraHeaderElementLeft?: JSX.Element; diff --git a/public/app/plugins/datasource/cloudwatch/components/shared/Dimensions/Dimensions.test.tsx b/public/app/plugins/datasource/cloudwatch/components/shared/Dimensions/Dimensions.test.tsx index 6374c95b8a6..8509a230c45 100644 --- a/public/app/plugins/datasource/cloudwatch/components/shared/Dimensions/Dimensions.test.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/shared/Dimensions/Dimensions.test.tsx @@ -1,8 +1,8 @@ import { fireEvent, render, screen, within } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import { CloudWatchMetricsQuery } from '../../../dataquery.gen'; import { setupMockedDataSource } from '../../../mocks/CloudWatchDataSource'; -import { CloudWatchMetricsQuery } from '../../../types'; import { Dimensions } from './Dimensions'; diff --git a/public/app/plugins/datasource/cloudwatch/components/shared/Dimensions/Dimensions.tsx b/public/app/plugins/datasource/cloudwatch/components/shared/Dimensions/Dimensions.tsx index 3ab31316fc9..5f66358636d 100644 --- a/public/app/plugins/datasource/cloudwatch/components/shared/Dimensions/Dimensions.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/shared/Dimensions/Dimensions.tsx @@ -3,8 +3,8 @@ import { useMemo, useState } from 'react'; import { EditorList } from '@grafana/plugin-ui'; +import { Dimensions as DimensionsType, MetricStat } from '../../../dataquery.gen'; import { CloudWatchDatasource } from '../../../datasource'; -import { Dimensions as DimensionsType, MetricStat } from '../../../types'; import { FilterItem } from './FilterItem'; diff --git a/public/app/plugins/datasource/cloudwatch/components/shared/Dimensions/FilterItem.test.tsx b/public/app/plugins/datasource/cloudwatch/components/shared/Dimensions/FilterItem.test.tsx index 2afbd64e195..a98deecd5ef 100644 --- a/public/app/plugins/datasource/cloudwatch/components/shared/Dimensions/FilterItem.test.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/shared/Dimensions/FilterItem.test.tsx @@ -1,8 +1,8 @@ import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import { CloudWatchMetricsQuery } from '../../../dataquery.gen'; import { setupMockedDataSource } from '../../../mocks/CloudWatchDataSource'; -import { CloudWatchMetricsQuery } from '../../../types'; import { FilterItem } from './FilterItem'; diff --git a/public/app/plugins/datasource/cloudwatch/components/shared/Dimensions/FilterItem.tsx b/public/app/plugins/datasource/cloudwatch/components/shared/Dimensions/FilterItem.tsx index 592ecfbe76f..673a193145a 100644 --- a/public/app/plugins/datasource/cloudwatch/components/shared/Dimensions/FilterItem.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/shared/Dimensions/FilterItem.tsx @@ -6,9 +6,9 @@ import { GrafanaTheme2, SelectableValue, toOption } from '@grafana/data'; import { AccessoryButton, InputGroup } from '@grafana/plugin-ui'; import { Alert, Select, useStyles2 } from '@grafana/ui'; +import { Dimensions, MetricStat } from '../../../dataquery.gen'; import { CloudWatchDatasource } from '../../../datasource'; import { useDimensionKeys, useEnsureVariableHasSingleSelection } from '../../../hooks'; -import { Dimensions, MetricStat } from '../../../types'; import { appendTemplateVariables } from '../../../utils/utils'; import { DimensionFilterCondition } from './Dimensions'; diff --git a/public/app/plugins/datasource/cloudwatch/components/shared/LogGroups/LogGroupsField.tsx b/public/app/plugins/datasource/cloudwatch/components/shared/LogGroups/LogGroupsField.tsx index 85c4cf74336..9190cc2bcc9 100644 --- a/public/app/plugins/datasource/cloudwatch/components/shared/LogGroups/LogGroupsField.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/shared/LogGroups/LogGroupsField.tsx @@ -3,10 +3,10 @@ import { useEffect, useState } from 'react'; import { config } from '@grafana/runtime'; +import { LogGroup } from '../../../dataquery.gen'; import { CloudWatchDatasource } from '../../../datasource'; import { useAccountOptions } from '../../../hooks'; import { DescribeLogGroupsRequest } from '../../../resources/types'; -import { LogGroup } from '../../../types'; import { isTemplateVariable } from '../../../utils/templateVariableUtils'; import { LegacyLogGroupSelection } from './LegacyLogGroupNamesSelection'; diff --git a/public/app/plugins/datasource/cloudwatch/components/shared/LogGroups/LogGroupsSelector.tsx b/public/app/plugins/datasource/cloudwatch/components/shared/LogGroups/LogGroupsSelector.tsx index 170359cdfcb..9f5eb9be6a7 100644 --- a/public/app/plugins/datasource/cloudwatch/components/shared/LogGroups/LogGroupsSelector.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/shared/LogGroups/LogGroupsSelector.tsx @@ -15,8 +15,8 @@ import { useStyles2, } from '@grafana/ui'; +import { LogGroup } from '../../../dataquery.gen'; import { DescribeLogGroupsRequest, ResourceResponse, LogGroupResponse } from '../../../resources/types'; -import { LogGroup } from '../../../types'; import getStyles from '../../styles'; import { Account, ALL_ACCOUNTS_OPTION } from '../Account'; diff --git a/public/app/plugins/datasource/cloudwatch/components/shared/LogGroups/SelectedLogGroups.test.tsx b/public/app/plugins/datasource/cloudwatch/components/shared/LogGroups/SelectedLogGroups.test.tsx index 7ad0fea4ec4..f404f50c436 100644 --- a/public/app/plugins/datasource/cloudwatch/components/shared/LogGroups/SelectedLogGroups.test.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/shared/LogGroups/SelectedLogGroups.test.tsx @@ -1,7 +1,7 @@ import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { LogGroup } from '../../../types'; +import { LogGroup } from '../../../dataquery.gen'; import { SelectedLogGroups } from './SelectedLogGroups'; diff --git a/public/app/plugins/datasource/cloudwatch/components/shared/LogGroups/SelectedLogGroups.tsx b/public/app/plugins/datasource/cloudwatch/components/shared/LogGroups/SelectedLogGroups.tsx index e1c20db7f50..6991861d7a4 100644 --- a/public/app/plugins/datasource/cloudwatch/components/shared/LogGroups/SelectedLogGroups.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/shared/LogGroups/SelectedLogGroups.tsx @@ -2,7 +2,7 @@ import { useEffect, useState } from 'react'; import { Button, ConfirmModal, useStyles2 } from '@grafana/ui'; -import { LogGroup } from '../../../types'; +import { LogGroup } from '../../../dataquery.gen'; import getStyles from '../../styles'; type CrossAccountLogsQueryProps = { diff --git a/public/app/plugins/datasource/cloudwatch/components/shared/MetricStatEditor/MetricStatEditor.test.tsx b/public/app/plugins/datasource/cloudwatch/components/shared/MetricStatEditor/MetricStatEditor.test.tsx index 5eec534a5ca..8a353ebd239 100644 --- a/public/app/plugins/datasource/cloudwatch/components/shared/MetricStatEditor/MetricStatEditor.test.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/shared/MetricStatEditor/MetricStatEditor.test.tsx @@ -4,9 +4,9 @@ import selectEvent from 'react-select-event'; import { config } from '@grafana/runtime'; +import { MetricStat } from '../../../dataquery.gen'; import { setupMockedDataSource, statisticVariable } from '../../../mocks/CloudWatchDataSource'; import { validMetricSearchBuilderQuery } from '../../../mocks/queries'; -import { MetricStat } from '../../../types'; import { MetricStatEditor } from './MetricStatEditor'; diff --git a/public/app/plugins/datasource/cloudwatch/components/shared/MetricStatEditor/MetricStatEditor.tsx b/public/app/plugins/datasource/cloudwatch/components/shared/MetricStatEditor/MetricStatEditor.tsx index cf98588d377..0aab1780826 100644 --- a/public/app/plugins/datasource/cloudwatch/components/shared/MetricStatEditor/MetricStatEditor.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/shared/MetricStatEditor/MetricStatEditor.tsx @@ -6,10 +6,10 @@ import { EditorField, EditorFieldGroup, EditorRow, EditorRows, EditorSwitch } fr import { config } from '@grafana/runtime'; import { Select, TextLink } from '@grafana/ui'; +import { MetricStat } from '../../../dataquery.gen'; import { CloudWatchDatasource } from '../../../datasource'; import { useAccountOptions, useMetrics, useNamespaces } from '../../../hooks'; import { standardStatistics } from '../../../standardStatistics'; -import { MetricStat } from '../../../types'; import { appendTemplateVariables, toOption } from '../../../utils/utils'; import { Account } from '../Account'; import { Dimensions } from '../Dimensions/Dimensions'; diff --git a/public/app/plugins/datasource/cloudwatch/datasource.test.ts b/public/app/plugins/datasource/cloudwatch/datasource.test.ts index 2a84dca2490..a922629e07f 100644 --- a/public/app/plugins/datasource/cloudwatch/datasource.test.ts +++ b/public/app/plugins/datasource/cloudwatch/datasource.test.ts @@ -3,6 +3,13 @@ import { toArray } from 'rxjs/operators'; import { CoreApp, Field } from '@grafana/data'; +import { + CloudWatchLogsQuery, + CloudWatchMetricsQuery, + LogsQueryLanguage, + MetricEditorMode, + MetricQueryType, +} from './dataquery.gen'; import { CloudWatchSettings, fieldsVariable, @@ -13,16 +20,7 @@ import { import { setupForLogs } from './mocks/logsTestContext'; import { validLogsQuery, validMetricSearchBuilderQuery } from './mocks/queries'; import { TimeRangeMock } from './mocks/timeRange'; -import { - CloudWatchDefaultQuery, - CloudWatchLogsQuery, - CloudWatchLogsRequest, - CloudWatchMetricsQuery, - CloudWatchQuery, - LogsQueryLanguage, - MetricEditorMode, - MetricQueryType, -} from './types'; +import { CloudWatchQuery, CloudWatchLogsRequest, CloudWatchDefaultQuery } from './types'; import * as templateUtils from './utils/templateVariableUtils'; describe('datasource', () => { diff --git a/public/app/plugins/datasource/cloudwatch/datasource.ts b/public/app/plugins/datasource/cloudwatch/datasource.ts index 27c240dd6cd..c5b0dc50d56 100644 --- a/public/app/plugins/datasource/cloudwatch/datasource.ts +++ b/public/app/plugins/datasource/cloudwatch/datasource.ts @@ -15,6 +15,12 @@ import { import { DataSourceWithBackend, TemplateSrv, getTemplateSrv } from '@grafana/runtime'; import { CloudWatchAnnotationSupport } from './annotationSupport'; +import { + CloudWatchAnnotationQuery, + CloudWatchLogsAnomaliesQuery, + CloudWatchLogsQuery, + CloudWatchMetricsQuery, +} from './dataquery.gen'; import { DEFAULT_METRICS_QUERY, getDefaultLogsQuery } from './defaultQueries'; import { isCloudWatchAnnotationQuery, @@ -42,14 +48,7 @@ import { CloudWatchAnnotationQueryRunner } from './query-runner/CloudWatchAnnota import { CloudWatchLogsQueryRunner } from './query-runner/CloudWatchLogsQueryRunner'; import { CloudWatchMetricsQueryRunner } from './query-runner/CloudWatchMetricsQueryRunner'; import { ResourcesAPI } from './resources/ResourcesAPI'; -import { - CloudWatchAnnotationQuery, - CloudWatchJsonData, - CloudWatchLogsAnomaliesQuery, - CloudWatchLogsQuery, - CloudWatchMetricsQuery, - CloudWatchQuery, -} from './types'; +import { CloudWatchQuery, CloudWatchJsonData } from './types'; import { CloudWatchVariableSupport } from './variables'; export class CloudWatchDatasource diff --git a/public/app/plugins/datasource/cloudwatch/defaultQueries.ts b/public/app/plugins/datasource/cloudwatch/defaultQueries.ts index 22055f17256..c8935405f6d 100644 --- a/public/app/plugins/datasource/cloudwatch/defaultQueries.ts +++ b/public/app/plugins/datasource/cloudwatch/defaultQueries.ts @@ -6,9 +6,8 @@ import { LogsQueryLanguage, MetricEditorMode, MetricQueryType, - VariableQuery, - VariableQueryType, -} from './types'; +} from './dataquery.gen'; +import { VariableQuery, VariableQueryType } from './types'; export const DEFAULT_METRICS_QUERY: Omit = { queryMode: 'Metrics', diff --git a/public/app/plugins/datasource/cloudwatch/expressions.ts b/public/app/plugins/datasource/cloudwatch/expressions.ts index 206e9284089..60b9d1eca8e 100644 --- a/public/app/plugins/datasource/cloudwatch/expressions.ts +++ b/public/app/plugins/datasource/cloudwatch/expressions.ts @@ -3,17 +3,6 @@ import { QueryEditorOperator as QueryEditorOperatorBase, QueryEditorOperatorValueType, } from './dataquery.gen'; -export { - QueryEditorPropertyType, - type QueryEditorProperty, - type QueryEditorPropertyExpression, - type QueryEditorGroupByExpression, - type QueryEditorFunctionExpression, - type QueryEditorFunctionParameterExpression, - type QueryEditorArrayExpression, - QueryEditorExpressionType, - type QueryEditorExpression, -} from './dataquery.gen'; export interface QueryEditorOperator extends QueryEditorOperatorBase { value?: T; diff --git a/public/app/plugins/datasource/cloudwatch/guards.ts b/public/app/plugins/datasource/cloudwatch/guards.ts index 2a54dd9719c..9c29f4b4c15 100644 --- a/public/app/plugins/datasource/cloudwatch/guards.ts +++ b/public/app/plugins/datasource/cloudwatch/guards.ts @@ -5,9 +5,9 @@ import { CloudWatchLogsAnomaliesQuery, CloudWatchLogsQuery, CloudWatchMetricsQuery, - CloudWatchQuery, LogsMode, -} from './types'; +} from './dataquery.gen'; +import { CloudWatchQuery } from './types'; export const isCloudWatchLogsQuery = (cloudwatchQuery: CloudWatchQuery): cloudwatchQuery is CloudWatchLogsQuery => cloudwatchQuery.queryMode === 'Logs'; diff --git a/public/app/plugins/datasource/cloudwatch/language/cloudwatch-logs-sql/completion/CompletionItemProvider.test.ts b/public/app/plugins/datasource/cloudwatch/language/cloudwatch-logs-sql/completion/CompletionItemProvider.test.ts index 04b71f35a6d..606527f4711 100644 --- a/public/app/plugins/datasource/cloudwatch/language/cloudwatch-logs-sql/completion/CompletionItemProvider.test.ts +++ b/public/app/plugins/datasource/cloudwatch/language/cloudwatch-logs-sql/completion/CompletionItemProvider.test.ts @@ -1,6 +1,7 @@ import { CustomVariableModel } from '@grafana/data'; import { Monaco, monacoTypes } from '@grafana/ui'; +import { LogGroup } from '../../../dataquery.gen'; import { setupMockedTemplateService, logGroupNamesVariable } from '../../../mocks/CloudWatchDataSource'; import { multiLineFullQuery } from '../../../mocks/cloudwatch-logs-sql-test-data/multiLineFullQuery'; import { multiLineFullQueryWithCaseClause } from '../../../mocks/cloudwatch-logs-sql-test-data/multiLineFullQueryWithCaseClause'; @@ -11,7 +12,7 @@ import MonacoMock from '../../../mocks/monarch/Monaco'; import TextModel from '../../../mocks/monarch/TextModel'; import { ResourcesAPI } from '../../../resources/ResourcesAPI'; import { ResourceResponse } from '../../../resources/types'; -import { LogGroup, LogGroupField } from '../../../types'; +import { LogGroupField } from '../../../types'; import cloudWatchLogsLanguageDefinition from '../definition'; import { SELECT, diff --git a/public/app/plugins/datasource/cloudwatch/language/cloudwatch-logs-sql/completion/CompletionItemProvider.ts b/public/app/plugins/datasource/cloudwatch/language/cloudwatch-logs-sql/completion/CompletionItemProvider.ts index ea8063ff196..cdba699b7a0 100644 --- a/public/app/plugins/datasource/cloudwatch/language/cloudwatch-logs-sql/completion/CompletionItemProvider.ts +++ b/public/app/plugins/datasource/cloudwatch/language/cloudwatch-logs-sql/completion/CompletionItemProvider.ts @@ -1,8 +1,8 @@ import { getTemplateSrv, TemplateSrv } from '@grafana/runtime'; import type { Monaco, monacoTypes } from '@grafana/ui'; +import { LogGroup } from '../../../dataquery.gen'; import { ResourcesAPI } from '../../../resources/ResourcesAPI'; -import { LogGroup } from '../../../types'; import { CompletionItemProvider } from '../../monarch/CompletionItemProvider'; import { LinkedToken } from '../../monarch/LinkedToken'; import { TRIGGER_SUGGEST } from '../../monarch/commands'; diff --git a/public/app/plugins/datasource/cloudwatch/language/cloudwatch-logs/CloudWatchLogsLanguageProvider.ts b/public/app/plugins/datasource/cloudwatch/language/cloudwatch-logs/CloudWatchLogsLanguageProvider.ts index ba36458e0be..bae8ee4785f 100644 --- a/public/app/plugins/datasource/cloudwatch/language/cloudwatch-logs/CloudWatchLogsLanguageProvider.ts +++ b/public/app/plugins/datasource/cloudwatch/language/cloudwatch-logs/CloudWatchLogsLanguageProvider.ts @@ -5,8 +5,9 @@ import { AbsoluteTimeRange, HistoryItem, LanguageProvider } from '@grafana/data' import { BackendDataSourceResponse, FetchResponse, TemplateSrv, getTemplateSrv } from '@grafana/runtime'; import { CompletionItemGroup, SearchFunctionType, Token, TypeaheadInput, TypeaheadOutput } from '@grafana/ui'; +import { LogGroup } from '../../dataquery.gen'; import { CloudWatchDatasource } from '../../datasource'; -import { CloudWatchQuery, LogGroup } from '../../types'; +import { CloudWatchQuery } from '../../types'; import { fetchLogGroupFields } from '../utils'; import syntax, { diff --git a/public/app/plugins/datasource/cloudwatch/language/cloudwatch-ppl/completion/PPLCompletionItemProvider.test.ts b/public/app/plugins/datasource/cloudwatch/language/cloudwatch-ppl/completion/PPLCompletionItemProvider.test.ts index 7809ac001b5..e9f197e7c4d 100644 --- a/public/app/plugins/datasource/cloudwatch/language/cloudwatch-ppl/completion/PPLCompletionItemProvider.test.ts +++ b/public/app/plugins/datasource/cloudwatch/language/cloudwatch-ppl/completion/PPLCompletionItemProvider.test.ts @@ -1,6 +1,7 @@ import { CustomVariableModel } from '@grafana/data'; import { Monaco, monacoTypes } from '@grafana/ui'; +import { LogGroup } from '../../../dataquery.gen'; import { logGroupNamesVariable, setupMockedTemplateService } from '../../../mocks/CloudWatchDataSource'; import { newCommandQuery } from '../../../mocks/cloudwatch-ppl-test-data/newCommandQuery'; import { @@ -22,7 +23,7 @@ import MonacoMock from '../../../mocks/monarch/Monaco'; import TextModel from '../../../mocks/monarch/TextModel'; import { ResourcesAPI } from '../../../resources/ResourcesAPI'; import { ResourceResponse } from '../../../resources/types'; -import { LogGroup, LogGroupField } from '../../../types'; +import { LogGroupField } from '../../../types'; import cloudWatchLogsPPLLanguageDefinition from '../definition'; import { BOOLEAN_LITERALS, diff --git a/public/app/plugins/datasource/cloudwatch/language/cloudwatch-ppl/completion/PPLCompletionItemProvider.ts b/public/app/plugins/datasource/cloudwatch/language/cloudwatch-ppl/completion/PPLCompletionItemProvider.ts index fee0f2e23fe..52626a27ed6 100644 --- a/public/app/plugins/datasource/cloudwatch/language/cloudwatch-ppl/completion/PPLCompletionItemProvider.ts +++ b/public/app/plugins/datasource/cloudwatch/language/cloudwatch-ppl/completion/PPLCompletionItemProvider.ts @@ -1,8 +1,8 @@ import { getTemplateSrv, type TemplateSrv } from '@grafana/runtime'; import { Monaco, monacoTypes } from '@grafana/ui'; +import { LogGroup } from '../../../dataquery.gen'; import { type ResourcesAPI } from '../../../resources/ResourcesAPI'; -import { LogGroup } from '../../../types'; import { CompletionItemProvider } from '../../monarch/CompletionItemProvider'; import { LinkedToken } from '../../monarch/LinkedToken'; import { TRIGGER_SUGGEST } from '../../monarch/commands'; diff --git a/public/app/plugins/datasource/cloudwatch/language/cloudwatch-sql/SQLGenerator.test.ts b/public/app/plugins/datasource/cloudwatch/language/cloudwatch-sql/SQLGenerator.test.ts index 83f943a4f95..20a990f5ba0 100644 --- a/public/app/plugins/datasource/cloudwatch/language/cloudwatch-sql/SQLGenerator.test.ts +++ b/public/app/plugins/datasource/cloudwatch/language/cloudwatch-sql/SQLGenerator.test.ts @@ -1,4 +1,4 @@ -import { QueryEditorExpressionType } from '../../expressions'; +import { SQLExpression, QueryEditorExpressionType } from '../../dataquery.gen'; import { aggregationvariable, labelsVariable, @@ -14,7 +14,6 @@ import { createFunction, createProperty, } from '../../mocks/sqlUtils'; -import { SQLExpression } from '../../types'; import SQLGenerator from './SQLGenerator'; diff --git a/public/app/plugins/datasource/cloudwatch/language/cloudwatch-sql/SQLGenerator.ts b/public/app/plugins/datasource/cloudwatch/language/cloudwatch-sql/SQLGenerator.ts index b6058dc2daf..a74a179c248 100644 --- a/public/app/plugins/datasource/cloudwatch/language/cloudwatch-sql/SQLGenerator.ts +++ b/public/app/plugins/datasource/cloudwatch/language/cloudwatch-sql/SQLGenerator.ts @@ -7,10 +7,10 @@ import { QueryEditorExpression, QueryEditorExpressionType, QueryEditorFunctionExpression, - QueryEditorOperatorExpression, QueryEditorPropertyExpression, -} from '../../expressions'; -import { SQLExpression } from '../../types'; + SQLExpression, +} from '../../dataquery.gen'; +import { QueryEditorOperatorExpression } from '../../expressions'; import { InsightsReservedKeywords } from './consts'; diff --git a/public/app/plugins/datasource/cloudwatch/language/logs/completion/CompletionItemProvider.test.ts b/public/app/plugins/datasource/cloudwatch/language/logs/completion/CompletionItemProvider.test.ts index e586b46ed64..bcf605b9a27 100644 --- a/public/app/plugins/datasource/cloudwatch/language/logs/completion/CompletionItemProvider.test.ts +++ b/public/app/plugins/datasource/cloudwatch/language/logs/completion/CompletionItemProvider.test.ts @@ -3,6 +3,7 @@ import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; import { CustomVariableModel } from '@grafana/data'; import { monacoTypes } from '@grafana/ui'; +import { LogGroup } from '../../../dataquery.gen'; import { setupMockedTemplateService, logGroupNamesVariable } from '../../../mocks/CloudWatchDataSource'; import { logsTestDataDiffModifierQuery } from '../../../mocks/cloudwatch-logs-test-data/diffModifierQuery'; import { logsTestDataDiffQuery } from '../../../mocks/cloudwatch-logs-test-data/diffQuery'; @@ -12,7 +13,7 @@ import { logsTestDataNewCommandQuery } from '../../../mocks/cloudwatch-logs-test import { logsTestDataSortQuery } from '../../../mocks/cloudwatch-logs-test-data/sortQuery'; import { ResourcesAPI } from '../../../resources/ResourcesAPI'; import { ResourceResponse } from '../../../resources/types'; -import { LogGroup, LogGroupField } from '../../../types'; +import { LogGroupField } from '../../../types'; import cloudWatchLogsLanguageDefinition, { CLOUDWATCH_LOGS_LANGUAGE_DEFINITION_ID } from '../definition'; import { DIFF_MODIFIERS, LOGS_COMMANDS, LOGS_FUNCTION_OPERATORS, SORT_DIRECTION_KEYWORDS, language } from '../language'; diff --git a/public/app/plugins/datasource/cloudwatch/language/logs/completion/CompletionItemProvider.ts b/public/app/plugins/datasource/cloudwatch/language/logs/completion/CompletionItemProvider.ts index 2d8a62920cb..e6f8ba31d74 100644 --- a/public/app/plugins/datasource/cloudwatch/language/logs/completion/CompletionItemProvider.ts +++ b/public/app/plugins/datasource/cloudwatch/language/logs/completion/CompletionItemProvider.ts @@ -1,8 +1,8 @@ import { getTemplateSrv, TemplateSrv } from '@grafana/runtime'; import { Monaco, monacoTypes } from '@grafana/ui'; +import { LogGroup } from '../../../dataquery.gen'; import { type ResourcesAPI } from '../../../resources/ResourcesAPI'; -import { LogGroup } from '../../../types'; import { CompletionItemProvider } from '../../monarch/CompletionItemProvider'; import { LinkedToken } from '../../monarch/LinkedToken'; import { TRIGGER_SUGGEST } from '../../monarch/commands'; diff --git a/public/app/plugins/datasource/cloudwatch/migrations/dashboardMigrations.test.ts b/public/app/plugins/datasource/cloudwatch/migrations/dashboardMigrations.test.ts index 848dda3d857..439b11037f7 100644 --- a/public/app/plugins/datasource/cloudwatch/migrations/dashboardMigrations.test.ts +++ b/public/app/plugins/datasource/cloudwatch/migrations/dashboardMigrations.test.ts @@ -1,6 +1,7 @@ import { AnnotationQuery, DataQuery } from '@grafana/data'; -import { CloudWatchMetricsQuery, LegacyAnnotationQuery, MetricEditorMode, MetricQueryType } from '../types'; +import { CloudWatchMetricsQuery, MetricEditorMode, MetricQueryType } from '../dataquery.gen'; +import { LegacyAnnotationQuery } from '../types'; import { migrateCloudWatchQuery, diff --git a/public/app/plugins/datasource/cloudwatch/migrations/dashboardMigrations.ts b/public/app/plugins/datasource/cloudwatch/migrations/dashboardMigrations.ts index 4d97404fdcb..19171a71b2f 100644 --- a/public/app/plugins/datasource/cloudwatch/migrations/dashboardMigrations.ts +++ b/public/app/plugins/datasource/cloudwatch/migrations/dashboardMigrations.ts @@ -5,7 +5,8 @@ import { AnnotationQuery, getNextRefId } from '@grafana/data'; import { DataQuery } from '@grafana/schema'; -import { CloudWatchMetricsQuery, LegacyAnnotationQuery, MetricQueryType, MetricEditorMode } from '../types'; +import { CloudWatchMetricsQuery, MetricQueryType, MetricEditorMode } from '../dataquery.gen'; +import { LegacyAnnotationQuery } from '../types'; // E.g query.statistics = ['Max', 'Min'] will be migrated to two queries - query1.statistic = 'Max' and query2.statistic = 'Min' export function migrateMultipleStatsMetricsQuery( diff --git a/public/app/plugins/datasource/cloudwatch/migrations/metricQueryMigrations.test.ts b/public/app/plugins/datasource/cloudwatch/migrations/metricQueryMigrations.test.ts index ae6476e29bd..a15bc132531 100644 --- a/public/app/plugins/datasource/cloudwatch/migrations/metricQueryMigrations.test.ts +++ b/public/app/plugins/datasource/cloudwatch/migrations/metricQueryMigrations.test.ts @@ -1,4 +1,4 @@ -import { CloudWatchMetricsQuery, MetricEditorMode, MetricQueryType } from '../types'; +import { CloudWatchMetricsQuery, MetricEditorMode, MetricQueryType } from '../dataquery.gen'; import { migrateAliasPatterns, migrateMetricQuery } from './metricQueryMigrations'; diff --git a/public/app/plugins/datasource/cloudwatch/migrations/metricQueryMigrations.ts b/public/app/plugins/datasource/cloudwatch/migrations/metricQueryMigrations.ts index 7d79fbd1d86..079f0cbf12a 100644 --- a/public/app/plugins/datasource/cloudwatch/migrations/metricQueryMigrations.ts +++ b/public/app/plugins/datasource/cloudwatch/migrations/metricQueryMigrations.ts @@ -1,6 +1,6 @@ import deepEqual from 'fast-deep-equal'; -import { CloudWatchMetricsQuery } from '../types'; +import { CloudWatchMetricsQuery } from '../dataquery.gen'; import { migrateCloudWatchQuery } from './dashboardMigrations'; diff --git a/public/app/plugins/datasource/cloudwatch/migrations/useMigratedMetricsQuery.test.ts b/public/app/plugins/datasource/cloudwatch/migrations/useMigratedMetricsQuery.test.ts index 5ac0dde4410..dd6c976737a 100644 --- a/public/app/plugins/datasource/cloudwatch/migrations/useMigratedMetricsQuery.test.ts +++ b/public/app/plugins/datasource/cloudwatch/migrations/useMigratedMetricsQuery.test.ts @@ -1,7 +1,7 @@ import { renderHook } from '@testing-library/react'; +import { CloudWatchMetricsQuery } from '../dataquery.gen'; import { DEFAULT_METRICS_QUERY } from '../defaultQueries'; -import { CloudWatchMetricsQuery } from '../types'; import { migrateAliasPatterns } from './metricQueryMigrations'; import useMigratedMetricsQuery from './useMigratedMetricsQuery'; diff --git a/public/app/plugins/datasource/cloudwatch/migrations/useMigratedMetricsQuery.ts b/public/app/plugins/datasource/cloudwatch/migrations/useMigratedMetricsQuery.ts index 890a0b20908..b0933284e77 100644 --- a/public/app/plugins/datasource/cloudwatch/migrations/useMigratedMetricsQuery.ts +++ b/public/app/plugins/datasource/cloudwatch/migrations/useMigratedMetricsQuery.ts @@ -1,6 +1,6 @@ import { useEffect, useMemo } from 'react'; -import { CloudWatchMetricsQuery } from '../types'; +import { CloudWatchMetricsQuery } from '../dataquery.gen'; import { migrateMetricQuery } from './metricQueryMigrations'; diff --git a/public/app/plugins/datasource/cloudwatch/migrations/variableQueryMigrations.ts b/public/app/plugins/datasource/cloudwatch/migrations/variableQueryMigrations.ts index 74e1e6a63e2..95102d0eb74 100644 --- a/public/app/plugins/datasource/cloudwatch/migrations/variableQueryMigrations.ts +++ b/public/app/plugins/datasource/cloudwatch/migrations/variableQueryMigrations.ts @@ -1,6 +1,7 @@ import { omit } from 'lodash'; -import { Dimensions, VariableQuery, VariableQueryType, OldVariableQuery, MultiFilters } from '../types'; +import { Dimensions } from '../dataquery.gen'; +import { VariableQuery, VariableQueryType, OldVariableQuery, MultiFilters } from '../types'; const jsonVariable = /\${(\w+):json}/g; diff --git a/public/app/plugins/datasource/cloudwatch/mocks/Request.ts b/public/app/plugins/datasource/cloudwatch/mocks/Request.ts index dfcdfbeff0d..f466526c4cd 100644 --- a/public/app/plugins/datasource/cloudwatch/mocks/Request.ts +++ b/public/app/plugins/datasource/cloudwatch/mocks/Request.ts @@ -1,6 +1,7 @@ import { DataQueryRequest } from '@grafana/data'; -import { CloudWatchQuery, CloudWatchLogsQuery } from '../types'; +import { CloudWatchLogsQuery } from '../dataquery.gen'; +import { CloudWatchQuery } from '../types'; import { TimeRangeMock } from './timeRange'; diff --git a/public/app/plugins/datasource/cloudwatch/mocks/queries.ts b/public/app/plugins/datasource/cloudwatch/mocks/queries.ts index 82e6e3ef1ae..cdfca0cea3f 100644 --- a/public/app/plugins/datasource/cloudwatch/mocks/queries.ts +++ b/public/app/plugins/datasource/cloudwatch/mocks/queries.ts @@ -1,5 +1,10 @@ -import { QueryEditorExpressionType } from '../expressions'; -import { CloudWatchMetricsQuery, MetricQueryType, MetricEditorMode, CloudWatchLogsQuery } from '../types'; +import { + CloudWatchMetricsQuery, + MetricQueryType, + MetricEditorMode, + CloudWatchLogsQuery, + QueryEditorExpressionType, +} from '../dataquery.gen'; export const validMetricSearchCodeQuery: CloudWatchMetricsQuery = { id: '', diff --git a/public/app/plugins/datasource/cloudwatch/mocks/sqlUtils.ts b/public/app/plugins/datasource/cloudwatch/mocks/sqlUtils.ts index f593d43f4e4..ebc4edf07e2 100644 --- a/public/app/plugins/datasource/cloudwatch/mocks/sqlUtils.ts +++ b/public/app/plugins/datasource/cloudwatch/mocks/sqlUtils.ts @@ -2,13 +2,13 @@ import { QueryEditorExpression, QueryEditorExpressionType, QueryEditorArrayExpression, - QueryEditorOperatorExpression, QueryEditorPropertyType, QueryEditorGroupByExpression, QueryEditorFunctionExpression, QueryEditorFunctionParameterExpression, QueryEditorPropertyExpression, -} from '../expressions'; +} from '../dataquery.gen'; +import { QueryEditorOperatorExpression } from '../expressions'; export function createArray( expressions: QueryEditorExpression[], diff --git a/public/app/plugins/datasource/cloudwatch/query-runner/CloudWatchAnnotationQueryRunner.test.ts b/public/app/plugins/datasource/cloudwatch/query-runner/CloudWatchAnnotationQueryRunner.test.ts index cac628cea6e..3e9105f82e1 100644 --- a/public/app/plugins/datasource/cloudwatch/query-runner/CloudWatchAnnotationQueryRunner.test.ts +++ b/public/app/plugins/datasource/cloudwatch/query-runner/CloudWatchAnnotationQueryRunner.test.ts @@ -1,6 +1,6 @@ +import { CloudWatchAnnotationQuery } from '../dataquery.gen'; import { setupMockedAnnotationQueryRunner } from '../mocks/AnnotationQueryRunner'; import { namespaceVariable, regionVariable } from '../mocks/CloudWatchDataSource'; -import { CloudWatchAnnotationQuery } from '../types'; describe('CloudWatchAnnotationQueryRunner', () => { const queries: CloudWatchAnnotationQuery[] = [ diff --git a/public/app/plugins/datasource/cloudwatch/query-runner/CloudWatchAnnotationQueryRunner.ts b/public/app/plugins/datasource/cloudwatch/query-runner/CloudWatchAnnotationQueryRunner.ts index 013d14bbd6f..a563cbbe9d7 100644 --- a/public/app/plugins/datasource/cloudwatch/query-runner/CloudWatchAnnotationQueryRunner.ts +++ b/public/app/plugins/datasource/cloudwatch/query-runner/CloudWatchAnnotationQueryRunner.ts @@ -3,7 +3,8 @@ import { Observable } from 'rxjs'; import { DataQueryRequest, DataQueryResponse, DataSourceInstanceSettings } from '@grafana/data'; import { TemplateSrv } from '@grafana/runtime'; -import { CloudWatchAnnotationQuery, CloudWatchJsonData, CloudWatchQuery } from '../types'; +import { CloudWatchAnnotationQuery } from '../dataquery.gen'; +import { CloudWatchJsonData, CloudWatchQuery } from '../types'; import { CloudWatchRequest } from './CloudWatchRequest'; diff --git a/public/app/plugins/datasource/cloudwatch/query-runner/CloudWatchLogsQueryRunner.test.ts b/public/app/plugins/datasource/cloudwatch/query-runner/CloudWatchLogsQueryRunner.test.ts index 8eee779cdcf..150a89aaad4 100644 --- a/public/app/plugins/datasource/cloudwatch/query-runner/CloudWatchLogsQueryRunner.test.ts +++ b/public/app/plugins/datasource/cloudwatch/query-runner/CloudWatchLogsQueryRunner.test.ts @@ -10,12 +10,12 @@ import { LogRowModel, } from '@grafana/data'; +import { CloudWatchLogsAnomaliesQuery, CloudWatchLogsQuery, LogsMode } from '../dataquery.gen'; // Add this import statement import { logGroupNamesVariable, regionVariable } from '../mocks/CloudWatchDataSource'; import { setupMockedLogsQueryRunner } from '../mocks/LogsQueryRunner'; import { LogsRequestMock } from '../mocks/Request'; import { validLogsQuery } from '../mocks/queries'; import { TimeRangeMock } from '../mocks/timeRange'; -import { CloudWatchLogsAnomaliesQuery, CloudWatchLogsQuery, LogsMode } from '../types'; // Add this import statement import { LOGSTREAM_IDENTIFIER_INTERNAL, diff --git a/public/app/plugins/datasource/cloudwatch/query-runner/CloudWatchLogsQueryRunner.ts b/public/app/plugins/datasource/cloudwatch/query-runner/CloudWatchLogsQueryRunner.ts index cd8e8b29f1c..470824f76c0 100644 --- a/public/app/plugins/datasource/cloudwatch/query-runner/CloudWatchLogsQueryRunner.ts +++ b/public/app/plugins/datasource/cloudwatch/query-runner/CloudWatchLogsQueryRunner.ts @@ -38,17 +38,14 @@ import { type CustomFormatterVariable } from '@grafana/scenes'; import { GraphDrawStyle } from '@grafana/schema/dist/esm/index'; import { TableCellDisplayMode } from '@grafana/ui'; +import { CloudWatchLogsQuery, LogsMode, CloudWatchLogsAnomaliesQuery, LogsQueryLanguage } from '../dataquery.gen'; import { CloudWatchJsonData, - CloudWatchLogsAnomaliesQuery, - CloudWatchLogsQuery, CloudWatchLogsQueryStatus, CloudWatchLogsRequest, CloudWatchQuery, GetLogEventsRequest, LogAction, - LogsMode, - LogsQueryLanguage, QueryParam, StartQueryRequest, } from '../types'; diff --git a/public/app/plugins/datasource/cloudwatch/query-runner/CloudWatchMetricsQueryRunner.test.ts b/public/app/plugins/datasource/cloudwatch/query-runner/CloudWatchMetricsQueryRunner.test.ts index 0234af74af8..59c84b7634c 100644 --- a/public/app/plugins/datasource/cloudwatch/query-runner/CloudWatchMetricsQueryRunner.test.ts +++ b/public/app/plugins/datasource/cloudwatch/query-runner/CloudWatchMetricsQueryRunner.test.ts @@ -3,6 +3,7 @@ import { of } from 'rxjs'; import { dateTime, CustomVariableModel, getFrameDisplayName, VariableHide } from '@grafana/data'; import { toDataQueryResponse } from '@grafana/runtime'; +import { MetricQueryType, MetricEditorMode, CloudWatchMetricsQuery } from '../dataquery.gen'; import { namespaceVariable, metricVariable, @@ -15,7 +16,6 @@ import { import { initialVariableModelState } from '../mocks/CloudWatchVariables'; import { setupMockedMetricsQueryRunner } from '../mocks/MetricsQueryRunner'; import { validMetricSearchBuilderQuery, validMetricSearchCodeQuery } from '../mocks/queries'; -import { MetricQueryType, MetricEditorMode, CloudWatchMetricsQuery } from '../types'; jest.mock('@grafana/runtime', () => ({ ...jest.requireActual('@grafana/runtime'), diff --git a/public/app/plugins/datasource/cloudwatch/query-runner/CloudWatchMetricsQueryRunner.ts b/public/app/plugins/datasource/cloudwatch/query-runner/CloudWatchMetricsQueryRunner.ts index 6560e3d4eed..852cfa93fd4 100644 --- a/public/app/plugins/datasource/cloudwatch/query-runner/CloudWatchMetricsQueryRunner.ts +++ b/public/app/plugins/datasource/cloudwatch/query-runner/CloudWatchMetricsQueryRunner.ts @@ -17,9 +17,10 @@ import { import { TemplateSrv, getAppEvents } from '@grafana/runtime'; import { ThrottlingErrorMessage } from '../components/Errors/ThrottlingErrorMessage'; +import { CloudWatchMetricsQuery } from '../dataquery.gen'; import memoizedDebounce from '../memoizedDebounce'; import { migrateMetricQuery } from '../migrations/metricQueryMigrations'; -import { CloudWatchJsonData, CloudWatchMetricsQuery, CloudWatchQuery } from '../types'; +import { CloudWatchJsonData, CloudWatchQuery } from '../types'; import { filterMetricsQuery } from '../utils/utils'; import { CloudWatchRequest } from './CloudWatchRequest'; diff --git a/public/app/plugins/datasource/cloudwatch/query-runner/CloudWatchRequest.ts b/public/app/plugins/datasource/cloudwatch/query-runner/CloudWatchRequest.ts index 460a2e7ee49..7a022e197c3 100644 --- a/public/app/plugins/datasource/cloudwatch/query-runner/CloudWatchRequest.ts +++ b/public/app/plugins/datasource/cloudwatch/query-runner/CloudWatchRequest.ts @@ -3,8 +3,9 @@ import { Observable } from 'rxjs'; import { DataSourceInstanceSettings, DataSourceRef, getDataSourceRef, ScopedVars, AppEvents } from '@grafana/data'; import { BackendDataSourceResponse, FetchResponse, getBackendSrv, TemplateSrv, getAppEvents } from '@grafana/runtime'; +import { Dimensions } from '../dataquery.gen'; import memoizedDebounce from '../memoizedDebounce'; -import { CloudWatchJsonData, Dimensions, MetricRequest, MultiFilters } from '../types'; +import { CloudWatchJsonData, MetricRequest, MultiFilters } from '../types'; import { getVariableName } from '../utils/templateVariableUtils'; export abstract class CloudWatchRequest { diff --git a/public/app/plugins/datasource/cloudwatch/resources/types.ts b/public/app/plugins/datasource/cloudwatch/resources/types.ts index 290da62c171..83c3252561a 100644 --- a/public/app/plugins/datasource/cloudwatch/resources/types.ts +++ b/public/app/plugins/datasource/cloudwatch/resources/types.ts @@ -1,6 +1,6 @@ import { SelectableValue } from '@grafana/data'; -import { Dimensions } from '../types'; +import { Dimensions } from '../dataquery.gen'; export interface ResourceResponse { accountId?: string; diff --git a/public/app/plugins/datasource/cloudwatch/tracking.ts b/public/app/plugins/datasource/cloudwatch/tracking.ts index 51a4559ee1e..e2d863f3d64 100644 --- a/public/app/plugins/datasource/cloudwatch/tracking.ts +++ b/public/app/plugins/datasource/cloudwatch/tracking.ts @@ -1,19 +1,19 @@ import { DashboardLoadedEvent } from '@grafana/data'; import { config, reportInteraction } from '@grafana/runtime'; +import { + CloudWatchLogsQuery, + CloudWatchLogsAnomaliesQuery, + CloudWatchMetricsQuery, + LogsMode, + LogsQueryLanguage, + MetricQueryType, + MetricEditorMode, +} from './dataquery.gen'; import { isCloudWatchLogsQuery, isCloudWatchMetricsQuery, isLogsAnomaliesQuery } from './guards'; import { migrateMetricQuery } from './migrations/metricQueryMigrations'; import pluginJson from './plugin.json'; -import { - CloudWatchLogsAnomaliesQuery, - CloudWatchLogsQuery, - CloudWatchMetricsQuery, - CloudWatchQuery, - LogsMode, - LogsQueryLanguage, - MetricEditorMode, - MetricQueryType, -} from './types'; +import { CloudWatchQuery } from './types'; import { filterMetricsQuery } from './utils/utils'; type CloudWatchOnDashboardLoadedTrackingEvent = { diff --git a/public/app/plugins/datasource/cloudwatch/types.ts b/public/app/plugins/datasource/cloudwatch/types.ts index b774bfade40..f1e92d47cbd 100644 --- a/public/app/plugins/datasource/cloudwatch/types.ts +++ b/public/app/plugins/datasource/cloudwatch/types.ts @@ -4,8 +4,6 @@ import { DataQuery } from '@grafana/schema'; import * as raw from './dataquery.gen'; -export * from './dataquery.gen'; - export type CloudWatchQuery = | raw.CloudWatchMetricsQuery | raw.CloudWatchLogsQuery diff --git a/public/app/plugins/datasource/cloudwatch/utils/datalinks.ts b/public/app/plugins/datasource/cloudwatch/utils/datalinks.ts index e69ae4e4e17..16bfc81f70e 100644 --- a/public/app/plugins/datasource/cloudwatch/utils/datalinks.ts +++ b/public/app/plugins/datasource/cloudwatch/utils/datalinks.ts @@ -10,7 +10,8 @@ import { import { getDataSourceSrv } from '@grafana/runtime'; import { AwsUrl, encodeUrl } from '../aws_url'; -import { CloudWatchLogsQuery, CloudWatchQuery } from '../types'; +import { CloudWatchLogsQuery } from '../dataquery.gen'; +import { CloudWatchQuery } from '../types'; type ReplaceFn = ( target?: string, diff --git a/public/app/plugins/datasource/cloudwatch/utils/utils.ts b/public/app/plugins/datasource/cloudwatch/utils/utils.ts index b98751ce156..38fc50067a7 100644 --- a/public/app/plugins/datasource/cloudwatch/utils/utils.ts +++ b/public/app/plugins/datasource/cloudwatch/utils/utils.ts @@ -1,6 +1,6 @@ import { SelectableValue } from '@grafana/data'; -import { CloudWatchMetricsQuery, MetricQueryType, MetricEditorMode } from '../types'; +import { CloudWatchMetricsQuery, MetricQueryType, MetricEditorMode } from '../dataquery.gen'; import { CloudWatchDatasource } from './../datasource'; From c8853f50cfbcd8f7bbdca4949a8bdf4dff895440 Mon Sep 17 00:00:00 2001 From: Ivan Ortega Alba Date: Thu, 4 Dec 2025 06:08:03 +0100 Subject: [PATCH 012/110] Dashboard migration: Implement v2 to v0 conversions (#114812) * Update docs * Remove 406 response since now it is converted * fix linter --------- Co-authored-by: Stephanie Hingtgen --- apps/dashboard/pkg/migration/README.md | 57 +- .../pkg/migration/conversion/conversion.go | 4 +- .../migration/conversion/conversion_test.go | 38 + ...v2beta1.v10.table_thresholds.v0alpha1.json | 367 +- .../v2beta1.v11.no-op-migration.v0alpha1.json | 339 +- ...beta1.v12.template-variables.v0alpha1.json | 401 +-- ...v2beta1.v13.graph_thresholds.v0alpha1.json | 529 +-- ...ta1.v13.minimal_graph_config.v0alpha1.json | 216 +- ...d_crosshair_to_graph_tooltip.v0alpha1.json | 345 +- ....v15.mimir_rollout_debugging.v0alpha1.json | 1782 +++++----- .../v2beta1.v15.no-op-migration.v0alpha1.json | 367 +- ....empty-rows-and-panels-array.v0alpha1.json | 2542 ++++++-------- ...eta1.v16.grid_layout_upgrade.v0alpha1.json | 1077 ++---- .../v2beta1.v16.span_zero_demo.v0alpha1.json | 2539 +++++--------- ...ta1.v17.minspan_to_maxperrow.v0alpha1.json | 934 ++--- .../v2beta1.v18.gauge_options.v0alpha1.json | 621 ++-- .../v2beta1.v19.panel_links.v0alpha1.json | 667 ++-- ...beta1.v2.panels-and-services.v0alpha1.json | 560 +-- ...a1.v20.variable_syntax_links.v0alpha1.json | 680 ++-- ...1.data_links_series_to_field.v0alpha1.json | 615 ++-- ...2beta1.v22.table_panel_align.v0alpha1.json | 205 +- ...v23.multi_variable_alignment.v0alpha1.json | 700 ++-- .../v2beta1.v24.table-angular.v0alpha1.json | 1569 +++------ .../v2beta1.v25.no-op-migration.v0alpha1.json | 358 +- .../v2beta1.v26.text2_to_text.v0alpha1.json | 373 +- ...panels_and_constant_variable.v0alpha1.json | 331 +- ...8.remove_variable_properties.v0alpha1.json | 245 +- ...stat_and_variable_properties.v0alpha1.json | 622 ++-- ...ta1.v28.singlestat_migration.v0alpha1.json | 935 ++--- ...ariables_refresh_and_options.v0alpha1.json | 816 ++--- .../v2beta1.v3.no-op.v0alpha1.json | 448 +-- ...mappings_and_tooltip_options.v0alpha1.json | 1098 +++--- ...1.v31.labels_to_fields_merge.v0alpha1.json | 1013 ++---- .../v2beta1.v32.no_op_migration.v0alpha1.json | 499 +-- ...ta1.v33.panel_ds_name_to_ref.v0alpha1.json | 1087 ++---- ...34.multiple_stats_cloudwatch.v0alpha1.json | 3027 ++++++----------- ...v35.ensure_x_axis_visibility.v0alpha1.json | 927 ++--- .../v2beta1.v36.ds_name_to_ref.v0alpha1.json | 1593 +++------ ...ta1.v37.legend_normalization.v0alpha1.json | 921 ++--- ...le_displaymode_comprehensive.v0alpha1.json | 1005 ++---- ...imeseries_table_display_mode.v0alpha1.json | 1005 ++---- ...9.transform_timeseries_table.v0alpha1.json | 881 ++--- .../v2beta1.v4.no-op.v0alpha1.json | 367 +- ...ta1.v40.refresh_empty_string.v0alpha1.json | 120 +- .../v2beta1.v40.refresh_false.v0alpha1.json | 120 +- .../v2beta1.v40.refresh_not_set.v0alpha1.json | 120 +- .../v2beta1.v40.refresh_numeric.v0alpha1.json | 120 +- .../v2beta1.v40.refresh_string.v0alpha1.json | 120 +- .../v2beta1.v40.refresh_true.v0alpha1.json | 120 +- .../v2beta1.v41.no_time_picker.v0alpha1.json | 120 +- ....time_picker_no_time_options.v0alpha1.json | 120 +- ...v41.time_picker_time_options.v0alpha1.json | 120 +- .../v2beta1.v42.harky_must.v0alpha1.json | 203 +- ...v2beta1.v42.hidefrom_tooltip.v0alpha1.json | 749 ++-- .../v2beta1.v5.no-op.v0alpha1.json | 367 +- ....v6.pulldowns_and_templating.v0alpha1.json | 553 ++- .../v2beta1.v7.timepicker.v0alpha1.json | 234 +- .../v2beta1.v9.no-op.v0alpha1.json | 367 +- ...beta1.annotation-conversions.v0alpha1.json | 2 +- .../output/v2alpha1.complete.v0alpha1.json | 876 ++--- .../v2alpha1.ds-data-query.v0alpha1.json | 1865 +++++----- .../v2alpha1.groupby-adhoc-vars.v0alpha1.json | 177 +- .../output/v2alpha1.viz-config.v0alpha1.json | 292 +- .../output/v2beta1.complete.v0alpha1.json | 919 +++-- ...v2beta1.dashboard-properties.v0alpha1.json | 363 +- ...2beta1.datasource-resolution.v0alpha1.json | 1362 +++----- .../v2beta1.ds-data-query.v0alpha1.json | 1940 +++++------ .../v2beta1.groupby-adhoc-vars.v0alpha1.json | 182 +- ...2beta1.rows-with-nested-tabs.v0alpha1.json | 947 +++--- ...beta1.tabs-and-rows-repeated.v0alpha1.json | 1694 ++++----- ...2beta1.tabs-with-nested-rows.v0alpha1.json | 1123 +++--- .../output/v2beta1.viz-config.v0alpha1.json | 311 +- apps/dashboard/pkg/migration/conversion/v2.go | 107 +- pkg/api/dashboard.go | 6 - pkg/api/dashboard_test.go | 40 - pkg/tests/apis/dashboard/dashboards_test.go | 4 +- .../transformSaveModelV2ToV1.test.ts | 182 +- 77 files changed, 18543 insertions(+), 32107 deletions(-) diff --git a/apps/dashboard/pkg/migration/README.md b/apps/dashboard/pkg/migration/README.md index abca58c9087..ab938e31f66 100644 --- a/apps/dashboard/pkg/migration/README.md +++ b/apps/dashboard/pkg/migration/README.md @@ -8,12 +8,13 @@ This document describes the Grafana dashboard migration system, focusing on conv - [Conversion Flow](#conversion-flow-v0--v1--v2) - [v0 to v1 Conversion](#v0-to-v1-conversion) - [v1 to v2 Conversion](#v1-to-v2-conversion) + - [v2 to v0/v1 Conversion](#v2-to-v0v1-conversion) - [Conversion Matrix](#conversion-matrix) - [API Versions](#api-versions) - [Schema Versions](#schema-versions) - [Testing](#testing) - [Backend conversion tests](#backend-conversion-tests) - - [Frontend migration comparison tests](#frontend-migration-comparison-tests) + - [Backend and frontend conversion parity tests](#backend-and-frontend-conversion-parity-tests) - [Monitoring Migrations](#monitoring-migrations) - [Metrics](#metrics) - [Dashboard conversion success metric](#1-dashboard-conversion-success-metric) @@ -54,6 +55,12 @@ v0alpha1 (Legacy JSON) → v1beta1 (Migrated JSON) → v2alpha1/v2beta1 (Structu - Handles modern dashboard features and Kubernetes-native storage - See [V2 to V1 Layout Conversion](./conversion/v2_to_v1_layout_conversion.md) for details on how V2 layouts are converted back to V1 panel arrays +#### v2 to v0/v1 Conversion: +- Converts structured v2 dashboards back to JSON format (v0alpha1 or v1beta1) +- Chains through intermediate versions: v2 → v1beta1 → v0alpha1 +- v0alpha1 and v1beta1 share the same spec structure (only API version differs) +- Enables backward compatibility when storing v2 dashboards in legacy format + ## Conversion Matrix The system supports conversions between all dashboard API versions: @@ -113,25 +120,51 @@ go test ./apps/dashboard/pkg/migration/conversion/... -v go test ./apps/dashboard/pkg/migration/... -run TestSchemaMigrationMetrics ``` -### Frontend migration comparison tests +### Backend and frontend conversion parity tests -The frontend migration comparison tests validate that backend and frontend conversion logic produce consistent results: +These tests ensure that backend (Go) and frontend (TypeScript) conversions produce identical outputs. This is critical because: -- **Test methodology**: Compares backend vs frontend conversion outputs through DashboardModel integration -- **Dataset coverage**: Tests run against curated test files covering various conversion scenarios -- **Test location**: `public/app/features/dashboard/state/DashboardMigratorToBackend.test.ts` -- **Test data**: Located in `apps/dashboard/pkg/migration/testdata/input/` and `testdata/output/` +- **Dual implementation**: Both backend and frontend implement dashboard version conversions +- **Consistency requirement**: Users should see the same dashboard regardless of which path is used +- **API flexibility**: The API may return dashboards in different versions depending on context + +**Why normalize through Scene?** + +Both backend and frontend outputs are passed through the same Scene load/save cycle before comparison. This normalization: +- Eliminates differences from default values added by Scene +- Handles field ordering variations +- Simulates the real-world flow: dashboard loaded → edited → saved + +**Test locations:** + +| Test File | Purpose | +|-----------|---------| +| `public/app/features/dashboard-scene/serialization/transformSaveModelV1ToV2.test.ts` | v1beta1 → v2beta1 conversion parity | +| `public/app/features/dashboard-scene/serialization/transformSaveModelV2ToV1.test.ts` | v2beta1 → v1beta1/v0alpha1 conversion parity | +| `public/app/features/dashboard/state/DashboardMigratorToBackend.test.ts` | Schema version migration parity | **Test execution:** ```bash -# Frontend migration comparison tests +# V1 to V2 conversion parity tests +yarn test transformSaveModelV1ToV2.test.ts + +# V2 to V1 conversion parity tests +yarn test transformSaveModelV2ToV1.test.ts + +# Schema migration parity tests yarn test DashboardMigratorToBackend.test.ts ``` -**Test approach:** -- **Frontend path**: `jsonInput → DashboardModel → DashboardMigrator → getSaveModelClone()` -- **Backend path**: `jsonInput → Backend Conversion → backendOutput → DashboardModel → getSaveModelClone()` -- **Comparison**: Direct comparison of final converted states from both paths +**Test approach (v1 → v2):** +- **Backend path**: `v1beta1 → Go conversion → v2beta1 → Scene → normalized output` +- **Frontend path**: `v1beta1 → Scene → v2beta1 → Scene → normalized output` +- **Test data**: Uses files from `apps/dashboard/pkg/migration/conversion/testdata/` and migrated dashboards + +**Test approach (v2 → v1/v0):** +- **Backend path**: `v2beta1 → Go conversion → v1beta1/v0alpha1 → Scene → normalized output` +- **Frontend path**: `v2beta1 → Scene → v1beta1 → Scene → normalized output` +- **Target versions**: Tests both v0alpha1 and v1beta1 (they share the same spec structure) +- **Test data**: Uses files from `apps/dashboard/pkg/migration/conversion/testdata/` For schema version migration testing details, see the [SchemaVersion Migration Guide](./schemaversion/README.md). diff --git a/apps/dashboard/pkg/migration/conversion/conversion.go b/apps/dashboard/pkg/migration/conversion/conversion.go index cd014dd1466..d0f90e1ba98 100644 --- a/apps/dashboard/pkg/migration/conversion/conversion.go +++ b/apps/dashboard/pkg/migration/conversion/conversion.go @@ -62,7 +62,7 @@ func RegisterConversions(s *runtime.Scheme, dsIndexProvider schemaversion.DataSo // v2alpha1 conversions if err := s.AddConversionFunc((*dashv2alpha1.Dashboard)(nil), (*dashv0.Dashboard)(nil), withConversionMetrics(dashv2alpha1.APIVERSION, dashv0.APIVERSION, func(a, b interface{}, scope conversion.Scope) error { - return Convert_V2alpha1_to_V0(a.(*dashv2alpha1.Dashboard), b.(*dashv0.Dashboard), scope) + return Convert_V2alpha1_to_V0(a.(*dashv2alpha1.Dashboard), b.(*dashv0.Dashboard), scope, dsIndexProvider) })); err != nil { return err } @@ -82,7 +82,7 @@ func RegisterConversions(s *runtime.Scheme, dsIndexProvider schemaversion.DataSo // v2beta1 conversions if err := s.AddConversionFunc((*dashv2beta1.Dashboard)(nil), (*dashv0.Dashboard)(nil), withConversionMetrics(dashv2beta1.APIVERSION, dashv0.APIVERSION, func(a, b interface{}, scope conversion.Scope) error { - return Convert_V2beta1_to_V0(a.(*dashv2beta1.Dashboard), b.(*dashv0.Dashboard), scope) + return Convert_V2beta1_to_V0(a.(*dashv2beta1.Dashboard), b.(*dashv0.Dashboard), scope, dsIndexProvider) })); err != nil { return err } diff --git a/apps/dashboard/pkg/migration/conversion/conversion_test.go b/apps/dashboard/pkg/migration/conversion/conversion_test.go index 66647c6b453..88dfb683225 100644 --- a/apps/dashboard/pkg/migration/conversion/conversion_test.go +++ b/apps/dashboard/pkg/migration/conversion/conversion_test.go @@ -605,6 +605,44 @@ func TestConversionMetrics(t *testing.T) { expectedSourceSchema: "v2alpha1", expectedTargetSchema: "v2beta1", }, + { + name: "successful v2alpha1 to v0 conversion", + source: &dashv2alpha1.Dashboard{ + ObjectMeta: metav1.ObjectMeta{UID: "test-uid-4"}, + Spec: dashv2alpha1.DashboardSpec{ + Title: "test dashboard", + Elements: map[string]dashv2alpha1.DashboardElement{}, + Annotations: []dashv2alpha1.DashboardAnnotationQueryKind{}, + Links: []dashv2alpha1.DashboardDashboardLink{}, + }, + }, + target: &dashv0.Dashboard{}, + expectAPISuccess: true, + expectMetricsSuccess: true, + expectedSourceAPI: dashv2alpha1.APIVERSION, + expectedTargetAPI: dashv0.APIVERSION, + expectedSourceSchema: "v2alpha1", + expectedTargetSchema: "42", // V2→V0 results in latest schema version + }, + { + name: "successful v2beta1 to v0 conversion", + source: &dashv2beta1.Dashboard{ + ObjectMeta: metav1.ObjectMeta{UID: "test-uid-5"}, + Spec: dashv2beta1.DashboardSpec{ + Title: "test dashboard", + Elements: map[string]dashv2beta1.DashboardElement{}, + Annotations: []dashv2beta1.DashboardAnnotationQueryKind{}, + Links: []dashv2beta1.DashboardDashboardLink{}, + }, + }, + target: &dashv0.Dashboard{}, + expectAPISuccess: true, + expectMetricsSuccess: true, + expectedSourceAPI: dashv2beta1.APIVERSION, + expectedTargetAPI: dashv0.APIVERSION, + expectedSourceSchema: "v2beta1", + expectedTargetSchema: "42", // V2→V0 results in latest schema version + }, } for _, tt := range tests { diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v10.table_thresholds.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v10.table_thresholds.v0alpha1.json index 624425c41eb..5a6cebb8a91 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v10.table_thresholds.v0alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v10.table_thresholds.v0alpha1.json @@ -4,260 +4,123 @@ "metadata": { "name": "v10.table_thresholds.v42" }, - "spec": null, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "liveNow": false, + "panels": [ + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 1, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "", + "type": "table" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 2, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "", + "type": "table" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 3, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "", + "type": "timeseries" + } + ], + "preload": false, + "refresh": "", + "schemaVersion": 42, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "V10 Table Thresholds Test" + }, "status": { "conversion": { - "failed": true, - "error": "backend conversion not yet implemented", - "storedVersion": "v2beta1", - "source": { - "kind": "Dashboard", - "apiVersion": "dashboard.grafana.app/v2beta1", - "metadata": { - "name": "v10.table_thresholds.v42" - }, - "spec": { - "annotations": [ - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana", - "version": "v0", - "datasource": { - "name": "-- Grafana --" - }, - "spec": {} - }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations \u0026 Alerts", - "builtIn": true, - "legacyOptions": { - "type": "dashboard" - } - } - } - ], - "cursorSync": "Off", - "editable": true, - "elements": { - "panel-1": { - "kind": "Panel", - "spec": { - "id": 1, - "title": "", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "table", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-2": { - "kind": "Panel", - "spec": { - "id": 2, - "title": "", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "table", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-3": { - "kind": "Panel", - "spec": { - "id": 3, - "title": "", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - } - }, - "layout": { - "kind": "GridLayout", - "spec": { - "items": [ - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-1" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-2" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-3" - } - } - } - ] - } - }, - "links": [], - "liveNow": false, - "preload": false, - "tags": [], - "timeSettings": { - "timezone": "", - "from": "now-6h", - "to": "now", - "autoRefresh": "", - "autoRefreshIntervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "hideTimepicker": false, - "fiscalYearStartMonth": 0 - }, - "title": "V10 Table Thresholds Test", - "variables": [] - }, - "status": {} - } + "failed": false, + "storedVersion": "v2beta1" } } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v11.no-op-migration.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v11.no-op-migration.v0alpha1.json index abf1618c7f8..0442d15c222 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v11.no-op-migration.v0alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v11.no-op-migration.v0alpha1.json @@ -4,229 +4,126 @@ "metadata": { "name": "v11.no-op-migration.v42" }, - "spec": null, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "liveNow": false, + "panels": [ + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 1, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "CPU Usage", + "type": "timeseries" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 2, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Memory Usage", + "type": "stat" + } + ], + "preload": false, + "refresh": "", + "schemaVersion": 42, + "templating": { + "list": [ + { + "allowCustomValue": true, + "current": { + "text": "", + "value": "" + }, + "datasource": { + "type": "prometheus" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "server", + "options": [], + "query": "label_values(server)", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "V11 No-Op Migration Test Dashboard" + }, "status": { "conversion": { - "failed": true, - "error": "backend conversion not yet implemented", - "storedVersion": "v2beta1", - "source": { - "kind": "Dashboard", - "apiVersion": "dashboard.grafana.app/v2beta1", - "metadata": { - "name": "v11.no-op-migration.v42" - }, - "spec": { - "annotations": [ - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana", - "version": "v0", - "datasource": { - "name": "-- Grafana --" - }, - "spec": {} - }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations \u0026 Alerts", - "builtIn": true, - "legacyOptions": { - "type": "dashboard" - } - } - } - ], - "cursorSync": "Off", - "editable": true, - "elements": { - "panel-1": { - "kind": "Panel", - "spec": { - "id": 1, - "title": "CPU Usage", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-2": { - "kind": "Panel", - "spec": { - "id": 2, - "title": "Memory Usage", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "stat", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - } - }, - "layout": { - "kind": "GridLayout", - "spec": { - "items": [ - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-1" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-2" - } - } - } - ] - } - }, - "links": [], - "liveNow": false, - "preload": false, - "tags": [], - "timeSettings": { - "timezone": "", - "from": "now-6h", - "to": "now", - "autoRefresh": "", - "autoRefreshIntervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "hideTimepicker": false, - "fiscalYearStartMonth": 0 - }, - "title": "V11 No-Op Migration Test Dashboard", - "variables": [ - { - "kind": "QueryVariable", - "spec": { - "name": "server", - "current": { - "text": "", - "value": "" - }, - "hide": "dontHide", - "refresh": "onDashboardLoad", - "skipUrlSync": false, - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "spec": { - "__legacyStringValue": "label_values(server)" - } - }, - "regex": "", - "sort": "disabled", - "options": [], - "multi": false, - "includeAll": false, - "allowCustomValue": true - } - } - ] - }, - "status": {} - } + "failed": false, + "storedVersion": "v2beta1" } } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v12.template-variables.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v12.template-variables.v0alpha1.json index 5b7ff4cfca5..7cf9b83cc46 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v12.template-variables.v0alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v12.template-variables.v0alpha1.json @@ -4,232 +4,185 @@ "metadata": { "name": "v12.template-variables.v42" }, - "spec": null, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "liveNow": false, + "preload": false, + "refresh": "", + "schemaVersion": 42, + "templating": { + "list": [ + { + "allowCustomValue": true, + "current": { + "text": "", + "value": "" + }, + "datasource": { + "type": "prometheus" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "refresh_true_var", + "options": [], + "query": {}, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "allowCustomValue": true, + "current": { + "text": "", + "value": "" + }, + "datasource": { + "type": "prometheus" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "refresh_false_var", + "options": [], + "query": {}, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "allowCustomValue": true, + "current": { + "text": "", + "value": "" + }, + "datasource": { + "type": "prometheus" + }, + "hide": 2, + "includeAll": false, + "multi": false, + "name": "hide_variable_var", + "options": [], + "query": {}, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "allowCustomValue": true, + "current": { + "text": "", + "value": "" + }, + "datasource": { + "type": "prometheus" + }, + "hide": 1, + "includeAll": false, + "multi": false, + "name": "hide_label_var", + "options": [], + "query": {}, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "allowCustomValue": true, + "current": { + "text": "", + "value": "" + }, + "datasource": { + "type": "prometheus" + }, + "hide": 2, + "includeAll": false, + "multi": false, + "name": "priority_var", + "options": [], + "query": {}, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "allowCustomValue": true, + "current": { + "text": "", + "value": "" + }, + "datasource": { + "type": "prometheus" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "no_properties_var", + "options": [], + "query": {}, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "V12 Template Variables Migration Test" + }, "status": { "conversion": { - "failed": true, - "error": "backend conversion not yet implemented", - "storedVersion": "v2beta1", - "source": { - "kind": "Dashboard", - "apiVersion": "dashboard.grafana.app/v2beta1", - "metadata": { - "name": "v12.template-variables.v42" - }, - "spec": { - "annotations": [ - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana", - "version": "v0", - "datasource": { - "name": "-- Grafana --" - }, - "spec": {} - }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations \u0026 Alerts", - "builtIn": true, - "legacyOptions": { - "type": "dashboard" - } - } - } - ], - "cursorSync": "Off", - "editable": true, - "elements": {}, - "layout": { - "kind": "GridLayout", - "spec": { - "items": [] - } - }, - "links": [], - "liveNow": false, - "preload": false, - "tags": [], - "timeSettings": { - "timezone": "", - "from": "now-6h", - "to": "now", - "autoRefresh": "", - "autoRefreshIntervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "hideTimepicker": false, - "fiscalYearStartMonth": 0 - }, - "title": "V12 Template Variables Migration Test", - "variables": [ - { - "kind": "QueryVariable", - "spec": { - "name": "refresh_true_var", - "current": { - "text": "", - "value": "" - }, - "hide": "dontHide", - "refresh": "onDashboardLoad", - "skipUrlSync": false, - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "spec": {} - }, - "regex": "", - "sort": "disabled", - "options": [], - "multi": false, - "includeAll": false, - "allowCustomValue": true - } - }, - { - "kind": "QueryVariable", - "spec": { - "name": "refresh_false_var", - "current": { - "text": "", - "value": "" - }, - "hide": "dontHide", - "refresh": "onDashboardLoad", - "skipUrlSync": false, - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "spec": {} - }, - "regex": "", - "sort": "disabled", - "options": [], - "multi": false, - "includeAll": false, - "allowCustomValue": true - } - }, - { - "kind": "QueryVariable", - "spec": { - "name": "hide_variable_var", - "current": { - "text": "", - "value": "" - }, - "hide": "hideVariable", - "refresh": "onDashboardLoad", - "skipUrlSync": false, - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "spec": {} - }, - "regex": "", - "sort": "disabled", - "options": [], - "multi": false, - "includeAll": false, - "allowCustomValue": true - } - }, - { - "kind": "QueryVariable", - "spec": { - "name": "hide_label_var", - "current": { - "text": "", - "value": "" - }, - "hide": "hideLabel", - "refresh": "onDashboardLoad", - "skipUrlSync": false, - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "spec": {} - }, - "regex": "", - "sort": "disabled", - "options": [], - "multi": false, - "includeAll": false, - "allowCustomValue": true - } - }, - { - "kind": "QueryVariable", - "spec": { - "name": "priority_var", - "current": { - "text": "", - "value": "" - }, - "hide": "hideVariable", - "refresh": "onDashboardLoad", - "skipUrlSync": false, - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "spec": {} - }, - "regex": "", - "sort": "disabled", - "options": [], - "multi": false, - "includeAll": false, - "allowCustomValue": true - } - }, - { - "kind": "QueryVariable", - "spec": { - "name": "no_properties_var", - "current": { - "text": "", - "value": "" - }, - "hide": "dontHide", - "refresh": "onDashboardLoad", - "skipUrlSync": false, - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "spec": {} - }, - "regex": "", - "sort": "disabled", - "options": [], - "multi": false, - "includeAll": false, - "allowCustomValue": true - } - } - ] - }, - "status": {} - } + "failed": false, + "storedVersion": "v2beta1" } } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v13.graph_thresholds.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v13.graph_thresholds.v0alpha1.json index c767888f7a4..7aadfcf8535 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v13.graph_thresholds.v0alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v13.graph_thresholds.v0alpha1.json @@ -4,378 +4,167 @@ "metadata": { "name": "v13.graph_thresholds.v42" }, - "spec": null, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "liveNow": false, + "panels": [ + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 1, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Graph with Line Thresholds", + "type": "timeseries" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 2, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Graph with Fill Thresholds", + "type": "timeseries" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 3, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Graph with Single Threshold", + "type": "timeseries" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 4, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Graph with Existing Thresholds", + "type": "timeseries" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 5, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Non-Graph Panel", + "type": "stat" + } + ], + "preload": false, + "refresh": "", + "schemaVersion": 42, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "V13 Graph Thresholds Migration Test" + }, "status": { "conversion": { - "failed": true, - "error": "backend conversion not yet implemented", - "storedVersion": "v2beta1", - "source": { - "kind": "Dashboard", - "apiVersion": "dashboard.grafana.app/v2beta1", - "metadata": { - "name": "v13.graph_thresholds.v42" - }, - "spec": { - "annotations": [ - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana", - "version": "v0", - "datasource": { - "name": "-- Grafana --" - }, - "spec": {} - }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations \u0026 Alerts", - "builtIn": true, - "legacyOptions": { - "type": "dashboard" - } - } - } - ], - "cursorSync": "Off", - "editable": true, - "elements": { - "panel-1": { - "kind": "Panel", - "spec": { - "id": 1, - "title": "Graph with Line Thresholds", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-2": { - "kind": "Panel", - "spec": { - "id": 2, - "title": "Graph with Fill Thresholds", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-3": { - "kind": "Panel", - "spec": { - "id": 3, - "title": "Graph with Single Threshold", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-4": { - "kind": "Panel", - "spec": { - "id": 4, - "title": "Graph with Existing Thresholds", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-5": { - "kind": "Panel", - "spec": { - "id": 5, - "title": "Non-Graph Panel", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "stat", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - } - }, - "layout": { - "kind": "GridLayout", - "spec": { - "items": [ - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-1" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-2" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-3" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-4" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-5" - } - } - } - ] - } - }, - "links": [], - "liveNow": false, - "preload": false, - "tags": [], - "timeSettings": { - "timezone": "", - "from": "now-6h", - "to": "now", - "autoRefresh": "", - "autoRefreshIntervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "hideTimepicker": false, - "fiscalYearStartMonth": 0 - }, - "title": "V13 Graph Thresholds Migration Test", - "variables": [] - }, - "status": {} - } + "failed": false, + "storedVersion": "v2beta1" } } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v13.minimal_graph_config.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v13.minimal_graph_config.v0alpha1.json index 0ba0e58612d..e768e617055 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v13.minimal_graph_config.v0alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v13.minimal_graph_config.v0alpha1.json @@ -4,148 +4,84 @@ "metadata": { "name": "v13.minimal_graph_config.v42" }, - "spec": null, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "liveNow": false, + "panels": [ + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 4, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "gdev-prometheus" + }, + "editorMode": "builder", + "expr": "{\"a.utf8.metric 🤘\", job=\"prometheus-utf8\"}", + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "", + "type": "timeseries" + } + ], + "preload": false, + "refresh": "", + "schemaVersion": 42, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "browser", + "title": "Dashboard with minimal graph panel settings" + }, "status": { "conversion": { - "failed": true, - "error": "backend conversion not yet implemented", - "storedVersion": "v2beta1", - "source": { - "kind": "Dashboard", - "apiVersion": "dashboard.grafana.app/v2beta1", - "metadata": { - "name": "v13.minimal_graph_config.v42" - }, - "spec": { - "annotations": [ - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana", - "version": "v0", - "datasource": { - "name": "-- Grafana --" - }, - "spec": {} - }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations \u0026 Alerts", - "builtIn": true, - "legacyOptions": { - "type": "dashboard" - } - } - } - ], - "cursorSync": "Off", - "editable": true, - "elements": { - "panel-4": { - "kind": "Panel", - "spec": { - "id": 4, - "title": "", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "gdev-prometheus" - }, - "spec": { - "editorMode": "builder", - "expr": "{\"a.utf8.metric 🤘\", job=\"prometheus-utf8\"}", - "instant": false, - "legendFormat": "__auto", - "range": true - } - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - } - }, - "layout": { - "kind": "GridLayout", - "spec": { - "items": [ - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-4" - } - } - } - ] - } - }, - "links": [], - "liveNow": false, - "preload": false, - "tags": [], - "timeSettings": { - "timezone": "browser", - "from": "now-6h", - "to": "now", - "autoRefresh": "", - "autoRefreshIntervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "hideTimepicker": false, - "fiscalYearStartMonth": 0 - }, - "title": "Dashboard with minimal graph panel settings", - "variables": [] - }, - "status": {} - } + "failed": false, + "storedVersion": "v2beta1" } } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v14.shared_crosshair_to_graph_tooltip.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v14.shared_crosshair_to_graph_tooltip.v0alpha1.json index 9dfb93934b8..6f6b04077da 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v14.shared_crosshair_to_graph_tooltip.v0alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v14.shared_crosshair_to_graph_tooltip.v0alpha1.json @@ -4,233 +4,128 @@ "metadata": { "name": "v14.shared_crosshair_to_graph_tooltip.v42" }, - "spec": null, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 1, + "liveNow": false, + "panels": [ + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 1, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "expr": "cpu_usage", + "refId": "A" + } + ], + "title": "CPU Usage Over Time", + "type": "timeseries" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 2, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "expr": "memory_usage", + "refId": "B" + } + ], + "title": "Memory Usage", + "type": "timeseries" + } + ], + "preload": false, + "refresh": "", + "schemaVersion": 42, + "templating": { + "list": [ + { + "allowCustomValue": true, + "current": { + "text": "", + "value": "" + }, + "datasource": { + "type": "prometheus" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "server", + "options": [], + "query": "label_values(server)", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + } + ] + }, + "time": { + "from": "now-1h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "V14 Shared Crosshair Migration Test Dashboard" + }, "status": { "conversion": { - "failed": true, - "error": "backend conversion not yet implemented", - "storedVersion": "v2beta1", - "source": { - "kind": "Dashboard", - "apiVersion": "dashboard.grafana.app/v2beta1", - "metadata": { - "name": "v14.shared_crosshair_to_graph_tooltip.v42" - }, - "spec": { - "annotations": [ - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana", - "version": "v0", - "datasource": { - "name": "-- Grafana --" - }, - "spec": {} - }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations \u0026 Alerts", - "builtIn": true, - "legacyOptions": { - "type": "dashboard" - } - } - } - ], - "cursorSync": "Crosshair", - "editable": true, - "elements": { - "panel-1": { - "kind": "Panel", - "spec": { - "id": 1, - "title": "CPU Usage Over Time", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": { - "expr": "cpu_usage" - } - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-2": { - "kind": "Panel", - "spec": { - "id": 2, - "title": "Memory Usage", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": { - "expr": "memory_usage" - } - }, - "refId": "B", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - } - }, - "layout": { - "kind": "GridLayout", - "spec": { - "items": [ - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-1" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-2" - } - } - } - ] - } - }, - "links": [], - "liveNow": false, - "preload": false, - "tags": [], - "timeSettings": { - "timezone": "", - "from": "now-1h", - "to": "now", - "autoRefresh": "", - "autoRefreshIntervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "hideTimepicker": false, - "fiscalYearStartMonth": 0 - }, - "title": "V14 Shared Crosshair Migration Test Dashboard", - "variables": [ - { - "kind": "QueryVariable", - "spec": { - "name": "server", - "current": { - "text": "", - "value": "" - }, - "hide": "dontHide", - "refresh": "onDashboardLoad", - "skipUrlSync": false, - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "spec": { - "__legacyStringValue": "label_values(server)" - } - }, - "regex": "", - "sort": "disabled", - "options": [], - "multi": false, - "includeAll": false, - "allowCustomValue": true - } - } - ] - }, - "status": {} - } + "failed": false, + "storedVersion": "v2beta1" } } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v15.mimir_rollout_debugging.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v15.mimir_rollout_debugging.v0alpha1.json index c16318c1bfd..cc366a688e3 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v15.mimir_rollout_debugging.v0alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v15.mimir_rollout_debugging.v0alpha1.json @@ -4,1039 +4,773 @@ "metadata": { "name": "v15.mimir_rollout_debugging.v42" }, - "spec": null, - "status": { - "conversion": { - "failed": true, - "error": "backend conversion not yet implemented", - "storedVersion": "v2beta1", - "source": { - "kind": "Dashboard", - "apiVersion": "dashboard.grafana.app/v2beta1", - "metadata": { - "name": "v15.mimir_rollout_debugging.v42" - }, - "spec": { - "annotations": [ - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana", - "version": "v0", - "datasource": { - "name": "-- Grafana --" - }, - "spec": {} - }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations \u0026 Alerts", - "builtIn": true, - "legacyOptions": { - "type": "dashboard" - } - } - }, - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "", - "version": "v0", - "spec": {} - }, - "enable": true, - "hide": false, - "iconColor": "rgba(255, 96, 96, 1)", - "name": "rollouts", - "legacyOptions": { - "expr": "up", - "showIn": 0, - "tags": [], - "titleFormat": "Rollout was underway in {{cluster}}/{{namespace}}", - "type": "tags" - } - } - } - ], - "cursorSync": "Crosshair", - "editable": false, - "elements": { - "panel-1": { - "kind": "Panel", - "spec": { - "id": 1, - "title": "Versions running", - "description": "### Versions running\nShows the versions reported by each running pod.\n\nThe rollout will fail if any pod is not running the expected version.\n\nPods in green are running the expected version, while pods running other versions are shown in orange.\n\n", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "", - "version": "v0", - "datasource": { - "name": "$datasource" - }, - "spec": { - "expr": "sum by (job, version) (up{job=~\".*\"})", - "format": "table", - "instant": true, - "legendFormat": "__auto" - } - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [ - { - "kind": "groupingToMatrix", - "spec": { - "id": "groupingToMatrix", - "options": { - "columnField": "version", - "rowField": "job", - "valueField": "Value" - } - } - }, - { - "kind": "sortBy", - "spec": { - "id": "sortBy", - "options": { - "sort": [ - { - "field": "job\\version" - } - ] - } - } - } - ], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "barchart", - "version": "", - "spec": { - "options": { - "barRadius": 0, - "barWidth": 0.97, - "fullHighlight": false, - "groupWidth": 0.7, - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "orientation": "horizontal", - "showValue": "auto", - "stacking": "percent", - "tooltip": { - "mode": "single", - "sort": "none" - }, - "xField": "job\\version", - "xTickLabelRotation": 0, - "xTickLabelSpacing": 0 - }, - "fieldConfig": { - "defaults": { - "unit": "short", - "min": 0, - "max": 1, - "thresholds": { - "mode": "absolute", - "steps": [] - }, - "color": { - "mode": "shades", - "fixedColor": "orange" - }, - "custom": { - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "fillOpacity": 80, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineWidth": 1, - "scaleDistribution": { - "type": "linear" - }, - "thresholdsStyle": { - "mode": "off" - } - } - }, - "overrides": [ - { - "matcher": { - "id": "byRegexp", - "options": "/.*(target)/" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "green", - "mode": "fixed" - } - } - ] - } - ] - } - } - } - } - }, - "panel-2": { - "kind": "Panel", - "spec": { - "id": 2, - "title": "Deployment rollout progress", - "description": "### Deployment rollout progress\nShows the number of pods for each `Deployment` that match the desired configuration, as a proportion of the desired number of pods.\n\nThe rollout will fail if insufficient pods match the desired configuration for any `Deployment`.\n\nPods in green match the desired configuration, while pods that do not match the desired configuration are shown in orange.\n\n", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "", - "version": "v0", - "datasource": { - "name": "$datasource" - }, - "spec": { - "expr": "sum by (deployment) (up{job=\"kube-state-metrics\"})", - "format": "table", - "instant": true, - "legendFormat": "__auto" - } - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [ - { - "kind": "sortBy", - "spec": { - "id": "sortBy", - "options": { - "fields": {}, - "sort": [ - { - "field": "deployment" - } - ] - } - } - } - ], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "barchart", - "version": "", - "spec": { - "options": { - "barRadius": 0, - "barWidth": 0.97, - "fullHighlight": false, - "groupWidth": 0.7, - "legend": { - "displayMode": "list", - "showLegend": false - }, - "orientation": "horizontal", - "showValue": "auto", - "stacking": "none", - "tooltip": { - "mode": "single", - "sort": "none" - }, - "xTickLabelRotation": 0, - "xTickLabelSpacing": 0 - }, - "fieldConfig": { - "defaults": { - "unit": "percentunit", - "min": 0, - "max": 1, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": null, - "color": "orange" - }, - { - "value": 1, - "color": "green" - } - ] - }, - "color": { - "mode": "thresholds" - }, - "custom": { - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "fillOpacity": 80, - "gradientMode": "scheme", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineWidth": 1, - "scaleDistribution": { - "type": "linear" - }, - "thresholdsStyle": { - "mode": "off" - } - } - }, - "overrides": [] - } - } - } - } - }, - "panel-3": { - "kind": "Panel", - "spec": { - "id": 3, - "title": "StatefulSet rollout progress", - "description": "### StatefulSet rollout progress\nShows the number of pods for each `StatefulSet` that match the desired configuration, as a proportion of the desired number of pods.\n\nThe rollout will fail if insufficient pods match the desired configuration for any `StatefulSet`.\n\nPods in green match the desired configuration, while pods that do not match the desired configuration are shown in orange.\n\n", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "", - "version": "v0", - "datasource": { - "name": "$datasource" - }, - "spec": { - "expr": "sum by (statefulset) (up{job=\"kube-state-metrics\"})", - "format": "table", - "instant": true, - "legendFormat": "__auto" - } - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [ - { - "kind": "sortBy", - "spec": { - "id": "sortBy", - "options": { - "fields": {}, - "sort": [ - { - "field": "statefulset" - } - ] - } - } - } - ], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "barchart", - "version": "", - "spec": { - "options": { - "barRadius": 0, - "barWidth": 0.97, - "fullHighlight": false, - "groupWidth": 0.7, - "legend": { - "displayMode": "list", - "showLegend": false - }, - "orientation": "horizontal", - "showValue": "auto", - "stacking": "none", - "tooltip": { - "mode": "single", - "sort": "none" - }, - "xTickLabelRotation": 0, - "xTickLabelSpacing": 0 - }, - "fieldConfig": { - "defaults": { - "unit": "percentunit", - "min": 0, - "max": 1, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": null, - "color": "orange" - }, - { - "value": 1, - "color": "green" - } - ] - }, - "color": { - "mode": "thresholds" - }, - "custom": { - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "fillOpacity": 80, - "gradientMode": "scheme", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineWidth": 1, - "scaleDistribution": { - "type": "linear" - }, - "thresholdsStyle": { - "mode": "off" - } - } - }, - "overrides": [] - } - } - } - } - }, - "panel-4": { - "kind": "Panel", - "spec": { - "id": 4, - "title": "Aggregator lag", - "description": "### Aggregator lag\nShows the consumption lag of each aggregator pod.\n\nThis panel may show no data if aggregators are not deployed to this cell.\n\nThe rollout will fail if any pod's consumption lag is both:\n* greater than 30s (red area on graph), and\n* trending upwards compared to 1 minute earlier\n\n", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "", - "version": "v0", - "datasource": { - "name": "$datasource" - }, - "spec": { - "expr": "max by (pod) (up{job=\"mimir-aggregator\"})", - "format": "time_series", - "legendFormat": "__auto" - } - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": { - "legend": { - "showLegend": true - }, - "tooltip": { - "mode": "multi", - "sort": "none" - } - }, - "fieldConfig": { - "defaults": { - "unit": "s", - "min": 0, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": null, - "color": "green" - }, - { - "value": 30, - "color": "red" - } - ] - }, - "noValue": "No data (are aggregators deployed in this cell?)", - "custom": { - "drawStyle": "line", - "fillOpacity": 0, - "lineWidth": 1, - "pointSize": 5, - "showPoints": "never", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "area" - } - } - }, - "overrides": [] - } - } - } - } - }, - "panel-5": { - "kind": "Panel", - "spec": { - "id": 5, - "title": "Unhealthy Deployment replicas", - "description": "### Unhealthy Deployment replicas\nShows the number of unavailable pods for each `Deployment`.\n\nThe rollout will fail if any `Deployment` has an unavailable pod.\n\nBoth this panel and the rollout check ignore any `Deployment`s that require spot nodes, as these are expected to be unavailable from time to time.\n\n`Deployment`s shown in green do not have any unavailable pods, while `Deployment`s shown in orange have one or more unavailable pods.\n\n", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "", - "version": "v0", - "datasource": { - "name": "$datasource" - }, - "spec": { - "expr": "sum by (deployment) (up{job=\"kube-state-metrics\"})", - "format": "table", - "instant": true, - "legendFormat": "__auto" - } - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [ - { - "kind": "sortBy", - "spec": { - "id": "sortBy", - "options": { - "fields": {}, - "sort": [ - { - "field": "deployment" - } - ] - } - } - } - ], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "barchart", - "version": "", - "spec": { - "options": { - "barRadius": 0, - "barWidth": 0.97, - "fullHighlight": false, - "groupWidth": 0.7, - "legend": { - "displayMode": "list", - "showLegend": false - }, - "orientation": "horizontal", - "showValue": "auto", - "stacking": "none", - "tooltip": { - "mode": "single", - "sort": "none" - }, - "xTickLabelRotation": 0, - "xTickLabelSpacing": 0 - }, - "fieldConfig": { - "defaults": { - "unit": "", - "min": 0, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": null, - "color": "green" - }, - { - "value": 1, - "color": "orange" - } - ] - }, - "color": { - "mode": "thresholds" - }, - "custom": { - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "axisSoftMax": 10, - "fillOpacity": 80, - "gradientMode": "scheme", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineWidth": 1, - "scaleDistribution": { - "type": "linear" - }, - "thresholdsStyle": { - "mode": "off" - } - } - }, - "overrides": [] - } - } - } - } - }, - "panel-6": { - "kind": "Panel", - "spec": { - "id": 6, - "title": "Unhealthy StatefulSet replicas", - "description": "### Unhealthy StatefulSet replicas\nShows the number of pods for each `StatefulSet` that are not ready.\n\nThe rollout will fail if any `StatefulSet` has fewer ready pods than requested.\n\nBoth this panel and the rollout check ignore any `StatefulSets`s that require spot nodes, as these are expected to be unavailable from time to time.\n\n`StatefulSets`s shown in green do not have any pods that are not ready, while `StatefulSet`s shown in orange have one or more pods that are not ready.\n\n", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "", - "version": "v0", - "datasource": { - "name": "$datasource" - }, - "spec": { - "expr": "sum by (statefulset) (up{job=\"kube-state-metrics\"})", - "format": "table", - "instant": true, - "legendFormat": "__auto" - } - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [ - { - "kind": "sortBy", - "spec": { - "id": "sortBy", - "options": { - "fields": {}, - "sort": [ - { - "field": "statefulset" - } - ] - } - } - } - ], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "barchart", - "version": "", - "spec": { - "options": { - "barRadius": 0, - "barWidth": 0.97, - "fullHighlight": false, - "groupWidth": 0.7, - "legend": { - "displayMode": "list", - "showLegend": false - }, - "orientation": "horizontal", - "showValue": "auto", - "stacking": "none", - "tooltip": { - "mode": "single", - "sort": "none" - }, - "xTickLabelRotation": 0, - "xTickLabelSpacing": 0 - }, - "fieldConfig": { - "defaults": { - "unit": "", - "min": 0, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": null, - "color": "green" - }, - { - "value": 1, - "color": "orange" - } - ] - }, - "color": { - "mode": "thresholds" - }, - "custom": { - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "axisSoftMax": 10, - "fillOpacity": 80, - "gradientMode": "scheme", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineWidth": 1, - "scaleDistribution": { - "type": "linear" - }, - "thresholdsStyle": { - "mode": "off" - } - } - }, - "overrides": [] - } - } - } - } - } + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" }, - "layout": { - "kind": "RowsLayout", - "spec": { - "rows": [ + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + }, + { + "enable": true, + "expr": "up", + "hide": false, + "iconColor": "rgba(255, 96, 96, 1)", + "name": "rollouts", + "showIn": 0, + "tags": [], + "titleFormat": "Rollout was underway in {{cluster}}/{{namespace}}", + "type": "tags" + } + ] + }, + "editable": false, + "fiscalYearStartMonth": 0, + "graphTooltip": 1, + "links": [ + { + "asDropdown": true, + "icon": "external link", + "includeVars": true, + "keepTime": true, + "tags": [ + "show-in-mimir-links-dropdown" + ], + "targetBlank": false, + "title": "Mimir dashboards", + "tooltip": "", + "type": "dashboards" + } + ], + "liveNow": false, + "panels": [ + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 0 + }, + "id": -1, + "title": "Rollout progress", + "type": "row" + }, + { + "description": "### Versions running\nShows the versions reported by each running pod.\n\nThe rollout will fail if any pod is not running the expected version.\n\nPods in green are running the expected version, while pods running other versions are shown in orange.\n\n", + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "orange", + "mode": "shades" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "max": 1, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [] + }, + "unit": "short" + }, + "overrides": [ + { + "matcher": { + "id": "byRegexp", + "options": "/.*(target)/" + }, + "properties": [ { - "kind": "RowsLayoutRow", - "spec": { - "title": "Rollout progress", - "collapse": false, - "layout": { - "kind": "GridLayout", - "spec": { - "items": [ - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 8, - "height": 19, - "element": { - "kind": "ElementReference", - "name": "panel-1" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 8, - "y": 0, - "width": 8, - "height": 19, - "element": { - "kind": "ElementReference", - "name": "panel-2" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 16, - "y": 0, - "width": 8, - "height": 19, - "element": { - "kind": "ElementReference", - "name": "panel-3" - } - } - } - ] - } - } - } - }, - { - "kind": "RowsLayoutRow", - "spec": { - "title": "Rollout health", - "collapse": false, - "layout": { - "kind": "GridLayout", - "spec": { - "items": [ - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 8, - "height": 19, - "element": { - "kind": "ElementReference", - "name": "panel-4" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 8, - "y": 0, - "width": 8, - "height": 19, - "element": { - "kind": "ElementReference", - "name": "panel-5" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 16, - "y": 0, - "width": 8, - "height": 19, - "element": { - "kind": "ElementReference", - "name": "panel-6" - } - } - } - ] - } - } + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" } } ] } - }, - "links": [ - { - "title": "Mimir dashboards", - "type": "dashboards", - "icon": "external link", - "tooltip": "", - "tags": [ - "show-in-mimir-links-dropdown" - ], - "asDropdown": true, - "targetBlank": false, - "includeVars": true, - "keepTime": true - } - ], - "liveNow": false, - "preload": false, - "tags": [ - "mimir", - "betterops-mimir", - "show-in-mimir-links-dropdown", - "as-code" - ], - "timeSettings": { - "timezone": "utc", - "from": "now-1h", - "to": "now", - "autoRefresh": "5m", - "autoRefreshIntervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "hideTimepicker": false, - "fiscalYearStartMonth": 0 - }, - "title": "Mimir / Rollout debugging", - "variables": [ - { - "kind": "DatasourceVariable", - "spec": { - "name": "datasource", - "pluginId": "prometheus", - "refresh": "onDashboardLoad", - "regex": "", - "current": { - "text": "", - "value": "grafanacloud-prom" - }, - "options": [], - "multi": false, - "includeAll": false, - "label": "Data source", - "hide": "dontHide", - "skipUrlSync": false, - "allowCustomValue": true - } - }, - { - "kind": "QueryVariable", - "spec": { - "name": "cluster", - "current": { - "text": "prod", - "value": "prod" - }, - "label": "cluster", - "hide": "dontHide", - "refresh": "onDashboardLoad", - "skipUrlSync": false, - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "spec": { - "__legacyStringValue": "label_values(up, job)" - } - }, - "regex": "", - "sort": "alphabeticalAsc", - "options": [], - "multi": false, - "includeAll": true, - "allValue": ".*", - "allowCustomValue": true - } - }, - { - "kind": "QueryVariable", - "spec": { - "name": "namespace", - "current": { - "text": "prod", - "value": "prod" - }, - "label": "namespace", - "hide": "dontHide", - "refresh": "onDashboardLoad", - "skipUrlSync": false, - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "spec": { - "__legacyStringValue": "label_values(up{job=~\"$cluster\"}, instance)" - } - }, - "regex": "", - "sort": "alphabeticalAsc", - "options": [], - "multi": false, - "includeAll": false, - "allowCustomValue": true - } - } ] }, - "status": {} + "gridPos": { + "h": 19, + "w": 8, + "x": 0, + "y": 0 + }, + "id": 1, + "options": { + "barRadius": 0, + "barWidth": 0.97, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "horizontal", + "showValue": "auto", + "stacking": "percent", + "tooltip": { + "mode": "single", + "sort": "none" + }, + "xField": "job\\version", + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "", + "uid": "$datasource" + }, + "expr": "sum by (job, version) (up{job=~\".*\"})", + "format": "table", + "instant": true, + "legendFormat": "__auto", + "refId": "A" + } + ], + "title": "Versions running", + "transformations": [ + { + "id": "groupingToMatrix", + "options": { + "columnField": "version", + "rowField": "job", + "valueField": "Value" + } + }, + { + "id": "sortBy", + "options": { + "sort": [ + { + "field": "job\\version" + } + ] + } + } + ], + "type": "barchart" + }, + { + "description": "### Deployment rollout progress\nShows the number of pods for each `Deployment` that match the desired configuration, as a proportion of the desired number of pods.\n\nThe rollout will fail if insufficient pods match the desired configuration for any `Deployment`.\n\nPods in green match the desired configuration, while pods that do not match the desired configuration are shown in orange.\n\n", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "fillOpacity": 80, + "gradientMode": "scheme", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "max": 1, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "orange", + "value": null + }, + { + "color": "green", + "value": 1 + } + ] + }, + "unit": "percentunit" + } + }, + "gridPos": { + "h": 19, + "w": 8, + "x": 8, + "y": 0 + }, + "id": 2, + "options": { + "barRadius": 0, + "barWidth": 0.97, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "displayMode": "list", + "showLegend": false + }, + "orientation": "horizontal", + "showValue": "auto", + "stacking": "none", + "tooltip": { + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "", + "uid": "$datasource" + }, + "expr": "sum by (deployment) (up{job=\"kube-state-metrics\"})", + "format": "table", + "instant": true, + "legendFormat": "__auto", + "refId": "A" + } + ], + "title": "Deployment rollout progress", + "transformations": [ + { + "id": "sortBy", + "options": { + "fields": {}, + "sort": [ + { + "field": "deployment" + } + ] + } + } + ], + "type": "barchart" + }, + { + "description": "### StatefulSet rollout progress\nShows the number of pods for each `StatefulSet` that match the desired configuration, as a proportion of the desired number of pods.\n\nThe rollout will fail if insufficient pods match the desired configuration for any `StatefulSet`.\n\nPods in green match the desired configuration, while pods that do not match the desired configuration are shown in orange.\n\n", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "fillOpacity": 80, + "gradientMode": "scheme", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "max": 1, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "orange", + "value": null + }, + { + "color": "green", + "value": 1 + } + ] + }, + "unit": "percentunit" + } + }, + "gridPos": { + "h": 19, + "w": 8, + "x": 16, + "y": 0 + }, + "id": 3, + "options": { + "barRadius": 0, + "barWidth": 0.97, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "displayMode": "list", + "showLegend": false + }, + "orientation": "horizontal", + "showValue": "auto", + "stacking": "none", + "tooltip": { + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "", + "uid": "$datasource" + }, + "expr": "sum by (statefulset) (up{job=\"kube-state-metrics\"})", + "format": "table", + "instant": true, + "legendFormat": "__auto", + "refId": "A" + } + ], + "title": "StatefulSet rollout progress", + "transformations": [ + { + "id": "sortBy", + "options": { + "fields": {}, + "sort": [ + { + "field": "statefulset" + } + ] + } + } + ], + "type": "barchart" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 19 + }, + "id": -1, + "title": "Rollout health", + "type": "row" + }, + { + "description": "### Aggregator lag\nShows the consumption lag of each aggregator pod.\n\nThis panel may show no data if aggregators are not deployed to this cell.\n\nThe rollout will fail if any pod's consumption lag is both:\n* greater than 30s (red area on graph), and\n* trending upwards compared to 1 minute earlier\n\n", + "fieldConfig": { + "defaults": { + "custom": { + "drawStyle": "line", + "fillOpacity": 0, + "lineWidth": 1, + "pointSize": 5, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "area" + } + }, + "min": 0, + "noValue": "No data (are aggregators deployed in this cell?)", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 30 + } + ] + }, + "unit": "s" + } + }, + "gridPos": { + "h": 19, + "w": 8, + "x": 0, + "y": 19 + }, + "id": 4, + "options": { + "legend": { + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "", + "uid": "$datasource" + }, + "expr": "max by (pod) (up{job=\"mimir-aggregator\"})", + "format": "time_series", + "legendFormat": "__auto", + "refId": "A" + } + ], + "title": "Aggregator lag", + "type": "timeseries" + }, + { + "description": "### Unhealthy Deployment replicas\nShows the number of unavailable pods for each `Deployment`.\n\nThe rollout will fail if any `Deployment` has an unavailable pod.\n\nBoth this panel and the rollout check ignore any `Deployment`s that require spot nodes, as these are expected to be unavailable from time to time.\n\n`Deployment`s shown in green do not have any unavailable pods, while `Deployment`s shown in orange have one or more unavailable pods.\n\n", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "axisSoftMax": 10, + "fillOpacity": 80, + "gradientMode": "scheme", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "orange", + "value": 1 + } + ] + }, + "unit": "" + } + }, + "gridPos": { + "h": 19, + "w": 8, + "x": 8, + "y": 19 + }, + "id": 5, + "options": { + "barRadius": 0, + "barWidth": 0.97, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "displayMode": "list", + "showLegend": false + }, + "orientation": "horizontal", + "showValue": "auto", + "stacking": "none", + "tooltip": { + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "", + "uid": "$datasource" + }, + "expr": "sum by (deployment) (up{job=\"kube-state-metrics\"})", + "format": "table", + "instant": true, + "legendFormat": "__auto", + "refId": "A" + } + ], + "title": "Unhealthy Deployment replicas", + "transformations": [ + { + "id": "sortBy", + "options": { + "fields": {}, + "sort": [ + { + "field": "deployment" + } + ] + } + } + ], + "type": "barchart" + }, + { + "description": "### Unhealthy StatefulSet replicas\nShows the number of pods for each `StatefulSet` that are not ready.\n\nThe rollout will fail if any `StatefulSet` has fewer ready pods than requested.\n\nBoth this panel and the rollout check ignore any `StatefulSets`s that require spot nodes, as these are expected to be unavailable from time to time.\n\n`StatefulSets`s shown in green do not have any pods that are not ready, while `StatefulSet`s shown in orange have one or more pods that are not ready.\n\n", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "axisSoftMax": 10, + "fillOpacity": 80, + "gradientMode": "scheme", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "orange", + "value": 1 + } + ] + }, + "unit": "" + } + }, + "gridPos": { + "h": 19, + "w": 8, + "x": 16, + "y": 19 + }, + "id": 6, + "options": { + "barRadius": 0, + "barWidth": 0.97, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "displayMode": "list", + "showLegend": false + }, + "orientation": "horizontal", + "showValue": "auto", + "stacking": "none", + "tooltip": { + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 0 + }, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "", + "uid": "$datasource" + }, + "expr": "sum by (statefulset) (up{job=\"kube-state-metrics\"})", + "format": "table", + "instant": true, + "legendFormat": "__auto", + "refId": "A" + } + ], + "title": "Unhealthy StatefulSet replicas", + "transformations": [ + { + "id": "sortBy", + "options": { + "fields": {}, + "sort": [ + { + "field": "statefulset" + } + ] + } + } + ], + "type": "barchart" } + ], + "preload": false, + "refresh": "5m", + "schemaVersion": 42, + "tags": [ + "mimir", + "betterops-mimir", + "show-in-mimir-links-dropdown", + "as-code" + ], + "templating": { + "list": [ + { + "allowCustomValue": true, + "current": { + "text": "", + "value": "grafanacloud-prom" + }, + "hide": 0, + "includeAll": false, + "label": "Data source", + "multi": false, + "name": "datasource", + "options": [], + "query": "prometheus", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "type": "datasource" + }, + { + "allValue": ".*", + "allowCustomValue": true, + "current": { + "text": "prod", + "value": "prod" + }, + "datasource": { + "type": "prometheus" + }, + "hide": 0, + "includeAll": true, + "label": "cluster", + "multi": false, + "name": "cluster", + "options": [], + "query": "label_values(up, job)", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 1, + "type": "query" + }, + { + "allowCustomValue": true, + "current": { + "text": "prod", + "value": "prod" + }, + "datasource": { + "type": "prometheus" + }, + "hide": 0, + "includeAll": false, + "label": "namespace", + "multi": false, + "name": "namespace", + "options": [], + "query": "label_values(up{job=~\"$cluster\"}, instance)", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 1, + "type": "query" + } + ] + }, + "time": { + "from": "now-1h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "utc", + "title": "Mimir / Rollout debugging" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v2beta1" } } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v15.no-op-migration.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v15.no-op-migration.v0alpha1.json index df0cc0329de..dd68f978199 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v15.no-op-migration.v0alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v15.no-op-migration.v0alpha1.json @@ -4,247 +4,136 @@ "metadata": { "name": "v15.no-op-migration.v42" }, - "spec": null, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + }, + { + "datasource": { + "type": "grafana", + "uid": "grafana" + }, + "enable": true, + "hide": false, + "iconColor": "", + "name": "Annotations \u0026 Alerts" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "liveNow": false, + "panels": [ + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 1, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "CPU Usage", + "type": "timeseries" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 2, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Memory Usage", + "type": "stat" + } + ], + "preload": false, + "refresh": "", + "schemaVersion": 42, + "templating": { + "list": [ + { + "allowCustomValue": true, + "current": { + "text": "", + "value": "" + }, + "datasource": { + "type": "prometheus" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "server", + "options": [], + "query": "label_values(server)", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "V15 No-Op Migration Test Dashboard" + }, "status": { "conversion": { - "failed": true, - "error": "backend conversion not yet implemented", - "storedVersion": "v2beta1", - "source": { - "kind": "Dashboard", - "apiVersion": "dashboard.grafana.app/v2beta1", - "metadata": { - "name": "v15.no-op-migration.v42" - }, - "spec": { - "annotations": [ - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana", - "version": "v0", - "datasource": { - "name": "-- Grafana --" - }, - "spec": {} - }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations \u0026 Alerts", - "builtIn": true, - "legacyOptions": { - "type": "dashboard" - } - } - }, - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana", - "version": "v0", - "datasource": { - "name": "grafana" - }, - "spec": {} - }, - "enable": true, - "hide": false, - "iconColor": "", - "name": "Annotations \u0026 Alerts" - } - } - ], - "cursorSync": "Off", - "editable": true, - "elements": { - "panel-1": { - "kind": "Panel", - "spec": { - "id": 1, - "title": "CPU Usage", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-2": { - "kind": "Panel", - "spec": { - "id": 2, - "title": "Memory Usage", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "stat", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - } - }, - "layout": { - "kind": "GridLayout", - "spec": { - "items": [ - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-1" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-2" - } - } - } - ] - } - }, - "links": [], - "liveNow": false, - "preload": false, - "tags": [], - "timeSettings": { - "timezone": "", - "from": "now-6h", - "to": "now", - "autoRefresh": "", - "autoRefreshIntervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "hideTimepicker": false, - "fiscalYearStartMonth": 0 - }, - "title": "V15 No-Op Migration Test Dashboard", - "variables": [ - { - "kind": "QueryVariable", - "spec": { - "name": "server", - "current": { - "text": "", - "value": "" - }, - "hide": "dontHide", - "refresh": "onDashboardLoad", - "skipUrlSync": false, - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "spec": { - "__legacyStringValue": "label_values(server)" - } - }, - "regex": "", - "sort": "disabled", - "options": [], - "multi": false, - "includeAll": false, - "allowCustomValue": true - } - } - ] - }, - "status": {} - } + "failed": false, + "storedVersion": "v2beta1" } } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v16.empty-rows-and-panels-array.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v16.empty-rows-and-panels-array.v0alpha1.json index e54e0a347d7..3d8d1b11ad3 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v16.empty-rows-and-panels-array.v0alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v16.empty-rows-and-panels-array.v0alpha1.json @@ -4,1497 +4,1075 @@ "metadata": { "name": "v16.empty-rows-and-panels-array.v42" }, - "spec": null, - "status": { - "conversion": { - "failed": true, - "error": "backend conversion not yet implemented", - "storedVersion": "v2beta1", - "source": { - "kind": "Dashboard", - "apiVersion": "dashboard.grafana.app/v2beta1", - "metadata": { - "name": "v16.empty-rows-and-panels-array.v42" - }, - "spec": { - "annotations": [ - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana", - "version": "v0", - "datasource": { - "name": "-- Grafana --" - }, - "spec": {} - }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations \u0026 Alerts", - "builtIn": true, - "legacyOptions": { - "type": "dashboard" - } - } - } - ], - "cursorSync": "Off", - "description": "Sample monitoring dashboard for testing purposes.", - "editable": false, - "elements": { - "panel-10": { - "kind": "Panel", - "spec": { - "id": 10, - "title": "Iota primary latency", - "description": "Sample latency metric for primary operations.", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "", - "version": "v0", - "datasource": { - "name": "${example_datasource}" - }, - "spec": { - "expr": "increase(demo_latency_iota{env=~\"$env\", region=~\"$region\", node=~\"$node\", service=~\"$service\", type=\"primary\"}[$__rate_interval])", - "format": "time_series", - "interval": "1m", - "intervalFactor": 2, - "legendFormat": "{{node}} - {{service}}" - } - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "fieldConfig": { - "defaults": { - "unit": "s", - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": null, - "color": "green" - } - ] - }, - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - } - }, - "overrides": [] - } - } - } - } - }, - "panel-11": { - "kind": "Panel", - "spec": { - "id": 11, - "title": "Iota secondary latency", - "description": "Sample latency metric for secondary operations.", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "", - "version": "v0", - "datasource": { - "name": "${example_datasource}" - }, - "spec": { - "expr": "increase(demo_latency_iota{env=~\"$env\", region=~\"$region\", node=~\"$node\", service=~\"$service\", type=\"secondary\"}[$__rate_interval])", - "format": "time_series", - "interval": "1m", - "intervalFactor": 2, - "legendFormat": "{{node}} - {{service}}" - } - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "fieldConfig": { - "defaults": { - "unit": "s", - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": null, - "color": "green" - } - ] - }, - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - } - }, - "overrides": [] - } - } - } - } - }, - "panel-12": { - "kind": "Panel", - "spec": { - "id": 12, - "title": "Kappa expansions", - "description": "Sample expansion events metric.", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "", - "version": "v0", - "datasource": { - "name": "${example_datasource}" - }, - "spec": { - "expr": "increase(example_events_kappa{env=~\"$env\", region=~\"$region\", node=~\"$node\", service=~\"$service\"}[$__rate_interval])", - "format": "time_series", - "interval": "1m", - "intervalFactor": 2, - "legendFormat": "{{node}} - {{service}}" - } - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "fieldConfig": { - "defaults": { - "unit": "short", - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": null, - "color": "green" - }, - { - "value": 80, - "color": "red" - } - ] - }, - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - } - }, - "overrides": [] - } - } - } - } - }, - "panel-2": { - "kind": "Panel", - "spec": { - "id": 2, - "title": "Alpha metric", - "description": "Sample metric showing connection count.", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "", - "version": "v0", - "datasource": { - "name": "${example_datasource}" - }, - "spec": { - "expr": "random_metric_alpha{env=~\"$env\", region=~\"$region\", node=~\"$node\"}", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "{{node}} - {{service}}" - } - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "fieldConfig": { - "defaults": { - "unit": "short", - "min": 0, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": null, - "color": "green" - }, - { - "value": 80, - "color": "red" - } - ] - }, - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 54, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "normal" - }, - "thresholdsStyle": { - "mode": "off" - } - } - }, - "overrides": [] - } - } - } - } - }, - "panel-3": { - "kind": "Panel", - "spec": { - "id": 3, - "title": "Beta counter", - "description": "Sample counter metric.", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "", - "version": "v0", - "datasource": { - "name": "${example_datasource}" - }, - "spec": { - "expr": "rate(sample_counter_beta{env=~\"$env\", region=~\"$region\", node=~\"$node\"}[$__rate_interval])", - "format": "time_series", - "interval": "1m", - "intervalFactor": 2, - "legendFormat": "{{node}}" - } - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "fieldConfig": { - "defaults": { - "unit": "ops", - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": null, - "color": "green" - }, - { - "value": 80, - "color": "red" - } - ] - }, - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - } - }, - "overrides": [] - } - } - } - } - }, - "panel-4": { - "kind": "Panel", - "spec": { - "id": 4, - "title": "Gamma errors", - "description": "Sample error metric.", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "", - "version": "v0", - "datasource": { - "name": "${example_datasource}" - }, - "spec": { - "expr": "increase(test_errors_gamma{env=~\"$env\", region=~\"$region\", node=~\"$node\"}[$__rate_interval])", - "format": "time_series", - "interval": "1m", - "intervalFactor": 2, - "legendFormat": "{{node}}" - } - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "fieldConfig": { - "defaults": { - "unit": "short", - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": null, - "color": "green" - }, - { - "value": 80, - "color": "red" - } - ] - }, - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - } - }, - "overrides": [] - } - } - } - } - }, - "panel-5": { - "kind": "Panel", - "spec": { - "id": 5, - "title": "Delta events", - "description": "Sample event rate metric.", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "", - "version": "v0", - "datasource": { - "name": "${example_datasource}" - }, - "spec": { - "expr": "rate(demo_events_delta{env=~\"$env\", region=~\"$region\", node=~\"$node\"}[$__rate_interval])", - "format": "time_series", - "interval": "1m", - "intervalFactor": 2, - "legendFormat": "{{node}}" - } - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "fieldConfig": { - "defaults": { - "unit": "short", - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": null, - "color": "green" - }, - { - "value": 80, - "color": "red" - } - ] - }, - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - } - }, - "overrides": [] - } - } - } - } - }, - "panel-6": { - "kind": "Panel", - "spec": { - "id": 6, - "title": "Epsilon usage", - "description": "Sample resource utilization metric.", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "", - "version": "v0", - "datasource": { - "name": "${example_datasource}" - }, - "spec": { - "expr": "example_usage_epsilon{env=~\"$env\", region=~\"$region\", node=~\"$node\"}", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "{{node}} - {{status}}" - } - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "fieldConfig": { - "defaults": { - "unit": "bytes", - "min": 0, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": null, - "color": "green" - } - ] - }, - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 51, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineStyle": { - "fill": "solid" - }, - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "normal" - }, - "thresholdsStyle": { - "mode": "off" - } - } - }, - "overrides": [] - } - } - } - } - }, - "panel-7": { - "kind": "Panel", - "spec": { - "id": 7, - "title": "Zeta and Eta", - "description": "Sample memory allocation metrics.", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "", - "version": "v0", - "datasource": { - "name": "${example_datasource}" - }, - "spec": { - "expr": "random_total_zeta{env=~\"$env\", region=~\"$region\", node=~\"$node\"}", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "{{node}} - total" - } - }, - "refId": "A", - "hidden": false - } - }, - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "", - "version": "v0", - "datasource": { - "name": "${example_datasource}" - }, - "spec": { - "expr": "sample_target_eta{env=~\"$env\", region=~\"$region\", node=~\"$node\"}", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "{{node}} - target" - } - }, - "refId": "B", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "multi", - "sort": "none" - } - }, - "fieldConfig": { - "defaults": { - "unit": "bytes", - "min": 0, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": null, - "color": "green" - } - ] - }, - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 51, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineStyle": { - "fill": "solid" - }, - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "normal" - }, - "thresholdsStyle": { - "mode": "off" - } - } - }, - "overrides": [] - } - } - } - } - }, - "panel-8": { - "kind": "Panel", - "spec": { - "id": 8, - "title": "Theta utilization", - "description": "Sample utilization percentage.", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "", - "version": "v0", - "datasource": { - "name": "${example_datasource}" - }, - "spec": { - "expr": "100 * random_total_zeta{env=~\"$env\", region=~\"$region\", node=~\"$node\"} / clamp_min(test_available_theta{env=~\"$env\", region=~\"$region\", node=~\"$node\"},1)", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "{{node}}" - } - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "gauge", - "version": "9.1.7", - "spec": { - "options": { - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showThresholdLabels": false, - "showThresholdMarkers": true - }, - "fieldConfig": { - "defaults": { - "unit": "percent", - "min": 0, - "max": 100, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": null, - "color": "green" - } - ] - }, - "color": { - "mode": "thresholds" - } - }, - "overrides": [] - } - } - } - } - } + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" }, - "layout": { - "kind": "RowsLayout", - "spec": { - "rows": [ + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "description": "Sample monitoring dashboard for testing purposes.", + "editable": false, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [ + { + "asDropdown": false, + "icon": "external link", + "includeVars": true, + "keepTime": true, + "tags": [ + "sample-monitoring" + ], + "targetBlank": false, + "title": "Related dashboards", + "tooltip": "", + "type": "dashboards", + "url": "" + } + ], + "liveNow": false, + "panels": [ + { + "description": "Sample metric showing connection count.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 54, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ { - "kind": "RowsLayoutRow", - "spec": { - "title": "", - "collapse": false, - "hideHeader": true, - "layout": { - "kind": "GridLayout", - "spec": { - "items": [ - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 12, - "height": 8, - "element": { - "kind": "ElementReference", - "name": "panel-2" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 12, - "y": 0, - "width": 12, - "height": 8, - "element": { - "kind": "ElementReference", - "name": "panel-3" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 8, - "width": 12, - "height": 8, - "element": { - "kind": "ElementReference", - "name": "panel-4" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 12, - "y": 8, - "width": 12, - "height": 8, - "element": { - "kind": "ElementReference", - "name": "panel-5" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 16, - "width": 24, - "height": 8, - "element": { - "kind": "ElementReference", - "name": "panel-6" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 24, - "width": 16, - "height": 8, - "element": { - "kind": "ElementReference", - "name": "panel-7" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 16, - "y": 24, - "width": 8, - "height": 8, - "element": { - "kind": "ElementReference", - "name": "panel-8" - } - } - } - ] - } - } - } + "color": "green", + "value": null }, { - "kind": "RowsLayoutRow", - "spec": { - "title": "Sample section", - "collapse": false, - "layout": { - "kind": "GridLayout", - "spec": { - "items": [ - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 12, - "height": 8, - "element": { - "kind": "ElementReference", - "name": "panel-10" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 12, - "y": 0, - "width": 12, - "height": 8, - "element": { - "kind": "ElementReference", - "name": "panel-11" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 8, - "width": 24, - "height": 8, - "element": { - "kind": "ElementReference", - "name": "panel-12" - } - } - } - ] - } - } - } + "color": "red", + "value": 80 } ] - } - }, - "links": [ - { - "title": "Related dashboards", - "type": "dashboards", - "icon": "external link", - "tooltip": "", - "url": "", - "tags": [ - "sample-monitoring" - ], - "asDropdown": false, - "targetBlank": false, - "includeVars": true, - "keepTime": true - } - ], - "liveNow": false, - "preload": false, - "tags": [ - "sample-monitoring" - ], - "timeSettings": { - "timezone": "default", - "from": "now-30m", - "to": "now", - "autoRefresh": "30s", - "autoRefreshIntervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "hideTimepicker": false, - "fiscalYearStartMonth": 0 - }, - "title": "Sample dashboard", - "variables": [ - { - "kind": "DatasourceVariable", - "spec": { - "name": "example_datasource", - "pluginId": "sample_source", - "refresh": "onDashboardLoad", - "regex": "", - "current": { - "text": "", - "value": "" - }, - "options": [], - "multi": false, - "includeAll": false, - "label": "Data Source", - "hide": "dontHide", - "skipUrlSync": false, - "allowCustomValue": true - } }, - { - "kind": "QueryVariable", - "spec": { - "name": "env", - "current": { - "text": "", - "value": "" - }, - "label": "Environment", - "hide": "dontHide", - "refresh": "onTimeRangeChanged", - "skipUrlSync": false, - "query": { - "kind": "DataQuery", - "group": "", - "version": "v0", - "datasource": { - "name": "${example_datasource}" - }, - "spec": { - "__legacyStringValue": "label_values(system_info_lambda{}, env)" - } - }, - "regex": "", - "sort": "alphabeticalDesc", - "options": [], - "multi": true, - "includeAll": true, - "allValue": ".+", - "allowCustomValue": true - } - }, - { - "kind": "QueryVariable", - "spec": { - "name": "region", - "current": { - "text": "", - "value": "" - }, - "label": "Region", - "hide": "dontHide", - "refresh": "onTimeRangeChanged", - "skipUrlSync": false, - "query": { - "kind": "DataQuery", - "group": "", - "version": "v0", - "datasource": { - "name": "${example_datasource}" - }, - "spec": { - "__legacyStringValue": "label_values(system_info_lambda{env=~\"$env\"}, region)" - } - }, - "regex": "", - "sort": "disabled", - "options": [], - "multi": true, - "includeAll": true, - "allValue": ".*", - "allowCustomValue": true - } - }, - { - "kind": "QueryVariable", - "spec": { - "name": "node", - "current": { - "text": "", - "value": "" - }, - "label": "Node", - "hide": "dontHide", - "refresh": "onTimeRangeChanged", - "skipUrlSync": false, - "query": { - "kind": "DataQuery", - "group": "", - "version": "v0", - "datasource": { - "name": "${example_datasource}" - }, - "spec": { - "__legacyStringValue": "label_values(system_info_lambda{env=~\"$env\", region=~\"$region\"}, node)" - } - }, - "regex": "", - "sort": "alphabeticalDesc", - "options": [], - "multi": true, - "includeAll": true, - "allValue": ".+", - "allowCustomValue": true - } - }, - { - "kind": "QueryVariable", - "spec": { - "name": "service", - "current": { - "text": "", - "value": "" - }, - "label": "Service", - "hide": "dontHide", - "refresh": "onTimeRangeChanged", - "skipUrlSync": false, - "query": { - "kind": "DataQuery", - "group": "", - "version": "v0", - "datasource": { - "name": "${example_datasource}" - }, - "spec": { - "__legacyStringValue": "label_values(example_events_kappa{env=~\"$env\", region=~\"$region\", node=~\"$node\"}, service)" - } - }, - "regex": "", - "sort": "alphabeticalDesc", - "options": [], - "multi": true, - "includeAll": true, - "allValue": ".+", - "allowCustomValue": true - } - } - ] + "unit": "short" + } }, - "status": {} + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 2, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "", + "uid": "${example_datasource}" + }, + "expr": "random_metric_alpha{env=~\"$env\", region=~\"$region\", node=~\"$node\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "{{node}} - {{service}}", + "refId": "A" + } + ], + "title": "Alpha metric", + "type": "timeseries" + }, + { + "description": "Sample counter metric.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "ops" + } + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 0 + }, + "id": 3, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "", + "uid": "${example_datasource}" + }, + "expr": "rate(sample_counter_beta{env=~\"$env\", region=~\"$region\", node=~\"$node\"}[$__rate_interval])", + "format": "time_series", + "interval": "1m", + "intervalFactor": 2, + "legendFormat": "{{node}}", + "refId": "A" + } + ], + "title": "Beta counter", + "type": "timeseries" + }, + { + "description": "Sample error metric.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + } + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 8 + }, + "id": 4, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "", + "uid": "${example_datasource}" + }, + "expr": "increase(test_errors_gamma{env=~\"$env\", region=~\"$region\", node=~\"$node\"}[$__rate_interval])", + "format": "time_series", + "interval": "1m", + "intervalFactor": 2, + "legendFormat": "{{node}}", + "refId": "A" + } + ], + "title": "Gamma errors", + "type": "timeseries" + }, + { + "description": "Sample event rate metric.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + } + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 8 + }, + "id": 5, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "", + "uid": "${example_datasource}" + }, + "expr": "rate(demo_events_delta{env=~\"$env\", region=~\"$region\", node=~\"$node\"}[$__rate_interval])", + "format": "time_series", + "interval": "1m", + "intervalFactor": 2, + "legendFormat": "{{node}}", + "refId": "A" + } + ], + "title": "Delta events", + "type": "timeseries" + }, + { + "description": "Sample resource utilization metric.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 51, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineStyle": { + "fill": "solid" + }, + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "bytes" + } + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 16 + }, + "id": 6, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "", + "uid": "${example_datasource}" + }, + "expr": "example_usage_epsilon{env=~\"$env\", region=~\"$region\", node=~\"$node\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "{{node}} - {{status}}", + "refId": "A" + } + ], + "title": "Epsilon usage", + "type": "timeseries" + }, + { + "description": "Sample memory allocation metrics.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 51, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineStyle": { + "fill": "solid" + }, + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "bytes" + } + }, + "gridPos": { + "h": 8, + "w": 16, + "x": 0, + "y": 24 + }, + "id": 7, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "", + "uid": "${example_datasource}" + }, + "expr": "random_total_zeta{env=~\"$env\", region=~\"$region\", node=~\"$node\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "{{node}} - total", + "refId": "A" + }, + { + "datasource": { + "type": "", + "uid": "${example_datasource}" + }, + "expr": "sample_target_eta{env=~\"$env\", region=~\"$region\", node=~\"$node\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "{{node}} - target", + "refId": "B" + } + ], + "title": "Zeta and Eta", + "type": "timeseries" + }, + { + "description": "Sample utilization percentage.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "percent" + } + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 16, + "y": 24 + }, + "id": 8, + "options": { + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true + }, + "pluginVersion": "9.1.7", + "targets": [ + { + "datasource": { + "type": "", + "uid": "${example_datasource}" + }, + "expr": "100 * random_total_zeta{env=~\"$env\", region=~\"$region\", node=~\"$node\"} / clamp_min(test_available_theta{env=~\"$env\", region=~\"$region\", node=~\"$node\"},1)", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "{{node}}", + "refId": "A" + } + ], + "title": "Theta utilization", + "type": "gauge" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 32 + }, + "id": -1, + "title": "Sample section", + "type": "row" + }, + { + "description": "Sample latency metric for primary operations.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "s" + } + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 32 + }, + "id": 10, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "", + "uid": "${example_datasource}" + }, + "expr": "increase(demo_latency_iota{env=~\"$env\", region=~\"$region\", node=~\"$node\", service=~\"$service\", type=\"primary\"}[$__rate_interval])", + "format": "time_series", + "interval": "1m", + "intervalFactor": 2, + "legendFormat": "{{node}} - {{service}}", + "refId": "A" + } + ], + "title": "Iota primary latency", + "type": "timeseries" + }, + { + "description": "Sample latency metric for secondary operations.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "s" + } + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 32 + }, + "id": 11, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "", + "uid": "${example_datasource}" + }, + "expr": "increase(demo_latency_iota{env=~\"$env\", region=~\"$region\", node=~\"$node\", service=~\"$service\", type=\"secondary\"}[$__rate_interval])", + "format": "time_series", + "interval": "1m", + "intervalFactor": 2, + "legendFormat": "{{node}} - {{service}}", + "refId": "A" + } + ], + "title": "Iota secondary latency", + "type": "timeseries" + }, + { + "description": "Sample expansion events metric.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + } + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 40 + }, + "id": 12, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "", + "uid": "${example_datasource}" + }, + "expr": "increase(example_events_kappa{env=~\"$env\", region=~\"$region\", node=~\"$node\", service=~\"$service\"}[$__rate_interval])", + "format": "time_series", + "interval": "1m", + "intervalFactor": 2, + "legendFormat": "{{node}} - {{service}}", + "refId": "A" + } + ], + "title": "Kappa expansions", + "type": "timeseries" } + ], + "preload": false, + "refresh": "30s", + "schemaVersion": 42, + "tags": [ + "sample-monitoring" + ], + "templating": { + "list": [ + { + "allowCustomValue": true, + "current": { + "text": "", + "value": "" + }, + "hide": 0, + "includeAll": false, + "label": "Data Source", + "multi": false, + "name": "example_datasource", + "options": [], + "query": "sample_source", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "type": "datasource" + }, + { + "allValue": ".+", + "allowCustomValue": true, + "current": { + "text": "", + "value": "" + }, + "datasource": { + "type": "", + "uid": "${example_datasource}" + }, + "hide": 0, + "includeAll": true, + "label": "Environment", + "multi": true, + "name": "env", + "options": [], + "query": "label_values(system_info_lambda{}, env)", + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 2, + "type": "query" + }, + { + "allValue": ".*", + "allowCustomValue": true, + "current": { + "text": "", + "value": "" + }, + "datasource": { + "type": "", + "uid": "${example_datasource}" + }, + "hide": 0, + "includeAll": true, + "label": "Region", + "multi": true, + "name": "region", + "options": [], + "query": "label_values(system_info_lambda{env=~\"$env\"}, region)", + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "allValue": ".+", + "allowCustomValue": true, + "current": { + "text": "", + "value": "" + }, + "datasource": { + "type": "", + "uid": "${example_datasource}" + }, + "hide": 0, + "includeAll": true, + "label": "Node", + "multi": true, + "name": "node", + "options": [], + "query": "label_values(system_info_lambda{env=~\"$env\", region=~\"$region\"}, node)", + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 2, + "type": "query" + }, + { + "allValue": ".+", + "allowCustomValue": true, + "current": { + "text": "", + "value": "" + }, + "datasource": { + "type": "", + "uid": "${example_datasource}" + }, + "hide": 0, + "includeAll": true, + "label": "Service", + "multi": true, + "name": "service", + "options": [], + "query": "label_values(example_events_kappa{env=~\"$env\", region=~\"$region\", node=~\"$node\"}, service)", + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 2, + "type": "query" + } + ] + }, + "time": { + "from": "now-30m", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "default", + "title": "Sample dashboard" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v2beta1" } } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v16.grid_layout_upgrade.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v16.grid_layout_upgrade.v0alpha1.json index cdeac70de04..286cc75e2c0 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v16.grid_layout_upgrade.v0alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v16.grid_layout_upgrade.v0alpha1.json @@ -4,755 +4,338 @@ "metadata": { "name": "v16.grid_layout_upgrade.v42" }, - "spec": null, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "liveNow": false, + "panels": [ + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 0 + }, + "id": -1, + "title": "", + "type": "row" + }, + { + "gridPos": { + "h": 7, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 1, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "CPU Usage", + "type": "timeseries" + }, + { + "gridPos": { + "h": 7, + "w": 12, + "x": 12, + "y": 0 + }, + "id": 2, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Memory Usage", + "type": "stat" + }, + { + "collapsed": true, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 7 + }, + "id": -1, + "panels": [ + { + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 8 + }, + "id": 3, + "options": {}, + "pluginVersion": "", + "targets": [], + "title": "Process List", + "type": "table" + }, + { + "gridPos": { + "h": 6, + "w": 12, + "x": 0, + "y": 16 + }, + "id": 4, + "options": {}, + "pluginVersion": "", + "targets": [], + "title": "Network I/O", + "type": "timeseries" + }, + { + "gridPos": { + "h": 6, + "w": 12, + "x": 12, + "y": 16 + }, + "id": 5, + "options": {}, + "pluginVersion": "", + "targets": [], + "title": "Disk I/O", + "type": "timeseries" + } + ], + "title": "Collapsed Row", + "type": "row" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 8 + }, + "id": -1, + "title": "Visible Row Title", + "type": "row" + }, + { + "gridPos": { + "h": 6, + "w": 8, + "x": 0, + "y": 8 + }, + "id": 6, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Temperature", + "type": "gauge" + }, + { + "gridPos": { + "h": 6, + "w": 8, + "x": 8, + "y": 8 + }, + "id": 7, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Uptime", + "type": "stat" + }, + { + "gridPos": { + "h": 6, + "w": 8, + "x": 16, + "y": 8 + }, + "id": 8, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Load Average", + "type": "bargauge" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 14 + }, + "id": -1, + "title": "", + "type": "row" + }, + { + "gridPos": { + "h": 4, + "w": 16, + "x": 0, + "y": 14 + }, + "id": 9, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Description Panel", + "type": "text" + }, + { + "gridPos": { + "h": 3, + "w": 8, + "x": 16, + "y": 14 + }, + "id": 10, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "System Logs", + "type": "logs" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 18 + }, + "id": -1, + "repeat": "server", + "title": "", + "type": "row" + }, + { + "gridPos": { + "h": 7, + "w": 24, + "x": 0, + "y": 18 + }, + "id": 11, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Server Metrics", + "type": "timeseries" + } + ], + "preload": false, + "refresh": "", + "schemaVersion": 42, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "V16 Grid Layout Migration Test Dashboard" + }, "status": { "conversion": { - "failed": true, - "error": "backend conversion not yet implemented", - "storedVersion": "v2beta1", - "source": { - "kind": "Dashboard", - "apiVersion": "dashboard.grafana.app/v2beta1", - "metadata": { - "name": "v16.grid_layout_upgrade.v42" - }, - "spec": { - "annotations": [ - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana", - "version": "v0", - "datasource": { - "name": "-- Grafana --" - }, - "spec": {} - }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations \u0026 Alerts", - "builtIn": true, - "legacyOptions": { - "type": "dashboard" - } - } - } - ], - "cursorSync": "Off", - "editable": true, - "elements": { - "panel-1": { - "kind": "Panel", - "spec": { - "id": 1, - "title": "CPU Usage", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-10": { - "kind": "Panel", - "spec": { - "id": 10, - "title": "System Logs", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "logs", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-11": { - "kind": "Panel", - "spec": { - "id": 11, - "title": "Server Metrics", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-2": { - "kind": "Panel", - "spec": { - "id": 2, - "title": "Memory Usage", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "stat", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-3": { - "kind": "Panel", - "spec": { - "id": 3, - "title": "Process List", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "table", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-4": { - "kind": "Panel", - "spec": { - "id": 4, - "title": "Network I/O", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-5": { - "kind": "Panel", - "spec": { - "id": 5, - "title": "Disk I/O", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-6": { - "kind": "Panel", - "spec": { - "id": 6, - "title": "Temperature", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "gauge", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-7": { - "kind": "Panel", - "spec": { - "id": 7, - "title": "Uptime", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "stat", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-8": { - "kind": "Panel", - "spec": { - "id": 8, - "title": "Load Average", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "bargauge", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-9": { - "kind": "Panel", - "spec": { - "id": 9, - "title": "Description Panel", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "text", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - } - }, - "layout": { - "kind": "RowsLayout", - "spec": { - "rows": [ - { - "kind": "RowsLayoutRow", - "spec": { - "title": "", - "collapse": false, - "layout": { - "kind": "GridLayout", - "spec": { - "items": [ - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 12, - "height": 7, - "element": { - "kind": "ElementReference", - "name": "panel-1" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 12, - "y": 0, - "width": 12, - "height": 7, - "element": { - "kind": "ElementReference", - "name": "panel-2" - } - } - } - ] - } - } - } - }, - { - "kind": "RowsLayoutRow", - "spec": { - "title": "Collapsed Row", - "collapse": true, - "layout": { - "kind": "GridLayout", - "spec": { - "items": [ - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 24, - "height": 8, - "element": { - "kind": "ElementReference", - "name": "panel-3" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 8, - "width": 12, - "height": 6, - "element": { - "kind": "ElementReference", - "name": "panel-4" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 12, - "y": 8, - "width": 12, - "height": 6, - "element": { - "kind": "ElementReference", - "name": "panel-5" - } - } - } - ] - } - } - } - }, - { - "kind": "RowsLayoutRow", - "spec": { - "title": "Visible Row Title", - "collapse": false, - "layout": { - "kind": "GridLayout", - "spec": { - "items": [ - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 8, - "height": 6, - "element": { - "kind": "ElementReference", - "name": "panel-6" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 8, - "y": 0, - "width": 8, - "height": 6, - "element": { - "kind": "ElementReference", - "name": "panel-7" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 16, - "y": 0, - "width": 8, - "height": 6, - "element": { - "kind": "ElementReference", - "name": "panel-8" - } - } - } - ] - } - } - } - }, - { - "kind": "RowsLayoutRow", - "spec": { - "title": "", - "collapse": false, - "layout": { - "kind": "GridLayout", - "spec": { - "items": [ - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 16, - "height": 4, - "element": { - "kind": "ElementReference", - "name": "panel-9" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 16, - "y": 0, - "width": 8, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-10" - } - } - } - ] - } - } - } - }, - { - "kind": "RowsLayoutRow", - "spec": { - "title": "", - "collapse": false, - "repeat": { - "mode": "variable", - "value": "server" - }, - "layout": { - "kind": "GridLayout", - "spec": { - "items": [ - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 24, - "height": 7, - "element": { - "kind": "ElementReference", - "name": "panel-11" - } - } - } - ] - } - } - } - } - ] - } - }, - "links": [], - "liveNow": false, - "preload": false, - "tags": [], - "timeSettings": { - "timezone": "", - "from": "now-6h", - "to": "now", - "autoRefresh": "", - "autoRefreshIntervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "hideTimepicker": false, - "fiscalYearStartMonth": 0 - }, - "title": "V16 Grid Layout Migration Test Dashboard", - "variables": [] - }, - "status": {} - } + "failed": false, + "storedVersion": "v2beta1" } } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v16.span_zero_demo.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v16.span_zero_demo.v0alpha1.json index d4bd349e923..a57a713ba61 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v16.span_zero_demo.v0alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v16.span_zero_demo.v0alpha1.json @@ -4,1723 +4,854 @@ "metadata": { "name": "v16.span_zero_demo.v42" }, - "spec": null, - "status": { - "conversion": { - "failed": true, - "error": "backend conversion not yet implemented", - "storedVersion": "v2beta1", - "source": { - "kind": "Dashboard", - "apiVersion": "dashboard.grafana.app/v2beta1", - "metadata": { - "name": "v16.span_zero_demo.v42" - }, - "spec": { - "annotations": [ - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana", - "version": "v0", - "datasource": { - "name": "-- Grafana --" - }, - "spec": {} - }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations \u0026 Alerts", - "builtIn": true, - "legacyOptions": { - "type": "dashboard" - } - } - } - ], - "cursorSync": "Off", - "editable": false, - "elements": { - "panel-1": { - "kind": "Panel", - "spec": { - "id": 1, - "title": "Application Monitoring", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "text", - "version": "", - "spec": { - "options": { - "content": "This dashboard demonstrates various monitoring components for application observability and performance metrics.\n", - "mode": "markdown" - }, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-10": { - "kind": "Panel", - "spec": { - "id": 10, - "title": "All", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "loki", - "version": "v0", - "datasource": { - "name": "${loki}" - }, - "spec": { - "expr": "{namespace=\"default\", cluster=\"$cluster\", job=\"app-service\"} | logfmt" - } - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "logs", - "version": "", - "spec": { - "options": { - "enableLogDetails": true, - "showTime": false, - "sortOrder": "Descending", - "wrapLogMessage": true - }, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-11": { - "kind": "Panel", - "spec": { - "id": 11, - "title": "Performance Analysis", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "text", - "version": "", - "spec": { - "options": { - "content": "Performance monitoring examines factors that affect system response times, including operation duration, queue lengths, and processing delays. This section provides metrics and traces for performance analysis.\n", - "mode": "markdown" - }, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-12": { - "kind": "Panel", - "spec": { - "id": 12, - "title": "Concurrent Job Drivers", - "description": "Number of concurrent processing threads available for handling operations", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "${prom}" - }, - "spec": { - "expr": "max(app_worker_threads_active{cluster=\"$cluster\", namespace=\"default\"})", - "instant": true - } - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "stat", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-13": { - "kind": "Panel", - "spec": { - "id": 13, - "title": "Recent Operation Traces", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "tempo", - "version": "v0", - "datasource": { - "name": "${tempo}" - }, - "spec": { - "filters": [ - { - "id": "span-name", - "operator": "=", - "scope": "span", - "tag": "name", - "value": [ - "provisioning.sync.process" - ] - }, - { - "id": "k8s-cluster-name", - "operator": "=", - "scope": "resource", - "tag": "k8s.cluster.name", - "value": [ - "$cluster" - ] - } - ], - "query": "{name=\"app.operation.process\"}", - "queryType": "traceqlSearch" - } - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "table", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-14": { - "kind": "Panel", - "spec": { - "id": 14, - "title": "7d avg of job durations", - "description": "Histogram showing p99, p95, p50, and p10 percentiles for job processing duration based on number of resources changed", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "${prom}" - }, - "spec": { - "expr": "histogram_quantile(0.99, sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[7d])) by (le, resources_changed_bucket, action)) and on(resources_changed_bucket, action) sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[7d])) by (resources_changed_bucket, action) \u003e 0", - "legendFormat": "{{action}} q0.99 - size {{resources_changed_bucket}}" - } - }, - "refId": "B", - "hidden": false - } - }, - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "${prom}" - }, - "spec": { - "expr": "histogram_quantile(0.9, sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[7d])) by (le, resources_changed_bucket, action)) and on(resources_changed_bucket, action) sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[7d])) by (resources_changed_bucket, action) \u003e 0", - "legendFormat": "{{action}} q0.95 - size {{resources_changed_bucket}}" - } - }, - "refId": "C", - "hidden": false - } - }, - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "${prom}" - }, - "spec": { - "expr": "histogram_quantile(0.5, sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[7d])) by (le, resources_changed_bucket, action)) and on(resources_changed_bucket, action) sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[7d])) by (resources_changed_bucket, action) \u003e 0", - "legendFormat": "{{action}} q0.5 - size {{resources_changed_bucket}}" - } - }, - "refId": "D", - "hidden": false - } - }, - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "${prom}" - }, - "spec": { - "expr": "histogram_quantile(0.1, sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[7d])) by (le, resources_changed_bucket, action)) and on(resources_changed_bucket, action) sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[7d])) by (resources_changed_bucket, action) \u003e 0", - "legendFormat": "{{action}} q0.1 - size {{resources_changed_bucket}}" - } - }, - "refId": "E", - "hidden": false - } - } - ], - "transformations": [ - { - "kind": "reduce", - "spec": { - "id": "reduce", - "options": { - "mode": "seriesToRows", - "reducers": [ - "mean" - ] - } - } - }, - { - "kind": "seriesToRows", - "spec": { - "id": "seriesToRows", - "options": null - } - }, - { - "kind": "organize", - "spec": { - "id": "organize", - "options": { - "renameByName": { - "Field": "Type", - "Mean": "Avg Duration", - "Metric": "Legend", - "Value": "Duration" - } - } - } - } - ], - "queryOptions": { - "timeFrom": "7d" - } - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "table", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": { - "unit": "s", - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": 0, - "color": "green" - }, - { - "value": 2, - "color": "yellow" - }, - { - "value": 5, - "color": "red" - } - ] - } - }, - "overrides": [] - } - } - } - } - }, - "panel-15": { - "kind": "Panel", - "spec": { - "id": 15, - "title": "Job Duration", - "description": "Histogram showing p99, p95, p50, and p10 percentiles for job processing duration based on number of resources changed", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "${prom}" - }, - "spec": { - "expr": "histogram_quantile(0.99, sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[5m])) by (le, resources_changed_bucket, action))", - "legendFormat": "{{action}} q0.99 - size {{resources_changed_bucket}}" - } - }, - "refId": "B", - "hidden": false - } - }, - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "${prom}" - }, - "spec": { - "expr": "histogram_quantile(0.95, sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[5m])) by (le, resources_changed_bucket, action))", - "legendFormat": "{{action}} q0.95 - size {{resources_changed_bucket}}" - } - }, - "refId": "C", - "hidden": false - } - }, - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "${prom}" - }, - "spec": { - "expr": "histogram_quantile(0.5, sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[5m])) by (le, resources_changed_bucket, action))", - "legendFormat": "{{action}} q0.5 - size {{resources_changed_bucket}}" - } - }, - "refId": "D", - "hidden": false - } - }, - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "${prom}" - }, - "spec": { - "expr": "histogram_quantile(0.1, sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[5m])) by (le, resources_changed_bucket, action))", - "legendFormat": "{{action}} q0.1 - size {{resources_changed_bucket}}" - } - }, - "refId": "E", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-16": { - "kind": "Panel", - "spec": { - "id": 16, - "title": "Queue Size", - "description": "Total number of jobs waiting to be processed", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "${prom}" - }, - "spec": { - "expr": "clamp_min(sum(app_operation_queue_size{cluster=\"$cluster\", namespace=\"default\"}), 0)", - "legendFormat": "Queue size" - } - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "stat", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-17": { - "kind": "Panel", - "spec": { - "id": 17, - "title": "7d avg Queue Wait Time", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "${prom}" - }, - "spec": { - "expr": "avg(histogram_quantile(0.5, sum(rate(app_operation_queue_wait_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[7d])) by (le)))", - "legendFormat": "Queue size" - } - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": { - "timeFrom": "7d" - } - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "stat", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": { - "unit": "s" - }, - "overrides": [] - } - } - } - } - }, - "panel-18": { - "kind": "Panel", - "spec": { - "id": 18, - "title": "Queue Wait Time", - "description": "How long a job is in the queue before being picked up", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "${prom}" - }, - "spec": { - "expr": "histogram_quantile(0.99, sum(rate(app_operation_queue_wait_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[$__rate_interval])) by (le))", - "legendFormat": "q0.99" - } - }, - "refId": "B", - "hidden": false - } - }, - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "${prom}" - }, - "spec": { - "expr": "histogram_quantile(0.95, sum(rate(app_operation_queue_wait_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[$__rate_interval])) by (le))", - "legendFormat": "q0.95" - } - }, - "refId": "C", - "hidden": false - } - }, - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "${prom}" - }, - "spec": { - "expr": "histogram_quantile(0.5, sum(rate(app_operation_queue_wait_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[$__rate_interval])) by (le))", - "legendFormat": "q0.5" - } - }, - "refId": "D", - "hidden": false - } - }, - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "${prom}" - }, - "spec": { - "expr": "histogram_quantile(0.1, sum(rate(app_operation_queue_wait_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[$__rate_interval])) by (le))", - "legendFormat": "q0.1" - } - }, - "refId": "E", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-19": { - "kind": "Panel", - "spec": { - "id": 19, - "title": "Resource Monitoring", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "text", - "version": "", - "spec": { - "options": { - "content": "Resource utilization monitoring for application containers", - "mode": "markdown" - }, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-20": { - "kind": "Panel", - "spec": { - "id": 20, - "title": "Running Pod(s)", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "${prom}" - }, - "spec": { - "expr": "count by (cluster, channel)(label_replace(label_replace(kube_pod_container_info{namespace=\"default\", container=\"app-worker\", pod=~\"app-worker.*\", cluster=~\"$cluster\"}, \"version\", \"$1\", \"image\", \".+:(.+)\"), \"channel\", \"$1\", \"container\", \".+-(.+)\"))", - "legendFormat": "{{cluster}}" - } - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-21": { - "kind": "Panel", - "spec": { - "id": 21, - "title": "Memory Utilization", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "${prom}" - }, - "spec": { - "expr": "max(kube_pod_container_resource_requests{namespace=\"default\", resource=\"memory\", cluster=~\"$cluster\", container=\"app-worker\", pod=~\"app-worker.*\"})", - "legendFormat": "Memory Request" - } - }, - "refId": "A", - "hidden": false - } - }, - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "${prom}" - }, - "spec": { - "expr": "max(kube_pod_container_resource_limits{namespace=\"default\", resource=\"memory\", cluster=~\"$cluster\", container=\"app-worker\", pod=~\"app-worker.*\"})", - "legendFormat": "Memory Limit" - } - }, - "refId": "B", - "hidden": false - } - }, - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "${prom}" - }, - "spec": { - "expr": "max(container_memory_usage_bytes{namespace=\"default\",cluster=~\"$cluster\", container=\"app-worker\", pod=~\"app-worker.*\"}) by (pod)", - "legendFormat": "Container usage {{pod}}" - } - }, - "refId": "C", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-22": { - "kind": "Panel", - "spec": { - "id": 22, - "title": "CPU Utilization", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "${prom}" - }, - "spec": { - "expr": "sum(irate(container_cpu_usage_seconds_total{namespace=\"default\", cluster=~\"$cluster\", container=\"app-worker\", pod=~\"app-worker-.*\"}[$__rate_interval])) by (pod, container, cpu)", - "legendFormat": "Usage {{pod}}" - } - }, - "refId": "A", - "hidden": false - } - }, - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "${prom}" - }, - "spec": { - "expr": "sum(irate(container_cpu_cfs_throttled_seconds_total{namespace=\"default\", cluster=~\"$cluster\", container=\"app-worker\", pod=~\"app-worker-.*\"}[$__rate_interval])) by (pod, container)", - "legendFormat": "Throttling {{pod}}" - } - }, - "refId": "B", - "hidden": false - } - }, - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "${prom}" - }, - "spec": { - "expr": "max(kube_pod_container_resource_limits{namespace=\"default\", cluster=~\"$cluster\", container=\"app-worker\", pod=~\"app-worker-.*\", resource=\"cpu\"})", - "legendFormat": "CPU limit" - } - }, - "refId": "C", - "hidden": false - } - }, - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "${prom}" - }, - "spec": { - "expr": "max(kube_pod_container_resource_requests{namespace=\"default\", cluster=~\"$cluster\", container=\"app-worker\", pod=~\"app-worker-.*\", resource=\"cpu\"})", - "legendFormat": "CPU request" - } - }, - "refId": "D", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-6": { - "kind": "Panel", - "spec": { - "id": 6, - "title": "Service Overview", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "text", - "version": "", - "spec": { - "options": { - "content": "This service handles background processing tasks for the application system. It manages various types of operations including data synchronization, resource management, and batch processing.\n\nSupported operation types:\n1. Sync: Synchronizes data between different systems\n2. Process: Handles batch data processing tasks\n3. Cleanup: Removes outdated or temporary resources\n4. Update: Applies configuration changes across services\n\nService dependencies:\n- Data API: For reading and writing application data\n- Configuration Service: For managing system settings\n- Queue Service: For handling task scheduling\n- Storage Service: For persistent data management\n- Auth Service: For authentication and authorization\n- Metrics Service: For collecting operational statistics\n", - "mode": "markdown" - }, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-7": { - "kind": "Panel", - "spec": { - "id": 7, - "title": "Error Monitoring", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "text", - "version": "", - "spec": { - "options": { - "content": "Error monitoring helps identify issues in the system. This section displays error logs and success rates for operations.", - "mode": "markdown" - }, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-8": { - "kind": "Panel", - "spec": { - "id": 8, - "title": "Job Success Rate", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "${prom}" - }, - "spec": { - "expr": "sum by (action) (app_jobs_processed_total{outcome=\"success\", cluster=\"$cluster\", namespace=\"default\"})\n/\nsum by (action) (app_jobs_processed_total{cluster=\"$cluster\", namespace=\"default\"})\n", - "legendFormat": "{{action}}" - } - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "stat", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": { - "unit": "percentunit", - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": 0, - "color": "red" - }, - { - "value": 0.95, - "color": "yellow" - }, - { - "value": 1, - "color": "green" - } - ] - } - }, - "overrides": [] - } - } - } - } - }, - "panel-9": { - "kind": "Panel", - "spec": { - "id": 9, - "title": "Errors", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "loki", - "version": "v0", - "datasource": { - "name": "${loki}" - }, - "spec": { - "expr": "{namespace=\"default\", cluster=\"$cluster\", job=\"app-service\"} | logfmt | level=\"error\"" - } - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "logs", - "version": "", - "spec": { - "options": { - "enableLogDetails": true, - "showTime": false, - "sortOrder": "Descending", - "wrapLogMessage": true - }, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - } + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" }, - "layout": { - "kind": "RowsLayout", - "spec": { - "rows": [ + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": false, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [ + { + "asDropdown": false, + "icon": "external link", + "includeVars": false, + "keepTime": false, + "tags": [], + "targetBlank": true, + "title": "External Documentation", + "tooltip": "", + "type": "link", + "url": "https://example.com/docs" + } + ], + "liveNow": false, + "panels": [ + { + "gridPos": { + "h": 3, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 1, + "options": { + "content": "This dashboard demonstrates various monitoring components for application observability and performance metrics.\n", + "mode": "markdown" + }, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Application Monitoring", + "type": "text" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 3 + }, + "id": -1, + "title": "Application Service", + "type": "row" + }, + { + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 3 + }, + "id": 6, + "options": { + "content": "This service handles background processing tasks for the application system. It manages various types of operations including data synchronization, resource management, and batch processing.\n\nSupported operation types:\n1. Sync: Synchronizes data between different systems\n2. Process: Handles batch data processing tasks\n3. Cleanup: Removes outdated or temporary resources\n4. Update: Applies configuration changes across services\n\nService dependencies:\n- Data API: For reading and writing application data\n- Configuration Service: For managing system settings\n- Queue Service: For handling task scheduling\n- Storage Service: For persistent data management\n- Auth Service: For authentication and authorization\n- Metrics Service: For collecting operational statistics\n", + "mode": "markdown" + }, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Service Overview", + "type": "text" + }, + { + "gridPos": { + "h": 7, + "w": 8, + "x": 8, + "y": 3 + }, + "id": 7, + "options": { + "content": "Error monitoring helps identify issues in the system. This section displays error logs and success rates for operations.", + "mode": "markdown" + }, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Error Monitoring", + "type": "text" + }, + { + "fieldConfig": { + "defaults": { + "thresholds": { + "mode": "absolute", + "steps": [ { - "kind": "RowsLayoutRow", - "spec": { - "title": "", - "collapse": false, - "hideHeader": true, - "layout": { - "kind": "GridLayout", - "spec": { - "items": [ - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 24, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-1" - } - } - } - ] - } - } - } + "color": "red", + "value": 0 }, { - "kind": "RowsLayoutRow", - "spec": { - "title": "Application Service", - "collapse": false, - "layout": { - "kind": "GridLayout", - "spec": { - "items": [ - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 8, - "height": 7, - "element": { - "kind": "ElementReference", - "name": "panel-6" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 8, - "y": 0, - "width": 8, - "height": 7, - "element": { - "kind": "ElementReference", - "name": "panel-7" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 16, - "y": 0, - "width": 8, - "height": 7, - "element": { - "kind": "ElementReference", - "name": "panel-8" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 7, - "width": 8, - "height": 7, - "element": { - "kind": "ElementReference", - "name": "panel-9" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 8, - "y": 7, - "width": 8, - "height": 7, - "element": { - "kind": "ElementReference", - "name": "panel-10" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 16, - "y": 7, - "width": 8, - "height": 7, - "element": { - "kind": "ElementReference", - "name": "panel-11" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 14, - "width": 8, - "height": 7, - "element": { - "kind": "ElementReference", - "name": "panel-12" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 8, - "y": 14, - "width": 8, - "height": 7, - "element": { - "kind": "ElementReference", - "name": "panel-13" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 16, - "y": 14, - "width": 8, - "height": 7, - "element": { - "kind": "ElementReference", - "name": "panel-14" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 21, - "width": 8, - "height": 7, - "element": { - "kind": "ElementReference", - "name": "panel-15" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 8, - "y": 21, - "width": 8, - "height": 7, - "element": { - "kind": "ElementReference", - "name": "panel-16" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 16, - "y": 21, - "width": 8, - "height": 7, - "element": { - "kind": "ElementReference", - "name": "panel-17" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 28, - "width": 8, - "height": 7, - "element": { - "kind": "ElementReference", - "name": "panel-18" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 8, - "y": 28, - "width": 8, - "height": 7, - "element": { - "kind": "ElementReference", - "name": "panel-19" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 16, - "y": 28, - "width": 8, - "height": 7, - "element": { - "kind": "ElementReference", - "name": "panel-20" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 35, - "width": 8, - "height": 7, - "element": { - "kind": "ElementReference", - "name": "panel-21" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 8, - "y": 35, - "width": 8, - "height": 7, - "element": { - "kind": "ElementReference", - "name": "panel-22" - } - } - } - ] - } - } - } + "color": "yellow", + "value": 0.95 + }, + { + "color": "green", + "value": 1 } ] + }, + "unit": "percentunit" + } + }, + "gridPos": { + "h": 7, + "w": 8, + "x": 16, + "y": 3 + }, + "id": 8, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "expr": "sum by (action) (app_jobs_processed_total{outcome=\"success\", cluster=\"$cluster\", namespace=\"default\"})\n/\nsum by (action) (app_jobs_processed_total{cluster=\"$cluster\", namespace=\"default\"})\n", + "legendFormat": "{{action}}", + "refId": "A" + } + ], + "title": "Job Success Rate", + "type": "stat" + }, + { + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 10 + }, + "id": 9, + "options": { + "enableLogDetails": true, + "showTime": false, + "sortOrder": "Descending", + "wrapLogMessage": true + }, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "${loki}" + }, + "expr": "{namespace=\"default\", cluster=\"$cluster\", job=\"app-service\"} | logfmt | level=\"error\"", + "refId": "A" + } + ], + "title": "Errors", + "type": "logs" + }, + { + "gridPos": { + "h": 7, + "w": 8, + "x": 8, + "y": 10 + }, + "id": 10, + "options": { + "enableLogDetails": true, + "showTime": false, + "sortOrder": "Descending", + "wrapLogMessage": true + }, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "${loki}" + }, + "expr": "{namespace=\"default\", cluster=\"$cluster\", job=\"app-service\"} | logfmt", + "refId": "A" + } + ], + "title": "All", + "type": "logs" + }, + { + "gridPos": { + "h": 7, + "w": 8, + "x": 16, + "y": 10 + }, + "id": 11, + "options": { + "content": "Performance monitoring examines factors that affect system response times, including operation duration, queue lengths, and processing delays. This section provides metrics and traces for performance analysis.\n", + "mode": "markdown" + }, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Performance Analysis", + "type": "text" + }, + { + "description": "Number of concurrent processing threads available for handling operations", + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 17 + }, + "id": 12, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "expr": "max(app_worker_threads_active{cluster=\"$cluster\", namespace=\"default\"})", + "instant": true, + "refId": "A" + } + ], + "title": "Concurrent Job Drivers", + "type": "stat" + }, + { + "gridPos": { + "h": 7, + "w": 8, + "x": 8, + "y": 17 + }, + "id": 13, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "tempo", + "uid": "${tempo}" + }, + "filters": [ + { + "id": "span-name", + "operator": "=", + "scope": "span", + "tag": "name", + "value": [ + "provisioning.sync.process" + ] + }, + { + "id": "k8s-cluster-name", + "operator": "=", + "scope": "resource", + "tag": "k8s.cluster.name", + "value": [ + "$cluster" + ] + } + ], + "query": "{name=\"app.operation.process\"}", + "queryType": "traceqlSearch", + "refId": "A" + } + ], + "title": "Recent Operation Traces", + "type": "table" + }, + { + "description": "Histogram showing p99, p95, p50, and p10 percentiles for job processing duration based on number of resources changed", + "fieldConfig": { + "defaults": { + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "yellow", + "value": 2 + }, + { + "color": "red", + "value": 5 + } + ] + }, + "unit": "s" + } + }, + "gridPos": { + "h": 7, + "w": 8, + "x": 16, + "y": 17 + }, + "id": 14, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "expr": "histogram_quantile(0.99, sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[7d])) by (le, resources_changed_bucket, action)) and on(resources_changed_bucket, action) sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[7d])) by (resources_changed_bucket, action) \u003e 0", + "legendFormat": "{{action}} q0.99 - size {{resources_changed_bucket}}", + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "expr": "histogram_quantile(0.9, sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[7d])) by (le, resources_changed_bucket, action)) and on(resources_changed_bucket, action) sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[7d])) by (resources_changed_bucket, action) \u003e 0", + "legendFormat": "{{action}} q0.95 - size {{resources_changed_bucket}}", + "refId": "C" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "expr": "histogram_quantile(0.5, sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[7d])) by (le, resources_changed_bucket, action)) and on(resources_changed_bucket, action) sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[7d])) by (resources_changed_bucket, action) \u003e 0", + "legendFormat": "{{action}} q0.5 - size {{resources_changed_bucket}}", + "refId": "D" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "expr": "histogram_quantile(0.1, sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[7d])) by (le, resources_changed_bucket, action)) and on(resources_changed_bucket, action) sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[7d])) by (resources_changed_bucket, action) \u003e 0", + "legendFormat": "{{action}} q0.1 - size {{resources_changed_bucket}}", + "refId": "E" + } + ], + "timeFrom": "7d", + "title": "7d avg of job durations", + "transformations": [ + { + "id": "reduce", + "options": { + "mode": "seriesToRows", + "reducers": [ + "mean" + ] } }, - "links": [ - { - "title": "External Documentation", - "type": "link", - "icon": "external link", - "tooltip": "", - "url": "https://example.com/docs", - "tags": [], - "asDropdown": false, - "targetBlank": true, - "includeVars": false, - "keepTime": false - } - ], - "liveNow": false, - "preload": false, - "tags": [ - "as-code" - ], - "timeSettings": { - "timezone": "utc", - "from": "now-6h", - "to": "now", - "autoRefresh": "10s", - "autoRefreshIntervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "hideTimepicker": false, - "fiscalYearStartMonth": 0 + { + "id": "seriesToRows", + "options": null }, - "title": "Span Zero Demo Dashboard", - "variables": [ - { - "kind": "DatasourceVariable", - "spec": { - "name": "datasource", - "pluginId": "prometheus", - "refresh": "onDashboardLoad", - "regex": "", - "current": { - "text": "", - "value": "prometheus-datasource" - }, - "options": [], - "multi": false, - "includeAll": false, - "label": "Data source", - "hide": "dontHide", - "skipUrlSync": false, - "allowCustomValue": true - } - }, - { - "kind": "DatasourceVariable", - "spec": { - "name": "prom", - "pluginId": "prometheus", - "refresh": "onDashboardLoad", - "regex": "", - "current": { - "text": "", - "value": "prometheus-datasource" - }, - "options": [], - "multi": false, - "includeAll": false, - "hide": "dontHide", - "skipUrlSync": false, - "allowCustomValue": true - } - }, - { - "kind": "DatasourceVariable", - "spec": { - "name": "loki", - "pluginId": "loki", - "refresh": "onDashboardLoad", - "regex": "", - "current": { - "text": "", - "value": "loki-datasource" - }, - "options": [], - "multi": false, - "includeAll": false, - "hide": "dontHide", - "skipUrlSync": false, - "allowCustomValue": true - } - }, - { - "kind": "DatasourceVariable", - "spec": { - "name": "tempo", - "pluginId": "tempo", - "refresh": "onDashboardLoad", - "regex": ".*tempo.*", - "current": { - "text": "tempo-datasource", - "value": "tempo-datasource" - }, - "options": [], - "multi": false, - "includeAll": false, - "hide": "dontHide", - "skipUrlSync": false, - "allowCustomValue": true - } - }, - { - "kind": "QueryVariable", - "spec": { - "name": "cluster", - "current": { - "text": "demo-cluster", - "value": "demo-cluster" - }, - "hide": "dontHide", - "refresh": "onDashboardLoad", - "skipUrlSync": false, - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "${prom}" - }, - "spec": { - "__legacyStringValue": "label_values(app_worker_threads_active,cluster)" - } - }, - "regex": "", - "sort": "disabled", - "options": [], - "multi": false, - "includeAll": false, - "allowCustomValue": true + { + "id": "organize", + "options": { + "renameByName": { + "Field": "Type", + "Mean": "Avg Duration", + "Metric": "Legend", + "Value": "Duration" } } - ] + } + ], + "type": "table" + }, + { + "description": "Histogram showing p99, p95, p50, and p10 percentiles for job processing duration based on number of resources changed", + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 24 }, - "status": {} + "id": 15, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "expr": "histogram_quantile(0.99, sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[5m])) by (le, resources_changed_bucket, action))", + "legendFormat": "{{action}} q0.99 - size {{resources_changed_bucket}}", + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "expr": "histogram_quantile(0.95, sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[5m])) by (le, resources_changed_bucket, action))", + "legendFormat": "{{action}} q0.95 - size {{resources_changed_bucket}}", + "refId": "C" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "expr": "histogram_quantile(0.5, sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[5m])) by (le, resources_changed_bucket, action))", + "legendFormat": "{{action}} q0.5 - size {{resources_changed_bucket}}", + "refId": "D" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "expr": "histogram_quantile(0.1, sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[5m])) by (le, resources_changed_bucket, action))", + "legendFormat": "{{action}} q0.1 - size {{resources_changed_bucket}}", + "refId": "E" + } + ], + "title": "Job Duration", + "type": "timeseries" + }, + { + "description": "Total number of jobs waiting to be processed", + "gridPos": { + "h": 7, + "w": 8, + "x": 8, + "y": 24 + }, + "id": 16, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "expr": "clamp_min(sum(app_operation_queue_size{cluster=\"$cluster\", namespace=\"default\"}), 0)", + "legendFormat": "Queue size", + "refId": "A" + } + ], + "title": "Queue Size", + "type": "stat" + }, + { + "fieldConfig": { + "defaults": { + "unit": "s" + } + }, + "gridPos": { + "h": 7, + "w": 8, + "x": 16, + "y": 24 + }, + "id": 17, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "expr": "avg(histogram_quantile(0.5, sum(rate(app_operation_queue_wait_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[7d])) by (le)))", + "legendFormat": "Queue size", + "refId": "A" + } + ], + "timeFrom": "7d", + "title": "7d avg Queue Wait Time", + "type": "stat" + }, + { + "description": "How long a job is in the queue before being picked up", + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 31 + }, + "id": 18, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "expr": "histogram_quantile(0.99, sum(rate(app_operation_queue_wait_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[$__rate_interval])) by (le))", + "legendFormat": "q0.99", + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "expr": "histogram_quantile(0.95, sum(rate(app_operation_queue_wait_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[$__rate_interval])) by (le))", + "legendFormat": "q0.95", + "refId": "C" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "expr": "histogram_quantile(0.5, sum(rate(app_operation_queue_wait_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[$__rate_interval])) by (le))", + "legendFormat": "q0.5", + "refId": "D" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "expr": "histogram_quantile(0.1, sum(rate(app_operation_queue_wait_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[$__rate_interval])) by (le))", + "legendFormat": "q0.1", + "refId": "E" + } + ], + "title": "Queue Wait Time", + "type": "timeseries" + }, + { + "gridPos": { + "h": 7, + "w": 8, + "x": 8, + "y": 31 + }, + "id": 19, + "options": { + "content": "Resource utilization monitoring for application containers", + "mode": "markdown" + }, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Resource Monitoring", + "type": "text" + }, + { + "gridPos": { + "h": 7, + "w": 8, + "x": 16, + "y": 31 + }, + "id": 20, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "expr": "count by (cluster, channel)(label_replace(label_replace(kube_pod_container_info{namespace=\"default\", container=\"app-worker\", pod=~\"app-worker.*\", cluster=~\"$cluster\"}, \"version\", \"$1\", \"image\", \".+:(.+)\"), \"channel\", \"$1\", \"container\", \".+-(.+)\"))", + "legendFormat": "{{cluster}}", + "refId": "A" + } + ], + "title": "Running Pod(s)", + "type": "timeseries" + }, + { + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 38 + }, + "id": 21, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "expr": "max(kube_pod_container_resource_requests{namespace=\"default\", resource=\"memory\", cluster=~\"$cluster\", container=\"app-worker\", pod=~\"app-worker.*\"})", + "legendFormat": "Memory Request", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "expr": "max(kube_pod_container_resource_limits{namespace=\"default\", resource=\"memory\", cluster=~\"$cluster\", container=\"app-worker\", pod=~\"app-worker.*\"})", + "legendFormat": "Memory Limit", + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "expr": "max(container_memory_usage_bytes{namespace=\"default\",cluster=~\"$cluster\", container=\"app-worker\", pod=~\"app-worker.*\"}) by (pod)", + "legendFormat": "Container usage {{pod}}", + "refId": "C" + } + ], + "title": "Memory Utilization", + "type": "timeseries" + }, + { + "gridPos": { + "h": 7, + "w": 8, + "x": 8, + "y": 38 + }, + "id": 22, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "expr": "sum(irate(container_cpu_usage_seconds_total{namespace=\"default\", cluster=~\"$cluster\", container=\"app-worker\", pod=~\"app-worker-.*\"}[$__rate_interval])) by (pod, container, cpu)", + "legendFormat": "Usage {{pod}}", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "expr": "sum(irate(container_cpu_cfs_throttled_seconds_total{namespace=\"default\", cluster=~\"$cluster\", container=\"app-worker\", pod=~\"app-worker-.*\"}[$__rate_interval])) by (pod, container)", + "legendFormat": "Throttling {{pod}}", + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "expr": "max(kube_pod_container_resource_limits{namespace=\"default\", cluster=~\"$cluster\", container=\"app-worker\", pod=~\"app-worker-.*\", resource=\"cpu\"})", + "legendFormat": "CPU limit", + "refId": "C" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "expr": "max(kube_pod_container_resource_requests{namespace=\"default\", cluster=~\"$cluster\", container=\"app-worker\", pod=~\"app-worker-.*\", resource=\"cpu\"})", + "legendFormat": "CPU request", + "refId": "D" + } + ], + "title": "CPU Utilization", + "type": "timeseries" } + ], + "preload": false, + "refresh": "10s", + "schemaVersion": 42, + "tags": [ + "as-code" + ], + "templating": { + "list": [ + { + "allowCustomValue": true, + "current": { + "text": "", + "value": "prometheus-datasource" + }, + "hide": 0, + "includeAll": false, + "label": "Data source", + "multi": false, + "name": "datasource", + "options": [], + "query": "prometheus", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "type": "datasource" + }, + { + "allowCustomValue": true, + "current": { + "text": "", + "value": "prometheus-datasource" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "prom", + "options": [], + "query": "prometheus", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "type": "datasource" + }, + { + "allowCustomValue": true, + "current": { + "text": "", + "value": "loki-datasource" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "loki", + "options": [], + "query": "loki", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "type": "datasource" + }, + { + "allowCustomValue": true, + "current": { + "text": "tempo-datasource", + "value": "tempo-datasource" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "tempo", + "options": [], + "query": "tempo", + "refresh": 1, + "regex": ".*tempo.*", + "skipUrlSync": false, + "type": "datasource" + }, + { + "allowCustomValue": true, + "current": { + "text": "demo-cluster", + "value": "demo-cluster" + }, + "datasource": { + "type": "prometheus", + "uid": "${prom}" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "cluster", + "options": [], + "query": "label_values(app_worker_threads_active,cluster)", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "utc", + "title": "Span Zero Demo Dashboard" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v2beta1" } } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v17.minspan_to_maxperrow.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v17.minspan_to_maxperrow.v0alpha1.json index 1eb62dfe79b..e169d2bfa64 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v17.minspan_to_maxperrow.v0alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v17.minspan_to_maxperrow.v0alpha1.json @@ -4,673 +4,277 @@ "metadata": { "name": "v17.minspan_to_maxperrow.v42" }, - "spec": null, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "liveNow": false, + "panels": [ + { + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 1, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Panel with minSpan 8", + "type": "timeseries" + }, + { + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 0 + }, + "id": 4, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Panel with minSpan 12", + "type": "timeseries" + }, + { + "gridPos": { + "h": 4, + "w": 6, + "x": 0, + "y": 8 + }, + "id": 2, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Panel with minSpan 4", + "type": "stat" + }, + { + "gridPos": { + "h": 3, + "w": 4, + "x": 6, + "y": 8 + }, + "id": 6, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Panel with minSpan 1", + "type": "stat" + }, + { + "gridPos": { + "h": 6, + "w": 8, + "x": 12, + "y": 8 + }, + "id": 7, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Panel without minSpan", + "type": "timeseries" + }, + { + "gridPos": { + "h": 4, + "w": 6, + "x": 18, + "y": 8 + }, + "id": 8, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Panel with invalid minSpan", + "type": "text" + }, + { + "gridPos": { + "h": 6, + "w": 24, + "x": 0, + "y": 12 + }, + "id": 3, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Panel with minSpan 2", + "type": "table" + }, + { + "gridPos": { + "h": 4, + "w": 24, + "x": 0, + "y": 18 + }, + "id": 5, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Panel with minSpan 24", + "type": "gauge" + }, + { + "gridPos": { + "h": 4, + "w": 6, + "x": 0, + "y": 22 + }, + "id": 9, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Panel with zero minSpan", + "type": "timeseries" + }, + { + "gridPos": { + "h": 4, + "w": 6, + "x": 6, + "y": 22 + }, + "id": 10, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Panel with negative minSpan", + "type": "timeseries" + } + ], + "preload": false, + "refresh": "", + "schemaVersion": 42, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "V17 MinSpan to MaxPerRow Migration Test Dashboard" + }, "status": { "conversion": { - "failed": true, - "error": "backend conversion not yet implemented", - "storedVersion": "v2beta1", - "source": { - "kind": "Dashboard", - "apiVersion": "dashboard.grafana.app/v2beta1", - "metadata": { - "name": "v17.minspan_to_maxperrow.v42" - }, - "spec": { - "annotations": [ - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana", - "version": "v0", - "datasource": { - "name": "-- Grafana --" - }, - "spec": {} - }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations \u0026 Alerts", - "builtIn": true, - "legacyOptions": { - "type": "dashboard" - } - } - } - ], - "cursorSync": "Off", - "editable": true, - "elements": { - "panel-1": { - "kind": "Panel", - "spec": { - "id": 1, - "title": "Panel with minSpan 8", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-10": { - "kind": "Panel", - "spec": { - "id": 10, - "title": "Panel with negative minSpan", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-2": { - "kind": "Panel", - "spec": { - "id": 2, - "title": "Panel with minSpan 4", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "stat", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-3": { - "kind": "Panel", - "spec": { - "id": 3, - "title": "Panel with minSpan 2", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "table", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-4": { - "kind": "Panel", - "spec": { - "id": 4, - "title": "Panel with minSpan 12", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-5": { - "kind": "Panel", - "spec": { - "id": 5, - "title": "Panel with minSpan 24", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "gauge", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-6": { - "kind": "Panel", - "spec": { - "id": 6, - "title": "Panel with minSpan 1", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "stat", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-7": { - "kind": "Panel", - "spec": { - "id": 7, - "title": "Panel without minSpan", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-8": { - "kind": "Panel", - "spec": { - "id": 8, - "title": "Panel with invalid minSpan", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "text", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-9": { - "kind": "Panel", - "spec": { - "id": 9, - "title": "Panel with zero minSpan", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - } - }, - "layout": { - "kind": "GridLayout", - "spec": { - "items": [ - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 12, - "height": 8, - "element": { - "kind": "ElementReference", - "name": "panel-1" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 12, - "y": 0, - "width": 12, - "height": 8, - "element": { - "kind": "ElementReference", - "name": "panel-4" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 8, - "width": 6, - "height": 4, - "element": { - "kind": "ElementReference", - "name": "panel-2" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 6, - "y": 8, - "width": 4, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-6" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 12, - "y": 8, - "width": 8, - "height": 6, - "element": { - "kind": "ElementReference", - "name": "panel-7" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 18, - "y": 8, - "width": 6, - "height": 4, - "element": { - "kind": "ElementReference", - "name": "panel-8" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 12, - "width": 24, - "height": 6, - "element": { - "kind": "ElementReference", - "name": "panel-3" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 18, - "width": 24, - "height": 4, - "element": { - "kind": "ElementReference", - "name": "panel-5" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 22, - "width": 6, - "height": 4, - "element": { - "kind": "ElementReference", - "name": "panel-9" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 6, - "y": 22, - "width": 6, - "height": 4, - "element": { - "kind": "ElementReference", - "name": "panel-10" - } - } - } - ] - } - }, - "links": [], - "liveNow": false, - "preload": false, - "tags": [], - "timeSettings": { - "timezone": "", - "from": "now-6h", - "to": "now", - "autoRefresh": "", - "autoRefreshIntervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "hideTimepicker": false, - "fiscalYearStartMonth": 0 - }, - "title": "V17 MinSpan to MaxPerRow Migration Test Dashboard", - "variables": [] - }, - "status": {} - } + "failed": false, + "storedVersion": "v2beta1" } } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v18.gauge_options.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v18.gauge_options.v0alpha1.json index a6f8caf4297..cc57f1b1914 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v18.gauge_options.v0alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v18.gauge_options.v0alpha1.json @@ -4,428 +4,217 @@ "metadata": { "name": "v18.gauge_options.v42" }, - "spec": null, - "status": { - "conversion": { - "failed": true, - "error": "backend conversion not yet implemented", - "storedVersion": "v2beta1", - "source": { - "kind": "Dashboard", - "apiVersion": "dashboard.grafana.app/v2beta1", - "metadata": { - "name": "v18.gauge_options.v42" + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "liveNow": false, + "panels": [ + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 }, - "spec": { - "annotations": [ + "id": 1, + "options": { + "thresholds": [ { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana", - "version": "v0", - "datasource": { - "name": "-- Grafana --" - }, - "spec": {} - }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations \u0026 Alerts", - "builtIn": true, - "legacyOptions": { - "type": "dashboard" - } - } + "color": "red", + "value": 100 + }, + { + "color": "yellow", + "value": 50 + }, + { + "color": "green", + "value": 0 } ], - "cursorSync": "Off", - "editable": true, - "elements": { - "panel-1": { - "kind": "Panel", - "spec": { - "id": 1, - "title": "Complete Gauge Panel", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "gauge", - "version": "", - "spec": { - "options": { - "thresholds": [ - { - "color": "red", - "value": 100 - }, - { - "color": "yellow", - "value": 50 - }, - { - "color": "green", - "value": 0 - } - ], - "valueOptions": { - "decimals": 2, - "prefix": "Value: ", - "stat": "last", - "suffix": " ms", - "unit": "ms" - } - }, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-2": { - "kind": "Panel", - "spec": { - "id": 2, - "title": "Partial Gauge Panel", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "gauge", - "version": "", - "spec": { - "options": { - "valueOptions": { - "decimals": 1, - "unit": "percent" - } - }, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-3": { - "kind": "Panel", - "spec": { - "id": 3, - "title": "Buggy Gauge Panel", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "gauge", - "version": "", - "spec": { - "options": { - "valueOptions": { - "decimals": 0, - "stat": "avg", - "unit": "bytes" - } - }, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-4": { - "kind": "Panel", - "spec": { - "id": 4, - "title": "Custom Properties Gauge Panel", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "gauge", - "version": "", - "spec": { - "options": { - "anotherProp": 42, - "customProperty": "customValue", - "thresholds": [ - { - "color": "blue", - "value": 10 - } - ], - "valueOptions": { - "unit": "short" - } - }, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-5": { - "kind": "Panel", - "spec": { - "id": 5, - "title": "Non-Gauge Panel", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": { - "legend": { - "show": true, - "showLegend": true - } - }, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - } - }, - "layout": { - "kind": "GridLayout", - "spec": { - "items": [ - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-1" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-2" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-3" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-4" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-5" - } - } - } - ] - } - }, - "links": [], - "liveNow": false, - "preload": false, - "tags": [], - "timeSettings": { - "timezone": "", - "from": "now-6h", - "to": "now", - "autoRefresh": "", - "autoRefreshIntervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "hideTimepicker": false, - "fiscalYearStartMonth": 0 - }, - "title": "V18 Gauge Options Migration Test Dashboard", - "variables": [] + "valueOptions": { + "decimals": 2, + "prefix": "Value: ", + "stat": "last", + "suffix": " ms", + "unit": "ms" + } }, - "status": {} + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Complete Gauge Panel", + "type": "gauge" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 2, + "options": { + "valueOptions": { + "decimals": 1, + "unit": "percent" + } + }, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Partial Gauge Panel", + "type": "gauge" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 3, + "options": { + "valueOptions": { + "decimals": 0, + "stat": "avg", + "unit": "bytes" + } + }, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Buggy Gauge Panel", + "type": "gauge" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 4, + "options": { + "anotherProp": 42, + "customProperty": "customValue", + "thresholds": [ + { + "color": "blue", + "value": 10 + } + ], + "valueOptions": { + "unit": "short" + } + }, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Custom Properties Gauge Panel", + "type": "gauge" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 5, + "options": { + "legend": { + "show": true, + "showLegend": true + } + }, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Non-Gauge Panel", + "type": "timeseries" } + ], + "preload": false, + "refresh": "", + "schemaVersion": 42, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "V18 Gauge Options Migration Test Dashboard" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v2beta1" } } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v19.panel_links.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v19.panel_links.v0alpha1.json index d48ee42151f..44bdfd7c1a0 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v19.panel_links.v0alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v19.panel_links.v0alpha1.json @@ -4,463 +4,220 @@ "metadata": { "name": "v19.panel_links.v42" }, - "spec": null, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "liveNow": false, + "panels": [ + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 1, + "links": [ + { + "title": "Dashboard Link", + "url": "dashboard/db/my-dashboard?$__url_time_range" + } + ], + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Panel with legacy dashboard link", + "type": "timeseries" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 2, + "links": [ + { + "title": "DashUri Link", + "url": "dashboard/my-dashboard-uid?$__all_variables" + } + ], + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Panel with dashUri link", + "type": "stat" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 3, + "links": [ + { + "title": "Custom Params Link", + "url": "http://example.com?customParam=value" + } + ], + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Panel with custom params", + "type": "table" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 4, + "links": [ + { + "targetBlank": true, + "title": "Complex Link", + "url": "dashboard/db/complex-dashboard?$__url_time_range\u0026$__all_variables\u0026param1=value1\u0026param2=value2" + } + ], + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Panel with complex link", + "type": "timeseries" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 5, + "links": [ + { + "title": "Existing URL Link", + "url": "http://existing-url.com" + } + ], + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Panel with existing URL", + "type": "gauge" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 6, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Panel with no links", + "type": "stat" + } + ], + "preload": false, + "refresh": "", + "schemaVersion": 42, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "V19 Panel Links Migration Test Dashboard" + }, "status": { "conversion": { - "failed": true, - "error": "backend conversion not yet implemented", - "storedVersion": "v2beta1", - "source": { - "kind": "Dashboard", - "apiVersion": "dashboard.grafana.app/v2beta1", - "metadata": { - "name": "v19.panel_links.v42" - }, - "spec": { - "annotations": [ - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana", - "version": "v0", - "datasource": { - "name": "-- Grafana --" - }, - "spec": {} - }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations \u0026 Alerts", - "builtIn": true, - "legacyOptions": { - "type": "dashboard" - } - } - } - ], - "cursorSync": "Off", - "editable": true, - "elements": { - "panel-1": { - "kind": "Panel", - "spec": { - "id": 1, - "title": "Panel with legacy dashboard link", - "description": "", - "links": [ - { - "title": "Dashboard Link", - "url": "dashboard/db/my-dashboard?$__url_time_range" - } - ], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-2": { - "kind": "Panel", - "spec": { - "id": 2, - "title": "Panel with dashUri link", - "description": "", - "links": [ - { - "title": "DashUri Link", - "url": "dashboard/my-dashboard-uid?$__all_variables" - } - ], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "stat", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-3": { - "kind": "Panel", - "spec": { - "id": 3, - "title": "Panel with custom params", - "description": "", - "links": [ - { - "title": "Custom Params Link", - "url": "http://example.com?customParam=value" - } - ], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "table", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-4": { - "kind": "Panel", - "spec": { - "id": 4, - "title": "Panel with complex link", - "description": "", - "links": [ - { - "title": "Complex Link", - "url": "dashboard/db/complex-dashboard?$__url_time_range\u0026$__all_variables\u0026param1=value1\u0026param2=value2", - "targetBlank": true - } - ], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-5": { - "kind": "Panel", - "spec": { - "id": 5, - "title": "Panel with existing URL", - "description": "", - "links": [ - { - "title": "Existing URL Link", - "url": "http://existing-url.com" - } - ], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "gauge", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-6": { - "kind": "Panel", - "spec": { - "id": 6, - "title": "Panel with no links", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "stat", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - } - }, - "layout": { - "kind": "GridLayout", - "spec": { - "items": [ - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-1" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-2" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-3" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-4" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-5" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-6" - } - } - } - ] - } - }, - "links": [], - "liveNow": false, - "preload": false, - "tags": [], - "timeSettings": { - "timezone": "", - "from": "now-6h", - "to": "now", - "autoRefresh": "", - "autoRefreshIntervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "hideTimepicker": false, - "fiscalYearStartMonth": 0 - }, - "title": "V19 Panel Links Migration Test Dashboard", - "variables": [] - }, - "status": {} - } + "failed": false, + "storedVersion": "v2beta1" } } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v2.panels-and-services.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v2.panels-and-services.v0alpha1.json index 6099960b9ee..ec7aab7bc7a 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v2.panels-and-services.v0alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v2.panels-and-services.v0alpha1.json @@ -4,381 +4,199 @@ "metadata": { "name": "v2.panels-and-services.v42" }, - "spec": null, - "status": { - "conversion": { - "failed": true, - "error": "backend conversion not yet implemented", - "storedVersion": "v2beta1", - "source": { - "kind": "Dashboard", - "apiVersion": "dashboard.grafana.app/v2beta1", - "metadata": { - "name": "v2.panels-and-services.v42" + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "liveNow": false, + "panels": [ + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 }, - "spec": { - "annotations": [ + "id": 1, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "target": "cpu.usage" + } + ], + "title": "CPU Usage", + "type": "timeseries" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 2, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "target": "memory.usage" + } + ], + "title": "Memory Usage", + "type": "timeseries" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 3, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Server Stats", + "type": "table" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 4, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "target": "disk.io" + } + ], + "title": "Disk I/O", + "type": "timeseries" + } + ], + "preload": false, + "refresh": "", + "schemaVersion": 42, + "templating": { + "list": [ + { + "allowCustomValue": true, + "current": { + "text": "", + "value": "" + }, + "datasource": { + "type": "prometheus" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "server", + "options": [], + "query": "label_values(server)", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "allowCustomValue": true, + "current": { + "text": "", + "value": "" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "env", + "options": [ { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana", - "version": "v0", - "datasource": { - "name": "-- Grafana --" - }, - "spec": {} - }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations \u0026 Alerts", - "builtIn": true, - "legacyOptions": { - "type": "dashboard" - } - } + "selected": false, + "text": "Production", + "value": "prod" + }, + { + "selected": false, + "text": "Staging", + "value": "stage" } ], - "cursorSync": "Off", - "editable": true, - "elements": { - "panel-1": { - "kind": "Panel", - "spec": { - "id": 1, - "title": "CPU Usage", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": { - "target": "cpu.usage" - } - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-2": { - "kind": "Panel", - "spec": { - "id": 2, - "title": "Memory Usage", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": { - "target": "memory.usage" - } - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-3": { - "kind": "Panel", - "spec": { - "id": 3, - "title": "Server Stats", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "table", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-4": { - "kind": "Panel", - "spec": { - "id": 4, - "title": "Disk I/O", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": { - "target": "disk.io" - } - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - } - }, - "layout": { - "kind": "GridLayout", - "spec": { - "items": [ - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-1" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-2" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-3" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-4" - } - } - } - ] - } - }, - "links": [], - "liveNow": false, - "preload": false, - "tags": [], - "timeSettings": { - "timezone": "", - "from": "now-6h", - "to": "now", - "autoRefresh": "", - "autoRefreshIntervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "hideTimepicker": false, - "fiscalYearStartMonth": 0 - }, - "title": "V2 Comprehensive Migration Test Dashboard", - "variables": [ - { - "kind": "QueryVariable", - "spec": { - "name": "server", - "current": { - "text": "", - "value": "" - }, - "hide": "dontHide", - "refresh": "onDashboardLoad", - "skipUrlSync": false, - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "spec": { - "__legacyStringValue": "label_values(server)" - } - }, - "regex": "", - "sort": "disabled", - "options": [], - "multi": false, - "includeAll": false, - "allowCustomValue": true - } - }, - { - "kind": "CustomVariable", - "spec": { - "name": "env", - "query": "", - "current": { - "text": "", - "value": "" - }, - "options": [ - { - "selected": false, - "text": "Production", - "value": "prod" - }, - { - "selected": false, - "text": "Staging", - "value": "stage" - } - ], - "multi": false, - "includeAll": false, - "hide": "dontHide", - "skipUrlSync": false, - "allowCustomValue": true - } - } - ] - }, - "status": {} - } + "query": "", + "skipUrlSync": false, + "type": "custom" + } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "V2 Comprehensive Migration Test Dashboard" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v2beta1" } } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v20.variable_syntax_links.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v20.variable_syntax_links.v0alpha1.json index 2c36d89c3c9..40a06e400a4 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v20.variable_syntax_links.v0alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v20.variable_syntax_links.v0alpha1.json @@ -4,453 +4,243 @@ "metadata": { "name": "v20.variable_syntax_links.v42" }, - "spec": null, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "liveNow": false, + "panels": [ + { + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 1, + "options": { + "dataLinks": [ + { + "targetBlank": true, + "title": "Link with series name", + "url": "http://example.com?series=${__series.name}\u0026timestamp=__value.time" + }, + { + "targetBlank": false, + "title": "Link with field name", + "url": "http://grafana.com/dashboard?field=__field.name\u0026series=${__series.name}" + } + ] + }, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Panel with data links using legacy variable syntax", + "type": "timeseries" + }, + { + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 0 + }, + "id": 2, + "options": { + "fieldOptions": { + "defaults": { + "links": [ + { + "targetBlank": true, + "title": "Field link", + "url": "http://monitoring.com?field=${__field.name}\u0026series=__series.name" + }, + { + "targetBlank": false, + "title": "Time-based link", + "url": "http://logs.com?time=__value.time\u0026field=__field.name" + } + ], + "title": "Series: __series.name, Field: ${__field.name}, Time: __value.time" + } + } + }, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Panel with field options using legacy variable syntax", + "type": "stat" + }, + { + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 8 + }, + "id": 3, + "options": { + "dataLinks": [ + { + "targetBlank": true, + "title": "Combined link", + "url": "http://combined.com?series=${__series.name}\u0026field=${__field.name}\u0026time=__value.time" + } + ], + "fieldOptions": { + "defaults": { + "links": [ + { + "targetBlank": false, + "title": "Comprehensive link", + "url": "http://comprehensive.com?s=${__series.name}\u0026f=__field.name\u0026t=__value.time" + } + ], + "title": "Complete: __series.name / __field.name / __value.time" + } + } + }, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Panel with both data links and field options", + "type": "gauge" + }, + { + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 16 + }, + "id": 4, + "options": { + "dataLinks": [ + { + "targetBlank": true, + "title": "Modern link", + "url": "http://modern.com?series=${__series.name}\u0026field=${__field.name}\u0026time=${__value.time}" + } + ], + "fieldOptions": { + "defaults": { + "links": [ + { + "targetBlank": false, + "title": "Modern field link", + "url": "http://modern-field.com?s=${__series.name}\u0026f=${__field.name}" + } + ], + "title": "Modern: ${__series.name} / ${__field.name} / ${__value.time}" + } + } + }, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Panel with no legacy variables (should remain unchanged)", + "type": "table" + }, + { + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 16 + }, + "id": 5, + "options": { + "content": "This panel has no data links or field options to migrate." + }, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Panel with no data links or field options", + "type": "text" + } + ], + "preload": false, + "refresh": "5s", + "schemaVersion": 42, + "tags": [ + "migration-test" + ], + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "V20 Variable Syntax Migration Test Dashboard" + }, "status": { "conversion": { - "failed": true, - "error": "backend conversion not yet implemented", - "storedVersion": "v2beta1", - "source": { - "kind": "Dashboard", - "apiVersion": "dashboard.grafana.app/v2beta1", - "metadata": { - "name": "v20.variable_syntax_links.v42" - }, - "spec": { - "annotations": [ - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana", - "version": "v0", - "datasource": { - "name": "-- Grafana --" - }, - "spec": {} - }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations \u0026 Alerts", - "builtIn": true, - "legacyOptions": { - "type": "dashboard" - } - } - } - ], - "cursorSync": "Off", - "editable": true, - "elements": { - "panel-1": { - "kind": "Panel", - "spec": { - "id": 1, - "title": "Panel with data links using legacy variable syntax", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": { - "dataLinks": [ - { - "targetBlank": true, - "title": "Link with series name", - "url": "http://example.com?series=${__series.name}\u0026timestamp=__value.time" - }, - { - "targetBlank": false, - "title": "Link with field name", - "url": "http://grafana.com/dashboard?field=__field.name\u0026series=${__series.name}" - } - ] - }, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-2": { - "kind": "Panel", - "spec": { - "id": 2, - "title": "Panel with field options using legacy variable syntax", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "stat", - "version": "", - "spec": { - "options": { - "fieldOptions": { - "defaults": { - "links": [ - { - "targetBlank": true, - "title": "Field link", - "url": "http://monitoring.com?field=${__field.name}\u0026series=__series.name" - }, - { - "targetBlank": false, - "title": "Time-based link", - "url": "http://logs.com?time=__value.time\u0026field=__field.name" - } - ], - "title": "Series: __series.name, Field: ${__field.name}, Time: __value.time" - } - } - }, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-3": { - "kind": "Panel", - "spec": { - "id": 3, - "title": "Panel with both data links and field options", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "gauge", - "version": "", - "spec": { - "options": { - "dataLinks": [ - { - "targetBlank": true, - "title": "Combined link", - "url": "http://combined.com?series=${__series.name}\u0026field=${__field.name}\u0026time=__value.time" - } - ], - "fieldOptions": { - "defaults": { - "links": [ - { - "targetBlank": false, - "title": "Comprehensive link", - "url": "http://comprehensive.com?s=${__series.name}\u0026f=__field.name\u0026t=__value.time" - } - ], - "title": "Complete: __series.name / __field.name / __value.time" - } - } - }, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-4": { - "kind": "Panel", - "spec": { - "id": 4, - "title": "Panel with no legacy variables (should remain unchanged)", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "table", - "version": "", - "spec": { - "options": { - "dataLinks": [ - { - "targetBlank": true, - "title": "Modern link", - "url": "http://modern.com?series=${__series.name}\u0026field=${__field.name}\u0026time=${__value.time}" - } - ], - "fieldOptions": { - "defaults": { - "links": [ - { - "targetBlank": false, - "title": "Modern field link", - "url": "http://modern-field.com?s=${__series.name}\u0026f=${__field.name}" - } - ], - "title": "Modern: ${__series.name} / ${__field.name} / ${__value.time}" - } - } - }, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-5": { - "kind": "Panel", - "spec": { - "id": 5, - "title": "Panel with no data links or field options", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "text", - "version": "", - "spec": { - "options": { - "content": "This panel has no data links or field options to migrate." - }, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - } - }, - "layout": { - "kind": "GridLayout", - "spec": { - "items": [ - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 12, - "height": 8, - "element": { - "kind": "ElementReference", - "name": "panel-1" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 12, - "y": 0, - "width": 12, - "height": 8, - "element": { - "kind": "ElementReference", - "name": "panel-2" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 8, - "width": 24, - "height": 8, - "element": { - "kind": "ElementReference", - "name": "panel-3" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 16, - "width": 12, - "height": 8, - "element": { - "kind": "ElementReference", - "name": "panel-4" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 12, - "y": 16, - "width": 12, - "height": 8, - "element": { - "kind": "ElementReference", - "name": "panel-5" - } - } - } - ] - } - }, - "links": [], - "liveNow": false, - "preload": false, - "tags": [ - "migration-test" - ], - "timeSettings": { - "timezone": "", - "from": "now-6h", - "to": "now", - "autoRefresh": "5s", - "autoRefreshIntervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "hideTimepicker": false, - "fiscalYearStartMonth": 0 - }, - "title": "V20 Variable Syntax Migration Test Dashboard", - "variables": [] - }, - "status": {} - } + "failed": false, + "storedVersion": "v2beta1" } } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v21.data_links_series_to_field.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v21.data_links_series_to_field.v0alpha1.json index 81d695680a8..25823b0fd99 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v21.data_links_series_to_field.v0alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v21.data_links_series_to_field.v0alpha1.json @@ -4,428 +4,217 @@ "metadata": { "name": "v21.data_links_series_to_field.v42" }, - "spec": null, - "status": { - "conversion": { - "failed": true, - "error": "backend conversion not yet implemented", - "storedVersion": "v2beta1", - "source": { - "kind": "Dashboard", - "apiVersion": "dashboard.grafana.app/v2beta1", - "metadata": { - "name": "v21.data_links_series_to_field.v42" - }, - "spec": { - "annotations": [ - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana", - "version": "v0", - "datasource": { - "name": "-- Grafana --" - }, - "spec": {} - }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations \u0026 Alerts", - "builtIn": true, - "legacyOptions": { - "type": "dashboard" - } - } - } - ], - "cursorSync": "Off", - "editable": true, - "elements": { - "panel-1": { - "kind": "Panel", - "spec": { - "id": 1, - "title": "Panel with data links", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": { - "dataLinks": [ - { - "title": "Data Link 1", - "url": "http://mylink.com?series=${__field.labels}\u0026${__field.labels.a}" - }, - { - "title": "Data Link 2", - "url": "http://anotherlink.com?param=${__field.labels}" - } - ] - }, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-2": { - "kind": "Panel", - "spec": { - "id": 2, - "title": "Panel with field options links", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "stat", - "version": "", - "spec": { - "options": { - "fieldOptions": { - "defaults": { - "links": [ - { - "title": "Field Link 1", - "url": "http://mylink.com?series=${__field.labels}\u0026${__field.labels.x}" - }, - { - "title": "Field Link 2", - "url": "http://fieldlink.com?field=${__field.labels}" - } - ] - } - } - }, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-3": { - "kind": "Panel", - "spec": { - "id": 3, - "title": "Panel with both link types", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": { - "dataLinks": [ - { - "title": "Graph Data Link", - "url": "http://mylink.com?series=${__field.labels}" - } - ], - "fieldOptions": { - "defaults": { - "links": [ - { - "title": "Graph Field Link", - "url": "http://mylink.com?field=${__field.labels}" - } - ] - } - } - }, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-4": { - "kind": "Panel", - "spec": { - "id": 4, - "title": "Panel without series labels", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": { - "dataLinks": [ - { - "title": "No Series Labels Link", - "url": "http://mylink.com?other=${__field.labels}" - } - ] - }, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-5": { - "kind": "Panel", - "spec": { - "id": 5, - "title": "Panel without options", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - } + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" }, - "layout": { - "kind": "GridLayout", - "spec": { - "items": [ + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "liveNow": false, + "panels": [ + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 1, + "options": { + "dataLinks": [ + { + "title": "Data Link 1", + "url": "http://mylink.com?series=${__field.labels}\u0026${__field.labels.a}" + }, + { + "title": "Data Link 2", + "url": "http://anotherlink.com?param=${__field.labels}" + } + ] + }, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Panel with data links", + "type": "timeseries" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 2, + "options": { + "fieldOptions": { + "defaults": { + "links": [ { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-1" - } - } + "title": "Field Link 1", + "url": "http://mylink.com?series=${__field.labels}\u0026${__field.labels.x}" }, { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-2" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-3" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-4" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-5" - } - } + "title": "Field Link 2", + "url": "http://fieldlink.com?field=${__field.labels}" } ] } - }, - "links": [], - "liveNow": false, - "preload": false, - "tags": [], - "timeSettings": { - "timezone": "", - "from": "now-6h", - "to": "now", - "autoRefresh": "", - "autoRefreshIntervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "hideTimepicker": false, - "fiscalYearStartMonth": 0 - }, - "title": "V21 Data Links Series to Field Migration Test Dashboard", - "variables": [] + } }, - "status": {} + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Panel with field options links", + "type": "stat" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 3, + "options": { + "dataLinks": [ + { + "title": "Graph Data Link", + "url": "http://mylink.com?series=${__field.labels}" + } + ], + "fieldOptions": { + "defaults": { + "links": [ + { + "title": "Graph Field Link", + "url": "http://mylink.com?field=${__field.labels}" + } + ] + } + } + }, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Panel with both link types", + "type": "timeseries" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 4, + "options": { + "dataLinks": [ + { + "title": "No Series Labels Link", + "url": "http://mylink.com?other=${__field.labels}" + } + ] + }, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Panel without series labels", + "type": "timeseries" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 5, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Panel without options", + "type": "timeseries" } + ], + "preload": false, + "refresh": "", + "schemaVersion": 42, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "V21 Data Links Series to Field Migration Test Dashboard" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v2beta1" } } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v22.table_panel_align.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v22.table_panel_align.v0alpha1.json index 22db42a1f9b..ab9f271d900 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v22.table_panel_align.v0alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v22.table_panel_align.v0alpha1.json @@ -4,142 +4,79 @@ "metadata": { "name": "v22.table_panel_align.v42" }, - "spec": null, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "liveNow": false, + "panels": [ + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 1, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "", + "type": "table" + } + ], + "preload": false, + "refresh": "", + "schemaVersion": 42, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "V22 Table Panel Styles Test" + }, "status": { "conversion": { - "failed": true, - "error": "backend conversion not yet implemented", - "storedVersion": "v2beta1", - "source": { - "kind": "Dashboard", - "apiVersion": "dashboard.grafana.app/v2beta1", - "metadata": { - "name": "v22.table_panel_align.v42" - }, - "spec": { - "annotations": [ - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana", - "version": "v0", - "datasource": { - "name": "-- Grafana --" - }, - "spec": {} - }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations \u0026 Alerts", - "builtIn": true, - "legacyOptions": { - "type": "dashboard" - } - } - } - ], - "cursorSync": "Off", - "editable": true, - "elements": { - "panel-1": { - "kind": "Panel", - "spec": { - "id": 1, - "title": "", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "table", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - } - }, - "layout": { - "kind": "GridLayout", - "spec": { - "items": [ - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-1" - } - } - } - ] - } - }, - "links": [], - "liveNow": false, - "preload": false, - "tags": [], - "timeSettings": { - "timezone": "", - "from": "now-6h", - "to": "now", - "autoRefresh": "", - "autoRefreshIntervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "hideTimepicker": false, - "fiscalYearStartMonth": 0 - }, - "title": "V22 Table Panel Styles Test", - "variables": [] - }, - "status": {} - } + "failed": false, + "storedVersion": "v2beta1" } } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v23.multi_variable_alignment.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v23.multi_variable_alignment.v0alpha1.json index fbc2d99ff02..6bf600c5014 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v23.multi_variable_alignment.v0alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v23.multi_variable_alignment.v0alpha1.json @@ -4,407 +4,309 @@ "metadata": { "name": "v23.multi_variable_alignment.v42" }, - "spec": null, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "liveNow": false, + "panels": [ + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 1, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "expr": "up", + "refId": "A" + } + ], + "title": "Test Panel", + "type": "stat" + } + ], + "preload": false, + "refresh": "", + "schemaVersion": 42, + "templating": { + "list": [ + { + "allowCustomValue": true, + "current": { + "text": [ + "A" + ], + "value": [ + "A" + ] + }, + "datasource": { + "type": "prometheus" + }, + "hide": 0, + "includeAll": false, + "multi": true, + "name": "multi_single_value", + "options": [], + "query": {}, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "allowCustomValue": true, + "current": { + "text": [ + "B", + "C" + ], + "value": [ + "B", + "C" + ] + }, + "datasource": { + "type": "prometheus" + }, + "hide": 0, + "includeAll": false, + "multi": true, + "name": "multi_array_value", + "options": [], + "query": {}, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "allowCustomValue": true, + "current": { + "text": "D", + "value": "D" + }, + "datasource": { + "type": "prometheus" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "non_multi_array_value", + "options": [], + "query": {}, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "allowCustomValue": true, + "current": { + "text": "E", + "value": "E" + }, + "datasource": { + "type": "prometheus" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "non_multi_single_value", + "options": [], + "query": {}, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "allowCustomValue": true, + "current": { + "text": "F", + "value": "F" + }, + "datasource": { + "type": "prometheus" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "no_multi_property", + "options": [], + "query": {}, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "allowCustomValue": true, + "current": { + "text": "", + "value": "" + }, + "datasource": { + "type": "prometheus" + }, + "hide": 0, + "includeAll": false, + "multi": true, + "name": "empty_current", + "options": [], + "query": {}, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "allowCustomValue": true, + "current": { + "text": "", + "value": "" + }, + "datasource": { + "type": "prometheus" + }, + "hide": 0, + "includeAll": false, + "multi": true, + "name": "nil_current", + "options": [], + "query": {}, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "allowCustomValue": true, + "current": { + "text": [ + "G" + ], + "value": [ + "G" + ] + }, + "hide": 0, + "includeAll": false, + "multi": true, + "name": "custom_variable", + "options": [], + "query": "", + "skipUrlSync": false, + "type": "custom" + }, + { + "current": { + "text": "H", + "value": "H" + }, + "hide": 0, + "name": "textbox_variable", + "query": "", + "skipUrlSync": false, + "type": "textbox" + }, + { + "allowCustomValue": true, + "current": { + "text": [ + "Prometheus", + "InfluxDB" + ], + "value": [ + "prometheus", + "influxdb" + ] + }, + "hide": 0, + "includeAll": false, + "multi": true, + "name": "datasource_variable", + "options": [], + "query": "prometheus", + "refresh": 0, + "regex": "", + "skipUrlSync": false, + "type": "datasource" + }, + { + "auto": false, + "auto_count": 0, + "auto_min": "", + "current": { + "text": "1m", + "value": "1m" + }, + "hide": 0, + "name": "interval_variable", + "options": [], + "query": "", + "skipUrlSync": false, + "type": "interval" + } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "V23 Multi Variables Migration Test Dashboard" + }, "status": { "conversion": { - "failed": true, - "error": "backend conversion not yet implemented", - "storedVersion": "v2beta1", - "source": { - "kind": "Dashboard", - "apiVersion": "dashboard.grafana.app/v2beta1", - "metadata": { - "name": "v23.multi_variable_alignment.v42" - }, - "spec": { - "annotations": [ - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana", - "version": "v0", - "datasource": { - "name": "-- Grafana --" - }, - "spec": {} - }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations \u0026 Alerts", - "builtIn": true, - "legacyOptions": { - "type": "dashboard" - } - } - } - ], - "cursorSync": "Off", - "editable": true, - "elements": { - "panel-1": { - "kind": "Panel", - "spec": { - "id": 1, - "title": "Test Panel", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": { - "expr": "up" - } - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "stat", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - } - }, - "layout": { - "kind": "GridLayout", - "spec": { - "items": [ - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-1" - } - } - } - ] - } - }, - "links": [], - "liveNow": false, - "preload": false, - "tags": [], - "timeSettings": { - "timezone": "", - "from": "now-6h", - "to": "now", - "autoRefresh": "", - "autoRefreshIntervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "hideTimepicker": false, - "fiscalYearStartMonth": 0 - }, - "title": "V23 Multi Variables Migration Test Dashboard", - "variables": [ - { - "kind": "QueryVariable", - "spec": { - "name": "multi_single_value", - "current": { - "text": [ - "A" - ], - "value": [ - "A" - ] - }, - "hide": "dontHide", - "refresh": "onDashboardLoad", - "skipUrlSync": false, - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "spec": {} - }, - "regex": "", - "sort": "disabled", - "options": [], - "multi": true, - "includeAll": false, - "allowCustomValue": true - } - }, - { - "kind": "QueryVariable", - "spec": { - "name": "multi_array_value", - "current": { - "text": [ - "B", - "C" - ], - "value": [ - "B", - "C" - ] - }, - "hide": "dontHide", - "refresh": "onDashboardLoad", - "skipUrlSync": false, - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "spec": {} - }, - "regex": "", - "sort": "disabled", - "options": [], - "multi": true, - "includeAll": false, - "allowCustomValue": true - } - }, - { - "kind": "QueryVariable", - "spec": { - "name": "non_multi_array_value", - "current": { - "text": "D", - "value": "D" - }, - "hide": "dontHide", - "refresh": "onDashboardLoad", - "skipUrlSync": false, - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "spec": {} - }, - "regex": "", - "sort": "disabled", - "options": [], - "multi": false, - "includeAll": false, - "allowCustomValue": true - } - }, - { - "kind": "QueryVariable", - "spec": { - "name": "non_multi_single_value", - "current": { - "text": "E", - "value": "E" - }, - "hide": "dontHide", - "refresh": "onDashboardLoad", - "skipUrlSync": false, - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "spec": {} - }, - "regex": "", - "sort": "disabled", - "options": [], - "multi": false, - "includeAll": false, - "allowCustomValue": true - } - }, - { - "kind": "QueryVariable", - "spec": { - "name": "no_multi_property", - "current": { - "text": "F", - "value": "F" - }, - "hide": "dontHide", - "refresh": "onDashboardLoad", - "skipUrlSync": false, - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "spec": {} - }, - "regex": "", - "sort": "disabled", - "options": [], - "multi": false, - "includeAll": false, - "allowCustomValue": true - } - }, - { - "kind": "QueryVariable", - "spec": { - "name": "empty_current", - "current": { - "text": "", - "value": "" - }, - "hide": "dontHide", - "refresh": "onDashboardLoad", - "skipUrlSync": false, - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "spec": {} - }, - "regex": "", - "sort": "disabled", - "options": [], - "multi": true, - "includeAll": false, - "allowCustomValue": true - } - }, - { - "kind": "QueryVariable", - "spec": { - "name": "nil_current", - "current": { - "text": "", - "value": "" - }, - "hide": "dontHide", - "refresh": "onDashboardLoad", - "skipUrlSync": false, - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "spec": {} - }, - "regex": "", - "sort": "disabled", - "options": [], - "multi": true, - "includeAll": false, - "allowCustomValue": true - } - }, - { - "kind": "CustomVariable", - "spec": { - "name": "custom_variable", - "query": "", - "current": { - "text": [ - "G" - ], - "value": [ - "G" - ] - }, - "options": [], - "multi": true, - "includeAll": false, - "hide": "dontHide", - "skipUrlSync": false, - "allowCustomValue": true - } - }, - { - "kind": "TextVariable", - "spec": { - "name": "textbox_variable", - "current": { - "text": "H", - "value": "H" - }, - "query": "", - "hide": "dontHide", - "skipUrlSync": false - } - }, - { - "kind": "DatasourceVariable", - "spec": { - "name": "datasource_variable", - "pluginId": "prometheus", - "refresh": "never", - "regex": "", - "current": { - "text": [ - "Prometheus", - "InfluxDB" - ], - "value": [ - "prometheus", - "influxdb" - ] - }, - "options": [], - "multi": true, - "includeAll": false, - "hide": "dontHide", - "skipUrlSync": false, - "allowCustomValue": true - } - }, - { - "kind": "IntervalVariable", - "spec": { - "name": "interval_variable", - "query": "", - "current": { - "text": "1m", - "value": "1m" - }, - "options": [], - "auto": false, - "auto_min": "", - "auto_count": 0, - "refresh": "onTimeRangeChanged", - "hide": "dontHide", - "skipUrlSync": false - } - } - ] - }, - "status": {} - } + "failed": false, + "storedVersion": "v2beta1" } } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v24.table-angular.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v24.table-angular.v0alpha1.json index 660bd5c00fb..876be802767 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v24.table-angular.v0alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v24.table-angular.v0alpha1.json @@ -4,1117 +4,468 @@ "metadata": { "name": "v24.table-angular.v42" }, - "spec": null, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "liveNow": false, + "panels": [ + { + "description": "Tests basic migration with default style pattern (/.*/) containing thresholds and colors. Should convert styles to fieldConfig.defaults with threshold steps.", + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 1, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "B" + } + ], + "title": "Basic Angular Table with Defaults", + "type": "table" + }, + { + "description": "Tests comprehensive migration including: default style with thresholds/colors/unit/decimals/align/colorMode, column overrides with exact name and regex patterns, date formatting, hidden columns, and links with tooltips.", + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 2, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Complex Table with All Style Features", + "type": "table" + }, + { + "description": "Tests migration of timeseries_aggregations transform to reduce transformation with column mappings (avg-\u003emean, max-\u003emax, min-\u003emin, total-\u003esum, current-\u003elastNotNull, count-\u003ecount).", + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 3, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Table with Timeseries Aggregations Transform", + "type": "table" + }, + { + "description": "Tests migration of timeseries_to_rows transform to seriesToRows transformation.", + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 4, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Table with Timeseries to Rows Transform", + "type": "table" + }, + { + "description": "Tests migration of timeseries_to_columns transform to seriesToColumns transformation.", + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 5, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Table with Timeseries to Columns Transform", + "type": "table" + }, + { + "description": "Tests migration of table transform to merge transformation. Also tests auto alignment conversion to empty string.", + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 6, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Table with Merge Transform", + "type": "table" + }, + { + "description": "Tests that existing transformations are preserved and new transformation from old format is appended to the list.", + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 7, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Table with Existing Transformations", + "transformations": [ + { + "id": "filterFieldsByName", + "options": { + "include": { + "names": [ + "field1", + "field2" + ] + } + } + } + ], + "type": "table" + }, + { + "description": "Tests handling of mixed numeric and string threshold values (int, string, float) with proper type conversion.", + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 8, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Mixed Threshold Types", + "type": "table" + }, + { + "description": "Tests all color mode mappings: cell-\u003ecolor-background, row-\u003ecolor-background, value-\u003ecolor-text.", + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 9, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "All Color Modes Test", + "type": "table" + }, + { + "description": "Tests all alignment options: left, center, right, and auto (should convert to empty string).", + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 10, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "All Alignment Options", + "type": "table" + }, + { + "description": "Tests both field matcher types: byName for exact matches and byRegexp for regex patterns.", + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 11, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Field Matcher Types Test", + "type": "table" + }, + { + "description": "Tests various link configurations: with and without tooltip, with and without target blank.", + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 12, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Link Configuration Test", + "type": "table" + }, + { + "description": "Tests various date format patterns and aliases.", + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 13, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Date Format Variations", + "type": "table" + }, + { + "description": "React table (table2) should not be migrated. Properties should remain unchanged.", + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 14, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "React Table - Should NOT Migrate", + "type": "table" + }, + { + "description": "Angular table without styles property should not be migrated.", + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 15, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Angular Table without Styles - Should NOT Migrate", + "type": "table" + }, + { + "description": "Non-table panels should remain completely unchanged.", + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 16, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Non-Table Panel - Should NOT Migrate", + "type": "timeseries" + }, + { + "description": "Other panel types should not be affected by table migration.", + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 17, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Singlestat Panel - Should NOT Migrate", + "type": "stat" + } + ], + "preload": false, + "refresh": "", + "schemaVersion": 42, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "No Title" + }, "status": { "conversion": { - "failed": true, - "error": "backend conversion not yet implemented", - "storedVersion": "v2beta1", - "source": { - "kind": "Dashboard", - "apiVersion": "dashboard.grafana.app/v2beta1", - "metadata": { - "name": "v24.table-angular.v42" - }, - "spec": { - "annotations": [ - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana", - "version": "v0", - "datasource": { - "name": "-- Grafana --" - }, - "spec": {} - }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations \u0026 Alerts", - "builtIn": true, - "legacyOptions": { - "type": "dashboard" - } - } - } - ], - "cursorSync": "Off", - "editable": true, - "elements": { - "panel-1": { - "kind": "Panel", - "spec": { - "id": 1, - "title": "Basic Angular Table with Defaults", - "description": "Tests basic migration with default style pattern (/.*/) containing thresholds and colors. Should convert styles to fieldConfig.defaults with threshold steps.", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - }, - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "B", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "table", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-10": { - "kind": "Panel", - "spec": { - "id": 10, - "title": "All Alignment Options", - "description": "Tests all alignment options: left, center, right, and auto (should convert to empty string).", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "table", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-11": { - "kind": "Panel", - "spec": { - "id": 11, - "title": "Field Matcher Types Test", - "description": "Tests both field matcher types: byName for exact matches and byRegexp for regex patterns.", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "table", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-12": { - "kind": "Panel", - "spec": { - "id": 12, - "title": "Link Configuration Test", - "description": "Tests various link configurations: with and without tooltip, with and without target blank.", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "table", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-13": { - "kind": "Panel", - "spec": { - "id": 13, - "title": "Date Format Variations", - "description": "Tests various date format patterns and aliases.", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "table", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-14": { - "kind": "Panel", - "spec": { - "id": 14, - "title": "React Table - Should NOT Migrate", - "description": "React table (table2) should not be migrated. Properties should remain unchanged.", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "table", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-15": { - "kind": "Panel", - "spec": { - "id": 15, - "title": "Angular Table without Styles - Should NOT Migrate", - "description": "Angular table without styles property should not be migrated.", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "table", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-16": { - "kind": "Panel", - "spec": { - "id": 16, - "title": "Non-Table Panel - Should NOT Migrate", - "description": "Non-table panels should remain completely unchanged.", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-17": { - "kind": "Panel", - "spec": { - "id": 17, - "title": "Singlestat Panel - Should NOT Migrate", - "description": "Other panel types should not be affected by table migration.", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "stat", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-2": { - "kind": "Panel", - "spec": { - "id": 2, - "title": "Complex Table with All Style Features", - "description": "Tests comprehensive migration including: default style with thresholds/colors/unit/decimals/align/colorMode, column overrides with exact name and regex patterns, date formatting, hidden columns, and links with tooltips.", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "table", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-3": { - "kind": "Panel", - "spec": { - "id": 3, - "title": "Table with Timeseries Aggregations Transform", - "description": "Tests migration of timeseries_aggregations transform to reduce transformation with column mappings (avg-\u003emean, max-\u003emax, min-\u003emin, total-\u003esum, current-\u003elastNotNull, count-\u003ecount).", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "table", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-4": { - "kind": "Panel", - "spec": { - "id": 4, - "title": "Table with Timeseries to Rows Transform", - "description": "Tests migration of timeseries_to_rows transform to seriesToRows transformation.", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "table", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-5": { - "kind": "Panel", - "spec": { - "id": 5, - "title": "Table with Timeseries to Columns Transform", - "description": "Tests migration of timeseries_to_columns transform to seriesToColumns transformation.", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "table", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-6": { - "kind": "Panel", - "spec": { - "id": 6, - "title": "Table with Merge Transform", - "description": "Tests migration of table transform to merge transformation. Also tests auto alignment conversion to empty string.", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "table", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-7": { - "kind": "Panel", - "spec": { - "id": 7, - "title": "Table with Existing Transformations", - "description": "Tests that existing transformations are preserved and new transformation from old format is appended to the list.", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [ - { - "kind": "filterFieldsByName", - "spec": { - "id": "filterFieldsByName", - "options": { - "include": { - "names": [ - "field1", - "field2" - ] - } - } - } - } - ], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "table", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-8": { - "kind": "Panel", - "spec": { - "id": 8, - "title": "Mixed Threshold Types", - "description": "Tests handling of mixed numeric and string threshold values (int, string, float) with proper type conversion.", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "table", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-9": { - "kind": "Panel", - "spec": { - "id": 9, - "title": "All Color Modes Test", - "description": "Tests all color mode mappings: cell-\u003ecolor-background, row-\u003ecolor-background, value-\u003ecolor-text.", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "table", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - } - }, - "layout": { - "kind": "GridLayout", - "spec": { - "items": [ - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-1" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-2" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-3" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-4" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-5" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-6" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-7" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-8" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-9" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-10" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-11" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-12" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-13" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-14" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-15" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-16" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-17" - } - } - } - ] - } - }, - "links": [], - "liveNow": false, - "preload": false, - "tags": [], - "timeSettings": { - "timezone": "", - "from": "now-6h", - "to": "now", - "autoRefresh": "", - "autoRefreshIntervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "hideTimepicker": false, - "fiscalYearStartMonth": 0 - }, - "title": "No Title", - "variables": [] - }, - "status": {} - } + "failed": false, + "storedVersion": "v2beta1" } } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v25.no-op-migration.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v25.no-op-migration.v0alpha1.json index c05a05ae948..704035dffdf 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v25.no-op-migration.v0alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v25.no-op-migration.v0alpha1.json @@ -4,242 +4,132 @@ "metadata": { "name": "v25.no-op-migration.v42" }, - "spec": null, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + }, + { + "enable": true, + "hide": false, + "iconColor": "", + "name": "Deployments" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "liveNow": false, + "panels": [ + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 1, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Panel with transformations remains unchanged", + "type": "timeseries" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 2, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Graph", + "type": "timeseries" + } + ], + "preload": false, + "refresh": "", + "schemaVersion": 42, + "templating": { + "list": [ + { + "allowCustomValue": true, + "current": { + "text": "", + "value": "" + }, + "datasource": { + "type": "prometheus" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "tags should not be removed", + "options": [], + "query": {}, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "V25 No-Op Migration Test Dashboard" + }, "status": { "conversion": { - "failed": true, - "error": "backend conversion not yet implemented", - "storedVersion": "v2beta1", - "source": { - "kind": "Dashboard", - "apiVersion": "dashboard.grafana.app/v2beta1", - "metadata": { - "name": "v25.no-op-migration.v42" - }, - "spec": { - "annotations": [ - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana", - "version": "v0", - "datasource": { - "name": "-- Grafana --" - }, - "spec": {} - }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations \u0026 Alerts", - "builtIn": true, - "legacyOptions": { - "type": "dashboard" - } - } - }, - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "", - "version": "v0", - "spec": {} - }, - "enable": true, - "hide": false, - "iconColor": "", - "name": "Deployments" - } - } - ], - "cursorSync": "Off", - "editable": true, - "elements": { - "panel-1": { - "kind": "Panel", - "spec": { - "id": 1, - "title": "Panel with transformations remains unchanged", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-2": { - "kind": "Panel", - "spec": { - "id": 2, - "title": "Graph", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - } - }, - "layout": { - "kind": "GridLayout", - "spec": { - "items": [ - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-1" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-2" - } - } - } - ] - } - }, - "links": [], - "liveNow": false, - "preload": false, - "tags": [], - "timeSettings": { - "timezone": "", - "from": "now-6h", - "to": "now", - "autoRefresh": "", - "autoRefreshIntervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "hideTimepicker": false, - "fiscalYearStartMonth": 0 - }, - "title": "V25 No-Op Migration Test Dashboard", - "variables": [ - { - "kind": "QueryVariable", - "spec": { - "name": "tags should not be removed", - "current": { - "text": "", - "value": "" - }, - "hide": "dontHide", - "refresh": "onDashboardLoad", - "skipUrlSync": false, - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "spec": {} - }, - "regex": "", - "sort": "disabled", - "options": [], - "multi": false, - "includeAll": false, - "allowCustomValue": true - } - } - ] - }, - "status": {} - } + "failed": false, + "storedVersion": "v2beta1" } } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v26.text2_to_text.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v26.text2_to_text.v0alpha1.json index 3b79b30aed1..4ddc375e3d0 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v26.text2_to_text.v0alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v26.text2_to_text.v0alpha1.json @@ -4,263 +4,126 @@ "metadata": { "name": "v26.text2_to_text.v42" }, - "spec": null, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "liveNow": false, + "panels": [ + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 1, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Text2 Panel", + "type": "text" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 2, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Angular Text Panel", + "type": "text" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 3, + "options": { + "content": "# React Text Panel from Angular Panel\n# $constant\n\nFor markdown syntax help: [commonmark.org/help](https://commonmark.org/help/)\n\n## $text\n\n", + "mode": "markdown" + }, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "React Text Panel from Angular Panel", + "type": "text" + } + ], + "preload": false, + "refresh": "", + "schemaVersion": 42, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "No Title" + }, "status": { "conversion": { - "failed": true, - "error": "backend conversion not yet implemented", - "storedVersion": "v2beta1", - "source": { - "kind": "Dashboard", - "apiVersion": "dashboard.grafana.app/v2beta1", - "metadata": { - "name": "v26.text2_to_text.v42" - }, - "spec": { - "annotations": [ - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana", - "version": "v0", - "datasource": { - "name": "-- Grafana --" - }, - "spec": {} - }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations \u0026 Alerts", - "builtIn": true, - "legacyOptions": { - "type": "dashboard" - } - } - } - ], - "cursorSync": "Off", - "editable": true, - "elements": { - "panel-1": { - "kind": "Panel", - "spec": { - "id": 1, - "title": "Text2 Panel", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "text", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-2": { - "kind": "Panel", - "spec": { - "id": 2, - "title": "Angular Text Panel", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "text", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-3": { - "kind": "Panel", - "spec": { - "id": 3, - "title": "React Text Panel from Angular Panel", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "text", - "version": "", - "spec": { - "options": { - "content": "# React Text Panel from Angular Panel\n# $constant\n\nFor markdown syntax help: [commonmark.org/help](https://commonmark.org/help/)\n\n## $text\n\n", - "mode": "markdown" - }, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - } - }, - "layout": { - "kind": "GridLayout", - "spec": { - "items": [ - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-1" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-2" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-3" - } - } - } - ] - } - }, - "links": [], - "liveNow": false, - "preload": false, - "tags": [], - "timeSettings": { - "timezone": "", - "from": "now-6h", - "to": "now", - "autoRefresh": "", - "autoRefreshIntervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "hideTimepicker": false, - "fiscalYearStartMonth": 0 - }, - "title": "No Title", - "variables": [] - }, - "status": {} - } + "failed": false, + "storedVersion": "v2beta1" } } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v27.repeated_panels_and_constant_variable.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v27.repeated_panels_and_constant_variable.v0alpha1.json index cd7befbf4e7..99bdb079082 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v27.repeated_panels_and_constant_variable.v0alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v27.repeated_panels_and_constant_variable.v0alpha1.json @@ -4,227 +4,120 @@ "metadata": { "name": "v27.repeated_panels_and_constant_variable.v42" }, - "spec": null, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "liveNow": false, + "panels": [ + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 2, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Normal Panel", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 3 + }, + "id": -1, + "title": "Row with repeated panels", + "type": "row" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 3 + }, + "id": 6, + "options": {}, + "pluginVersion": "", + "targets": [], + "title": "Normal nested panel", + "type": "timeseries" + } + ], + "preload": false, + "refresh": "", + "schemaVersion": 42, + "templating": { + "list": [ + { + "current": { + "text": "default_value", + "value": "default_value" + }, + "hide": 0, + "name": "constant_var", + "query": "default_value", + "skipUrlSync": false, + "type": "textbox" + } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "V27 Repeated Panels and Constant Variable Migration Test Dashboard" + }, "status": { "conversion": { - "failed": true, - "error": "backend conversion not yet implemented", - "storedVersion": "v2beta1", - "source": { - "kind": "Dashboard", - "apiVersion": "dashboard.grafana.app/v2beta1", - "metadata": { - "name": "v27.repeated_panels_and_constant_variable.v42" - }, - "spec": { - "annotations": [ - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana", - "version": "v0", - "datasource": { - "name": "-- Grafana --" - }, - "spec": {} - }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations \u0026 Alerts", - "builtIn": true, - "legacyOptions": { - "type": "dashboard" - } - } - } - ], - "cursorSync": "Off", - "editable": true, - "elements": { - "panel-2": { - "kind": "Panel", - "spec": { - "id": 2, - "title": "Normal Panel", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-6": { - "kind": "Panel", - "spec": { - "id": 6, - "title": "Normal nested panel", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - } - }, - "layout": { - "kind": "RowsLayout", - "spec": { - "rows": [ - { - "kind": "RowsLayoutRow", - "spec": { - "title": "", - "collapse": false, - "hideHeader": true, - "layout": { - "kind": "GridLayout", - "spec": { - "items": [ - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-2" - } - } - } - ] - } - } - } - }, - { - "kind": "RowsLayoutRow", - "spec": { - "title": "Row with repeated panels", - "collapse": false, - "layout": { - "kind": "GridLayout", - "spec": { - "items": [ - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-6" - } - } - } - ] - } - } - } - } - ] - } - }, - "links": [], - "liveNow": false, - "preload": false, - "tags": [], - "timeSettings": { - "timezone": "", - "from": "now-6h", - "to": "now", - "autoRefresh": "", - "autoRefreshIntervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "hideTimepicker": false, - "fiscalYearStartMonth": 0 - }, - "title": "V27 Repeated Panels and Constant Variable Migration Test Dashboard", - "variables": [ - { - "kind": "TextVariable", - "spec": { - "name": "constant_var", - "current": { - "text": "default_value", - "value": "default_value" - }, - "query": "default_value", - "hide": "dontHide", - "skipUrlSync": false - } - } - ] - }, - "status": {} - } + "failed": false, + "storedVersion": "v2beta1" } } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v28.remove_variable_properties.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v28.remove_variable_properties.v0alpha1.json index 6c987fc8588..77f44169ab6 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v28.remove_variable_properties.v0alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v28.remove_variable_properties.v0alpha1.json @@ -4,150 +4,117 @@ "metadata": { "name": "v28.remove_variable_properties.v42" }, - "spec": null, - "status": { - "conversion": { - "failed": true, - "error": "backend conversion not yet implemented", - "storedVersion": "v2beta1", - "source": { - "kind": "Dashboard", - "apiVersion": "dashboard.grafana.app/v2beta1", - "metadata": { - "name": "v28.remove_variable_properties.v42" + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "liveNow": false, + "preload": false, + "refresh": "", + "schemaVersion": 42, + "templating": { + "list": [ + { + "allowCustomValue": true, + "current": { + "text": "", + "value": "" + }, + "datasource": { + "type": "prometheus" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "query_variable_with_tags", + "options": [], + "query": "label_values(up, instance)", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" }, - "spec": { - "annotations": [ + { + "allowCustomValue": true, + "current": { + "text": "", + "value": "" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "custom_variable_with_tags", + "options": [ { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana", - "version": "v0", - "datasource": { - "name": "-- Grafana --" - }, - "spec": {} - }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations \u0026 Alerts", - "builtIn": true, - "legacyOptions": { - "type": "dashboard" - } - } + "selected": false, + "text": "Option 1", + "value": "opt1" + }, + { + "selected": false, + "text": "Option 2", + "value": "opt2" } ], - "cursorSync": "Off", - "editable": true, - "elements": {}, - "layout": { - "kind": "GridLayout", - "spec": { - "items": [] - } - }, - "links": [], - "liveNow": false, - "preload": false, - "tags": [], - "timeSettings": { - "timezone": "", - "from": "now-6h", - "to": "now", - "autoRefresh": "", - "autoRefreshIntervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "hideTimepicker": false, - "fiscalYearStartMonth": 0 - }, - "title": "V28 Singlestat and Variable Properties Migration Test Dashboard", - "variables": [ - { - "kind": "QueryVariable", - "spec": { - "name": "query_variable_with_tags", - "current": { - "text": "", - "value": "" - }, - "hide": "dontHide", - "refresh": "onDashboardLoad", - "skipUrlSync": false, - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "spec": { - "__legacyStringValue": "label_values(up, instance)" - } - }, - "regex": "", - "sort": "disabled", - "options": [], - "multi": false, - "includeAll": false, - "allowCustomValue": true - } - }, - { - "kind": "CustomVariable", - "spec": { - "name": "custom_variable_with_tags", - "query": "", - "current": { - "text": "", - "value": "" - }, - "options": [ - { - "selected": false, - "text": "Option 1", - "value": "opt1" - }, - { - "selected": false, - "text": "Option 2", - "value": "opt2" - } - ], - "multi": false, - "includeAll": false, - "hide": "dontHide", - "skipUrlSync": false, - "allowCustomValue": true - } - }, - { - "kind": "TextVariable", - "spec": { - "name": "clean_variable", - "current": { - "text": "", - "value": "" - }, - "query": "", - "hide": "dontHide", - "skipUrlSync": false - } - } - ] + "query": "", + "skipUrlSync": false, + "type": "custom" }, - "status": {} - } + { + "current": { + "text": "", + "value": "" + }, + "hide": 0, + "name": "clean_variable", + "query": "", + "skipUrlSync": false, + "type": "textbox" + } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "V28 Singlestat and Variable Properties Migration Test Dashboard" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v2beta1" } } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v28.singlestat_and_variable_properties.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v28.singlestat_and_variable_properties.v0alpha1.json index 71e112bda5f..549d23f389c 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v28.singlestat_and_variable_properties.v0alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v28.singlestat_and_variable_properties.v0alpha1.json @@ -4,422 +4,222 @@ "metadata": { "name": "v28.singlestat_and_variable_properties.v42" }, - "spec": null, - "status": { - "conversion": { - "failed": true, - "error": "backend conversion not yet implemented", - "storedVersion": "v2beta1", - "source": { - "kind": "Dashboard", - "apiVersion": "dashboard.grafana.app/v2beta1", - "metadata": { - "name": "v28.singlestat_and_variable_properties.v42" + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "liveNow": false, + "panels": [ + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 }, - "spec": { - "annotations": [ + "id": 1, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "B" + } + ], + "title": "", + "type": "stat" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 2, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "", + "type": "stat" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 3, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "B" + } + ], + "title": "", + "type": "stat" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 4, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "expr": "rate(http_requests_total[5m])", + "refId": "A" + } + ], + "title": "", + "type": "timeseries" + } + ], + "preload": false, + "refresh": "", + "schemaVersion": 42, + "templating": { + "list": [ + { + "allowCustomValue": true, + "current": { + "text": "", + "value": "" + }, + "datasource": { + "type": "prometheus" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "query_variable_with_tags", + "options": [], + "query": "label_values(up, instance)", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "allowCustomValue": true, + "current": { + "text": "", + "value": "" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "custom_variable_with_tags", + "options": [ { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana", - "version": "v0", - "datasource": { - "name": "-- Grafana --" - }, - "spec": {} - }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations \u0026 Alerts", - "builtIn": true, - "legacyOptions": { - "type": "dashboard" - } - } + "selected": false, + "text": "Option 1", + "value": "opt1" + }, + { + "selected": false, + "text": "Option 2", + "value": "opt2" } ], - "cursorSync": "Off", - "editable": true, - "elements": { - "panel-1": { - "kind": "Panel", - "spec": { - "id": 1, - "title": "", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - }, - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "B", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "stat", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-2": { - "kind": "Panel", - "spec": { - "id": 2, - "title": "", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "stat", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-3": { - "kind": "Panel", - "spec": { - "id": 3, - "title": "", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - }, - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "B", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "stat", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-4": { - "kind": "Panel", - "spec": { - "id": 4, - "title": "", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": { - "expr": "rate(http_requests_total[5m])" - } - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - } - }, - "layout": { - "kind": "GridLayout", - "spec": { - "items": [ - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-1" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-2" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-3" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-4" - } - } - } - ] - } - }, - "links": [], - "liveNow": false, - "preload": false, - "tags": [], - "timeSettings": { - "timezone": "", - "from": "now-6h", - "to": "now", - "autoRefresh": "", - "autoRefreshIntervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "hideTimepicker": false, - "fiscalYearStartMonth": 0 - }, - "title": "V28 Singlestat and Variable Properties Migration Test Dashboard", - "variables": [ - { - "kind": "QueryVariable", - "spec": { - "name": "query_variable_with_tags", - "current": { - "text": "", - "value": "" - }, - "hide": "dontHide", - "refresh": "onDashboardLoad", - "skipUrlSync": false, - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "spec": { - "__legacyStringValue": "label_values(up, instance)" - } - }, - "regex": "", - "sort": "disabled", - "options": [], - "multi": false, - "includeAll": false, - "allowCustomValue": true - } - }, - { - "kind": "CustomVariable", - "spec": { - "name": "custom_variable_with_tags", - "query": "", - "current": { - "text": "", - "value": "" - }, - "options": [ - { - "selected": false, - "text": "Option 1", - "value": "opt1" - }, - { - "selected": false, - "text": "Option 2", - "value": "opt2" - } - ], - "multi": false, - "includeAll": false, - "hide": "dontHide", - "skipUrlSync": false, - "allowCustomValue": true - } - }, - { - "kind": "TextVariable", - "spec": { - "name": "clean_variable", - "current": { - "text": "", - "value": "" - }, - "query": "", - "hide": "dontHide", - "skipUrlSync": false - } - } - ] + "query": "", + "skipUrlSync": false, + "type": "custom" }, - "status": {} - } + { + "current": { + "text": "", + "value": "" + }, + "hide": 0, + "name": "clean_variable", + "query": "", + "skipUrlSync": false, + "type": "textbox" + } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "V28 Singlestat and Variable Properties Migration Test Dashboard" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v2beta1" } } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v28.singlestat_migration.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v28.singlestat_migration.v0alpha1.json index cda275b5015..f47146292f4 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v28.singlestat_migration.v0alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v28.singlestat_migration.v0alpha1.json @@ -4,658 +4,309 @@ "metadata": { "name": "v28.singlestat_migration.v42" }, - "spec": null, - "status": { - "conversion": { - "failed": true, - "error": "backend conversion not yet implemented", - "storedVersion": "v2beta1", - "source": { - "kind": "Dashboard", - "apiVersion": "dashboard.grafana.app/v2beta1", - "metadata": { - "name": "v28.singlestat_migration.v42" - }, - "spec": { - "annotations": [ - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana", - "version": "v0", - "datasource": { - "name": "-- Grafana --" - }, - "spec": {} - }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations \u0026 Alerts", - "builtIn": true, - "legacyOptions": { - "type": "dashboard" - } - } - } - ], - "cursorSync": "Off", - "editable": true, - "elements": { - "panel-1": { - "kind": "Panel", - "spec": { - "id": 1, - "title": "", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - }, - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "B", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "stat", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-2": { - "kind": "Panel", - "spec": { - "id": 2, - "title": "", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - }, - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "B", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "stat", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-3": { - "kind": "Panel", - "spec": { - "id": 3, - "title": "", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "stat", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-4": { - "kind": "Panel", - "spec": { - "id": 4, - "title": "", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - }, - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "B", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "stat", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-5": { - "kind": "Panel", - "spec": { - "id": 5, - "title": "grafana-singlestat-panel", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "PD8C576611E62080A" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": { - "maxDataPoints": 100 - } - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "stat", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-6": { - "kind": "Panel", - "spec": { - "id": 6, - "title": "singlestat (old, internal. Migrated if schema \u003c 28)", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "PD8C576611E62080A" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": { - "maxDataPoints": 100 - } - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "stat", - "version": "1.0.0", - "spec": { - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "horizontal", - "reduceOptions": { - "calcs": [ - "mean" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "fieldConfig": { - "defaults": { - "unit": "ms", - "mappings": [ - { - "type": "special", - "options": { - "match": "null", - "result": { - "text": "N/A" - } - } - } - ], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": null, - "color": "green" - }, - { - "value": 80, - "color": "red" - } - ] - } - }, - "overrides": [] - } - } - } - } - }, - "panel-7": { - "kind": "Panel", - "spec": { - "id": 7, - "title": "Status + Notes", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "PD8C576611E62080A" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "text", - "version": "1.0.0", - "spec": { - "options": { - "code": { - "language": "plaintext", - "showLineNumbers": false, - "showMiniMap": false - }, - "content": "# Singlestat \u003e\u003e Stat\n\nKnown issues:\n* limited options", - "mode": "markdown" - }, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-8": { - "kind": "Panel", - "spec": { - "id": 8, - "title": "", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": { - "expr": "rate(http_requests_total[5m])" - } - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - } + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" }, - "layout": { - "kind": "GridLayout", - "spec": { - "items": [ - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-1" - } + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "liveNow": false, + "panels": [ + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 1, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "B" + } + ], + "title": "", + "type": "stat" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 2, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "B" + } + ], + "title": "", + "type": "stat" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 3, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "", + "type": "stat" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 4, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "B" + } + ], + "title": "", + "type": "stat" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 8, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "expr": "rate(http_requests_total[5m])", + "refId": "A" + } + ], + "title": "", + "type": "timeseries" + }, + { + "gridPos": { + "h": 8, + "w": 8, + "x": 0, + "y": 43 + }, + "id": 5, + "maxDataPoints": 100, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PD8C576611E62080A" + }, + "refId": "A" + } + ], + "title": "grafana-singlestat-panel", + "type": "stat" + }, + { + "fieldConfig": { + "defaults": { + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" } }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-2" - } - } + "color": "green", + "value": null }, { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-3" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-4" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-8" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 43, - "width": 8, - "height": 8, - "element": { - "kind": "ElementReference", - "name": "panel-5" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 8, - "y": 43, - "width": 8, - "height": 8, - "element": { - "kind": "ElementReference", - "name": "panel-6" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 16, - "y": 43, - "width": 8, - "height": 8, - "element": { - "kind": "ElementReference", - "name": "panel-7" - } - } + "color": "red", + "value": 80 } ] - } - }, - "links": [], - "liveNow": false, - "preload": false, - "tags": [], - "timeSettings": { - "timezone": "", - "from": "now-6h", - "to": "now", - "autoRefresh": "", - "autoRefreshIntervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "hideTimepicker": false, - "fiscalYearStartMonth": 0 - }, - "title": "V28 Singlestat and Variable Properties Migration Test Dashboard", - "variables": [] + }, + "unit": "ms" + } }, - "status": {} + "gridPos": { + "h": 8, + "w": 8, + "x": 8, + "y": 43 + }, + "id": 6, + "maxDataPoints": 100, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "mean" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "1.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PD8C576611E62080A" + }, + "refId": "A" + } + ], + "title": "singlestat (old, internal. Migrated if schema \u003c 28)", + "type": "stat" + }, + { + "gridPos": { + "h": 8, + "w": 8, + "x": 16, + "y": 43 + }, + "id": 7, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "# Singlestat \u003e\u003e Stat\n\nKnown issues:\n* limited options", + "mode": "markdown" + }, + "pluginVersion": "1.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PD8C576611E62080A" + }, + "refId": "A" + } + ], + "title": "Status + Notes", + "type": "text" } + ], + "preload": false, + "refresh": "", + "schemaVersion": 42, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "V28 Singlestat and Variable Properties Migration Test Dashboard" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v2beta1" } } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v29.query_variables_refresh_and_options.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v29.query_variables_refresh_and_options.v0alpha1.json index 71b0544d532..46a5be539cf 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v29.query_variables_refresh_and_options.v0alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v29.query_variables_refresh_and_options.v0alpha1.json @@ -4,474 +4,364 @@ "metadata": { "name": "v29.query_variables_refresh_and_options.v42" }, - "spec": null, - "status": { - "conversion": { - "failed": true, - "error": "backend conversion not yet implemented", - "storedVersion": "v2beta1", - "source": { - "kind": "Dashboard", - "apiVersion": "dashboard.grafana.app/v2beta1", - "metadata": { - "name": "v29.query_variables_refresh_and_options.v42" + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "liveNow": false, + "panels": [ + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 }, - "spec": { - "annotations": [ + "id": 1, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "expr": "up", + "refId": "A" + } + ], + "title": "Test Panel", + "type": "timeseries" + } + ], + "preload": false, + "refresh": "", + "schemaVersion": 42, + "templating": { + "list": [ + { + "allowCustomValue": true, + "current": { + "text": "", + "value": "" + }, + "datasource": { + "type": "prometheus" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "never_refresh_with_options", + "options": [], + "query": {}, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "allowCustomValue": true, + "current": { + "text": "", + "value": "" + }, + "datasource": { + "type": "prometheus" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "never_refresh_without_options", + "options": [], + "query": {}, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "allowCustomValue": true, + "current": { + "text": "", + "value": "" + }, + "datasource": { + "type": "prometheus" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "dashboard_refresh_with_options", + "options": [], + "query": {}, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "allowCustomValue": true, + "current": { + "text": "", + "value": "" + }, + "datasource": { + "type": "prometheus" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "dashboard_refresh_without_options", + "options": [], + "query": {}, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "allowCustomValue": true, + "current": { + "text": "", + "value": "" + }, + "datasource": { + "type": "prometheus" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "timerange_refresh_with_options", + "options": [], + "query": {}, + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "allowCustomValue": true, + "current": { + "text": "", + "value": "" + }, + "datasource": { + "type": "prometheus" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "timerange_refresh_without_options", + "options": [], + "query": {}, + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "allowCustomValue": true, + "current": { + "text": "", + "value": "" + }, + "datasource": { + "type": "prometheus" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "no_refresh_with_options", + "options": [], + "query": {}, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "allowCustomValue": true, + "current": { + "text": "", + "value": "" + }, + "datasource": { + "type": "prometheus" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "no_refresh_without_options", + "options": [], + "query": {}, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "allowCustomValue": true, + "current": { + "text": "", + "value": "" + }, + "datasource": { + "type": "prometheus" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "unknown_refresh_with_options", + "options": [], + "query": {}, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "allowCustomValue": true, + "current": { + "text": "", + "value": "" + }, + "datasource": { + "type": "prometheus" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "unknown_refresh_without_options", + "options": [], + "query": {}, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "allowCustomValue": true, + "current": { + "text": "", + "value": "" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "custom_variable", + "options": [ { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana", - "version": "v0", - "datasource": { - "name": "-- Grafana --" - }, - "spec": {} - }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations \u0026 Alerts", - "builtIn": true, - "legacyOptions": { - "type": "dashboard" - } - } + "selected": false, + "text": "custom", + "value": "custom" } ], - "cursorSync": "Off", - "editable": true, - "elements": { - "panel-1": { - "kind": "Panel", - "spec": { - "id": 1, - "title": "Test Panel", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "prometheus" - }, - "spec": { - "expr": "up" - } - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - } - }, - "layout": { - "kind": "GridLayout", - "spec": { - "items": [ - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-1" - } - } - } - ] - } - }, - "links": [], - "liveNow": false, - "preload": false, - "tags": [], - "timeSettings": { - "timezone": "", - "from": "now-6h", - "to": "now", - "autoRefresh": "", - "autoRefreshIntervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "hideTimepicker": false, - "fiscalYearStartMonth": 0 - }, - "title": "V29 Query Variables Refresh and Options Migration Test Dashboard", - "variables": [ - { - "kind": "QueryVariable", - "spec": { - "name": "never_refresh_with_options", - "current": { - "text": "", - "value": "" - }, - "hide": "dontHide", - "refresh": "onDashboardLoad", - "skipUrlSync": false, - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "spec": {} - }, - "regex": "", - "sort": "disabled", - "options": [], - "multi": false, - "includeAll": false, - "allowCustomValue": true - } - }, - { - "kind": "QueryVariable", - "spec": { - "name": "never_refresh_without_options", - "current": { - "text": "", - "value": "" - }, - "hide": "dontHide", - "refresh": "onDashboardLoad", - "skipUrlSync": false, - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "spec": {} - }, - "regex": "", - "sort": "disabled", - "options": [], - "multi": false, - "includeAll": false, - "allowCustomValue": true - } - }, - { - "kind": "QueryVariable", - "spec": { - "name": "dashboard_refresh_with_options", - "current": { - "text": "", - "value": "" - }, - "hide": "dontHide", - "refresh": "onDashboardLoad", - "skipUrlSync": false, - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "spec": {} - }, - "regex": "", - "sort": "disabled", - "options": [], - "multi": false, - "includeAll": false, - "allowCustomValue": true - } - }, - { - "kind": "QueryVariable", - "spec": { - "name": "dashboard_refresh_without_options", - "current": { - "text": "", - "value": "" - }, - "hide": "dontHide", - "refresh": "onDashboardLoad", - "skipUrlSync": false, - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "spec": {} - }, - "regex": "", - "sort": "disabled", - "options": [], - "multi": false, - "includeAll": false, - "allowCustomValue": true - } - }, - { - "kind": "QueryVariable", - "spec": { - "name": "timerange_refresh_with_options", - "current": { - "text": "", - "value": "" - }, - "hide": "dontHide", - "refresh": "onTimeRangeChanged", - "skipUrlSync": false, - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "spec": {} - }, - "regex": "", - "sort": "disabled", - "options": [], - "multi": false, - "includeAll": false, - "allowCustomValue": true - } - }, - { - "kind": "QueryVariable", - "spec": { - "name": "timerange_refresh_without_options", - "current": { - "text": "", - "value": "" - }, - "hide": "dontHide", - "refresh": "onTimeRangeChanged", - "skipUrlSync": false, - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "spec": {} - }, - "regex": "", - "sort": "disabled", - "options": [], - "multi": false, - "includeAll": false, - "allowCustomValue": true - } - }, - { - "kind": "QueryVariable", - "spec": { - "name": "no_refresh_with_options", - "current": { - "text": "", - "value": "" - }, - "hide": "dontHide", - "refresh": "onDashboardLoad", - "skipUrlSync": false, - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "spec": {} - }, - "regex": "", - "sort": "disabled", - "options": [], - "multi": false, - "includeAll": false, - "allowCustomValue": true - } - }, - { - "kind": "QueryVariable", - "spec": { - "name": "no_refresh_without_options", - "current": { - "text": "", - "value": "" - }, - "hide": "dontHide", - "refresh": "onDashboardLoad", - "skipUrlSync": false, - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "spec": {} - }, - "regex": "", - "sort": "disabled", - "options": [], - "multi": false, - "includeAll": false, - "allowCustomValue": true - } - }, - { - "kind": "QueryVariable", - "spec": { - "name": "unknown_refresh_with_options", - "current": { - "text": "", - "value": "" - }, - "hide": "dontHide", - "refresh": "onDashboardLoad", - "skipUrlSync": false, - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "spec": {} - }, - "regex": "", - "sort": "disabled", - "options": [], - "multi": false, - "includeAll": false, - "allowCustomValue": true - } - }, - { - "kind": "QueryVariable", - "spec": { - "name": "unknown_refresh_without_options", - "current": { - "text": "", - "value": "" - }, - "hide": "dontHide", - "refresh": "onDashboardLoad", - "skipUrlSync": false, - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "spec": {} - }, - "regex": "", - "sort": "disabled", - "options": [], - "multi": false, - "includeAll": false, - "allowCustomValue": true - } - }, - { - "kind": "CustomVariable", - "spec": { - "name": "custom_variable", - "query": "", - "current": { - "text": "", - "value": "" - }, - "options": [ - { - "selected": false, - "text": "custom", - "value": "custom" - } - ], - "multi": false, - "includeAll": false, - "hide": "dontHide", - "skipUrlSync": false, - "allowCustomValue": true - } - }, - { - "kind": "TextVariable", - "spec": { - "name": "textbox_variable", - "current": { - "text": "", - "value": "" - }, - "query": "", - "hide": "dontHide", - "skipUrlSync": false - } - }, - { - "kind": "DatasourceVariable", - "spec": { - "name": "datasource_variable", - "pluginId": "prometheus", - "refresh": "never", - "regex": "", - "current": { - "text": "", - "value": "" - }, - "options": [], - "multi": false, - "includeAll": false, - "hide": "dontHide", - "skipUrlSync": false, - "allowCustomValue": true - } - }, - { - "kind": "IntervalVariable", - "spec": { - "name": "interval_variable", - "query": "1m", - "current": { - "text": "", - "value": "" - }, - "options": [ - { - "selected": false, - "text": "1m", - "value": "1m" - } - ], - "auto": false, - "auto_min": "", - "auto_count": 0, - "refresh": "onTimeRangeChanged", - "hide": "dontHide", - "skipUrlSync": false - } - } - ] + "query": "", + "skipUrlSync": false, + "type": "custom" }, - "status": {} - } + { + "current": { + "text": "", + "value": "" + }, + "hide": 0, + "name": "textbox_variable", + "query": "", + "skipUrlSync": false, + "type": "textbox" + }, + { + "allowCustomValue": true, + "current": { + "text": "", + "value": "" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "datasource_variable", + "options": [], + "query": "prometheus", + "refresh": 0, + "regex": "", + "skipUrlSync": false, + "type": "datasource" + }, + { + "auto": false, + "auto_count": 0, + "auto_min": "", + "current": { + "text": "", + "value": "" + }, + "hide": 0, + "name": "interval_variable", + "options": [ + { + "selected": false, + "text": "1m", + "value": "1m" + } + ], + "query": "1m", + "skipUrlSync": false, + "type": "interval" + } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "V29 Query Variables Refresh and Options Migration Test Dashboard" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v2beta1" } } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v3.no-op.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v3.no-op.v0alpha1.json index 0c6fd287427..811f9a00d89 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v3.no-op.v0alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v3.no-op.v0alpha1.json @@ -4,319 +4,145 @@ "metadata": { "name": "v3.no-op.v42" }, - "spec": null, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "liveNow": false, + "panels": [ + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 1, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "", + "type": "timeseries" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 2, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "", + "type": "timeseries" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 3, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "", + "type": "barchart" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 4, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "", + "type": "barchart" + } + ], + "preload": false, + "refresh": "", + "schemaVersion": 42, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "V3 No-Op Migration - but tests ensuring panel IDs are unique" + }, "status": { "conversion": { - "failed": true, - "error": "backend conversion not yet implemented", - "storedVersion": "v2beta1", - "source": { - "kind": "Dashboard", - "apiVersion": "dashboard.grafana.app/v2beta1", - "metadata": { - "name": "v3.no-op.v42" - }, - "spec": { - "annotations": [ - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana", - "version": "v0", - "datasource": { - "name": "-- Grafana --" - }, - "spec": {} - }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations \u0026 Alerts", - "builtIn": true, - "legacyOptions": { - "type": "dashboard" - } - } - } - ], - "cursorSync": "Off", - "editable": true, - "elements": { - "panel-1": { - "kind": "Panel", - "spec": { - "id": 1, - "title": "", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-2": { - "kind": "Panel", - "spec": { - "id": 2, - "title": "", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-3": { - "kind": "Panel", - "spec": { - "id": 3, - "title": "", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "barchart", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-4": { - "kind": "Panel", - "spec": { - "id": 4, - "title": "", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "barchart", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - } - }, - "layout": { - "kind": "GridLayout", - "spec": { - "items": [ - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-1" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-2" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-3" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-4" - } - } - } - ] - } - }, - "links": [], - "liveNow": false, - "preload": false, - "tags": [], - "timeSettings": { - "timezone": "", - "from": "now-6h", - "to": "now", - "autoRefresh": "", - "autoRefreshIntervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "hideTimepicker": false, - "fiscalYearStartMonth": 0 - }, - "title": "V3 No-Op Migration - but tests ensuring panel IDs are unique", - "variables": [] - }, - "status": {} - } + "failed": false, + "storedVersion": "v2beta1" } } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v30.value_mappings_and_tooltip_options.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v30.value_mappings_and_tooltip_options.v0alpha1.json index f4c9878142d..e6ec721a7f3 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v30.value_mappings_and_tooltip_options.v0alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v30.value_mappings_and_tooltip_options.v0alpha1.json @@ -4,721 +4,409 @@ "metadata": { "name": "v30.value_mappings_and_tooltip_options.v42" }, - "spec": null, - "status": { - "conversion": { - "failed": true, - "error": "backend conversion not yet implemented", - "storedVersion": "v2beta1", - "source": { - "kind": "Dashboard", - "apiVersion": "dashboard.grafana.app/v2beta1", - "metadata": { - "name": "v30.value_mappings_and_tooltip_options.v42" - }, - "spec": { - "annotations": [ - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana", - "version": "v0", - "datasource": { - "name": "-- Grafana --" - }, - "spec": {} - }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations \u0026 Alerts", - "builtIn": true, - "legacyOptions": { - "type": "dashboard" - } - } - } - ], - "cursorSync": "Off", - "editable": true, - "elements": { - "panel-1": { - "kind": "Panel", - "spec": { - "id": 1, - "title": "Panel with legacy value mappings and tooltip options", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": { - "tooltip": { - "mode": "multi" - } - }, - "fieldConfig": { - "defaults": { - "mappings": [ - { - "type": "value", - "options": { - "0": { - "text": "Down" - }, - "1": { - "text": "Up" - } - } - }, - { - "type": "range", - "options": { - "from": 10, - "to": 20, - "result": { - "text": "Medium" - } - } - }, - { - "type": "special", - "options": { - "match": "null", - "result": { - "text": "Null Value" - } - } - } - ], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": null, - "color": "green" - }, - { - "value": 80, - "color": "red" - } - ] - } - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "test-field" - }, - "properties": [ - { - "id": "mappings", - "value": [ - { - "options": { - "1": { - "text": "Override Up" - } - }, - "type": "value" - } - ] - } - ] - } - ] - } - } - } - } - }, - "panel-2": { - "kind": "Panel", - "spec": { - "id": 2, - "title": "XY Chart with tooltip options only", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "xychart", - "version": "", - "spec": { - "options": { - "tooltip": { - "mode": "single" - } - }, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-3": { - "kind": "Panel", - "spec": { - "id": 3, - "title": "XY Chart2 with tooltip options", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "xychart2", - "version": "", - "spec": { - "options": { - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-4": { - "kind": "Panel", - "spec": { - "id": 4, - "title": "Graph panel gets migrated to timeseries and tooltip", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": { - "tooltip": { - "mode": "single" - } - }, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-5": { - "kind": "Panel", - "spec": { - "id": 5, - "title": "Panel with complex value mappings", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "stat", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": { - "mappings": [ - { - "type": "value", - "options": { - "100": { - "text": "Critical" - } - } - }, - { - "type": "range", - "options": { - "from": 50, - "to": 99, - "result": { - "text": "Warning" - } - } - }, - { - "type": "range", - "options": { - "from": 0, - "to": 49, - "result": { - "text": "OK" - } - } - } - ] - }, - "overrides": [] - } - } - } - } - }, - "panel-7": { - "kind": "Panel", - "spec": { - "id": 7, - "title": "Nested panel with both migrations", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": { - "tooltip": { - "mode": "multi" - } - }, - "fieldConfig": { - "defaults": { - "mappings": [ - { - "type": "value", - "options": { - "0": { - "text": "Off" - }, - "1": { - "text": "On" - } - } - } - ] - }, - "overrides": [] - } - } - } - } - }, - "panel-8": { - "kind": "Panel", - "spec": { - "id": 8, - "title": "Panel with no relevant configurations", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": { - "legend": { - "displayMode": "list", - "showLegend": true - } - }, - "fieldConfig": { - "defaults": { - "unit": "bytes" - }, - "overrides": [] - } - } - } - } - }, - "panel-9": { - "kind": "Panel", - "spec": { - "id": 9, - "title": "Panel with empty mappings array - should return null", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "stat", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "empty-field" - }, - "properties": [ - { - "id": "mappings", - "value": [] - } - ] - } - ] - } - } - } - } - } + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" }, - "layout": { - "kind": "RowsLayout", - "spec": { - "rows": [ - { - "kind": "RowsLayoutRow", - "spec": { - "title": "", - "collapse": true, - "hideHeader": true, - "layout": { - "kind": "GridLayout", - "spec": { - "items": [ - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-1" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-2" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-3" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-4" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-5" - } - } - } - ] - } - } + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "liveNow": false, + "panels": [ + { + "fieldConfig": { + "defaults": { + "mappings": [ + { + "options": { + "0": { + "text": "Down" + }, + "1": { + "text": "Up" } }, - { - "kind": "RowsLayoutRow", - "spec": { - "title": "Collapsed Row with nested panels", - "collapse": true, - "layout": { - "kind": "GridLayout", - "spec": { - "items": [ - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-7" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-8" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-9" - } - } - } - ] - } - } + "type": "value" + }, + { + "options": { + "from": 10, + "result": { + "text": "Medium" + }, + "to": 20 + }, + "type": "range" + }, + { + "options": { + "match": "null", + "result": { + "text": "Null Value" } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 } ] } }, - "links": [], - "liveNow": false, - "preload": false, - "tags": [], - "timeSettings": { - "timezone": "", - "from": "now-6h", - "to": "now", - "autoRefresh": "", - "autoRefreshIntervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "hideTimepicker": false, - "fiscalYearStartMonth": 0 - }, - "title": "V30 Value Mappings and Tooltip Options Migration Test Dashboard", - "variables": [] + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "test-field" + }, + "properties": [ + { + "id": "mappings", + "value": [ + { + "options": { + "1": { + "text": "Override Up" + } + }, + "type": "value" + } + ] + } + ] + } + ] }, - "status": {} + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 1, + "options": { + "tooltip": { + "mode": "multi" + } + }, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Panel with legacy value mappings and tooltip options", + "type": "timeseries" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 2, + "options": { + "tooltip": { + "mode": "single" + } + }, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "XY Chart with tooltip options only", + "type": "xychart" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 3, + "options": { + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "XY Chart2 with tooltip options", + "type": "xychart2" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 4, + "options": { + "tooltip": { + "mode": "single" + } + }, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Graph panel gets migrated to timeseries and tooltip", + "type": "timeseries" + }, + { + "fieldConfig": { + "defaults": { + "mappings": [ + { + "options": { + "100": { + "text": "Critical" + } + }, + "type": "value" + }, + { + "options": { + "from": 50, + "result": { + "text": "Warning" + }, + "to": 99 + }, + "type": "range" + }, + { + "options": { + "from": 0, + "result": { + "text": "OK" + }, + "to": 49 + }, + "type": "range" + } + ] + } + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 5, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Panel with complex value mappings", + "type": "stat" + }, + { + "collapsed": true, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 3 + }, + "id": -1, + "panels": [ + { + "fieldConfig": { + "defaults": { + "mappings": [ + { + "options": { + "0": { + "text": "Off" + }, + "1": { + "text": "On" + } + }, + "type": "value" + } + ] + } + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 4 + }, + "id": 7, + "options": { + "tooltip": { + "mode": "multi" + } + }, + "pluginVersion": "", + "targets": [], + "title": "Nested panel with both migrations", + "type": "timeseries" + }, + { + "fieldConfig": { + "defaults": { + "unit": "bytes" + } + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 4 + }, + "id": 8, + "options": { + "legend": { + "displayMode": "list", + "showLegend": true + } + }, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Panel with no relevant configurations", + "type": "timeseries" + }, + { + "fieldConfig": { + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "empty-field" + }, + "properties": [ + { + "id": "mappings", + "value": [] + } + ] + } + ] + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 4 + }, + "id": 9, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Panel with empty mappings array - should return null", + "type": "stat" + } + ], + "title": "Collapsed Row with nested panels", + "type": "row" } + ], + "preload": false, + "refresh": "", + "schemaVersion": 42, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "V30 Value Mappings and Tooltip Options Migration Test Dashboard" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v2beta1" } } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v31.labels_to_fields_merge.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v31.labels_to_fields_merge.v0alpha1.json index faba0b459ca..b3261291c34 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v31.labels_to_fields_merge.v0alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v31.labels_to_fields_merge.v0alpha1.json @@ -4,700 +4,329 @@ "metadata": { "name": "v31.labels_to_fields_merge.v42" }, - "spec": null, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "liveNow": false, + "panels": [ + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 1, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Panel with basic labelsToFields transformation", + "transformations": [ + { + "id": "labelsToFields", + "options": {} + }, + { + "id": "merge", + "options": {} + } + ], + "type": "timeseries" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 9, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Panel with labelsToFields options preserved", + "transformations": [ + { + "id": "labelsToFields", + "options": { + "keepLabels": [ + "job", + "instance", + "region" + ], + "mode": "rows", + "valueLabel": "value" + } + }, + { + "id": "merge", + "options": {} + } + ], + "type": "timeseries" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 2, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Panel with multiple labelsToFields transformations", + "transformations": [ + { + "id": "organize", + "options": {} + }, + { + "id": "labelsToFields", + "options": {} + }, + { + "id": "merge", + "options": {} + }, + { + "id": "calculateField", + "options": {} + }, + { + "id": "labelsToFields", + "options": { + "mode": "rows" + } + }, + { + "id": "merge", + "options": {} + } + ], + "type": "timeseries" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 3, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Panel with no transformations", + "type": "timeseries" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 4, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Panel with other transformations only", + "transformations": [ + { + "id": "organize", + "options": {} + }, + { + "id": "reduce", + "options": {} + } + ], + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 3 + }, + "id": -1, + "title": "Row with nested panels", + "type": "row" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 3 + }, + "id": 6, + "options": {}, + "pluginVersion": "", + "targets": [], + "title": "Nested panel with labelsToFields", + "transformations": [ + { + "id": "labelsToFields", + "options": {} + }, + { + "id": "merge", + "options": {} + } + ], + "type": "timeseries" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 3 + }, + "id": 7, + "options": {}, + "pluginVersion": "", + "targets": [], + "title": "Nested panel without labelsToFields", + "transformations": [ + { + "id": "organize", + "options": {} + } + ], + "type": "timeseries" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 3 + }, + "id": 8, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Panel with labelsToFields and existing merge", + "transformations": [ + { + "id": "labelsToFields", + "options": {} + }, + { + "id": "merge", + "options": {} + }, + { + "id": "merge", + "options": {} + }, + { + "id": "reduce", + "options": {} + } + ], + "type": "timeseries" + } + ], + "preload": false, + "refresh": "", + "schemaVersion": 42, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "V31 LabelsToFields Merge Migration Test Dashboard" + }, "status": { "conversion": { - "failed": true, - "error": "backend conversion not yet implemented", - "storedVersion": "v2beta1", - "source": { - "kind": "Dashboard", - "apiVersion": "dashboard.grafana.app/v2beta1", - "metadata": { - "name": "v31.labels_to_fields_merge.v42" - }, - "spec": { - "annotations": [ - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana", - "version": "v0", - "datasource": { - "name": "-- Grafana --" - }, - "spec": {} - }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations \u0026 Alerts", - "builtIn": true, - "legacyOptions": { - "type": "dashboard" - } - } - } - ], - "cursorSync": "Off", - "editable": true, - "elements": { - "panel-1": { - "kind": "Panel", - "spec": { - "id": 1, - "title": "Panel with basic labelsToFields transformation", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [ - { - "kind": "labelsToFields", - "spec": { - "id": "labelsToFields", - "options": {} - } - }, - { - "kind": "merge", - "spec": { - "id": "merge", - "options": {} - } - } - ], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-2": { - "kind": "Panel", - "spec": { - "id": 2, - "title": "Panel with multiple labelsToFields transformations", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [ - { - "kind": "organize", - "spec": { - "id": "organize", - "options": {} - } - }, - { - "kind": "labelsToFields", - "spec": { - "id": "labelsToFields", - "options": {} - } - }, - { - "kind": "merge", - "spec": { - "id": "merge", - "options": {} - } - }, - { - "kind": "calculateField", - "spec": { - "id": "calculateField", - "options": {} - } - }, - { - "kind": "labelsToFields", - "spec": { - "id": "labelsToFields", - "options": { - "mode": "rows" - } - } - }, - { - "kind": "merge", - "spec": { - "id": "merge", - "options": {} - } - } - ], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-3": { - "kind": "Panel", - "spec": { - "id": 3, - "title": "Panel with no transformations", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-4": { - "kind": "Panel", - "spec": { - "id": 4, - "title": "Panel with other transformations only", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [ - { - "kind": "organize", - "spec": { - "id": "organize", - "options": {} - } - }, - { - "kind": "reduce", - "spec": { - "id": "reduce", - "options": {} - } - } - ], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-6": { - "kind": "Panel", - "spec": { - "id": 6, - "title": "Nested panel with labelsToFields", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [], - "transformations": [ - { - "kind": "labelsToFields", - "spec": { - "id": "labelsToFields", - "options": {} - } - }, - { - "kind": "merge", - "spec": { - "id": "merge", - "options": {} - } - } - ], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-7": { - "kind": "Panel", - "spec": { - "id": 7, - "title": "Nested panel without labelsToFields", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [], - "transformations": [ - { - "kind": "organize", - "spec": { - "id": "organize", - "options": {} - } - } - ], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-8": { - "kind": "Panel", - "spec": { - "id": 8, - "title": "Panel with labelsToFields and existing merge", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [ - { - "kind": "labelsToFields", - "spec": { - "id": "labelsToFields", - "options": {} - } - }, - { - "kind": "merge", - "spec": { - "id": "merge", - "options": {} - } - }, - { - "kind": "merge", - "spec": { - "id": "merge", - "options": {} - } - }, - { - "kind": "reduce", - "spec": { - "id": "reduce", - "options": {} - } - } - ], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-9": { - "kind": "Panel", - "spec": { - "id": 9, - "title": "Panel with labelsToFields options preserved", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [ - { - "kind": "labelsToFields", - "spec": { - "id": "labelsToFields", - "options": { - "keepLabels": [ - "job", - "instance", - "region" - ], - "mode": "rows", - "valueLabel": "value" - } - } - }, - { - "kind": "merge", - "spec": { - "id": "merge", - "options": {} - } - } - ], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - } - }, - "layout": { - "kind": "RowsLayout", - "spec": { - "rows": [ - { - "kind": "RowsLayoutRow", - "spec": { - "title": "", - "collapse": false, - "hideHeader": true, - "layout": { - "kind": "GridLayout", - "spec": { - "items": [ - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-1" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-9" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-2" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-3" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-4" - } - } - } - ] - } - } - } - }, - { - "kind": "RowsLayoutRow", - "spec": { - "title": "Row with nested panels", - "collapse": false, - "layout": { - "kind": "GridLayout", - "spec": { - "items": [ - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-6" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-7" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-8" - } - } - } - ] - } - } - } - } - ] - } - }, - "links": [], - "liveNow": false, - "preload": false, - "tags": [], - "timeSettings": { - "timezone": "", - "from": "now-6h", - "to": "now", - "autoRefresh": "", - "autoRefreshIntervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "hideTimepicker": false, - "fiscalYearStartMonth": 0 - }, - "title": "V31 LabelsToFields Merge Migration Test Dashboard", - "variables": [] - }, - "status": {} - } + "failed": false, + "storedVersion": "v2beta1" } } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v32.no_op_migration.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v32.no_op_migration.v0alpha1.json index 97a04da908a..bde81f8e5b9 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v32.no_op_migration.v0alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v32.no_op_migration.v0alpha1.json @@ -4,336 +4,179 @@ "metadata": { "name": "v32.no_op_migration.v42" }, - "spec": null, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + }, + { + "enable": true, + "hide": false, + "iconColor": "", + "name": "Deployments" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "liveNow": false, + "panels": [ + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 1, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Panel with transformations remains unchanged", + "transformations": [ + { + "id": "labelsToFields", + "options": { + "keepLabels": [ + "job", + "instance" + ], + "mode": "rows" + } + }, + { + "id": "merge", + "options": {} + } + ], + "type": "timeseries" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 2, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Graph panel remains unchanged", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 3 + }, + "id": -1, + "title": "Row with nested panels", + "type": "row" + }, + { + "fieldConfig": { + "defaults": { + "unit": "bytes" + } + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 3 + }, + "id": 4, + "options": {}, + "pluginVersion": "", + "targets": [], + "title": "Nested stat panel", + "type": "stat" + } + ], + "preload": false, + "refresh": "", + "schemaVersion": 42, + "templating": { + "list": [ + { + "allowCustomValue": true, + "current": { + "text": "", + "value": "" + }, + "datasource": { + "type": "prometheus" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "environment", + "options": [], + "query": {}, + "refresh": 0, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "V32 No-Op Migration Test Dashboard" + }, "status": { "conversion": { - "failed": true, - "error": "backend conversion not yet implemented", - "storedVersion": "v2beta1", - "source": { - "kind": "Dashboard", - "apiVersion": "dashboard.grafana.app/v2beta1", - "metadata": { - "name": "v32.no_op_migration.v42" - }, - "spec": { - "annotations": [ - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana", - "version": "v0", - "datasource": { - "name": "-- Grafana --" - }, - "spec": {} - }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations \u0026 Alerts", - "builtIn": true, - "legacyOptions": { - "type": "dashboard" - } - } - }, - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "", - "version": "v0", - "spec": {} - }, - "enable": true, - "hide": false, - "iconColor": "", - "name": "Deployments" - } - } - ], - "cursorSync": "Off", - "editable": true, - "elements": { - "panel-1": { - "kind": "Panel", - "spec": { - "id": 1, - "title": "Panel with transformations remains unchanged", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [ - { - "kind": "labelsToFields", - "spec": { - "id": "labelsToFields", - "options": { - "keepLabels": [ - "job", - "instance" - ], - "mode": "rows" - } - } - }, - { - "kind": "merge", - "spec": { - "id": "merge", - "options": {} - } - } - ], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-2": { - "kind": "Panel", - "spec": { - "id": 2, - "title": "Graph panel remains unchanged", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-4": { - "kind": "Panel", - "spec": { - "id": 4, - "title": "Nested stat panel", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "stat", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": { - "unit": "bytes" - }, - "overrides": [] - } - } - } - } - } - }, - "layout": { - "kind": "RowsLayout", - "spec": { - "rows": [ - { - "kind": "RowsLayoutRow", - "spec": { - "title": "", - "collapse": false, - "hideHeader": true, - "layout": { - "kind": "GridLayout", - "spec": { - "items": [ - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-1" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-2" - } - } - } - ] - } - } - } - }, - { - "kind": "RowsLayoutRow", - "spec": { - "title": "Row with nested panels", - "collapse": false, - "layout": { - "kind": "GridLayout", - "spec": { - "items": [ - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-4" - } - } - } - ] - } - } - } - } - ] - } - }, - "links": [], - "liveNow": false, - "preload": false, - "tags": [], - "timeSettings": { - "timezone": "", - "from": "now-6h", - "to": "now", - "autoRefresh": "", - "autoRefreshIntervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "hideTimepicker": false, - "fiscalYearStartMonth": 0 - }, - "title": "V32 No-Op Migration Test Dashboard", - "variables": [ - { - "kind": "QueryVariable", - "spec": { - "name": "environment", - "current": { - "text": "", - "value": "" - }, - "hide": "dontHide", - "refresh": "never", - "skipUrlSync": false, - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "spec": {} - }, - "regex": "", - "sort": "disabled", - "options": [], - "multi": false, - "includeAll": false, - "allowCustomValue": true - } - } - ] - }, - "status": {} - } + "failed": false, + "storedVersion": "v2beta1" } } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v33.panel_ds_name_to_ref.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v33.panel_ds_name_to_ref.v0alpha1.json index 06f420e30b9..a860cf2dd06 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v33.panel_ds_name_to_ref.v0alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v33.panel_ds_name_to_ref.v0alpha1.json @@ -4,762 +4,341 @@ "metadata": { "name": "v33.panel_ds_name_to_ref.v42" }, - "spec": null, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "liveNow": false, + "panels": [ + { + "description": "Tests v33 migration behavior when panel datasource is explicitly null. Should remain null after migration (returnDefaultAsNull: true).", + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 1, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "non-default-test-ds-uid" + }, + "description": "Target with UID reference should migrate to full object", + "refId": "A" + } + ], + "title": "Panel Datasource: null → should stay null", + "type": "stat" + }, + { + "description": "Tests v33 migration behavior when panel datasource is already a proper object reference. Should remain unchanged.", + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 2, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "elasticsearch", + "uid": "existing-target-uid" + }, + "description": "Target with existing object should remain unchanged", + "refId": "A" + } + ], + "title": "Panel Datasource: existing object → should stay unchanged", + "type": "stat" + }, + { + "description": "Tests v33 migration when panel datasource is a string name. Should convert to proper object with uid, type, apiVersion.", + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 3, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "non-default-test-ds-uid" + }, + "refId": "A" + } + ], + "title": "Panel Datasource: string name → should migrate to object", + "type": "table" + }, + { + "description": "Tests v33 migration when panel has datasource string but empty targets array. Panel datasource should still migrate.", + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 4, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Panel Datasource: string name with empty targets → should migrate", + "type": "table" + }, + { + "datasource": { + "type": "mixed", + "uid": "-- Mixed --" + }, + "description": "Tests v33 target migration with various edge cases: null target (unchanged), valid string (migrated), non-existing string (preserved), missing datasource field (unchanged).", + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 5, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "non-default-test-ds-uid" + }, + "description": "Null target datasource should remain null", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "description": "Valid string should migrate to object", + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "non-existing-ds" + }, + "description": "Non-existing datasource should be preserved as-is (migration returns nil)", + "refId": "C" + }, + { + "datasource": { + "type": "loki", + "uid": "non-default-test-ds-uid" + }, + "description": "Target without datasource field should remain unchanged", + "refId": "D" + } + ], + "title": "Target Datasources: mixed null/string/non-existing scenarios", + "type": "timeseries" + }, + { + "datasource": { + "type": "mixed", + "uid": "-- Mixed --" + }, + "description": "Tests v33 migration when panel datasource is null but targets have mixed reference types (object, string). Panel should stay null, targets should migrate appropriately.", + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 6, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "existing-ref" + }, + "description": "Existing object target should remain unchanged", + "refId": "A" + }, + { + "datasource": { + "type": "loki", + "uid": "non-default-test-ds-uid" + }, + "description": "String target should migrate to object", + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "description": "Default datasource string should migrate to object", + "refId": "C" + } + ], + "title": "Panel: null datasource with mixed target types", + "type": "timeseries" + }, + { + "description": "Tests v33 migration behavior with empty string datasource. Should migrate to empty object {} based on MigrateDatasourceNameToRef logic.", + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 7, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "description": "Empty string target should also migrate to empty object {}", + "refId": "A" + } + ], + "title": "Empty string datasource → should return empty object {}", + "type": "stat" + }, + { + "datasource": { + "type": "mixed", + "uid": "-- Mixed --" + }, + "description": "Tests v33 migration with completely unknown datasource names. Since migration returns nil for unknown datasources, they should be preserved unchanged.", + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 8, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "also-missing-ds" + }, + "description": "Unknown target datasource should remain unchanged (migration returns nil)", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "completely-missing-ds" + }, + "description": "Empty string target should migrate to {}", + "refId": "B" + } + ], + "title": "Non-existing datasources → should be preserved as-is", + "type": "table" + }, + { + "collapsed": true, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 3 + }, + "id": -1, + "panels": [ + { + "description": "Nested panel with string datasource should migrate to proper object reference, proving row panel recursion works.", + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 4 + }, + "id": 10, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "description": "Nested target should also migrate from string to object", + "refId": "A" + } + ], + "title": "Nested Panel: string datasource → should migrate to object", + "type": "timeseries" + } + ], + "title": "Row Panel: nested panels should also migrate", + "type": "row" + } + ], + "preload": false, + "refresh": "", + "schemaVersion": 42, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "V33 Panel Datasource Name to Ref Test" + }, "status": { "conversion": { - "failed": true, - "error": "backend conversion not yet implemented", - "storedVersion": "v2beta1", - "source": { - "kind": "Dashboard", - "apiVersion": "dashboard.grafana.app/v2beta1", - "metadata": { - "name": "v33.panel_ds_name_to_ref.v42" - }, - "spec": { - "annotations": [ - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana", - "version": "v0", - "datasource": { - "name": "-- Grafana --" - }, - "spec": {} - }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations \u0026 Alerts", - "builtIn": true, - "legacyOptions": { - "type": "dashboard" - } - } - } - ], - "cursorSync": "Off", - "editable": true, - "elements": { - "panel-1": { - "kind": "Panel", - "spec": { - "id": 1, - "title": "Panel Datasource: null → should stay null", - "description": "Tests v33 migration behavior when panel datasource is explicitly null. Should remain null after migration (returnDefaultAsNull: true).", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "loki", - "version": "v0", - "datasource": { - "name": "non-default-test-ds-uid" - }, - "spec": { - "description": "Target with UID reference should migrate to full object" - } - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "stat", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-10": { - "kind": "Panel", - "spec": { - "id": 10, - "title": "Nested Panel: string datasource → should migrate to object", - "description": "Nested panel with string datasource should migrate to proper object reference, proving row panel recursion works.", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": { - "description": "Nested target should also migrate from string to object" - } - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-2": { - "kind": "Panel", - "spec": { - "id": 2, - "title": "Panel Datasource: existing object → should stay unchanged", - "description": "Tests v33 migration behavior when panel datasource is already a proper object reference. Should remain unchanged.", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "elasticsearch", - "version": "v0", - "datasource": { - "name": "existing-target-uid" - }, - "spec": { - "description": "Target with existing object should remain unchanged" - } - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "stat", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-3": { - "kind": "Panel", - "spec": { - "id": 3, - "title": "Panel Datasource: string name → should migrate to object", - "description": "Tests v33 migration when panel datasource is a string name. Should convert to proper object with uid, type, apiVersion.", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "loki", - "version": "v0", - "datasource": { - "name": "non-default-test-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "table", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-4": { - "kind": "Panel", - "spec": { - "id": 4, - "title": "Panel Datasource: string name with empty targets → should migrate", - "description": "Tests v33 migration when panel has datasource string but empty targets array. Panel datasource should still migrate.", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "table", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-5": { - "kind": "Panel", - "spec": { - "id": 5, - "title": "Target Datasources: mixed null/string/non-existing scenarios", - "description": "Tests v33 target migration with various edge cases: null target (unchanged), valid string (migrated), non-existing string (preserved), missing datasource field (unchanged).", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "loki", - "version": "v0", - "datasource": { - "name": "non-default-test-ds-uid" - }, - "spec": { - "description": "Null target datasource should remain null" - } - }, - "refId": "A", - "hidden": false - } - }, - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": { - "description": "Valid string should migrate to object" - } - }, - "refId": "B", - "hidden": false - } - }, - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "non-existing-ds" - }, - "spec": { - "description": "Non-existing datasource should be preserved as-is (migration returns nil)" - } - }, - "refId": "C", - "hidden": false - } - }, - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "loki", - "version": "v0", - "datasource": { - "name": "non-default-test-ds-uid" - }, - "spec": { - "description": "Target without datasource field should remain unchanged" - } - }, - "refId": "D", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-6": { - "kind": "Panel", - "spec": { - "id": 6, - "title": "Panel: null datasource with mixed target types", - "description": "Tests v33 migration when panel datasource is null but targets have mixed reference types (object, string). Panel should stay null, targets should migrate appropriately.", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "existing-ref" - }, - "spec": { - "description": "Existing object target should remain unchanged" - } - }, - "refId": "A", - "hidden": false - } - }, - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "loki", - "version": "v0", - "datasource": { - "name": "non-default-test-ds-uid" - }, - "spec": { - "description": "String target should migrate to object" - } - }, - "refId": "B", - "hidden": false - } - }, - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": { - "description": "Default datasource string should migrate to object" - } - }, - "refId": "C", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-7": { - "kind": "Panel", - "spec": { - "id": 7, - "title": "Empty string datasource → should return empty object {}", - "description": "Tests v33 migration behavior with empty string datasource. Should migrate to empty object {} based on MigrateDatasourceNameToRef logic.", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "", - "version": "v0", - "spec": { - "description": "Empty string target should also migrate to empty object {}" - } - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "stat", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-8": { - "kind": "Panel", - "spec": { - "id": 8, - "title": "Non-existing datasources → should be preserved as-is", - "description": "Tests v33 migration with completely unknown datasource names. Since migration returns nil for unknown datasources, they should be preserved unchanged.", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "also-missing-ds" - }, - "spec": { - "description": "Unknown target datasource should remain unchanged (migration returns nil)" - } - }, - "refId": "A", - "hidden": false - } - }, - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "completely-missing-ds" - }, - "spec": { - "description": "Empty string target should migrate to {}" - } - }, - "refId": "B", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "table", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - } - }, - "layout": { - "kind": "RowsLayout", - "spec": { - "rows": [ - { - "kind": "RowsLayoutRow", - "spec": { - "title": "", - "collapse": true, - "hideHeader": true, - "layout": { - "kind": "GridLayout", - "spec": { - "items": [ - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-1" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-2" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-3" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-4" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-5" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-6" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-7" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-8" - } - } - } - ] - } - } - } - }, - { - "kind": "RowsLayoutRow", - "spec": { - "title": "Row Panel: nested panels should also migrate", - "collapse": true, - "layout": { - "kind": "GridLayout", - "spec": { - "items": [ - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-10" - } - } - } - ] - } - } - } - } - ] - } - }, - "links": [], - "liveNow": false, - "preload": false, - "tags": [], - "timeSettings": { - "timezone": "", - "from": "now-6h", - "to": "now", - "autoRefresh": "", - "autoRefreshIntervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "hideTimepicker": false, - "fiscalYearStartMonth": 0 - }, - "title": "V33 Panel Datasource Name to Ref Test", - "variables": [] - }, - "status": {} - } + "failed": false, + "storedVersion": "v2beta1" } } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v34.multiple_stats_cloudwatch.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v34.multiple_stats_cloudwatch.v0alpha1.json index 12b14926782..af5a9eb4fb2 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v34.multiple_stats_cloudwatch.v0alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v34.multiple_stats_cloudwatch.v0alpha1.json @@ -4,1980 +4,1063 @@ "metadata": { "name": "v34.multiple_stats_cloudwatch.v42" }, - "spec": null, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + }, + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-123456" + }, + "enable": true, + "hide": false, + "iconColor": "red", + "name": "CloudWatch Annotation Single Statistic", + "namespace": "AWS/EC2", + "prefixMatching": false, + "region": "us-east-1", + "statistic": "Average" + }, + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-789012" + }, + "enable": true, + "hide": false, + "iconColor": "blue", + "name": "CloudWatch Annotation Multiple Statistics - Maximum", + "namespace": "AWS/RDS", + "prefixMatching": false, + "region": "us-west-2", + "statistic": "Maximum" + }, + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "LoadBalancer": "my-lb" + }, + "enable": true, + "hide": false, + "iconColor": "green", + "name": "CloudWatch Annotation Empty Statistics", + "namespace": "AWS/ApplicationELB", + "prefixMatching": false, + "region": "us-west-1" + }, + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "TableName": "my-table" + }, + "enable": true, + "hide": false, + "iconColor": "yellow", + "name": "CloudWatch Annotation Invalid Statistics - InvalidStat", + "namespace": "AWS/DynamoDB", + "prefixMatching": false, + "region": "us-east-1", + "statistic": "InvalidStat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-null-annotation" + }, + "enable": true, + "hide": false, + "iconColor": "orange", + "name": "CloudWatch Annotation with Null in Statistics - null", + "namespace": "AWS/EC2", + "prefixMatching": false, + "region": "us-east-1" + }, + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-invalid-annotation" + }, + "enable": true, + "hide": false, + "iconColor": "pink", + "name": "CloudWatch Annotation Only Invalid Statistics - 123", + "namespace": "AWS/EC2", + "prefixMatching": false, + "region": "us-east-1", + "statistic": 123 + }, + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "enable": true, + "hide": false, + "iconColor": "purple", + "name": "Non-CloudWatch Annotation" + }, + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-789012" + }, + "enable": true, + "hide": false, + "iconColor": "blue", + "name": "CloudWatch Annotation Multiple Statistics - Minimum", + "namespace": "AWS/RDS", + "prefixMatching": false, + "region": "us-west-2", + "statistic": "Minimum" + }, + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-789012" + }, + "enable": true, + "hide": false, + "iconColor": "blue", + "name": "CloudWatch Annotation Multiple Statistics - Sum", + "namespace": "AWS/RDS", + "prefixMatching": false, + "region": "us-west-2", + "statistic": "Sum" + }, + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "TableName": "my-table" + }, + "enable": true, + "hide": false, + "iconColor": "yellow", + "name": "CloudWatch Annotation Invalid Statistics - Sum", + "namespace": "AWS/DynamoDB", + "prefixMatching": false, + "region": "us-east-1", + "statistic": "Sum" + }, + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "TableName": "my-table" + }, + "enable": true, + "hide": false, + "iconColor": "yellow", + "name": "CloudWatch Annotation Invalid Statistics - null", + "namespace": "AWS/DynamoDB", + "prefixMatching": false, + "region": "us-east-1" + }, + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "TableName": "my-table" + }, + "enable": true, + "hide": false, + "iconColor": "yellow", + "name": "CloudWatch Annotation Invalid Statistics - Average", + "namespace": "AWS/DynamoDB", + "prefixMatching": false, + "region": "us-east-1", + "statistic": "Average" + }, + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-null-annotation" + }, + "enable": true, + "hide": false, + "iconColor": "orange", + "name": "CloudWatch Annotation with Null in Statistics - Average", + "namespace": "AWS/EC2", + "prefixMatching": false, + "region": "us-east-1", + "statistic": "Average" + }, + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-null-annotation" + }, + "enable": true, + "hide": false, + "iconColor": "orange", + "name": "CloudWatch Annotation with Null in Statistics - ", + "namespace": "AWS/EC2", + "prefixMatching": false, + "region": "us-east-1", + "statistic": "" + }, + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-invalid-annotation" + }, + "enable": true, + "hide": false, + "iconColor": "pink", + "name": "CloudWatch Annotation Only Invalid Statistics - true", + "namespace": "AWS/EC2", + "prefixMatching": false, + "region": "us-east-1", + "statistic": true + }, + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-invalid-annotation" + }, + "enable": true, + "hide": false, + "iconColor": "pink", + "name": "CloudWatch Annotation Only Invalid Statistics - [object Object]", + "namespace": "AWS/EC2", + "prefixMatching": false, + "region": "us-east-1", + "statistic": {} + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "liveNow": false, + "panels": [ + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 1, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-123456" + }, + "metricEditorMode": 0, + "metricName": "CPUUtilization", + "metricQueryType": 0, + "namespace": "AWS/EC2", + "period": "300", + "refId": "A", + "region": "us-east-1", + "statistic": "Average" + }, + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-123456" + }, + "metricEditorMode": 0, + "metricName": "CPUUtilization", + "metricQueryType": 0, + "namespace": "AWS/EC2", + "period": "300", + "refId": "B", + "region": "us-east-1", + "statistic": "Maximum" + }, + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-123456" + }, + "metricEditorMode": 0, + "metricName": "CPUUtilization", + "metricQueryType": 0, + "namespace": "AWS/EC2", + "period": "300", + "refId": "C", + "region": "us-east-1", + "statistic": "Minimum" + } + ], + "title": "CloudWatch Single Query Multiple Statistics", + "type": "timeseries" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 2, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "LoadBalancer": "my-load-balancer" + }, + "metricEditorMode": 0, + "metricName": "RequestCount", + "metricQueryType": 0, + "namespace": "AWS/ApplicationELB", + "refId": "A", + "region": "us-west-2", + "statistic": "Sum" + } + ], + "title": "CloudWatch Single Query Single Statistic", + "type": "timeseries" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 3, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "DBInstanceIdentifier": "my-db" + }, + "metricEditorMode": 0, + "metricName": "DatabaseConnections", + "metricQueryType": 0, + "namespace": "AWS/RDS", + "refId": "A", + "region": "us-east-1", + "statistic": "Maximum" + } + ], + "title": "CloudWatch Query No Statistics Array", + "type": "timeseries" + }, + { + "datasource": { + "type": "mixed", + "uid": "-- Mixed --" + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 4, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "QueueName": "my-queue" + }, + "metricEditorMode": 0, + "metricName": "ApproximateNumberOfMessages", + "metricQueryType": 0, + "namespace": "AWS/SQS", + "refId": "A", + "region": "us-east-1", + "statistic": "Average" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "expr": "up", + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "TopicName": "my-topic" + }, + "metricEditorMode": 0, + "metricName": "NumberOfMessagesPublished", + "metricQueryType": 0, + "namespace": "AWS/SNS", + "refId": "C", + "region": "us-west-1", + "statistic": "Sum" + }, + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "QueueName": "my-queue" + }, + "metricEditorMode": 0, + "metricName": "ApproximateNumberOfMessages", + "metricQueryType": 0, + "namespace": "AWS/SQS", + "refId": "D", + "region": "us-east-1", + "statistic": "Maximum" + } + ], + "title": "Mixed CloudWatch and Non-CloudWatch Queries", + "type": "timeseries" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 5, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "BucketName": "my-bucket" + }, + "metricEditorMode": 0, + "metricName": "BucketSizeBytes", + "metricQueryType": 0, + "namespace": "AWS/S3", + "refId": "A", + "region": "us-east-1" + } + ], + "title": "CloudWatch Query Empty Statistics", + "type": "timeseries" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 6, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "FunctionName": "my-function" + }, + "metricEditorMode": 0, + "metricName": "Duration", + "metricQueryType": 0, + "namespace": "AWS/Lambda", + "refId": "A", + "region": "us-west-2", + "statistic": "InvalidStat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "FunctionName": "my-function" + }, + "metricEditorMode": 0, + "metricName": "Duration", + "metricQueryType": 0, + "namespace": "AWS/Lambda", + "refId": "B", + "region": "us-west-2", + "statistic": "Average" + }, + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "FunctionName": "my-function" + }, + "metricEditorMode": 0, + "metricName": "Duration", + "metricQueryType": 0, + "namespace": "AWS/Lambda", + "refId": "C", + "region": "us-west-2" + }, + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "FunctionName": "my-function" + }, + "metricEditorMode": 0, + "metricName": "Duration", + "metricQueryType": 0, + "namespace": "AWS/Lambda", + "refId": "D", + "region": "us-west-2", + "statistic": "Maximum" + }, + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "FunctionName": "my-function" + }, + "metricEditorMode": 0, + "metricName": "Duration", + "metricQueryType": 0, + "namespace": "AWS/Lambda", + "refId": "E", + "region": "us-west-2", + "statistic": "" + } + ], + "title": "CloudWatch Query Invalid Statistics", + "type": "timeseries" + }, + { + "collapsed": true, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 3 + }, + "id": -1, + "panels": [ + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 4 + }, + "id": 8, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "StreamName": "my-stream" + }, + "metricEditorMode": 0, + "metricName": "IncomingRecords", + "metricQueryType": 0, + "namespace": "AWS/Kinesis", + "refId": "A", + "region": "us-east-1", + "statistic": "Sum" + }, + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "StreamName": "my-stream" + }, + "metricEditorMode": 0, + "metricName": "IncomingRecords", + "metricQueryType": 0, + "namespace": "AWS/Kinesis", + "refId": "B", + "region": "us-east-1", + "statistic": "Average" + }, + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "StreamName": "my-stream" + }, + "metricEditorMode": 0, + "metricName": "IncomingRecords", + "metricQueryType": 0, + "namespace": "AWS/Kinesis", + "refId": "C", + "region": "us-east-1", + "statistic": "Maximum" + } + ], + "title": "Nested CloudWatch Query Multiple Statistics", + "type": "timeseries" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 4 + }, + "id": 9, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "ClusterName": "my-cluster" + }, + "metricEditorMode": 1, + "metricName": "CPUUtilization", + "metricQueryType": 1, + "namespace": "AWS/ECS", + "period": "300", + "refId": "A", + "region": "us-east-1", + "statistic": "Average" + }, + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "ClusterName": "my-cluster" + }, + "metricEditorMode": 1, + "metricName": "CPUUtilization", + "metricQueryType": 1, + "namespace": "AWS/ECS", + "period": "300", + "refId": "B", + "region": "us-east-1", + "statistic": "Maximum" + } + ], + "title": "CloudWatch Query with Existing Editor Mode", + "type": "timeseries" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 4 + }, + "id": 10, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-missing-fields" + }, + "metricEditorMode": 0, + "metricName": "CPUUtilization", + "metricQueryType": 0, + "namespace": "AWS/EC2", + "refId": "A", + "region": "us-east-1", + "statistic": "Average" + } + ], + "title": "CloudWatch Query Missing Editor Fields", + "type": "timeseries" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 4 + }, + "id": 11, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-with-expression" + }, + "expression": "SEARCH('{AWS/EC2,InstanceId} MetricName=\"CPUUtilization\"', 'Average', 300)", + "metricEditorMode": 1, + "metricName": "CPUUtilization", + "metricQueryType": 0, + "namespace": "AWS/EC2", + "refId": "A", + "region": "us-east-1", + "statistic": "Average" + }, + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-with-expression" + }, + "expression": "SEARCH('{AWS/EC2,InstanceId} MetricName=\"CPUUtilization\"', 'Average', 300)", + "metricEditorMode": 1, + "metricName": "CPUUtilization", + "metricQueryType": 0, + "namespace": "AWS/EC2", + "refId": "B", + "region": "us-east-1", + "statistic": "Maximum" + } + ], + "title": "CloudWatch Query with Expression (Code Mode)", + "type": "timeseries" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 4 + }, + "id": 12, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-insights" + }, + "metricEditorMode": 1, + "metricName": "CPUUtilization", + "metricQueryType": 1, + "namespace": "AWS/EC2", + "refId": "A", + "region": "us-east-1", + "statistic": "Average" + } + ], + "title": "CloudWatch Insights Query Missing Editor Mode", + "type": "timeseries" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 4 + }, + "id": 13, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-null-stats" + }, + "metricEditorMode": 0, + "metricName": "CPUUtilization", + "metricQueryType": 0, + "namespace": "AWS/EC2", + "refId": "A", + "region": "us-east-1" + }, + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-null-stats" + }, + "metricEditorMode": 0, + "metricName": "CPUUtilization", + "metricQueryType": 0, + "namespace": "AWS/EC2", + "refId": "B", + "region": "us-east-1", + "statistic": "Average" + }, + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-null-stats" + }, + "metricEditorMode": 0, + "metricName": "CPUUtilization", + "metricQueryType": 0, + "namespace": "AWS/EC2", + "refId": "C", + "region": "us-east-1", + "statistic": "" + }, + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-null-stats" + }, + "metricEditorMode": 0, + "metricName": "CPUUtilization", + "metricQueryType": 0, + "namespace": "AWS/EC2", + "refId": "D", + "region": "us-east-1", + "statistic": "Maximum" + } + ], + "title": "CloudWatch Query with Null Statistics", + "type": "timeseries" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 4 + }, + "id": 14, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-invalid-only" + }, + "metricEditorMode": 0, + "metricName": "CPUUtilization", + "metricQueryType": 0, + "namespace": "AWS/EC2", + "refId": "A", + "region": "us-east-1", + "statistic": 123 + }, + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-invalid-only" + }, + "metricEditorMode": 0, + "metricName": "CPUUtilization", + "metricQueryType": 0, + "namespace": "AWS/EC2", + "refId": "B", + "region": "us-east-1", + "statistic": true + }, + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-invalid-only" + }, + "metricEditorMode": 0, + "metricName": "CPUUtilization", + "metricQueryType": 0, + "namespace": "AWS/EC2", + "refId": "C", + "region": "us-east-1", + "statistic": {} + }, + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-invalid-only" + }, + "metricEditorMode": 0, + "metricName": "CPUUtilization", + "metricQueryType": 0, + "namespace": "AWS/EC2", + "refId": "D", + "region": "us-east-1", + "statistic": [] + } + ], + "title": "CloudWatch Query with Only Invalid Statistics", + "type": "timeseries" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 4 + }, + "id": 15, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "expr": "cpu_usage", + "refId": "A" + } + ], + "title": "Non-CloudWatch Panel", + "type": "timeseries" + } + ], + "title": "Collapsed Row with CloudWatch", + "type": "row" + } + ], + "preload": false, + "refresh": "", + "schemaVersion": 42, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "CloudWatch Multiple Statistics Test Dashboard" + }, "status": { "conversion": { - "failed": true, - "error": "backend conversion not yet implemented", - "storedVersion": "v2beta1", - "source": { - "kind": "Dashboard", - "apiVersion": "dashboard.grafana.app/v2beta1", - "metadata": { - "name": "v34.multiple_stats_cloudwatch.v42" - }, - "spec": { - "annotations": [ - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana", - "version": "v0", - "datasource": { - "name": "-- Grafana --" - }, - "spec": {} - }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations \u0026 Alerts", - "builtIn": true, - "legacyOptions": { - "type": "dashboard" - } - } - }, - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "enable": true, - "hide": false, - "iconColor": "red", - "name": "CloudWatch Annotation Single Statistic", - "legacyOptions": { - "dimensions": { - "InstanceId": "i-123456" - }, - "namespace": "AWS/EC2", - "prefixMatching": false, - "region": "us-east-1", - "statistic": "Average" - } - } - }, - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "enable": true, - "hide": false, - "iconColor": "blue", - "name": "CloudWatch Annotation Multiple Statistics - Maximum", - "legacyOptions": { - "dimensions": { - "InstanceId": "i-789012" - }, - "namespace": "AWS/RDS", - "prefixMatching": false, - "region": "us-west-2", - "statistic": "Maximum" - } - } - }, - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "enable": true, - "hide": false, - "iconColor": "green", - "name": "CloudWatch Annotation Empty Statistics", - "legacyOptions": { - "dimensions": { - "LoadBalancer": "my-lb" - }, - "namespace": "AWS/ApplicationELB", - "prefixMatching": false, - "region": "us-west-1" - } - } - }, - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "enable": true, - "hide": false, - "iconColor": "yellow", - "name": "CloudWatch Annotation Invalid Statistics - InvalidStat", - "legacyOptions": { - "dimensions": { - "TableName": "my-table" - }, - "namespace": "AWS/DynamoDB", - "prefixMatching": false, - "region": "us-east-1", - "statistic": "InvalidStat" - } - } - }, - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "enable": true, - "hide": false, - "iconColor": "orange", - "name": "CloudWatch Annotation with Null in Statistics - null", - "legacyOptions": { - "dimensions": { - "InstanceId": "i-null-annotation" - }, - "namespace": "AWS/EC2", - "prefixMatching": false, - "region": "us-east-1" - } - } - }, - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "enable": true, - "hide": false, - "iconColor": "pink", - "name": "CloudWatch Annotation Only Invalid Statistics - 123", - "legacyOptions": { - "dimensions": { - "InstanceId": "i-invalid-annotation" - }, - "namespace": "AWS/EC2", - "prefixMatching": false, - "region": "us-east-1", - "statistic": 123 - } - } - }, - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "enable": true, - "hide": false, - "iconColor": "purple", - "name": "Non-CloudWatch Annotation" - } - }, - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "enable": true, - "hide": false, - "iconColor": "blue", - "name": "CloudWatch Annotation Multiple Statistics - Minimum", - "legacyOptions": { - "dimensions": { - "InstanceId": "i-789012" - }, - "namespace": "AWS/RDS", - "prefixMatching": false, - "region": "us-west-2", - "statistic": "Minimum" - } - } - }, - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "enable": true, - "hide": false, - "iconColor": "blue", - "name": "CloudWatch Annotation Multiple Statistics - Sum", - "legacyOptions": { - "dimensions": { - "InstanceId": "i-789012" - }, - "namespace": "AWS/RDS", - "prefixMatching": false, - "region": "us-west-2", - "statistic": "Sum" - } - } - }, - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "enable": true, - "hide": false, - "iconColor": "yellow", - "name": "CloudWatch Annotation Invalid Statistics - Sum", - "legacyOptions": { - "dimensions": { - "TableName": "my-table" - }, - "namespace": "AWS/DynamoDB", - "prefixMatching": false, - "region": "us-east-1", - "statistic": "Sum" - } - } - }, - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "enable": true, - "hide": false, - "iconColor": "yellow", - "name": "CloudWatch Annotation Invalid Statistics - null", - "legacyOptions": { - "dimensions": { - "TableName": "my-table" - }, - "namespace": "AWS/DynamoDB", - "prefixMatching": false, - "region": "us-east-1" - } - } - }, - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "enable": true, - "hide": false, - "iconColor": "yellow", - "name": "CloudWatch Annotation Invalid Statistics - Average", - "legacyOptions": { - "dimensions": { - "TableName": "my-table" - }, - "namespace": "AWS/DynamoDB", - "prefixMatching": false, - "region": "us-east-1", - "statistic": "Average" - } - } - }, - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "enable": true, - "hide": false, - "iconColor": "orange", - "name": "CloudWatch Annotation with Null in Statistics - Average", - "legacyOptions": { - "dimensions": { - "InstanceId": "i-null-annotation" - }, - "namespace": "AWS/EC2", - "prefixMatching": false, - "region": "us-east-1", - "statistic": "Average" - } - } - }, - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "enable": true, - "hide": false, - "iconColor": "orange", - "name": "CloudWatch Annotation with Null in Statistics - ", - "legacyOptions": { - "dimensions": { - "InstanceId": "i-null-annotation" - }, - "namespace": "AWS/EC2", - "prefixMatching": false, - "region": "us-east-1", - "statistic": "" - } - } - }, - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "enable": true, - "hide": false, - "iconColor": "pink", - "name": "CloudWatch Annotation Only Invalid Statistics - true", - "legacyOptions": { - "dimensions": { - "InstanceId": "i-invalid-annotation" - }, - "namespace": "AWS/EC2", - "prefixMatching": false, - "region": "us-east-1", - "statistic": true - } - } - }, - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "enable": true, - "hide": false, - "iconColor": "pink", - "name": "CloudWatch Annotation Only Invalid Statistics - [object Object]", - "legacyOptions": { - "dimensions": { - "InstanceId": "i-invalid-annotation" - }, - "namespace": "AWS/EC2", - "prefixMatching": false, - "region": "us-east-1", - "statistic": {} - } - } - } - ], - "cursorSync": "Off", - "editable": true, - "elements": { - "panel-1": { - "kind": "Panel", - "spec": { - "id": 1, - "title": "CloudWatch Single Query Multiple Statistics", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": { - "dimensions": { - "InstanceId": "i-123456" - }, - "metricEditorMode": 0, - "metricName": "CPUUtilization", - "metricQueryType": 0, - "namespace": "AWS/EC2", - "period": "300", - "region": "us-east-1", - "statistic": "Average" - } - }, - "refId": "A", - "hidden": false - } - }, - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": { - "dimensions": { - "InstanceId": "i-123456" - }, - "metricEditorMode": 0, - "metricName": "CPUUtilization", - "metricQueryType": 0, - "namespace": "AWS/EC2", - "period": "300", - "region": "us-east-1", - "statistic": "Maximum" - } - }, - "refId": "B", - "hidden": false - } - }, - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": { - "dimensions": { - "InstanceId": "i-123456" - }, - "metricEditorMode": 0, - "metricName": "CPUUtilization", - "metricQueryType": 0, - "namespace": "AWS/EC2", - "period": "300", - "region": "us-east-1", - "statistic": "Minimum" - } - }, - "refId": "C", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-10": { - "kind": "Panel", - "spec": { - "id": 10, - "title": "CloudWatch Query Missing Editor Fields", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": { - "dimensions": { - "InstanceId": "i-missing-fields" - }, - "metricEditorMode": 0, - "metricName": "CPUUtilization", - "metricQueryType": 0, - "namespace": "AWS/EC2", - "region": "us-east-1", - "statistic": "Average" - } - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-11": { - "kind": "Panel", - "spec": { - "id": 11, - "title": "CloudWatch Query with Expression (Code Mode)", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": { - "dimensions": { - "InstanceId": "i-with-expression" - }, - "expression": "SEARCH('{AWS/EC2,InstanceId} MetricName=\"CPUUtilization\"', 'Average', 300)", - "metricEditorMode": 1, - "metricName": "CPUUtilization", - "metricQueryType": 0, - "namespace": "AWS/EC2", - "region": "us-east-1", - "statistic": "Average" - } - }, - "refId": "A", - "hidden": false - } - }, - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": { - "dimensions": { - "InstanceId": "i-with-expression" - }, - "expression": "SEARCH('{AWS/EC2,InstanceId} MetricName=\"CPUUtilization\"', 'Average', 300)", - "metricEditorMode": 1, - "metricName": "CPUUtilization", - "metricQueryType": 0, - "namespace": "AWS/EC2", - "region": "us-east-1", - "statistic": "Maximum" - } - }, - "refId": "B", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-12": { - "kind": "Panel", - "spec": { - "id": 12, - "title": "CloudWatch Insights Query Missing Editor Mode", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": { - "dimensions": { - "InstanceId": "i-insights" - }, - "metricEditorMode": 1, - "metricName": "CPUUtilization", - "metricQueryType": 1, - "namespace": "AWS/EC2", - "region": "us-east-1", - "statistic": "Average" - } - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-13": { - "kind": "Panel", - "spec": { - "id": 13, - "title": "CloudWatch Query with Null Statistics", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": { - "dimensions": { - "InstanceId": "i-null-stats" - }, - "metricEditorMode": 0, - "metricName": "CPUUtilization", - "metricQueryType": 0, - "namespace": "AWS/EC2", - "region": "us-east-1" - } - }, - "refId": "A", - "hidden": false - } - }, - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": { - "dimensions": { - "InstanceId": "i-null-stats" - }, - "metricEditorMode": 0, - "metricName": "CPUUtilization", - "metricQueryType": 0, - "namespace": "AWS/EC2", - "region": "us-east-1", - "statistic": "Average" - } - }, - "refId": "B", - "hidden": false - } - }, - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": { - "dimensions": { - "InstanceId": "i-null-stats" - }, - "metricEditorMode": 0, - "metricName": "CPUUtilization", - "metricQueryType": 0, - "namespace": "AWS/EC2", - "region": "us-east-1", - "statistic": "" - } - }, - "refId": "C", - "hidden": false - } - }, - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": { - "dimensions": { - "InstanceId": "i-null-stats" - }, - "metricEditorMode": 0, - "metricName": "CPUUtilization", - "metricQueryType": 0, - "namespace": "AWS/EC2", - "region": "us-east-1", - "statistic": "Maximum" - } - }, - "refId": "D", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-14": { - "kind": "Panel", - "spec": { - "id": 14, - "title": "CloudWatch Query with Only Invalid Statistics", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": { - "dimensions": { - "InstanceId": "i-invalid-only" - }, - "metricEditorMode": 0, - "metricName": "CPUUtilization", - "metricQueryType": 0, - "namespace": "AWS/EC2", - "region": "us-east-1", - "statistic": 123 - } - }, - "refId": "A", - "hidden": false - } - }, - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": { - "dimensions": { - "InstanceId": "i-invalid-only" - }, - "metricEditorMode": 0, - "metricName": "CPUUtilization", - "metricQueryType": 0, - "namespace": "AWS/EC2", - "region": "us-east-1", - "statistic": true - } - }, - "refId": "B", - "hidden": false - } - }, - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": { - "dimensions": { - "InstanceId": "i-invalid-only" - }, - "metricEditorMode": 0, - "metricName": "CPUUtilization", - "metricQueryType": 0, - "namespace": "AWS/EC2", - "region": "us-east-1", - "statistic": {} - } - }, - "refId": "C", - "hidden": false - } - }, - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": { - "dimensions": { - "InstanceId": "i-invalid-only" - }, - "metricEditorMode": 0, - "metricName": "CPUUtilization", - "metricQueryType": 0, - "namespace": "AWS/EC2", - "region": "us-east-1", - "statistic": [] - } - }, - "refId": "D", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-15": { - "kind": "Panel", - "spec": { - "id": 15, - "title": "Non-CloudWatch Panel", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "prometheus" - }, - "spec": { - "expr": "cpu_usage" - } - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-2": { - "kind": "Panel", - "spec": { - "id": 2, - "title": "CloudWatch Single Query Single Statistic", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": { - "dimensions": { - "LoadBalancer": "my-load-balancer" - }, - "metricEditorMode": 0, - "metricName": "RequestCount", - "metricQueryType": 0, - "namespace": "AWS/ApplicationELB", - "region": "us-west-2", - "statistic": "Sum" - } - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-3": { - "kind": "Panel", - "spec": { - "id": 3, - "title": "CloudWatch Query No Statistics Array", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": { - "dimensions": { - "DBInstanceIdentifier": "my-db" - }, - "metricEditorMode": 0, - "metricName": "DatabaseConnections", - "metricQueryType": 0, - "namespace": "AWS/RDS", - "region": "us-east-1", - "statistic": "Maximum" - } - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-4": { - "kind": "Panel", - "spec": { - "id": 4, - "title": "Mixed CloudWatch and Non-CloudWatch Queries", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": { - "dimensions": { - "QueueName": "my-queue" - }, - "metricEditorMode": 0, - "metricName": "ApproximateNumberOfMessages", - "metricQueryType": 0, - "namespace": "AWS/SQS", - "region": "us-east-1", - "statistic": "Average" - } - }, - "refId": "A", - "hidden": false - } - }, - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "prometheus" - }, - "spec": { - "expr": "up" - } - }, - "refId": "B", - "hidden": false - } - }, - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": { - "dimensions": { - "TopicName": "my-topic" - }, - "metricEditorMode": 0, - "metricName": "NumberOfMessagesPublished", - "metricQueryType": 0, - "namespace": "AWS/SNS", - "region": "us-west-1", - "statistic": "Sum" - } - }, - "refId": "C", - "hidden": false - } - }, - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": { - "dimensions": { - "QueueName": "my-queue" - }, - "metricEditorMode": 0, - "metricName": "ApproximateNumberOfMessages", - "metricQueryType": 0, - "namespace": "AWS/SQS", - "region": "us-east-1", - "statistic": "Maximum" - } - }, - "refId": "D", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-5": { - "kind": "Panel", - "spec": { - "id": 5, - "title": "CloudWatch Query Empty Statistics", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": { - "dimensions": { - "BucketName": "my-bucket" - }, - "metricEditorMode": 0, - "metricName": "BucketSizeBytes", - "metricQueryType": 0, - "namespace": "AWS/S3", - "region": "us-east-1" - } - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-6": { - "kind": "Panel", - "spec": { - "id": 6, - "title": "CloudWatch Query Invalid Statistics", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": { - "dimensions": { - "FunctionName": "my-function" - }, - "metricEditorMode": 0, - "metricName": "Duration", - "metricQueryType": 0, - "namespace": "AWS/Lambda", - "region": "us-west-2", - "statistic": "InvalidStat" - } - }, - "refId": "A", - "hidden": false - } - }, - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": { - "dimensions": { - "FunctionName": "my-function" - }, - "metricEditorMode": 0, - "metricName": "Duration", - "metricQueryType": 0, - "namespace": "AWS/Lambda", - "region": "us-west-2", - "statistic": "Average" - } - }, - "refId": "B", - "hidden": false - } - }, - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": { - "dimensions": { - "FunctionName": "my-function" - }, - "metricEditorMode": 0, - "metricName": "Duration", - "metricQueryType": 0, - "namespace": "AWS/Lambda", - "region": "us-west-2" - } - }, - "refId": "C", - "hidden": false - } - }, - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": { - "dimensions": { - "FunctionName": "my-function" - }, - "metricEditorMode": 0, - "metricName": "Duration", - "metricQueryType": 0, - "namespace": "AWS/Lambda", - "region": "us-west-2", - "statistic": "Maximum" - } - }, - "refId": "D", - "hidden": false - } - }, - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": { - "dimensions": { - "FunctionName": "my-function" - }, - "metricEditorMode": 0, - "metricName": "Duration", - "metricQueryType": 0, - "namespace": "AWS/Lambda", - "region": "us-west-2", - "statistic": "" - } - }, - "refId": "E", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-8": { - "kind": "Panel", - "spec": { - "id": 8, - "title": "Nested CloudWatch Query Multiple Statistics", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": { - "dimensions": { - "StreamName": "my-stream" - }, - "metricEditorMode": 0, - "metricName": "IncomingRecords", - "metricQueryType": 0, - "namespace": "AWS/Kinesis", - "region": "us-east-1", - "statistic": "Sum" - } - }, - "refId": "A", - "hidden": false - } - }, - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": { - "dimensions": { - "StreamName": "my-stream" - }, - "metricEditorMode": 0, - "metricName": "IncomingRecords", - "metricQueryType": 0, - "namespace": "AWS/Kinesis", - "region": "us-east-1", - "statistic": "Average" - } - }, - "refId": "B", - "hidden": false - } - }, - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": { - "dimensions": { - "StreamName": "my-stream" - }, - "metricEditorMode": 0, - "metricName": "IncomingRecords", - "metricQueryType": 0, - "namespace": "AWS/Kinesis", - "region": "us-east-1", - "statistic": "Maximum" - } - }, - "refId": "C", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-9": { - "kind": "Panel", - "spec": { - "id": 9, - "title": "CloudWatch Query with Existing Editor Mode", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": { - "dimensions": { - "ClusterName": "my-cluster" - }, - "metricEditorMode": 1, - "metricName": "CPUUtilization", - "metricQueryType": 1, - "namespace": "AWS/ECS", - "period": "300", - "region": "us-east-1", - "statistic": "Average" - } - }, - "refId": "A", - "hidden": false - } - }, - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": { - "dimensions": { - "ClusterName": "my-cluster" - }, - "metricEditorMode": 1, - "metricName": "CPUUtilization", - "metricQueryType": 1, - "namespace": "AWS/ECS", - "period": "300", - "region": "us-east-1", - "statistic": "Maximum" - } - }, - "refId": "B", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - } - }, - "layout": { - "kind": "RowsLayout", - "spec": { - "rows": [ - { - "kind": "RowsLayoutRow", - "spec": { - "title": "", - "collapse": true, - "hideHeader": true, - "layout": { - "kind": "GridLayout", - "spec": { - "items": [ - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-1" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-2" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-3" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-4" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-5" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-6" - } - } - } - ] - } - } - } - }, - { - "kind": "RowsLayoutRow", - "spec": { - "title": "Collapsed Row with CloudWatch", - "collapse": true, - "layout": { - "kind": "GridLayout", - "spec": { - "items": [ - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-8" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-9" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-10" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-11" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-12" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-13" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-14" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-15" - } - } - } - ] - } - } - } - } - ] - } - }, - "links": [], - "liveNow": false, - "preload": false, - "tags": [], - "timeSettings": { - "timezone": "", - "from": "now-6h", - "to": "now", - "autoRefresh": "", - "autoRefreshIntervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "hideTimepicker": false, - "fiscalYearStartMonth": 0 - }, - "title": "CloudWatch Multiple Statistics Test Dashboard", - "variables": [] - }, - "status": {} - } + "failed": false, + "storedVersion": "v2beta1" } } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v35.ensure_x_axis_visibility.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v35.ensure_x_axis_visibility.v0alpha1.json index 4aa5fd009aa..83e0d94fd78 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v35.ensure_x_axis_visibility.v0alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v35.ensure_x_axis_visibility.v0alpha1.json @@ -4,628 +4,327 @@ "metadata": { "name": "v35.ensure_x_axis_visibility.v42" }, - "spec": null, - "status": { - "conversion": { - "failed": true, - "error": "backend conversion not yet implemented", - "storedVersion": "v2beta1", - "source": { - "kind": "Dashboard", - "apiVersion": "dashboard.grafana.app/v2beta1", - "metadata": { - "name": "v35.ensure_x_axis_visibility.v42" - }, - "spec": { - "annotations": [ - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana", - "version": "v0", - "datasource": { - "name": "-- Grafana --" - }, - "spec": {} - }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations \u0026 Alerts", - "builtIn": true, - "legacyOptions": { - "type": "dashboard" - } - } - } - ], - "cursorSync": "Off", - "editable": true, - "elements": { - "panel-10": { - "kind": "Panel", - "spec": { - "id": 10, - "title": "Timeseries with Missing Defaults", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-11": { - "kind": "Panel", - "spec": { - "id": 11, - "title": "Timeseries with Missing Custom Config", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": { - "unit": "bytes" - }, - "overrides": [] - } - } - } - } - }, - "panel-12": { - "kind": "Panel", - "spec": { - "id": 12, - "title": "Timeseries with Missing Overrides Array", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": { - "custom": { - "axisPlacement": "hidden" - } - }, - "overrides": [ - { - "matcher": { - "id": "byType", - "options": "time" - }, - "properties": [ - { - "id": "custom.axisPlacement", - "value": "auto" - } - ] - } - ] - } - } - } - } - }, - "panel-5": { - "kind": "Panel", - "spec": { - "id": 5, - "title": "Timeseries with Missing FieldConfig", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-6": { - "kind": "Panel", - "spec": { - "id": 6, - "title": "Timeseries with Hidden Axis", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": { - "custom": { - "axisPlacement": "hidden" - } - }, - "overrides": [ - { - "matcher": { - "id": "byType", - "options": "time" - }, - "properties": [ - { - "id": "custom.axisPlacement", - "value": "auto" - } - ] - } - ] - } - } - } - } - }, - "panel-7": { - "kind": "Panel", - "spec": { - "id": 7, - "title": "Timeseries with Hidden Axis and Existing Overrides", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": { - "custom": { - "axisPlacement": "hidden" - } - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "Series A" - }, - "properties": [ - { - "id": "color.mode", - "value": "palette-classic" - } - ] - }, - { - "matcher": { - "id": "byType", - "options": "time" - }, - "properties": [ - { - "id": "custom.axisPlacement", - "value": "auto" - } - ] - } - ] - } - } - } - } - }, - "panel-8": { - "kind": "Panel", - "spec": { - "id": 8, - "title": "Timeseries with Auto Axis (No Change Expected)", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": { - "custom": { - "axisPlacement": "auto" - } - }, - "overrides": [] - } - } - } - } - }, - "panel-9": { - "kind": "Panel", - "spec": { - "id": 9, - "title": "Stat Panel with Hidden Axis (No Change Expected)", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "stat", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": { - "custom": { - "axisPlacement": "hidden" - } - }, - "overrides": [] - } - } - } - } + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "liveNow": false, + "panels": [ + { + "fieldConfig": { + "defaults": { + "custom": { + "axisPlacement": "hidden" } }, - "layout": { - "kind": "GridLayout", - "spec": { - "items": [ + "overrides": [ + { + "matcher": { + "id": "byType", + "options": "time" + }, + "properties": [ { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-6" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-7" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-8" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-9" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-5" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-10" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-11" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-12" - } - } + "id": "custom.axisPlacement", + "value": "auto" } ] } - }, - "links": [], - "liveNow": false, - "preload": false, - "tags": [], - "timeSettings": { - "timezone": "", - "from": "now-6h", - "to": "now", - "autoRefresh": "", - "autoRefreshIntervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "hideTimepicker": false, - "fiscalYearStartMonth": 0 - }, - "title": "X-Axis Visibility Test Dashboard", - "variables": [] + ] }, - "status": {} + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 6, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Timeseries with Hidden Axis", + "type": "timeseries" + }, + { + "fieldConfig": { + "defaults": { + "custom": { + "axisPlacement": "hidden" + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Series A" + }, + "properties": [ + { + "id": "color.mode", + "value": "palette-classic" + } + ] + }, + { + "matcher": { + "id": "byType", + "options": "time" + }, + "properties": [ + { + "id": "custom.axisPlacement", + "value": "auto" + } + ] + } + ] + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 7, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Timeseries with Hidden Axis and Existing Overrides", + "type": "timeseries" + }, + { + "fieldConfig": { + "defaults": { + "custom": { + "axisPlacement": "auto" + } + } + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 8, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Timeseries with Auto Axis (No Change Expected)", + "type": "timeseries" + }, + { + "fieldConfig": { + "defaults": { + "custom": { + "axisPlacement": "hidden" + } + } + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 9, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Stat Panel with Hidden Axis (No Change Expected)", + "type": "stat" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 5, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Timeseries with Missing FieldConfig", + "type": "timeseries" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 10, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Timeseries with Missing Defaults", + "type": "timeseries" + }, + { + "fieldConfig": { + "defaults": { + "unit": "bytes" + } + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 11, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Timeseries with Missing Custom Config", + "type": "timeseries" + }, + { + "fieldConfig": { + "defaults": { + "custom": { + "axisPlacement": "hidden" + } + }, + "overrides": [ + { + "matcher": { + "id": "byType", + "options": "time" + }, + "properties": [ + { + "id": "custom.axisPlacement", + "value": "auto" + } + ] + } + ] + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 12, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Timeseries with Missing Overrides Array", + "type": "timeseries" } + ], + "preload": false, + "refresh": "", + "schemaVersion": 42, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "X-Axis Visibility Test Dashboard" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v2beta1" } } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v36.ds_name_to_ref.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v36.ds_name_to_ref.v0alpha1.json index f1aaa0a3418..6cfba3afdaf 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v36.ds_name_to_ref.v0alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v36.ds_name_to_ref.v0alpha1.json @@ -4,1081 +4,528 @@ "metadata": { "name": "v36.ds_name_to_ref.v42" }, - "spec": null, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + }, + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "enable": false, + "hide": false, + "iconColor": "", + "name": "Default Annotation - Tests default datasource migration" + }, + { + "datasource": { + "type": "elasticsearch", + "uid": "existing-target-uid" + }, + "enable": false, + "hide": false, + "iconColor": "", + "name": "Named Datasource Annotation - Tests migration by datasource name" + }, + { + "datasource": { + "type": "elasticsearch", + "uid": "existing-target-uid" + }, + "enable": false, + "hide": false, + "iconColor": "", + "name": "UID Datasource Annotation - Tests migration by datasource UID" + }, + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "enable": false, + "hide": false, + "iconColor": "", + "name": "Null Datasource Annotation - Tests null datasource fallback to default" + }, + { + "datasource": { + "type": "prometheus", + "uid": "unknown-datasource-name" + }, + "enable": false, + "hide": false, + "iconColor": "", + "name": "Unknown Datasource Annotation - Tests unknown datasource preserved as UID" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "liveNow": false, + "panels": [ + { + "description": "Tests null panel datasource migration with targets - should fallback to default", + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 1, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Panel with Null Datasource and Targets", + "type": "timeseries" + }, + { + "description": "Tests null panel datasource with empty targets array - should create default target", + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 2, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Panel with Null Datasource and Empty Targets", + "type": "timeseries" + }, + { + "description": "Tests null panel datasource with missing targets - should create default target array", + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 3, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Panel with No Targets Array", + "type": "timeseries" + }, + { + "datasource": { + "type": "mixed", + "uid": "-- Mixed --" + }, + "description": "Tests mixed datasource panel - targets should migrate independently", + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 4, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + }, + { + "datasource": { + "type": "elasticsearch", + "uid": "existing-target-uid" + }, + "refId": "B" + } + ], + "title": "Panel with Mixed Datasources", + "type": "timeseries" + }, + { + "description": "Tests panel with already migrated datasource object - should preserve existing refs", + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 5, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "elasticsearch", + "uid": "existing-target-uid" + }, + "refId": "A" + } + ], + "title": "Panel with Existing Object Datasource", + "type": "timeseries" + }, + { + "description": "Tests panel with unknown datasource - should preserve as UID-only reference", + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 6, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "unknown-target-datasource" + }, + "refId": "A" + } + ], + "title": "Panel with Unknown Datasource Name", + "type": "timeseries" + }, + { + "datasource": { + "type": "mixed", + "uid": "-- Mixed --" + }, + "description": "Tests panel with expression query - should not inherit expression as panel datasource", + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 7, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "elasticsearch", + "uid": "existing-target-uid" + }, + "refId": "A" + }, + { + "datasource": { + "type": "__expr__", + "uid": "__expr__" + }, + "refId": "B" + } + ], + "title": "Panel with Expression Query", + "type": "timeseries" + }, + { + "description": "Tests panel inheriting datasource from target when panel datasource was default", + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 8, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "elasticsearch", + "uid": "existing-target-uid" + }, + "refId": "A" + } + ], + "title": "Panel Inheriting from Target", + "type": "timeseries" + }, + { + "description": "Tests panel with datasource referenced by name - should migrate to full object", + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 9, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "elasticsearch", + "uid": "existing-target-uid" + }, + "refId": "A" + } + ], + "title": "Panel with Named Datasource", + "type": "timeseries" + }, + { + "description": "Tests panel with datasource referenced by UID - should migrate to full object", + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 10, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "elasticsearch", + "uid": "existing-target-uid" + }, + "refId": "A" + } + ], + "title": "Panel with UID Datasource", + "type": "timeseries" + }, + { + "collapsed": false, + "id": -1, + "title": "Simple Row Panel", + "type": "row" + }, + { + "collapsed": true, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 0 + }, + "id": -1, + "panels": [ + { + "description": "Nested panel in collapsed row with default datasource", + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 1 + }, + "id": 13, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "elasticsearch", + "uid": "existing-target-uid" + }, + "refId": "A" + } + ], + "title": "Nested Panel with Default Datasource", + "type": "timeseries" + }, + { + "description": "Nested panel in collapsed row with unknown datasource", + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 1 + }, + "id": 14, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "elasticsearch", + "uid": "existing-target-uid" + }, + "refId": "A" + } + ], + "title": "Nested Panel with Unknown Datasource", + "type": "timeseries" + } + ], + "title": "Collapsed Row with Nested Panels", + "type": "row" + } + ], + "preload": false, + "refresh": "", + "schemaVersion": 42, + "templating": { + "list": [ + { + "allowCustomValue": true, + "current": { + "text": "", + "value": "" + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "query_var_null", + "options": [], + "query": {}, + "refresh": 0, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "allowCustomValue": true, + "current": { + "text": "", + "value": "" + }, + "datasource": { + "type": "elasticsearch", + "uid": "existing-target-uid" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "query_var_named", + "options": [], + "query": {}, + "refresh": 0, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "allowCustomValue": true, + "current": { + "text": "", + "value": "" + }, + "datasource": { + "type": "elasticsearch", + "uid": "existing-target-uid" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "query_var_uid", + "options": [], + "query": {}, + "refresh": 0, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "allowCustomValue": true, + "current": { + "text": "", + "value": "" + }, + "datasource": { + "type": "prometheus", + "uid": "unknown-datasource" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "query_var_unknown", + "options": [], + "query": {}, + "refresh": 0, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "current": { + "text": "", + "value": "" + }, + "hide": 2, + "name": "non_query_var", + "query": "", + "skipUrlSync": false, + "type": "constant" + } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "Datasource Reference Migration Test Dashboard" + }, "status": { "conversion": { - "failed": true, - "error": "backend conversion not yet implemented", - "storedVersion": "v2beta1", - "source": { - "kind": "Dashboard", - "apiVersion": "dashboard.grafana.app/v2beta1", - "metadata": { - "name": "v36.ds_name_to_ref.v42" - }, - "spec": { - "annotations": [ - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana", - "version": "v0", - "datasource": { - "name": "-- Grafana --" - }, - "spec": {} - }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations \u0026 Alerts", - "builtIn": true, - "legacyOptions": { - "type": "dashboard" - } - } - }, - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "enable": false, - "hide": false, - "iconColor": "", - "name": "Default Annotation - Tests default datasource migration" - } - }, - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "elasticsearch", - "version": "v0", - "datasource": { - "name": "existing-target-uid" - }, - "spec": {} - }, - "enable": false, - "hide": false, - "iconColor": "", - "name": "Named Datasource Annotation - Tests migration by datasource name" - } - }, - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "elasticsearch", - "version": "v0", - "datasource": { - "name": "existing-target-uid" - }, - "spec": {} - }, - "enable": false, - "hide": false, - "iconColor": "", - "name": "UID Datasource Annotation - Tests migration by datasource UID" - } - }, - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "enable": false, - "hide": false, - "iconColor": "", - "name": "Null Datasource Annotation - Tests null datasource fallback to default" - } - }, - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "unknown-datasource-name" - }, - "spec": {} - }, - "enable": false, - "hide": false, - "iconColor": "", - "name": "Unknown Datasource Annotation - Tests unknown datasource preserved as UID" - } - } - ], - "cursorSync": "Off", - "editable": true, - "elements": { - "panel-1": { - "kind": "Panel", - "spec": { - "id": 1, - "title": "Panel with Null Datasource and Targets", - "description": "Tests null panel datasource migration with targets - should fallback to default", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-10": { - "kind": "Panel", - "spec": { - "id": 10, - "title": "Panel with UID Datasource", - "description": "Tests panel with datasource referenced by UID - should migrate to full object", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "elasticsearch", - "version": "v0", - "datasource": { - "name": "existing-target-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-13": { - "kind": "Panel", - "spec": { - "id": 13, - "title": "Nested Panel with Default Datasource", - "description": "Nested panel in collapsed row with default datasource", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "elasticsearch", - "version": "v0", - "datasource": { - "name": "existing-target-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-14": { - "kind": "Panel", - "spec": { - "id": 14, - "title": "Nested Panel with Unknown Datasource", - "description": "Nested panel in collapsed row with unknown datasource", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "elasticsearch", - "version": "v0", - "datasource": { - "name": "existing-target-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-2": { - "kind": "Panel", - "spec": { - "id": 2, - "title": "Panel with Null Datasource and Empty Targets", - "description": "Tests null panel datasource with empty targets array - should create default target", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-3": { - "kind": "Panel", - "spec": { - "id": 3, - "title": "Panel with No Targets Array", - "description": "Tests null panel datasource with missing targets - should create default target array", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-4": { - "kind": "Panel", - "spec": { - "id": 4, - "title": "Panel with Mixed Datasources", - "description": "Tests mixed datasource panel - targets should migrate independently", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - }, - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "elasticsearch", - "version": "v0", - "datasource": { - "name": "existing-target-uid" - }, - "spec": {} - }, - "refId": "B", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-5": { - "kind": "Panel", - "spec": { - "id": 5, - "title": "Panel with Existing Object Datasource", - "description": "Tests panel with already migrated datasource object - should preserve existing refs", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "elasticsearch", - "version": "v0", - "datasource": { - "name": "existing-target-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-6": { - "kind": "Panel", - "spec": { - "id": 6, - "title": "Panel with Unknown Datasource Name", - "description": "Tests panel with unknown datasource - should preserve as UID-only reference", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "unknown-target-datasource" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-7": { - "kind": "Panel", - "spec": { - "id": 7, - "title": "Panel with Expression Query", - "description": "Tests panel with expression query - should not inherit expression as panel datasource", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "elasticsearch", - "version": "v0", - "datasource": { - "name": "existing-target-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - }, - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "__expr__", - "version": "v0", - "datasource": { - "name": "__expr__" - }, - "spec": {} - }, - "refId": "B", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-8": { - "kind": "Panel", - "spec": { - "id": 8, - "title": "Panel Inheriting from Target", - "description": "Tests panel inheriting datasource from target when panel datasource was default", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "elasticsearch", - "version": "v0", - "datasource": { - "name": "existing-target-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-9": { - "kind": "Panel", - "spec": { - "id": 9, - "title": "Panel with Named Datasource", - "description": "Tests panel with datasource referenced by name - should migrate to full object", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "elasticsearch", - "version": "v0", - "datasource": { - "name": "existing-target-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - } - }, - "layout": { - "kind": "RowsLayout", - "spec": { - "rows": [ - { - "kind": "RowsLayoutRow", - "spec": { - "title": "", - "collapse": false, - "hideHeader": true, - "layout": { - "kind": "GridLayout", - "spec": { - "items": [ - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-1" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-2" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-3" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-4" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-5" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-6" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-7" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-8" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-9" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-10" - } - } - } - ] - } - } - } - }, - { - "kind": "RowsLayoutRow", - "spec": { - "title": "Simple Row Panel", - "collapse": false, - "layout": { - "kind": "GridLayout", - "spec": { - "items": [] - } - } - } - }, - { - "kind": "RowsLayoutRow", - "spec": { - "title": "Collapsed Row with Nested Panels", - "collapse": true, - "layout": { - "kind": "GridLayout", - "spec": { - "items": [ - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-13" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-14" - } - } - } - ] - } - } - } - } - ] - } - }, - "links": [], - "liveNow": false, - "preload": false, - "tags": [], - "timeSettings": { - "timezone": "", - "from": "now-6h", - "to": "now", - "autoRefresh": "", - "autoRefreshIntervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "hideTimepicker": false, - "fiscalYearStartMonth": 0 - }, - "title": "Datasource Reference Migration Test Dashboard", - "variables": [ - { - "kind": "QueryVariable", - "spec": { - "name": "query_var_null", - "current": { - "text": "", - "value": "" - }, - "hide": "dontHide", - "refresh": "never", - "skipUrlSync": false, - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "regex": "", - "sort": "disabled", - "options": [], - "multi": false, - "includeAll": false, - "allowCustomValue": true - } - }, - { - "kind": "QueryVariable", - "spec": { - "name": "query_var_named", - "current": { - "text": "", - "value": "" - }, - "hide": "dontHide", - "refresh": "never", - "skipUrlSync": false, - "query": { - "kind": "DataQuery", - "group": "elasticsearch", - "version": "v0", - "datasource": { - "name": "existing-target-uid" - }, - "spec": {} - }, - "regex": "", - "sort": "disabled", - "options": [], - "multi": false, - "includeAll": false, - "allowCustomValue": true - } - }, - { - "kind": "QueryVariable", - "spec": { - "name": "query_var_uid", - "current": { - "text": "", - "value": "" - }, - "hide": "dontHide", - "refresh": "never", - "skipUrlSync": false, - "query": { - "kind": "DataQuery", - "group": "elasticsearch", - "version": "v0", - "datasource": { - "name": "existing-target-uid" - }, - "spec": {} - }, - "regex": "", - "sort": "disabled", - "options": [], - "multi": false, - "includeAll": false, - "allowCustomValue": true - } - }, - { - "kind": "QueryVariable", - "spec": { - "name": "query_var_unknown", - "current": { - "text": "", - "value": "" - }, - "hide": "dontHide", - "refresh": "never", - "skipUrlSync": false, - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "unknown-datasource" - }, - "spec": {} - }, - "regex": "", - "sort": "disabled", - "options": [], - "multi": false, - "includeAll": false, - "allowCustomValue": true - } - }, - { - "kind": "ConstantVariable", - "spec": { - "name": "non_query_var", - "query": "", - "current": { - "text": "", - "value": "" - }, - "hide": "dontHide", - "skipUrlSync": false - } - } - ] - }, - "status": {} - } + "failed": false, + "storedVersion": "v2beta1" } } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v37.legend_normalization.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v37.legend_normalization.v0alpha1.json index 4df4909750e..02605744e6d 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v37.legend_normalization.v0alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v37.legend_normalization.v0alpha1.json @@ -4,658 +4,279 @@ "metadata": { "name": "v37.legend_normalization.v42" }, - "spec": null, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "liveNow": false, + "panels": [ + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 1, + "options": { + "legend": true + }, + "pluginVersion": "", + "targets": [], + "title": "Panel with Boolean Legend True", + "type": "timeseries" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 2, + "options": { + "legend": false + }, + "pluginVersion": "", + "targets": [], + "title": "Panel with Boolean Legend False", + "type": "timeseries" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 3, + "options": { + "legend": { + "displayMode": "list", + "showLegend": false + } + }, + "pluginVersion": "", + "targets": [], + "title": "Panel with Hidden DisplayMode", + "type": "timeseries" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 4, + "options": { + "legend": { + "displayMode": "list", + "showLegend": false + } + }, + "pluginVersion": "", + "targets": [], + "title": "Panel with ShowLegend False", + "type": "stat" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 5, + "options": { + "legend": { + "displayMode": "table", + "placement": "bottom", + "showLegend": true + } + }, + "pluginVersion": "", + "targets": [], + "title": "Panel with Table Legend", + "type": "barchart" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 6, + "options": { + "legend": { + "displayMode": "list", + "placement": "right", + "showLegend": true + } + }, + "pluginVersion": "", + "targets": [], + "title": "Panel with List Legend", + "type": "histogram" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 7, + "options": {}, + "pluginVersion": "", + "targets": [], + "title": "Panel with No Options", + "type": "text" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 8, + "options": { + "reduceOptions": { + "fields": "/.*temperature.*/" + } + }, + "pluginVersion": "", + "targets": [], + "title": "Panel with No Legend Config", + "type": "gauge" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 9, + "options": {}, + "pluginVersion": "", + "targets": [], + "title": "Panel with Null Legend", + "type": "piechart" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 3 + }, + "id": -1, + "title": "Row with Nested Panels Having Various Legend Configs", + "type": "row" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 3 + }, + "id": 11, + "options": { + "legend": true + }, + "pluginVersion": "", + "targets": [], + "title": "Nested Panel with Boolean Legend", + "type": "timeseries" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 3 + }, + "id": 12, + "options": { + "legend": { + "displayMode": "list", + "showLegend": false + } + }, + "pluginVersion": "", + "targets": [], + "title": "Nested Panel with Hidden DisplayMode", + "type": "timeseries" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 3 + }, + "id": 13, + "options": { + "legend": { + "displayMode": "list", + "showLegend": false + } + }, + "pluginVersion": "", + "targets": [], + "title": "Nested Panel with Conflicting Properties", + "type": "stat" + } + ], + "preload": false, + "refresh": "", + "schemaVersion": 42, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "V37 Legend Normalization Test Dashboard" + }, "status": { "conversion": { - "failed": true, - "error": "backend conversion not yet implemented", - "storedVersion": "v2beta1", - "source": { - "kind": "Dashboard", - "apiVersion": "dashboard.grafana.app/v2beta1", - "metadata": { - "name": "v37.legend_normalization.v42" - }, - "spec": { - "annotations": [ - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana", - "version": "v0", - "datasource": { - "name": "-- Grafana --" - }, - "spec": {} - }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations \u0026 Alerts", - "builtIn": true, - "legacyOptions": { - "type": "dashboard" - } - } - } - ], - "cursorSync": "Off", - "editable": true, - "elements": { - "panel-1": { - "kind": "Panel", - "spec": { - "id": 1, - "title": "Panel with Boolean Legend True", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": { - "legend": true - }, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-11": { - "kind": "Panel", - "spec": { - "id": 11, - "title": "Nested Panel with Boolean Legend", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": { - "legend": true - }, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-12": { - "kind": "Panel", - "spec": { - "id": 12, - "title": "Nested Panel with Hidden DisplayMode", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": { - "legend": { - "displayMode": "list", - "showLegend": false - } - }, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-13": { - "kind": "Panel", - "spec": { - "id": 13, - "title": "Nested Panel with Conflicting Properties", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "stat", - "version": "", - "spec": { - "options": { - "legend": { - "displayMode": "list", - "showLegend": false - } - }, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-2": { - "kind": "Panel", - "spec": { - "id": 2, - "title": "Panel with Boolean Legend False", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": { - "legend": false - }, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-3": { - "kind": "Panel", - "spec": { - "id": 3, - "title": "Panel with Hidden DisplayMode", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": { - "legend": { - "displayMode": "list", - "showLegend": false - } - }, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-4": { - "kind": "Panel", - "spec": { - "id": 4, - "title": "Panel with ShowLegend False", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "stat", - "version": "", - "spec": { - "options": { - "legend": { - "displayMode": "list", - "showLegend": false - } - }, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-5": { - "kind": "Panel", - "spec": { - "id": 5, - "title": "Panel with Table Legend", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "barchart", - "version": "", - "spec": { - "options": { - "legend": { - "displayMode": "table", - "placement": "bottom", - "showLegend": true - } - }, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-6": { - "kind": "Panel", - "spec": { - "id": 6, - "title": "Panel with List Legend", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "histogram", - "version": "", - "spec": { - "options": { - "legend": { - "displayMode": "list", - "placement": "right", - "showLegend": true - } - }, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-7": { - "kind": "Panel", - "spec": { - "id": 7, - "title": "Panel with No Options", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "text", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-8": { - "kind": "Panel", - "spec": { - "id": 8, - "title": "Panel with No Legend Config", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "gauge", - "version": "", - "spec": { - "options": { - "reduceOptions": { - "fields": "/.*temperature.*/" - } - }, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-9": { - "kind": "Panel", - "spec": { - "id": 9, - "title": "Panel with Null Legend", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "piechart", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - } - }, - "layout": { - "kind": "RowsLayout", - "spec": { - "rows": [ - { - "kind": "RowsLayoutRow", - "spec": { - "title": "", - "collapse": false, - "hideHeader": true, - "layout": { - "kind": "GridLayout", - "spec": { - "items": [ - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-1" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-2" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-3" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-4" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-5" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-6" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-7" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-8" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-9" - } - } - } - ] - } - } - } - }, - { - "kind": "RowsLayoutRow", - "spec": { - "title": "Row with Nested Panels Having Various Legend Configs", - "collapse": false, - "layout": { - "kind": "GridLayout", - "spec": { - "items": [ - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-11" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-12" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-13" - } - } - } - ] - } - } - } - } - ] - } - }, - "links": [], - "liveNow": false, - "preload": false, - "tags": [], - "timeSettings": { - "timezone": "", - "from": "now-6h", - "to": "now", - "autoRefresh": "", - "autoRefreshIntervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "hideTimepicker": false, - "fiscalYearStartMonth": 0 - }, - "title": "V37 Legend Normalization Test Dashboard", - "variables": [] - }, - "status": {} - } + "failed": false, + "storedVersion": "v2beta1" } } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v38.table_displaymode_comprehensive.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v38.table_displaymode_comprehensive.v0alpha1.json index e7b413717ff..b874385c59b 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v38.table_displaymode_comprehensive.v0alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v38.table_displaymode_comprehensive.v0alpha1.json @@ -4,687 +4,368 @@ "metadata": { "name": "v38.table_displaymode_comprehensive.v42" }, - "spec": null, - "status": { - "conversion": { - "failed": true, - "error": "backend conversion not yet implemented", - "storedVersion": "v2beta1", - "source": { - "kind": "Dashboard", - "apiVersion": "dashboard.grafana.app/v2beta1", - "metadata": { - "name": "v38.table_displaymode_comprehensive.v42" - }, - "spec": { - "annotations": [ - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana", - "version": "v0", - "datasource": { - "name": "-- Grafana --" - }, - "spec": {} - }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations \u0026 Alerts", - "builtIn": true, - "legacyOptions": { - "type": "dashboard" - } + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "liveNow": false, + "panels": [ + { + "fieldConfig": { + "defaults": { + "custom": { + "cellOptions": { + "mode": "basic", + "type": "gauge" } } - ], - "cursorSync": "Off", - "editable": true, - "elements": { - "panel-1": { - "kind": "Panel", - "spec": { - "id": 1, - "title": "Table with Basic Gauge", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "table", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": { - "custom": { - "cellOptions": { - "mode": "basic", - "type": "gauge" - } - } - }, - "overrides": [] - } - } - } + } + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 1, + "options": {}, + "pluginVersion": "", + "targets": [], + "title": "Table with Basic Gauge", + "type": "table" + }, + { + "fieldConfig": { + "defaults": { + "custom": { + "cellOptions": { + "mode": "gradient", + "type": "gauge" } - }, - "panel-11": { - "kind": "Panel", - "spec": { - "id": 11, - "title": "Nested Table with Basic Mode", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "table", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": { - "custom": { - "cellOptions": { - "mode": "basic", - "type": "gauge" - } - } - }, - "overrides": [] - } - } - } + } + } + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 2, + "options": {}, + "pluginVersion": "", + "targets": [], + "title": "Table with Gradient Gauge", + "type": "table" + }, + { + "fieldConfig": { + "defaults": { + "custom": { + "cellOptions": { + "mode": "lcd", + "type": "gauge" } - }, - "panel-12": { - "kind": "Panel", - "spec": { - "id": 12, - "title": "Nested Table with Gradient Gauge", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "table", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": { - "custom": { - "cellOptions": { - "mode": "gradient", - "type": "gauge" - } - } - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "NestedField" - }, - "properties": [ - { - "id": "custom.cellOptions", - "value": { - "mode": "lcd", - "type": "gauge" - } - } - ] - } - ] - } - } - } + } + } + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 3, + "options": {}, + "pluginVersion": "", + "targets": [], + "title": "Table with LCD Gauge", + "type": "table" + }, + { + "fieldConfig": { + "defaults": { + "custom": { + "cellOptions": { + "mode": "gradient", + "type": "color-background" } - }, - "panel-2": { - "kind": "Panel", - "spec": { - "id": 2, - "title": "Table with Gradient Gauge", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "table", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": { - "custom": { - "cellOptions": { - "mode": "gradient", - "type": "gauge" - } - } - }, - "overrides": [] - } - } - } + } + } + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 4, + "options": {}, + "pluginVersion": "", + "targets": [], + "title": "Table with Color Background", + "type": "table" + }, + { + "fieldConfig": { + "defaults": { + "custom": { + "cellOptions": { + "mode": "basic", + "type": "color-background" } - }, - "panel-3": { - "kind": "Panel", - "spec": { - "id": 3, - "title": "Table with LCD Gauge", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "table", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": { - "custom": { - "cellOptions": { - "mode": "lcd", - "type": "gauge" - } - } - }, - "overrides": [] - } - } - } + } + } + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 5, + "options": {}, + "pluginVersion": "", + "targets": [], + "title": "Table with Color Background Solid", + "type": "table" + }, + { + "fieldConfig": { + "defaults": { + "custom": { + "cellOptions": { + "type": "some-other-mode" } - }, - "panel-4": { - "kind": "Panel", - "spec": { - "id": 4, - "title": "Table with Color Background", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "table", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": { - "custom": { - "cellOptions": { - "mode": "gradient", - "type": "color-background" - } - } - }, - "overrides": [] - } - } - } - } - }, - "panel-5": { - "kind": "Panel", - "spec": { - "id": 5, - "title": "Table with Color Background Solid", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "table", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": { - "custom": { - "cellOptions": { - "mode": "basic", - "type": "color-background" - } - } - }, - "overrides": [] - } - } - } - } - }, - "panel-6": { - "kind": "Panel", - "spec": { - "id": 6, - "title": "Table with Unknown Mode", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "table", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": { - "custom": { - "cellOptions": { - "type": "some-other-mode" - } - } - }, - "overrides": [] - } - } - } - } - }, - "panel-7": { - "kind": "Panel", - "spec": { - "id": 7, - "title": "Table with No Display Mode", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "table", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": { - "custom": { - "width": 100 - } - }, - "overrides": [] - } - } - } - } - }, - "panel-8": { - "kind": "Panel", - "spec": { - "id": 8, - "title": "Table with Overrides", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "table", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": { - "custom": { - "cellOptions": { - "mode": "basic", - "type": "gauge" - } - } - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "Field1" - }, - "properties": [ - { - "id": "custom.cellOptions", - "value": { - "mode": "gradient", - "type": "gauge" - } - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "Field2" - }, - "properties": [ - { - "id": "custom.cellOptions", - "value": { - "mode": "gradient", - "type": "color-background" - } - } - ] - } - ] - } - } - } - } - }, - "panel-9": { - "kind": "Panel", - "spec": { - "id": 9, - "title": "Non-table Panel (Should Remain Unchanged)", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } + } + } + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 6, + "options": {}, + "pluginVersion": "", + "targets": [], + "title": "Table with Unknown Mode", + "type": "table" + }, + { + "fieldConfig": { + "defaults": { + "custom": { + "width": 100 + } + } + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 7, + "options": {}, + "pluginVersion": "", + "targets": [], + "title": "Table with No Display Mode", + "type": "table" + }, + { + "fieldConfig": { + "defaults": { + "custom": { + "cellOptions": { + "mode": "basic", + "type": "gauge" } } }, - "layout": { - "kind": "RowsLayout", - "spec": { - "rows": [ + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Field1" + }, + "properties": [ { - "kind": "RowsLayoutRow", - "spec": { - "title": "", - "collapse": false, - "hideHeader": true, - "layout": { - "kind": "GridLayout", - "spec": { - "items": [ - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-1" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-2" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-3" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-4" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-5" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-6" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-7" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-8" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-9" - } - } - } - ] - } - } + "id": "custom.cellOptions", + "value": { + "mode": "gradient", + "type": "gauge" } - }, + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Field2" + }, + "properties": [ { - "kind": "RowsLayoutRow", - "spec": { - "title": "Row with Nested Table Panels", - "collapse": false, - "layout": { - "kind": "GridLayout", - "spec": { - "items": [ - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-11" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-12" - } - } - } - ] - } - } + "id": "custom.cellOptions", + "value": { + "mode": "gradient", + "type": "color-background" } } ] } - }, - "links": [], - "liveNow": false, - "preload": false, - "tags": [], - "timeSettings": { - "timezone": "", - "from": "now-6h", - "to": "now", - "autoRefresh": "", - "autoRefreshIntervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "hideTimepicker": false, - "fiscalYearStartMonth": 0 - }, - "title": "V38 Table Migration Comprehensive Test Dashboard", - "variables": [] + ] }, - "status": {} + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 8, + "options": {}, + "pluginVersion": "", + "targets": [], + "title": "Table with Overrides", + "type": "table" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 9, + "options": {}, + "pluginVersion": "", + "targets": [], + "title": "Non-table Panel (Should Remain Unchanged)", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 3 + }, + "id": -1, + "title": "Row with Nested Table Panels", + "type": "row" + }, + { + "fieldConfig": { + "defaults": { + "custom": { + "cellOptions": { + "mode": "basic", + "type": "gauge" + } + } + } + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 3 + }, + "id": 11, + "options": {}, + "pluginVersion": "", + "targets": [], + "title": "Nested Table with Basic Mode", + "type": "table" + }, + { + "fieldConfig": { + "defaults": { + "custom": { + "cellOptions": { + "mode": "gradient", + "type": "gauge" + } + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "NestedField" + }, + "properties": [ + { + "id": "custom.cellOptions", + "value": { + "mode": "lcd", + "type": "gauge" + } + } + ] + } + ] + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 3 + }, + "id": 12, + "options": {}, + "pluginVersion": "", + "targets": [], + "title": "Nested Table with Gradient Gauge", + "type": "table" } + ], + "preload": false, + "refresh": "", + "schemaVersion": 42, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "V38 Table Migration Comprehensive Test Dashboard" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v2beta1" } } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v38.timeseries_table_display_mode.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v38.timeseries_table_display_mode.v0alpha1.json index d9de3d6d3bf..d22e01dd10d 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v38.timeseries_table_display_mode.v0alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v38.timeseries_table_display_mode.v0alpha1.json @@ -4,687 +4,368 @@ "metadata": { "name": "v38.timeseries_table_display_mode.v42" }, - "spec": null, - "status": { - "conversion": { - "failed": true, - "error": "backend conversion not yet implemented", - "storedVersion": "v2beta1", - "source": { - "kind": "Dashboard", - "apiVersion": "dashboard.grafana.app/v2beta1", - "metadata": { - "name": "v38.timeseries_table_display_mode.v42" - }, - "spec": { - "annotations": [ - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana", - "version": "v0", - "datasource": { - "name": "-- Grafana --" - }, - "spec": {} - }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations \u0026 Alerts", - "builtIn": true, - "legacyOptions": { - "type": "dashboard" - } + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "liveNow": false, + "panels": [ + { + "fieldConfig": { + "defaults": { + "custom": { + "cellOptions": { + "mode": "basic", + "type": "gauge" } } - ], - "cursorSync": "Off", - "editable": true, - "elements": { - "panel-1": { - "kind": "Panel", - "spec": { - "id": 1, - "title": "Table with Basic Gauge", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "table", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": { - "custom": { - "cellOptions": { - "mode": "basic", - "type": "gauge" - } - } - }, - "overrides": [] - } - } - } + } + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 1, + "options": {}, + "pluginVersion": "", + "targets": [], + "title": "Table with Basic Gauge", + "type": "table" + }, + { + "fieldConfig": { + "defaults": { + "custom": { + "cellOptions": { + "mode": "gradient", + "type": "gauge" } - }, - "panel-11": { - "kind": "Panel", - "spec": { - "id": 11, - "title": "Nested Table with Basic Mode", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "table", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": { - "custom": { - "cellOptions": { - "mode": "basic", - "type": "gauge" - } - } - }, - "overrides": [] - } - } - } + } + } + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 2, + "options": {}, + "pluginVersion": "", + "targets": [], + "title": "Table with Gradient Gauge", + "type": "table" + }, + { + "fieldConfig": { + "defaults": { + "custom": { + "cellOptions": { + "mode": "lcd", + "type": "gauge" } - }, - "panel-12": { - "kind": "Panel", - "spec": { - "id": 12, - "title": "Nested Table with Gradient Gauge", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "table", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": { - "custom": { - "cellOptions": { - "mode": "gradient", - "type": "gauge" - } - } - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "NestedField" - }, - "properties": [ - { - "id": "custom.cellOptions", - "value": { - "mode": "lcd", - "type": "gauge" - } - } - ] - } - ] - } - } - } + } + } + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 3, + "options": {}, + "pluginVersion": "", + "targets": [], + "title": "Table with LCD Gauge", + "type": "table" + }, + { + "fieldConfig": { + "defaults": { + "custom": { + "cellOptions": { + "mode": "gradient", + "type": "color-background" } - }, - "panel-2": { - "kind": "Panel", - "spec": { - "id": 2, - "title": "Table with Gradient Gauge", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "table", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": { - "custom": { - "cellOptions": { - "mode": "gradient", - "type": "gauge" - } - } - }, - "overrides": [] - } - } - } + } + } + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 4, + "options": {}, + "pluginVersion": "", + "targets": [], + "title": "Table with Color Background", + "type": "table" + }, + { + "fieldConfig": { + "defaults": { + "custom": { + "cellOptions": { + "mode": "basic", + "type": "color-background" } - }, - "panel-3": { - "kind": "Panel", - "spec": { - "id": 3, - "title": "Table with LCD Gauge", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "table", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": { - "custom": { - "cellOptions": { - "mode": "lcd", - "type": "gauge" - } - } - }, - "overrides": [] - } - } - } + } + } + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 5, + "options": {}, + "pluginVersion": "", + "targets": [], + "title": "Table with Color Background Solid", + "type": "table" + }, + { + "fieldConfig": { + "defaults": { + "custom": { + "cellOptions": { + "type": "some-other-mode" } - }, - "panel-4": { - "kind": "Panel", - "spec": { - "id": 4, - "title": "Table with Color Background", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "table", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": { - "custom": { - "cellOptions": { - "mode": "gradient", - "type": "color-background" - } - } - }, - "overrides": [] - } - } - } - } - }, - "panel-5": { - "kind": "Panel", - "spec": { - "id": 5, - "title": "Table with Color Background Solid", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "table", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": { - "custom": { - "cellOptions": { - "mode": "basic", - "type": "color-background" - } - } - }, - "overrides": [] - } - } - } - } - }, - "panel-6": { - "kind": "Panel", - "spec": { - "id": 6, - "title": "Table with Unknown Mode", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "table", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": { - "custom": { - "cellOptions": { - "type": "some-other-mode" - } - } - }, - "overrides": [] - } - } - } - } - }, - "panel-7": { - "kind": "Panel", - "spec": { - "id": 7, - "title": "Table with No Display Mode", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "table", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": { - "custom": { - "width": 100 - } - }, - "overrides": [] - } - } - } - } - }, - "panel-8": { - "kind": "Panel", - "spec": { - "id": 8, - "title": "Table with Overrides", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "table", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": { - "custom": { - "cellOptions": { - "mode": "basic", - "type": "gauge" - } - } - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "Field1" - }, - "properties": [ - { - "id": "custom.cellOptions", - "value": { - "mode": "gradient", - "type": "gauge" - } - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "Field2" - }, - "properties": [ - { - "id": "custom.cellOptions", - "value": { - "mode": "gradient", - "type": "color-background" - } - } - ] - } - ] - } - } - } - } - }, - "panel-9": { - "kind": "Panel", - "spec": { - "id": 9, - "title": "Non-table Panel (Should Remain Unchanged)", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } + } + } + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 6, + "options": {}, + "pluginVersion": "", + "targets": [], + "title": "Table with Unknown Mode", + "type": "table" + }, + { + "fieldConfig": { + "defaults": { + "custom": { + "width": 100 + } + } + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 7, + "options": {}, + "pluginVersion": "", + "targets": [], + "title": "Table with No Display Mode", + "type": "table" + }, + { + "fieldConfig": { + "defaults": { + "custom": { + "cellOptions": { + "mode": "basic", + "type": "gauge" } } }, - "layout": { - "kind": "RowsLayout", - "spec": { - "rows": [ + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Field1" + }, + "properties": [ { - "kind": "RowsLayoutRow", - "spec": { - "title": "", - "collapse": false, - "hideHeader": true, - "layout": { - "kind": "GridLayout", - "spec": { - "items": [ - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-1" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-2" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-3" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-4" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-5" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-6" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-7" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-8" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-9" - } - } - } - ] - } - } + "id": "custom.cellOptions", + "value": { + "mode": "gradient", + "type": "gauge" } - }, + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Field2" + }, + "properties": [ { - "kind": "RowsLayoutRow", - "spec": { - "title": "Row with Nested Table Panels", - "collapse": false, - "layout": { - "kind": "GridLayout", - "spec": { - "items": [ - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-11" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-12" - } - } - } - ] - } - } + "id": "custom.cellOptions", + "value": { + "mode": "gradient", + "type": "color-background" } } ] } - }, - "links": [], - "liveNow": false, - "preload": false, - "tags": [], - "timeSettings": { - "timezone": "", - "from": "now-6h", - "to": "now", - "autoRefresh": "", - "autoRefreshIntervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "hideTimepicker": false, - "fiscalYearStartMonth": 0 - }, - "title": "V38 Table Migration Test Dashboard", - "variables": [] + ] }, - "status": {} + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 8, + "options": {}, + "pluginVersion": "", + "targets": [], + "title": "Table with Overrides", + "type": "table" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 9, + "options": {}, + "pluginVersion": "", + "targets": [], + "title": "Non-table Panel (Should Remain Unchanged)", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 3 + }, + "id": -1, + "title": "Row with Nested Table Panels", + "type": "row" + }, + { + "fieldConfig": { + "defaults": { + "custom": { + "cellOptions": { + "mode": "basic", + "type": "gauge" + } + } + } + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 3 + }, + "id": 11, + "options": {}, + "pluginVersion": "", + "targets": [], + "title": "Nested Table with Basic Mode", + "type": "table" + }, + { + "fieldConfig": { + "defaults": { + "custom": { + "cellOptions": { + "mode": "gradient", + "type": "gauge" + } + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "NestedField" + }, + "properties": [ + { + "id": "custom.cellOptions", + "value": { + "mode": "lcd", + "type": "gauge" + } + } + ] + } + ] + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 3 + }, + "id": 12, + "options": {}, + "pluginVersion": "", + "targets": [], + "title": "Nested Table with Gradient Gauge", + "type": "table" } + ], + "preload": false, + "refresh": "", + "schemaVersion": 42, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "V38 Table Migration Test Dashboard" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v2beta1" } } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v39.transform_timeseries_table.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v39.transform_timeseries_table.v0alpha1.json index 531febf7672..e57acfe6aa2 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v39.transform_timeseries_table.v0alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v39.transform_timeseries_table.v0alpha1.json @@ -4,612 +4,295 @@ "metadata": { "name": "v39.transform_timeseries_table.v42" }, - "spec": null, - "status": { - "conversion": { - "failed": true, - "error": "backend conversion not yet implemented", - "storedVersion": "v2beta1", - "source": { - "kind": "Dashboard", - "apiVersion": "dashboard.grafana.app/v2beta1", - "metadata": { - "name": "v39.transform_timeseries_table.v42" - }, - "spec": { - "annotations": [ - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana", - "version": "v0", - "datasource": { - "name": "-- Grafana --" - }, - "spec": {} - }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations \u0026 Alerts", - "builtIn": true, - "legacyOptions": { - "type": "dashboard" - } - } - } - ], - "cursorSync": "Off", - "editable": true, - "elements": { - "panel-1": { - "kind": "Panel", - "spec": { - "id": 1, - "title": "Panel with TimeSeriesTable Transformation - Single Stat", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [], - "transformations": [ - { - "kind": "timeSeriesTable", - "spec": { - "id": "timeSeriesTable", - "options": { - "A": { - "stat": "mean" - } - } - } - } - ], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "table", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-10": { - "kind": "Panel", - "spec": { - "id": 10, - "title": "Nested Panel with TimeSeriesTable", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [], - "transformations": [ - { - "kind": "timeSeriesTable", - "spec": { - "id": "timeSeriesTable", - "options": { - "NestedA": { - "stat": "median" - }, - "NestedB": { - "stat": "stdDev" - } - } - } - } - ], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "table", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-2": { - "kind": "Panel", - "spec": { - "id": 2, - "title": "Panel with TimeSeriesTable Transformation - Multiple Stats", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [], - "transformations": [ - { - "kind": "timeSeriesTable", - "spec": { - "id": "timeSeriesTable", - "options": { - "A": { - "stat": "mean" - }, - "B": { - "stat": "max" - }, - "C": { - "stat": "min" - }, - "D": { - "stat": "sum" - } - } - } - } - ], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "table", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-3": { - "kind": "Panel", - "spec": { - "id": 3, - "title": "Panel with TimeSeriesTable Transformation - Mixed with Other Transforms", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [], - "transformations": [ - { - "kind": "reduce", - "spec": { - "id": "reduce", - "options": { - "reducers": [ - "mean" - ] - } - } - }, - { - "kind": "timeSeriesTable", - "spec": { - "id": "timeSeriesTable", - "options": { - "A": { - "stat": "last" - }, - "B": { - "stat": "first" - } - } - } - }, - { - "kind": "organize", - "spec": { - "id": "organize", - "options": { - "excludeByName": {} - } - } - } - ], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-4": { - "kind": "Panel", - "spec": { - "id": 4, - "title": "Panel with Non-TimeSeriesTable Transformation (Should Remain Unchanged)", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [], - "transformations": [ - { - "kind": "reduce", - "spec": { - "id": "reduce", - "options": { - "reducers": [ - "mean", - "max" - ] - } - } - } - ], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "stat", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-5": { - "kind": "Panel", - "spec": { - "id": 5, - "title": "Panel with TimeSeriesTable - Empty RefIdToStat", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [], - "transformations": [ - { - "kind": "timeSeriesTable", - "spec": { - "id": "timeSeriesTable", - "options": {} - } - } - ], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "table", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-6": { - "kind": "Panel", - "spec": { - "id": 6, - "title": "Panel with TimeSeriesTable - No Options (Should Skip)", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [], - "transformations": [ - { - "kind": "timeSeriesTable", - "spec": { - "id": "timeSeriesTable", - "options": null - } - } - ], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "table", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-7": { - "kind": "Panel", - "spec": { - "id": 7, - "title": "Panel with TimeSeriesTable - Invalid Options (Should Skip)", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [], - "transformations": [ - { - "kind": "timeSeriesTable", - "spec": { - "id": "timeSeriesTable", - "options": { - "someOtherOption": "value" - } - } - } - ], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "table", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-8": { - "kind": "Panel", - "spec": { - "id": 8, - "title": "Panel with No Transformations (Should Remain Unchanged)", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - } + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" }, - "layout": { - "kind": "RowsLayout", - "spec": { - "rows": [ - { - "kind": "RowsLayoutRow", - "spec": { - "title": "", - "collapse": false, - "hideHeader": true, - "layout": { - "kind": "GridLayout", - "spec": { - "items": [ - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-1" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-2" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-3" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-4" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-5" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-6" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-7" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-8" - } - } - } - ] - } - } - } - }, - { - "kind": "RowsLayoutRow", - "spec": { - "title": "Row with Nested Panels Having TimeSeriesTable Transformations", - "collapse": false, - "layout": { - "kind": "GridLayout", - "spec": { - "items": [ - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-10" - } - } - } - ] - } - } - } - } + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "liveNow": false, + "panels": [ + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 1, + "options": {}, + "pluginVersion": "", + "targets": [], + "title": "Panel with TimeSeriesTable Transformation - Single Stat", + "transformations": [ + { + "id": "timeSeriesTable", + "options": { + "A": { + "stat": "mean" + } + } + } + ], + "type": "table" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 2, + "options": {}, + "pluginVersion": "", + "targets": [], + "title": "Panel with TimeSeriesTable Transformation - Multiple Stats", + "transformations": [ + { + "id": "timeSeriesTable", + "options": { + "A": { + "stat": "mean" + }, + "B": { + "stat": "max" + }, + "C": { + "stat": "min" + }, + "D": { + "stat": "sum" + } + } + } + ], + "type": "table" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 3, + "options": {}, + "pluginVersion": "", + "targets": [], + "title": "Panel with TimeSeriesTable Transformation - Mixed with Other Transforms", + "transformations": [ + { + "id": "reduce", + "options": { + "reducers": [ + "mean" ] } }, - "links": [], - "liveNow": false, - "preload": false, - "tags": [], - "timeSettings": { - "timezone": "", - "from": "now-6h", - "to": "now", - "autoRefresh": "", - "autoRefreshIntervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "hideTimepicker": false, - "fiscalYearStartMonth": 0 + { + "id": "timeSeriesTable", + "options": { + "A": { + "stat": "last" + }, + "B": { + "stat": "first" + } + } }, - "title": "V39 TimeSeriesTable Transformation Migration Test Dashboard", - "variables": [] + { + "id": "organize", + "options": { + "excludeByName": {} + } + } + ], + "type": "timeseries" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 }, - "status": {} + "id": 4, + "options": {}, + "pluginVersion": "", + "targets": [], + "title": "Panel with Non-TimeSeriesTable Transformation (Should Remain Unchanged)", + "transformations": [ + { + "id": "reduce", + "options": { + "reducers": [ + "mean", + "max" + ] + } + } + ], + "type": "stat" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 5, + "options": {}, + "pluginVersion": "", + "targets": [], + "title": "Panel with TimeSeriesTable - Empty RefIdToStat", + "transformations": [ + { + "id": "timeSeriesTable", + "options": {} + } + ], + "type": "table" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 6, + "options": {}, + "pluginVersion": "", + "targets": [], + "title": "Panel with TimeSeriesTable - No Options (Should Skip)", + "transformations": [ + { + "id": "timeSeriesTable", + "options": null + } + ], + "type": "table" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 7, + "options": {}, + "pluginVersion": "", + "targets": [], + "title": "Panel with TimeSeriesTable - Invalid Options (Should Skip)", + "transformations": [ + { + "id": "timeSeriesTable", + "options": { + "someOtherOption": "value" + } + } + ], + "type": "table" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 8, + "options": {}, + "pluginVersion": "", + "targets": [], + "title": "Panel with No Transformations (Should Remain Unchanged)", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 3 + }, + "id": -1, + "title": "Row with Nested Panels Having TimeSeriesTable Transformations", + "type": "row" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 3 + }, + "id": 10, + "options": {}, + "pluginVersion": "", + "targets": [], + "title": "Nested Panel with TimeSeriesTable", + "transformations": [ + { + "id": "timeSeriesTable", + "options": { + "NestedA": { + "stat": "median" + }, + "NestedB": { + "stat": "stdDev" + } + } + } + ], + "type": "table" } + ], + "preload": false, + "refresh": "", + "schemaVersion": 42, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "V39 TimeSeriesTable Transformation Migration Test Dashboard" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v2beta1" } } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v4.no-op.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v4.no-op.v0alpha1.json index 3a560f9acf5..5daafe44636 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v4.no-op.v0alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v4.no-op.v0alpha1.json @@ -4,260 +4,123 @@ "metadata": { "name": "v4.no-op.v42" }, - "spec": null, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "liveNow": false, + "panels": [ + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 1, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "", + "type": "stat" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 2, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "", + "type": "stat" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 3, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "", + "type": "timeseries" + } + ], + "preload": false, + "refresh": "", + "schemaVersion": 42, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "V4 No-Op Migration Test" + }, "status": { "conversion": { - "failed": true, - "error": "backend conversion not yet implemented", - "storedVersion": "v2beta1", - "source": { - "kind": "Dashboard", - "apiVersion": "dashboard.grafana.app/v2beta1", - "metadata": { - "name": "v4.no-op.v42" - }, - "spec": { - "annotations": [ - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana", - "version": "v0", - "datasource": { - "name": "-- Grafana --" - }, - "spec": {} - }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations \u0026 Alerts", - "builtIn": true, - "legacyOptions": { - "type": "dashboard" - } - } - } - ], - "cursorSync": "Off", - "editable": true, - "elements": { - "panel-1": { - "kind": "Panel", - "spec": { - "id": 1, - "title": "", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "stat", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-2": { - "kind": "Panel", - "spec": { - "id": 2, - "title": "", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "stat", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-3": { - "kind": "Panel", - "spec": { - "id": 3, - "title": "", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - } - }, - "layout": { - "kind": "GridLayout", - "spec": { - "items": [ - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-1" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-2" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-3" - } - } - } - ] - } - }, - "links": [], - "liveNow": false, - "preload": false, - "tags": [], - "timeSettings": { - "timezone": "", - "from": "now-6h", - "to": "now", - "autoRefresh": "", - "autoRefreshIntervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "hideTimepicker": false, - "fiscalYearStartMonth": 0 - }, - "title": "V4 No-Op Migration Test", - "variables": [] - }, - "status": {} - } + "failed": false, + "storedVersion": "v2beta1" } } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v40.refresh_empty_string.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v40.refresh_empty_string.v0alpha1.json index f017236b335..238fd0e0861 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v40.refresh_empty_string.v0alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v40.refresh_empty_string.v0alpha1.json @@ -4,81 +4,55 @@ "metadata": { "name": "v40.refresh_empty_string.v42" }, - "spec": null, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "liveNow": false, + "preload": false, + "refresh": "", + "schemaVersion": 42, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "Empty String Refresh Test Dashboard" + }, "status": { "conversion": { - "failed": true, - "error": "backend conversion not yet implemented", - "storedVersion": "v2beta1", - "source": { - "kind": "Dashboard", - "apiVersion": "dashboard.grafana.app/v2beta1", - "metadata": { - "name": "v40.refresh_empty_string.v42" - }, - "spec": { - "annotations": [ - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana", - "version": "v0", - "datasource": { - "name": "-- Grafana --" - }, - "spec": {} - }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations \u0026 Alerts", - "builtIn": true, - "legacyOptions": { - "type": "dashboard" - } - } - } - ], - "cursorSync": "Off", - "editable": true, - "elements": {}, - "layout": { - "kind": "GridLayout", - "spec": { - "items": [] - } - }, - "links": [], - "liveNow": false, - "preload": false, - "tags": [], - "timeSettings": { - "timezone": "", - "from": "now-6h", - "to": "now", - "autoRefresh": "", - "autoRefreshIntervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "hideTimepicker": false, - "fiscalYearStartMonth": 0 - }, - "title": "Empty String Refresh Test Dashboard", - "variables": [] - }, - "status": {} - } + "failed": false, + "storedVersion": "v2beta1" } } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v40.refresh_false.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v40.refresh_false.v0alpha1.json index a1f67edc2fe..2e51011773f 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v40.refresh_false.v0alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v40.refresh_false.v0alpha1.json @@ -4,81 +4,55 @@ "metadata": { "name": "v40.refresh_false.v42" }, - "spec": null, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "liveNow": false, + "preload": false, + "refresh": "", + "schemaVersion": 42, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "Boolean False Refresh Test Dashboard" + }, "status": { "conversion": { - "failed": true, - "error": "backend conversion not yet implemented", - "storedVersion": "v2beta1", - "source": { - "kind": "Dashboard", - "apiVersion": "dashboard.grafana.app/v2beta1", - "metadata": { - "name": "v40.refresh_false.v42" - }, - "spec": { - "annotations": [ - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana", - "version": "v0", - "datasource": { - "name": "-- Grafana --" - }, - "spec": {} - }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations \u0026 Alerts", - "builtIn": true, - "legacyOptions": { - "type": "dashboard" - } - } - } - ], - "cursorSync": "Off", - "editable": true, - "elements": {}, - "layout": { - "kind": "GridLayout", - "spec": { - "items": [] - } - }, - "links": [], - "liveNow": false, - "preload": false, - "tags": [], - "timeSettings": { - "timezone": "", - "from": "now-6h", - "to": "now", - "autoRefresh": "", - "autoRefreshIntervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "hideTimepicker": false, - "fiscalYearStartMonth": 0 - }, - "title": "Boolean False Refresh Test Dashboard", - "variables": [] - }, - "status": {} - } + "failed": false, + "storedVersion": "v2beta1" } } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v40.refresh_not_set.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v40.refresh_not_set.v0alpha1.json index 7e3758e1f87..43bd2fe817e 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v40.refresh_not_set.v0alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v40.refresh_not_set.v0alpha1.json @@ -4,81 +4,55 @@ "metadata": { "name": "v40.refresh_not_set.v42" }, - "spec": null, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "liveNow": false, + "preload": false, + "refresh": "", + "schemaVersion": 42, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "Refresh Not Set Test Dashboard" + }, "status": { "conversion": { - "failed": true, - "error": "backend conversion not yet implemented", - "storedVersion": "v2beta1", - "source": { - "kind": "Dashboard", - "apiVersion": "dashboard.grafana.app/v2beta1", - "metadata": { - "name": "v40.refresh_not_set.v42" - }, - "spec": { - "annotations": [ - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana", - "version": "v0", - "datasource": { - "name": "-- Grafana --" - }, - "spec": {} - }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations \u0026 Alerts", - "builtIn": true, - "legacyOptions": { - "type": "dashboard" - } - } - } - ], - "cursorSync": "Off", - "editable": true, - "elements": {}, - "layout": { - "kind": "GridLayout", - "spec": { - "items": [] - } - }, - "links": [], - "liveNow": false, - "preload": false, - "tags": [], - "timeSettings": { - "timezone": "", - "from": "now-6h", - "to": "now", - "autoRefresh": "", - "autoRefreshIntervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "hideTimepicker": false, - "fiscalYearStartMonth": 0 - }, - "title": "Refresh Not Set Test Dashboard", - "variables": [] - }, - "status": {} - } + "failed": false, + "storedVersion": "v2beta1" } } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v40.refresh_numeric.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v40.refresh_numeric.v0alpha1.json index 8af0bfdc64d..30b4531dcd0 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v40.refresh_numeric.v0alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v40.refresh_numeric.v0alpha1.json @@ -4,81 +4,55 @@ "metadata": { "name": "v40.refresh_numeric.v42" }, - "spec": null, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "liveNow": false, + "preload": false, + "refresh": "", + "schemaVersion": 42, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "Numeric Refresh Test Dashboard" + }, "status": { "conversion": { - "failed": true, - "error": "backend conversion not yet implemented", - "storedVersion": "v2beta1", - "source": { - "kind": "Dashboard", - "apiVersion": "dashboard.grafana.app/v2beta1", - "metadata": { - "name": "v40.refresh_numeric.v42" - }, - "spec": { - "annotations": [ - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana", - "version": "v0", - "datasource": { - "name": "-- Grafana --" - }, - "spec": {} - }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations \u0026 Alerts", - "builtIn": true, - "legacyOptions": { - "type": "dashboard" - } - } - } - ], - "cursorSync": "Off", - "editable": true, - "elements": {}, - "layout": { - "kind": "GridLayout", - "spec": { - "items": [] - } - }, - "links": [], - "liveNow": false, - "preload": false, - "tags": [], - "timeSettings": { - "timezone": "", - "from": "now-6h", - "to": "now", - "autoRefresh": "", - "autoRefreshIntervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "hideTimepicker": false, - "fiscalYearStartMonth": 0 - }, - "title": "Numeric Refresh Test Dashboard", - "variables": [] - }, - "status": {} - } + "failed": false, + "storedVersion": "v2beta1" } } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v40.refresh_string.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v40.refresh_string.v0alpha1.json index 99ff57db3b0..e16149e4bea 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v40.refresh_string.v0alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v40.refresh_string.v0alpha1.json @@ -4,81 +4,55 @@ "metadata": { "name": "v40.refresh_string.v42" }, - "spec": null, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "liveNow": false, + "preload": false, + "refresh": "1m", + "schemaVersion": 42, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "String Refresh Test Dashboard" + }, "status": { "conversion": { - "failed": true, - "error": "backend conversion not yet implemented", - "storedVersion": "v2beta1", - "source": { - "kind": "Dashboard", - "apiVersion": "dashboard.grafana.app/v2beta1", - "metadata": { - "name": "v40.refresh_string.v42" - }, - "spec": { - "annotations": [ - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana", - "version": "v0", - "datasource": { - "name": "-- Grafana --" - }, - "spec": {} - }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations \u0026 Alerts", - "builtIn": true, - "legacyOptions": { - "type": "dashboard" - } - } - } - ], - "cursorSync": "Off", - "editable": true, - "elements": {}, - "layout": { - "kind": "GridLayout", - "spec": { - "items": [] - } - }, - "links": [], - "liveNow": false, - "preload": false, - "tags": [], - "timeSettings": { - "timezone": "", - "from": "now-6h", - "to": "now", - "autoRefresh": "1m", - "autoRefreshIntervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "hideTimepicker": false, - "fiscalYearStartMonth": 0 - }, - "title": "String Refresh Test Dashboard", - "variables": [] - }, - "status": {} - } + "failed": false, + "storedVersion": "v2beta1" } } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v40.refresh_true.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v40.refresh_true.v0alpha1.json index d02ec352241..08a1150b27f 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v40.refresh_true.v0alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v40.refresh_true.v0alpha1.json @@ -4,81 +4,55 @@ "metadata": { "name": "v40.refresh_true.v42" }, - "spec": null, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "liveNow": false, + "preload": false, + "refresh": "", + "schemaVersion": 42, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "Boolean Refresh Test Dashboard" + }, "status": { "conversion": { - "failed": true, - "error": "backend conversion not yet implemented", - "storedVersion": "v2beta1", - "source": { - "kind": "Dashboard", - "apiVersion": "dashboard.grafana.app/v2beta1", - "metadata": { - "name": "v40.refresh_true.v42" - }, - "spec": { - "annotations": [ - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana", - "version": "v0", - "datasource": { - "name": "-- Grafana --" - }, - "spec": {} - }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations \u0026 Alerts", - "builtIn": true, - "legacyOptions": { - "type": "dashboard" - } - } - } - ], - "cursorSync": "Off", - "editable": true, - "elements": {}, - "layout": { - "kind": "GridLayout", - "spec": { - "items": [] - } - }, - "links": [], - "liveNow": false, - "preload": false, - "tags": [], - "timeSettings": { - "timezone": "", - "from": "now-6h", - "to": "now", - "autoRefresh": "", - "autoRefreshIntervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "hideTimepicker": false, - "fiscalYearStartMonth": 0 - }, - "title": "Boolean Refresh Test Dashboard", - "variables": [] - }, - "status": {} - } + "failed": false, + "storedVersion": "v2beta1" } } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v41.no_time_picker.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v41.no_time_picker.v0alpha1.json index fb208d18b60..077deeb8d51 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v41.no_time_picker.v0alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v41.no_time_picker.v0alpha1.json @@ -4,81 +4,55 @@ "metadata": { "name": "v41.no_time_picker.v42" }, - "spec": null, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "liveNow": false, + "preload": false, + "refresh": "", + "schemaVersion": 42, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "No Time Picker Test Dashboard" + }, "status": { "conversion": { - "failed": true, - "error": "backend conversion not yet implemented", - "storedVersion": "v2beta1", - "source": { - "kind": "Dashboard", - "apiVersion": "dashboard.grafana.app/v2beta1", - "metadata": { - "name": "v41.no_time_picker.v42" - }, - "spec": { - "annotations": [ - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana", - "version": "v0", - "datasource": { - "name": "-- Grafana --" - }, - "spec": {} - }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations \u0026 Alerts", - "builtIn": true, - "legacyOptions": { - "type": "dashboard" - } - } - } - ], - "cursorSync": "Off", - "editable": true, - "elements": {}, - "layout": { - "kind": "GridLayout", - "spec": { - "items": [] - } - }, - "links": [], - "liveNow": false, - "preload": false, - "tags": [], - "timeSettings": { - "timezone": "", - "from": "now-6h", - "to": "now", - "autoRefresh": "", - "autoRefreshIntervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "hideTimepicker": false, - "fiscalYearStartMonth": 0 - }, - "title": "No Time Picker Test Dashboard", - "variables": [] - }, - "status": {} - } + "failed": false, + "storedVersion": "v2beta1" } } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v41.time_picker_no_time_options.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v41.time_picker_no_time_options.v0alpha1.json index c97f595093f..4547a681dfe 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v41.time_picker_no_time_options.v0alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v41.time_picker_no_time_options.v0alpha1.json @@ -4,81 +4,55 @@ "metadata": { "name": "v41.time_picker_no_time_options.v42" }, - "spec": null, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "liveNow": false, + "preload": false, + "refresh": "", + "schemaVersion": 42, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "Time Picker No Time Options Test Dashboard" + }, "status": { "conversion": { - "failed": true, - "error": "backend conversion not yet implemented", - "storedVersion": "v2beta1", - "source": { - "kind": "Dashboard", - "apiVersion": "dashboard.grafana.app/v2beta1", - "metadata": { - "name": "v41.time_picker_no_time_options.v42" - }, - "spec": { - "annotations": [ - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana", - "version": "v0", - "datasource": { - "name": "-- Grafana --" - }, - "spec": {} - }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations \u0026 Alerts", - "builtIn": true, - "legacyOptions": { - "type": "dashboard" - } - } - } - ], - "cursorSync": "Off", - "editable": true, - "elements": {}, - "layout": { - "kind": "GridLayout", - "spec": { - "items": [] - } - }, - "links": [], - "liveNow": false, - "preload": false, - "tags": [], - "timeSettings": { - "timezone": "", - "from": "now-6h", - "to": "now", - "autoRefresh": "", - "autoRefreshIntervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "hideTimepicker": false, - "fiscalYearStartMonth": 0 - }, - "title": "Time Picker No Time Options Test Dashboard", - "variables": [] - }, - "status": {} - } + "failed": false, + "storedVersion": "v2beta1" } } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v41.time_picker_time_options.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v41.time_picker_time_options.v0alpha1.json index fd6af872656..708f2cb7bdc 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v41.time_picker_time_options.v0alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v41.time_picker_time_options.v0alpha1.json @@ -4,81 +4,55 @@ "metadata": { "name": "v41.time_picker_time_options.v42" }, - "spec": null, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "liveNow": false, + "preload": false, + "refresh": "", + "schemaVersion": 42, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "Time Picker Time Options Test Dashboard" + }, "status": { "conversion": { - "failed": true, - "error": "backend conversion not yet implemented", - "storedVersion": "v2beta1", - "source": { - "kind": "Dashboard", - "apiVersion": "dashboard.grafana.app/v2beta1", - "metadata": { - "name": "v41.time_picker_time_options.v42" - }, - "spec": { - "annotations": [ - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana", - "version": "v0", - "datasource": { - "name": "-- Grafana --" - }, - "spec": {} - }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations \u0026 Alerts", - "builtIn": true, - "legacyOptions": { - "type": "dashboard" - } - } - } - ], - "cursorSync": "Off", - "editable": true, - "elements": {}, - "layout": { - "kind": "GridLayout", - "spec": { - "items": [] - } - }, - "links": [], - "liveNow": false, - "preload": false, - "tags": [], - "timeSettings": { - "timezone": "", - "from": "now-6h", - "to": "now", - "autoRefresh": "", - "autoRefreshIntervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "hideTimepicker": false, - "fiscalYearStartMonth": 0 - }, - "title": "Time Picker Time Options Test Dashboard", - "variables": [] - }, - "status": {} - } + "failed": false, + "storedVersion": "v2beta1" } } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v42.harky_must.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v42.harky_must.v0alpha1.json index 519b1852887..da0b6aed0a7 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v42.harky_must.v0alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v42.harky_must.v0alpha1.json @@ -4,141 +4,90 @@ "metadata": { "name": "v42.harky_must.v42" }, - "spec": null, - "status": { - "conversion": { - "failed": true, - "error": "backend conversion not yet implemented", - "storedVersion": "v2beta1", - "source": { - "kind": "Dashboard", - "apiVersion": "dashboard.grafana.app/v2beta1", - "metadata": { - "name": "v42.harky_must.v42" - }, - "spec": { - "annotations": [ - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana", - "version": "v0", - "datasource": { - "name": "-- Grafana --" - }, - "spec": {} - }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations \u0026 Alerts", - "builtIn": true, - "legacyOptions": { - "type": "dashboard" - } - } - } - ], - "cursorSync": "Off", - "editable": true, - "elements": { - "panel-1": { - "kind": "Panel", - "spec": { - "id": 1, - "title": "Panel with hideFrom.viz = true", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "Field1" - }, - "properties": [ - { - "id": "custom.hideFrom", - "value": { - "tooltip": true, - "viz": true - } - } - ] - } - ] - } - } - } - } - } + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" }, - "layout": { - "kind": "GridLayout", - "spec": { - "items": [ + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "liveNow": false, + "panels": [ + { + "fieldConfig": { + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Field1" + }, + "properties": [ { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-1" - } + "id": "custom.hideFrom", + "value": { + "tooltip": true, + "viz": true } } ] } - }, - "links": [], - "liveNow": false, - "preload": false, - "tags": [], - "timeSettings": { - "timezone": "", - "from": "now-6h", - "to": "now", - "autoRefresh": "", - "autoRefreshIntervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "hideTimepicker": false, - "fiscalYearStartMonth": 0 - }, - "title": "v42 Migration Test - Harky Must", - "variables": [] + ] }, - "status": {} + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 1, + "options": {}, + "pluginVersion": "", + "targets": [], + "title": "Panel with hideFrom.viz = true", + "type": "timeseries" } + ], + "preload": false, + "refresh": "", + "schemaVersion": 42, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "v42 Migration Test - Harky Must" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v2beta1" } } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v42.hidefrom_tooltip.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v42.hidefrom_tooltip.v0alpha1.json index 14b9e4e7d31..4f1a1cd446e 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v42.hidefrom_tooltip.v0alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v42.hidefrom_tooltip.v0alpha1.json @@ -4,484 +4,293 @@ "metadata": { "name": "v42.hidefrom_tooltip.v42" }, - "spec": null, - "status": { - "conversion": { - "failed": true, - "error": "backend conversion not yet implemented", - "storedVersion": "v2beta1", - "source": { - "kind": "Dashboard", - "apiVersion": "dashboard.grafana.app/v2beta1", - "metadata": { - "name": "v42.hidefrom_tooltip.v42" - }, - "spec": { - "annotations": [ - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana", - "version": "v0", - "datasource": { - "name": "-- Grafana --" - }, - "spec": {} - }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations \u0026 Alerts", - "builtIn": true, - "legacyOptions": { - "type": "dashboard" - } - } - } - ], - "cursorSync": "Off", - "editable": true, - "elements": { - "panel-1": { - "kind": "Panel", - "spec": { - "id": 1, - "title": "Panel with hideFrom.viz = true", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "Field1" - }, - "properties": [ - { - "id": "custom.hideFrom", - "value": { - "tooltip": true, - "viz": true - } - } - ] - } - ] - } - } - } - } - }, - "panel-2": { - "kind": "Panel", - "spec": { - "id": 2, - "title": "Panel with multiple overrides", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "Field2" - }, - "properties": [ - { - "id": "custom.hideFrom", - "value": { - "legend": false, - "tooltip": true, - "viz": true - } - } - ] - }, - { - "__systemRef": "hideSeriesFrom", - "matcher": { - "id": "byNames", - "options": { - "mode": "exclude", - "names": [ - "foo" - ], - "prefix": "All except:", - "readOnly": true - } - }, - "properties": [ - { - "id": "custom.hideFrom", - "value": { - "legend": false, - "tooltip": true, - "viz": true - } - } - ] - } - ] - } - } - } - } - }, - "panel-4": { - "kind": "Panel", - "spec": { - "id": 4, - "title": "Nested panel with hideFrom", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "stat", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [ - { - "matcher": { - "id": "byRegexp", - "options": "/.*/" - }, - "properties": [ - { - "id": "custom.hideFrom", - "value": { - "tooltip": true, - "viz": true - } - } - ] - } - ] - } - } - } - } - }, - "panel-5": { - "kind": "Panel", - "spec": { - "id": 5, - "title": "Panel without hideFrom", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "table", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "Time" - }, - "properties": [ - { - "id": "unit", - "value": "short" - } - ] - } - ] - } - } - } - } - }, - "panel-6": { - "kind": "Panel", - "spec": { - "id": 6, - "title": "Panel with viz false (should not be modified)", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "gauge", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [ - { - "matcher": { - "id": "byValue", - "options": { - "op": "gte", - "reducer": "allIsZero", - "value": 0 - } - }, - "properties": [ - { - "id": "unit", - "value": "short" - } - ] - } - ] - } - } - } - } - }, - "panel-7": { - "kind": "Panel", - "spec": { - "id": 7, - "title": "Panel with already set tooltip (should not be modified)", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "barchart", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "{__name__=\"ALERTS\", alertname=\"k6CloudServiceErrorsLogged\"}" - }, - "properties": [ - { - "id": "custom.hideFrom", - "value": { - "legend": false, - "tooltip": true, - "viz": true - } - } - ] - } - ] - } - } - } - } - } + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" }, - "layout": { - "kind": "RowsLayout", - "spec": { - "rows": [ + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "liveNow": false, + "panels": [ + { + "fieldConfig": { + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Field1" + }, + "properties": [ { - "kind": "RowsLayoutRow", - "spec": { - "title": "", - "collapse": true, - "hideHeader": true, - "layout": { - "kind": "GridLayout", - "spec": { - "items": [ - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-1" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-2" - } - } - } - ] - } - } - } - }, - { - "kind": "RowsLayoutRow", - "spec": { - "title": "Row with nested panels", - "collapse": true, - "layout": { - "kind": "GridLayout", - "spec": { - "items": [ - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-4" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-5" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-6" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-7" - } - } - } - ] - } - } + "id": "custom.hideFrom", + "value": { + "tooltip": true, + "viz": true } } ] } - }, - "links": [], - "liveNow": false, - "preload": false, - "tags": [], - "timeSettings": { - "timezone": "", - "from": "now-6h", - "to": "now", - "autoRefresh": "", - "autoRefreshIntervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "hideTimepicker": false, - "fiscalYearStartMonth": 0 - }, - "title": "v42 Migration Test - HideFrom Tooltip", - "variables": [] + ] }, - "status": {} + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 1, + "options": {}, + "pluginVersion": "", + "targets": [], + "title": "Panel with hideFrom.viz = true", + "type": "timeseries" + }, + { + "fieldConfig": { + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Field2" + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "legend": false, + "tooltip": true, + "viz": true + } + } + ] + }, + { + "__systemRef": "hideSeriesFrom", + "matcher": { + "id": "byNames", + "options": { + "mode": "exclude", + "names": [ + "foo" + ], + "prefix": "All except:", + "readOnly": true + } + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "legend": false, + "tooltip": true, + "viz": true + } + } + ] + } + ] + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 2, + "options": {}, + "pluginVersion": "", + "targets": [], + "title": "Panel with multiple overrides", + "type": "timeseries" + }, + { + "collapsed": true, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 3 + }, + "id": -1, + "panels": [ + { + "fieldConfig": { + "overrides": [ + { + "matcher": { + "id": "byRegexp", + "options": "/.*/" + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "tooltip": true, + "viz": true + } + } + ] + } + ] + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 4 + }, + "id": 4, + "options": {}, + "pluginVersion": "", + "targets": [], + "title": "Nested panel with hideFrom", + "type": "stat" + }, + { + "fieldConfig": { + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Time" + }, + "properties": [ + { + "id": "unit", + "value": "short" + } + ] + } + ] + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 4 + }, + "id": 5, + "options": {}, + "pluginVersion": "", + "targets": [], + "title": "Panel without hideFrom", + "type": "table" + }, + { + "fieldConfig": { + "overrides": [ + { + "matcher": { + "id": "byValue", + "options": { + "op": "gte", + "reducer": "allIsZero", + "value": 0 + } + }, + "properties": [ + { + "id": "unit", + "value": "short" + } + ] + } + ] + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 4 + }, + "id": 6, + "options": {}, + "pluginVersion": "", + "targets": [], + "title": "Panel with viz false (should not be modified)", + "type": "gauge" + }, + { + "fieldConfig": { + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "{__name__=\"ALERTS\", alertname=\"k6CloudServiceErrorsLogged\"}" + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "legend": false, + "tooltip": true, + "viz": true + } + } + ] + } + ] + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 4 + }, + "id": 7, + "options": {}, + "pluginVersion": "", + "targets": [], + "title": "Panel with already set tooltip (should not be modified)", + "type": "barchart" + } + ], + "title": "Row with nested panels", + "type": "row" } + ], + "preload": false, + "refresh": "", + "schemaVersion": 42, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "v42 Migration Test - HideFrom Tooltip" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v2beta1" } } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v5.no-op.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v5.no-op.v0alpha1.json index c411f444308..4872dd21894 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v5.no-op.v0alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v5.no-op.v0alpha1.json @@ -4,260 +4,123 @@ "metadata": { "name": "v5.no-op.v42" }, - "spec": null, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "liveNow": false, + "panels": [ + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 1, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "", + "type": "stat" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 2, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "", + "type": "stat" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 3, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "", + "type": "timeseries" + } + ], + "preload": false, + "refresh": "", + "schemaVersion": 42, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "V5 No-Op Migration Test" + }, "status": { "conversion": { - "failed": true, - "error": "backend conversion not yet implemented", - "storedVersion": "v2beta1", - "source": { - "kind": "Dashboard", - "apiVersion": "dashboard.grafana.app/v2beta1", - "metadata": { - "name": "v5.no-op.v42" - }, - "spec": { - "annotations": [ - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana", - "version": "v0", - "datasource": { - "name": "-- Grafana --" - }, - "spec": {} - }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations \u0026 Alerts", - "builtIn": true, - "legacyOptions": { - "type": "dashboard" - } - } - } - ], - "cursorSync": "Off", - "editable": true, - "elements": { - "panel-1": { - "kind": "Panel", - "spec": { - "id": 1, - "title": "", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "stat", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-2": { - "kind": "Panel", - "spec": { - "id": 2, - "title": "", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "stat", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-3": { - "kind": "Panel", - "spec": { - "id": 3, - "title": "", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - } - }, - "layout": { - "kind": "GridLayout", - "spec": { - "items": [ - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-1" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-2" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-3" - } - } - } - ] - } - }, - "links": [], - "liveNow": false, - "preload": false, - "tags": [], - "timeSettings": { - "timezone": "", - "from": "now-6h", - "to": "now", - "autoRefresh": "", - "autoRefreshIntervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "hideTimepicker": false, - "fiscalYearStartMonth": 0 - }, - "title": "V5 No-Op Migration Test", - "variables": [] - }, - "status": {} - } + "failed": false, + "storedVersion": "v2beta1" } } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v6.pulldowns_and_templating.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v6.pulldowns_and_templating.v0alpha1.json index 2691389f814..7f61b3e7b90 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v6.pulldowns_and_templating.v0alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v6.pulldowns_and_templating.v0alpha1.json @@ -4,360 +4,219 @@ "metadata": { "name": "v6.pulldowns_and_templating.v42" }, - "spec": null, - "status": { - "conversion": { - "failed": true, - "error": "backend conversion not yet implemented", - "storedVersion": "v2beta1", - "source": { - "kind": "Dashboard", - "apiVersion": "dashboard.grafana.app/v2beta1", - "metadata": { - "name": "v6.pulldowns_and_templating.v42" + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" }, - "spec": { - "annotations": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "enable": true, + "hide": false, + "iconColor": "red", + "name": "deployment", + "query": "ALERTS{alertname=\"DeploymentStarted\"}" + }, + { + "datasource": { + "type": "loki", + "uid": "loki-uid" + }, + "enable": false, + "hide": false, + "iconColor": "yellow", + "name": "alerts", + "query": "{job=\"alertmanager\"}" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "liveNow": false, + "panels": [ + { + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 1, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "expr": "cpu_usage{environment=\"$environment\", service=\"$service\"}", + "refId": "A" + } + ], + "title": "CPU Usage", + "type": "timeseries" + }, + { + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 0 + }, + "id": 2, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "expr": "memory_usage{region=\"$region\"}", + "refId": "B" + } + ], + "title": "Memory Usage", + "type": "stat" + } + ], + "preload": false, + "refresh": "30s", + "schemaVersion": 42, + "templating": { + "list": [ + { + "allowCustomValue": true, + "current": { + "text": "", + "value": "" + }, + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "environment", + "options": [], + "query": "label_values(up, instance)", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "allowCustomValue": true, + "current": { + "text": "", + "value": "" + }, + "datasource": { + "type": "prometheus" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "service", + "options": [], + "query": "label_values(up, instance)", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "allowCustomValue": true, + "current": { + "text": "", + "value": "" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "region", + "options": [ { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana", - "version": "v0", - "datasource": { - "name": "-- Grafana --" - }, - "spec": {} - }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations \u0026 Alerts", - "builtIn": true, - "legacyOptions": { - "type": "dashboard" - } - } + "selected": false, + "text": "us-east-1", + "value": "us-east-1" }, { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "enable": true, - "hide": false, - "iconColor": "red", - "name": "deployment", - "legacyOptions": { - "query": "ALERTS{alertname=\"DeploymentStarted\"}" - } - } - }, - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "loki", - "version": "v0", - "datasource": { - "name": "loki-uid" - }, - "spec": {} - }, - "enable": false, - "hide": false, - "iconColor": "yellow", - "name": "alerts", - "legacyOptions": { - "query": "{job=\"alertmanager\"}" - } - } + "selected": false, + "text": "us-west-2", + "value": "us-west-2" } ], - "cursorSync": "Off", - "editable": true, - "elements": { - "panel-1": { - "kind": "Panel", - "spec": { - "id": 1, - "title": "CPU Usage", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": { - "expr": "cpu_usage{environment=\"$environment\", service=\"$service\"}" - } - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-2": { - "kind": "Panel", - "spec": { - "id": 2, - "title": "Memory Usage", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": { - "expr": "memory_usage{region=\"$region\"}" - } - }, - "refId": "B", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "stat", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - } - }, - "layout": { - "kind": "GridLayout", - "spec": { - "items": [ - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 12, - "height": 8, - "element": { - "kind": "ElementReference", - "name": "panel-1" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 12, - "y": 0, - "width": 12, - "height": 8, - "element": { - "kind": "ElementReference", - "name": "panel-2" - } - } - } - ] - } - }, - "links": [], - "liveNow": false, - "preload": false, - "tags": [], - "timeSettings": { - "timezone": "", - "from": "now-1h", - "to": "now", - "autoRefresh": "30s", - "autoRefreshIntervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "hideTimepicker": false, - "fiscalYearStartMonth": 0 - }, - "title": "V6 Pulldowns and Template Variables Migration Test", - "variables": [ - { - "kind": "QueryVariable", - "spec": { - "name": "environment", - "current": { - "text": "", - "value": "" - }, - "hide": "dontHide", - "refresh": "onDashboardLoad", - "skipUrlSync": false, - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": { - "__legacyStringValue": "label_values(up, instance)" - } - }, - "regex": "", - "sort": "disabled", - "options": [], - "multi": false, - "includeAll": false, - "allowCustomValue": true - } - }, - { - "kind": "QueryVariable", - "spec": { - "name": "service", - "current": { - "text": "", - "value": "" - }, - "hide": "dontHide", - "refresh": "onDashboardLoad", - "skipUrlSync": false, - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "spec": { - "__legacyStringValue": "label_values(up, instance)" - } - }, - "regex": "", - "sort": "disabled", - "options": [], - "multi": false, - "includeAll": false, - "allowCustomValue": true - } - }, - { - "kind": "CustomVariable", - "spec": { - "name": "region", - "query": "", - "current": { - "text": "", - "value": "" - }, - "options": [ - { - "selected": false, - "text": "us-east-1", - "value": "us-east-1" - }, - { - "selected": false, - "text": "us-west-2", - "value": "us-west-2" - } - ], - "multi": false, - "includeAll": false, - "hide": "dontHide", - "skipUrlSync": false, - "allowCustomValue": true - } - }, - { - "kind": "QueryVariable", - "spec": { - "name": "instance", - "current": { - "text": "", - "value": "" - }, - "hide": "dontHide", - "refresh": "onDashboardLoad", - "skipUrlSync": false, - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "spec": { - "__legacyStringValue": "label_values(up, instance)" - } - }, - "regex": "", - "sort": "disabled", - "options": [], - "multi": false, - "includeAll": false, - "allowCustomValue": true - } - } - ] + "query": "", + "skipUrlSync": false, + "type": "custom" }, - "status": {} - } + { + "allowCustomValue": true, + "current": { + "text": "", + "value": "" + }, + "datasource": { + "type": "prometheus" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "instance", + "options": [], + "query": "label_values(up, instance)", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + } + ] + }, + "time": { + "from": "now-1h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "V6 Pulldowns and Template Variables Migration Test" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v2beta1" } } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v7.timepicker.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v7.timepicker.v0alpha1.json index 5efa0ef30b2..100c239dc08 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v7.timepicker.v0alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v7.timepicker.v0alpha1.json @@ -4,162 +4,88 @@ "metadata": { "name": "v7.timepicker.v42" }, - "spec": null, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "liveNow": false, + "panels": [ + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 1, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "expr": "up", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "expr": "cpu_usage", + "refId": "B" + } + ], + "title": "", + "type": "timeseries" + } + ], + "preload": false, + "refresh": "", + "schemaVersion": 42, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "No Title" + }, "status": { "conversion": { - "failed": true, - "error": "backend conversion not yet implemented", - "storedVersion": "v2beta1", - "source": { - "kind": "Dashboard", - "apiVersion": "dashboard.grafana.app/v2beta1", - "metadata": { - "name": "v7.timepicker.v42" - }, - "spec": { - "annotations": [ - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana", - "version": "v0", - "datasource": { - "name": "-- Grafana --" - }, - "spec": {} - }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations \u0026 Alerts", - "builtIn": true, - "legacyOptions": { - "type": "dashboard" - } - } - } - ], - "cursorSync": "Off", - "editable": true, - "elements": { - "panel-1": { - "kind": "Panel", - "spec": { - "id": 1, - "title": "", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": { - "expr": "up" - } - }, - "refId": "A", - "hidden": false - } - }, - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": { - "expr": "cpu_usage" - } - }, - "refId": "B", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - } - }, - "layout": { - "kind": "GridLayout", - "spec": { - "items": [ - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-1" - } - } - } - ] - } - }, - "links": [], - "liveNow": false, - "preload": false, - "tags": [], - "timeSettings": { - "timezone": "", - "from": "now-6h", - "to": "now", - "autoRefresh": "", - "autoRefreshIntervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "hideTimepicker": false, - "fiscalYearStartMonth": 0 - }, - "title": "No Title", - "variables": [] - }, - "status": {} - } + "failed": false, + "storedVersion": "v2beta1" } } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v9.no-op.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v9.no-op.v0alpha1.json index 4c97717ced1..3f575f7f5fc 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v9.no-op.v0alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dashboards_from_v0_to_v2/v2beta1.v9.no-op.v0alpha1.json @@ -4,260 +4,123 @@ "metadata": { "name": "v9.no-op.v42" }, - "spec": null, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "liveNow": false, + "panels": [ + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 1, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "", + "type": "stat" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 2, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "", + "type": "stat" + }, + { + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 3, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "", + "type": "timeseries" + } + ], + "preload": false, + "refresh": "", + "schemaVersion": 42, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "V9 No-Op Migration Test" + }, "status": { "conversion": { - "failed": true, - "error": "backend conversion not yet implemented", - "storedVersion": "v2beta1", - "source": { - "kind": "Dashboard", - "apiVersion": "dashboard.grafana.app/v2beta1", - "metadata": { - "name": "v9.no-op.v42" - }, - "spec": { - "annotations": [ - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana", - "version": "v0", - "datasource": { - "name": "-- Grafana --" - }, - "spec": {} - }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations \u0026 Alerts", - "builtIn": true, - "legacyOptions": { - "type": "dashboard" - } - } - } - ], - "cursorSync": "Off", - "editable": true, - "elements": { - "panel-1": { - "kind": "Panel", - "spec": { - "id": 1, - "title": "", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "stat", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-2": { - "kind": "Panel", - "spec": { - "id": 2, - "title": "", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "stat", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - }, - "panel-3": { - "kind": "Panel", - "spec": { - "id": 3, - "title": "", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "default-ds-uid" - }, - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - } - }, - "layout": { - "kind": "GridLayout", - "spec": { - "items": [ - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-1" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-2" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 6, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-3" - } - } - } - ] - } - }, - "links": [], - "liveNow": false, - "preload": false, - "tags": [], - "timeSettings": { - "timezone": "", - "from": "now-6h", - "to": "now", - "autoRefresh": "", - "autoRefreshIntervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "hideTimepicker": false, - "fiscalYearStartMonth": 0 - }, - "title": "V9 No-Op Migration Test", - "variables": [] - }, - "status": {} - } + "failed": false, + "storedVersion": "v2beta1" } } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.annotation-conversions.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.annotation-conversions.v0alpha1.json index 0e488407f34..10463c8e142 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.annotation-conversions.v0alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.annotation-conversions.v0alpha1.json @@ -239,4 +239,4 @@ "storedVersion": "v1beta1" } } -} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/v2alpha1.complete.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/v2alpha1.complete.v0alpha1.json index 18d0371f1c4..e3e0747bfc4 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/v2alpha1.complete.v0alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/v2alpha1.complete.v0alpha1.json @@ -10,516 +10,390 @@ "description": "Complete example of v2alpha1 dashboard features" } }, - "spec": null, - "status": { - "conversion": { - "failed": true, - "error": "backend conversion not yet implemented", - "storedVersion": "v2alpha1", - "source": { - "kind": "Dashboard", - "apiVersion": "dashboard.grafana.app/v2alpha1", - "metadata": { - "name": "test-v2alpha1-complete", - "labels": { - "category": "test" + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" }, - "annotations": { - "description": "Complete example of v2alpha1 dashboard features" - } + "enable": true, + "hide": false, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" }, - "spec": { - "annotations": [ - { - "kind": "AnnotationQuery", - "spec": { - "datasource": { - "type": "grafana", - "uid": "-- Grafana --" - }, - "query": { - "kind": "grafana", - "spec": { - "limit": 100, - "matchAny": false, - "tags": [], - "type": "dashboard" - } - }, - "enable": true, - "hide": false, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations \u0026 Alerts", - "builtIn": true - } - }, - { - "kind": "AnnotationQuery", - "spec": { - "datasource": { - "type": "prometheus", - "uid": "gdev-prometheus" - }, - "query": { - "kind": "prometheus", - "spec": { - "expr": "changes(process_start_time_seconds[1m])", - "refId": "Anno" - } - }, - "enable": true, - "hide": false, - "iconColor": "yellow", - "name": "Prometheus Annotations", - "builtIn": false - } - } - ], - "cursorSync": "Tooltip", - "description": "This dashboard demonstrates all features that need to be converted from v2alpha1 to v2beta1", - "editable": true, - "elements": { - "panel-1": { - "kind": "Panel", - "spec": { - "id": 1, - "title": "Panel with Conditional Rendering", - "description": "This panel demonstrates conditional rendering features", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "prometheus", - "spec": { - "expr": "up{job=\"grafana\"}" - } - }, - "datasource": { - "type": "prometheus", - "uid": "gdev-prometheus" - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [ - { - "kind": "reduce", - "spec": { - "id": "reduce", - "options": { - "includeTimeField": false, - "mode": "reduceFields", - "reducers": [ - "mean" - ] - } - } - } - ], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "stat", - "spec": { - "pluginVersion": "12.1.0-pre", - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "textMode": "auto" - }, - "fieldConfig": { - "defaults": { - "mappings": [ - { - "type": "value", - "options": { - "0": { - "text": "Down", - "color": "red" - }, - "1": { - "text": "Up", - "color": "green" - } - } - } - ], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": 0, - "color": "red" - }, - { - "value": 1, - "color": "green" - } - ] - }, - "color": { - "mode": "thresholds" - } - }, - "overrides": [] - } - } - } - } - } + { + "datasource": { + "type": "prometheus", + "uid": "gdev-prometheus" }, - "layout": { - "kind": "RowsLayout", - "spec": { - "rows": [ - { - "kind": "Row", - "spec": { - "title": "Conditional Row", - "collapse": false, - "hideHeader": false, - "fillScreen": false, - "conditionalRendering": { - "kind": "ConditionalRenderingGroup", - "spec": { - "visibility": "show", - "condition": "and", - "items": [ - { - "kind": "ConditionalRenderingVariable", - "spec": { - "variable": "group_by", - "operator": "includes", - "value": "instance" - } - }, - { - "kind": "ConditionalRenderingData", - "spec": { - "value": true - } - }, - { - "kind": "ConditionalRenderingTimeRangeSize", - "spec": { - "value": "1h" - } - } - ] - } - }, - "layout": { - "kind": "GridLayout", - "spec": { - "items": [ - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 24, - "height": 8, - "element": { - "kind": "ElementReference", - "name": "panel-1" - } - } - } - ] - } - } + "enable": true, + "hide": false, + "iconColor": "yellow", + "name": "Prometheus Annotations", + "target": { + "expr": "changes(process_start_time_seconds[1m])", + "refId": "Anno" + } + } + ] + }, + "description": "This dashboard demonstrates all features that need to be converted from v2alpha1 to v2beta1", + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 2, + "liveNow": true, + "panels": [ + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 0 + }, + "id": -1, + "title": "Conditional Row", + "type": "row" + }, + { + "description": "This panel demonstrates conditional rendering features", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [ + { + "options": { + "0": { + "color": "red", + "text": "Down" + }, + "1": { + "color": "green", + "text": "Up" } + }, + "type": "value" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red", + "value": 0 + }, + { + "color": "green", + "value": 1 } ] } - }, - "links": [], - "liveNow": true, - "preload": true, - "tags": [ - "test", - "example", - "migration" - ], - "timeSettings": { - "timezone": "browser", - "from": "now-6h", - "to": "now", - "autoRefresh": "10s", - "autoRefreshIntervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "hideTimepicker": false, - "weekStart": "monday", - "fiscalYearStartMonth": 0 - }, - "title": "Test: Complete V2alpha1 Dashboard Example", - "variables": [ - { - "kind": "QueryVariable", - "spec": { - "name": "prometheus_query", - "current": { - "text": "All", - "value": [ - "$__all" - ] - }, - "label": "Prometheus Query", - "hide": "dontHide", - "refresh": "time", - "skipUrlSync": false, - "description": "Shows all up metrics", - "datasource": { - "type": "prometheus", - "uid": "gdev-prometheus" - }, - "query": { - "kind": "prometheus", - "spec": { - "expr": "up" - } - }, - "regex": "", - "sort": "alphabetical", - "definition": "up", - "options": null, - "multi": true, - "includeAll": true, - "allowCustomValue": false - } - }, - { - "kind": "TextVariable", - "spec": { - "name": "text_var", - "current": { - "selected": true, - "text": "server1", - "value": "server1" - }, - "query": "server1,server2,server3", - "label": "Text Variable", - "hide": "dontHide", - "skipUrlSync": false, - "description": "A simple text variable" - } - }, - { - "kind": "ConstantVariable", - "spec": { - "name": "constant_var", - "query": "production", - "current": { - "selected": true, - "text": "production", - "value": "production" - }, - "label": "Constant", - "hide": "dontHide", - "skipUrlSync": true, - "description": "A constant value" - } - }, - { - "kind": "DatasourceVariable", - "spec": { - "name": "ds_var", - "pluginId": "prometheus", - "refresh": "load", - "regex": "/^gdev-/", - "current": { - "text": "gdev-prometheus", - "value": "gdev-prometheus" - }, - "options": [ - { - "text": "gdev-prometheus", - "value": "gdev-prometheus" - } - ], - "multi": false, - "includeAll": false, - "label": "Datasource", - "hide": "dontHide", - "skipUrlSync": false, - "description": "Select a datasource", - "allowCustomValue": false - } - }, - { - "kind": "IntervalVariable", - "spec": { - "name": "interval", - "query": "1m,5m,10m,30m,1h,6h,12h,1d", - "current": { - "selected": true, - "text": "5m", - "value": "5m" - }, - "options": [ - { - "text": "1m", - "value": "1m" - }, - { - "text": "5m", - "value": "5m" - }, - { - "text": "10m", - "value": "10m" - }, - { - "text": "30m", - "value": "30m" - }, - { - "text": "1h", - "value": "1h" - }, - { - "text": "6h", - "value": "6h" - }, - { - "text": "12h", - "value": "12h" - }, - { - "text": "1d", - "value": "1d" - } - ], - "auto": true, - "auto_min": "10s", - "auto_count": 30, - "refresh": "load", - "label": "Interval", - "hide": "dontHide", - "skipUrlSync": false, - "description": "Time interval selection" - } - }, - { - "kind": "CustomVariable", - "spec": { - "name": "custom_var", - "query": "prod : Production, staging : Staging, dev : Development", - "current": { - "text": [ - "Production" - ], - "value": [ - "prod" - ] - }, - "options": [ - { - "text": "Production", - "value": "prod" - }, - { - "text": "Staging", - "value": "staging" - }, - { - "text": "Development", - "value": "dev" - } - ], - "multi": true, - "includeAll": true, - "allValue": "*", - "label": "Custom Options", - "hide": "dontHide", - "skipUrlSync": false, - "description": "Custom multi-value variable", - "allowCustomValue": true - } - }, - { - "kind": "GroupByVariable", - "spec": { - "name": "group_by", - "datasource": { - "type": "prometheus", - "uid": "gdev-prometheus" - }, - "current": { - "text": "instance", - "value": "instance" - }, - "options": null, - "multi": false, - "label": "Group By", - "hide": "dontHide", - "skipUrlSync": false, - "description": "Group metrics by label" - } - }, - { - "kind": "AdhocVariable", - "spec": { - "name": "filters", - "datasource": { - "type": "prometheus", - "uid": "gdev-prometheus" - }, - "baseFilters": [ - { - "key": "job", - "operator": "=", - "value": "grafana", - "condition": "AND" - } - ], - "filters": [], - "defaultKeys": [ - { - "text": "job", - "value": "job", - "expandable": true - }, - { - "text": "instance", - "value": "instance", - "expandable": true - } - ], - "label": "Filters", - "hide": "dontHide", - "skipUrlSync": false, - "allowCustomValue": false - } - } - ] - }, - "status": { - "conversion": { - "failed": false, - "storedVersion": "v2beta1" } - } + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 1, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "textMode": "auto" + }, + "pluginVersion": "12.1.0-pre", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "gdev-prometheus" + }, + "expr": "up{job=\"grafana\"}", + "refId": "A" + } + ], + "title": "Panel with Conditional Rendering", + "transformations": [ + { + "id": "reduce", + "options": { + "includeTimeField": false, + "mode": "reduceFields", + "reducers": [ + "mean" + ] + } + } + ], + "type": "stat" } + ], + "preload": true, + "refresh": "10s", + "schemaVersion": 42, + "tags": [ + "test", + "example", + "migration" + ], + "templating": { + "list": [ + { + "allowCustomValue": false, + "current": { + "text": "All", + "value": [ + "$__all" + ] + }, + "datasource": { + "type": "prometheus", + "uid": "gdev-prometheus" + }, + "definition": "up", + "description": "Shows all up metrics", + "hide": 0, + "includeAll": true, + "label": "Prometheus Query", + "multi": true, + "name": "prometheus_query", + "options": [], + "query": { + "expr": "up" + }, + "refresh": 0, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "current": { + "text": "server1", + "value": "server1" + }, + "description": "A simple text variable", + "hide": 0, + "label": "Text Variable", + "name": "text_var", + "query": "server1,server2,server3", + "skipUrlSync": false, + "type": "textbox" + }, + { + "current": { + "text": "production", + "value": "production" + }, + "description": "A constant value", + "hide": 2, + "label": "Constant", + "name": "constant_var", + "query": "production", + "skipUrlSync": true, + "type": "constant" + }, + { + "allowCustomValue": false, + "current": { + "text": "gdev-prometheus", + "value": "gdev-prometheus" + }, + "description": "Select a datasource", + "hide": 0, + "includeAll": false, + "label": "Datasource", + "multi": false, + "name": "ds_var", + "options": [ + { + "text": "gdev-prometheus", + "value": "gdev-prometheus" + } + ], + "query": "prometheus", + "refresh": 0, + "regex": "/^gdev-/", + "skipUrlSync": false, + "type": "datasource" + }, + { + "auto": true, + "auto_count": 30, + "auto_min": "10s", + "current": { + "text": "5m", + "value": "5m" + }, + "description": "Time interval selection", + "hide": 0, + "label": "Interval", + "name": "interval", + "options": [ + { + "text": "1m", + "value": "1m" + }, + { + "text": "5m", + "value": "5m" + }, + { + "text": "10m", + "value": "10m" + }, + { + "text": "30m", + "value": "30m" + }, + { + "text": "1h", + "value": "1h" + }, + { + "text": "6h", + "value": "6h" + }, + { + "text": "12h", + "value": "12h" + }, + { + "text": "1d", + "value": "1d" + } + ], + "query": "1m,5m,10m,30m,1h,6h,12h,1d", + "skipUrlSync": false, + "type": "interval" + }, + { + "allValue": "*", + "allowCustomValue": true, + "current": { + "text": [ + "Production" + ], + "value": [ + "prod" + ] + }, + "description": "Custom multi-value variable", + "hide": 0, + "includeAll": true, + "label": "Custom Options", + "multi": true, + "name": "custom_var", + "options": [ + { + "text": "Production", + "value": "prod" + }, + { + "text": "Staging", + "value": "staging" + }, + { + "text": "Development", + "value": "dev" + } + ], + "query": "prod : Production, staging : Staging, dev : Development", + "skipUrlSync": false, + "type": "custom" + }, + { + "current": { + "text": "instance", + "value": "instance" + }, + "datasource": { + "type": "prometheus", + "uid": "gdev-prometheus" + }, + "description": "Group metrics by label", + "hide": 0, + "label": "Group By", + "multi": false, + "name": "group_by", + "options": [], + "skipUrlSync": false, + "type": "groupby" + }, + { + "allowCustomValue": false, + "baseFilters": [ + { + "condition": "AND", + "key": "job", + "operator": "=", + "value": "grafana" + } + ], + "datasource": { + "type": "prometheus", + "uid": "gdev-prometheus" + }, + "defaultKeys": [ + { + "expandable": true, + "text": "job", + "value": "job" + }, + { + "expandable": true, + "text": "instance", + "value": "instance" + } + ], + "hide": 0, + "label": "Filters", + "name": "filters", + "skipUrlSync": false, + "type": "adhoc" + } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "browser", + "title": "Test: Complete V2alpha1 Dashboard Example", + "weekStart": "monday" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v2alpha1" } } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/v2alpha1.ds-data-query.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/v2alpha1.ds-data-query.v0alpha1.json index 50b2b4e9038..e648923e684 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/v2alpha1.ds-data-query.v0alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/v2alpha1.ds-data-query.v0alpha1.json @@ -4,1070 +4,825 @@ "metadata": { "name": "test-v2alpha1-annotations" }, - "spec": null, - "status": { - "conversion": { - "failed": true, - "error": "backend conversion not yet implemented", - "storedVersion": "v2alpha1", - "source": { - "kind": "Dashboard", - "apiVersion": "dashboard.grafana.app/v2alpha1", - "metadata": { - "name": "test-v2alpha1-annotations" - }, - "spec": { - "annotations": [ - { - "kind": "AnnotationQuery", - "spec": { - "datasource": { - "type": "grafana", - "uid": "-- Grafana --" - }, - "query": { - "kind": "grafana", - "spec": { - "limit": 100, - "matchAny": false, - "tags": [], - "type": "dashboard" - } - }, - "enable": true, - "hide": false, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations \u0026 Alerts", - "builtIn": true - } - }, - { - "kind": "AnnotationQuery", - "spec": { - "datasource": { - "type": "grafana-testdata-datasource", - "uid": "PD8C576611E62080A" - }, - "query": { - "kind": "grafana-testdata-datasource", - "spec": { - "lines": 10, - "refId": "Anno", - "scenarioId": "annotations" - } - }, - "enable": true, - "hide": false, - "iconColor": "blue", - "name": "testdata-annos", - "builtIn": false - } - }, - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "prometheus", - "spec": { - "lines": 10, - "refId": "Anno", - "scenarioId": "annotations" - } - }, - "enable": true, - "hide": false, - "iconColor": "blue", - "name": "no-ds-testdata-annos", - "builtIn": false - } - }, - { - "kind": "AnnotationQuery", - "spec": { - "datasource": { - "type": "prometheus", - "uid": "gdev-prometheus" - }, - "query": { - "kind": "prometheus", - "spec": { - "expr": "{action=\"add_client\"}", - "interval": "", - "lines": 10, - "refId": "Anno", - "scenarioId": "annotations" - } - }, - "enable": true, - "hide": false, - "iconColor": "yellow", - "name": "prom-annos", - "builtIn": false - } - }, - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "prometheus", - "spec": { - "expr": "{action=\"add_client\"}", - "interval": "", - "lines": 10, - "refId": "Anno", - "scenarioId": "annotations" - } - }, - "enable": true, - "hide": false, - "iconColor": "yellow", - "name": "no-ds-prom-annos", - "builtIn": false - } - }, - { - "kind": "AnnotationQuery", - "spec": { - "datasource": { - "type": "grafana-postgresql-datasource", - "uid": "PBBCEC2D313BC06C3" - }, - "query": { - "kind": "grafana-postgresql-datasource", - "spec": { - "editorMode": "builder", - "format": "table", - "lines": 10, - "rawSql": "", - "refId": "Anno", - "scenarioId": "annotations", - "sql": { - "columns": [ - { - "parameters": [], - "type": "function" - } - ], - "groupBy": [ - { - "property": { - "type": "string" - }, - "type": "groupBy" - } - ], - "limit": 50 - } - } - }, - "enable": true, - "hide": false, - "iconColor": "red", - "name": "postgress-annos", - "builtIn": false - } - }, - { - "kind": "AnnotationQuery", - "spec": { - "datasource": { - "type": "elasticsearch", - "uid": "gdev-elasticsearch" - }, - "query": { - "kind": "elasticsearch", - "spec": { - "lines": 10, - "query": "test query", - "refId": "Anno", - "scenarioId": "annotations" - } - }, - "enable": true, - "hide": false, - "iconColor": "red", - "name": "elastic - annos", - "builtIn": false, - "legacyOptions": { - "tagsField": "asd", - "textField": "asd", - "timeEndField": "asdas", - "timeField": "asd" - } - } - } - ], - "cursorSync": "Off", - "editable": true, - "elements": { - "panel-1": { - "kind": "Panel", - "spec": { - "id": 1, - "title": "Simple timeseries (WITH DS REF)", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "grafana-testdata-datasource", - "spec": { - "scenarioId": "random_walk", - "seriesCount": 3 - } - }, - "datasource": { - "type": "grafana-testdata-datasource", - "uid": "gdev-testdata" - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "timeseries", - "spec": { - "pluginVersion": "12.1.0-pre", - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "hideZeros": false, - "mode": "single", - "sort": "none" - } - }, - "fieldConfig": { - "defaults": { - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": 0, - "color": "green" - }, - { - "value": 80, - "color": "red" - } - ] - }, - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - } - }, - "overrides": [] - } - } - } - } - }, - "panel-2": { - "kind": "Panel", - "spec": { - "id": 2, - "title": "Simple stat (NO DS REF)", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "grafana-testdata-datasource", - "spec": { - "scenarioId": "random_walk", - "seriesCount": 4 - } - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "stat", - "spec": { - "pluginVersion": "12.1.0-pre", - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "percentChangeColorMode": "standard", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "fieldConfig": { - "defaults": { - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": 0, - "color": "green" - }, - { - "value": 80, - "color": "red" - } - ] - }, - "color": { - "mode": "thresholds" - } - }, - "overrides": [] - } - } - } - } - }, - "panel-3": { - "kind": "Panel", - "spec": { - "id": 3, - "title": "Panel with NO REF to gdev-prometheus", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "prometheus", - "spec": { - "disableTextWrap": false, - "editorMode": "builder", - "expr": "rate(counters_requests[$__rate_interval])", - "fullMetaSearch": false, - "includeNullMetadata": false, - "instant": false, - "legendFormat": "__auto", - "range": true, - "useBackend": false - } - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "timeseries", - "spec": { - "pluginVersion": "12.1.0-pre", - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "hideZeros": false, - "mode": "single", - "sort": "none" - } - }, - "fieldConfig": { - "defaults": { - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": 0, - "color": "green" - }, - { - "value": 80, - "color": "red" - } - ] - }, - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - } - }, - "overrides": [] - } - } - } - } - }, - "panel-4": { - "kind": "Panel", - "spec": { - "id": 4, - "title": "Panel with ref to gdev-prometheus", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "prometheus", - "spec": { - "disableTextWrap": false, - "editorMode": "builder", - "expr": "rate(counters_requests[$__rate_interval])", - "fullMetaSearch": false, - "includeNullMetadata": false, - "instant": false, - "legendFormat": "__auto", - "range": true, - "useBackend": false - } - }, - "datasource": { - "type": "prometheus", - "uid": "gdev-prometheus" - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "timeseries", - "spec": { - "pluginVersion": "12.1.0-pre", - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "hideZeros": false, - "mode": "single", - "sort": "none" - } - }, - "fieldConfig": { - "defaults": { - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": 0, - "color": "green" - }, - { - "value": 80, - "color": "red" - } - ] - }, - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - } - }, - "overrides": [] - } - } - } - } - }, - "panel-5": { - "kind": "Panel", - "spec": { - "id": 5, - "title": "Mixed DS WITH REFS", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "prometheus", - "spec": { - "disableTextWrap": false, - "editorMode": "builder", - "expr": "rate(counters_requests{server=\"backend-01\"}[$__rate_interval])", - "fullMetaSearch": false, - "includeNullMetadata": false, - "legendFormat": "__auto", - "range": true, - "useBackend": false - } - }, - "datasource": { - "type": "prometheus", - "uid": "gdev-prometheus" - }, - "refId": "A", - "hidden": false - } - }, - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "grafana-testdata-datasource", - "spec": {} - }, - "datasource": { - "type": "grafana-testdata-datasource", - "uid": "gdev-testdata" - }, - "refId": "B", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "timeseries", - "spec": { - "pluginVersion": "12.1.0-pre", - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "hideZeros": false, - "mode": "single", - "sort": "none" - } - }, - "fieldConfig": { - "defaults": { - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": 0, - "color": "green" - }, - { - "value": 80, - "color": "red" - } - ] - }, - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - } - }, - "overrides": [] - } - } - } - } - }, - "panel-6": { - "kind": "Panel", - "spec": { - "id": 6, - "title": "Mixed DS WITHOUT REFS", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "prometheus", - "spec": { - "disableTextWrap": false, - "editorMode": "builder", - "expr": "rate(counters_requests{server=\"backend-01\"}[$__rate_interval])", - "fullMetaSearch": false, - "includeNullMetadata": false, - "legendFormat": "__auto", - "range": true, - "useBackend": false - } - }, - "refId": "A", - "hidden": false - } - }, - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "grafana-testdata-datasource", - "spec": {} - }, - "refId": "B", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "timeseries", - "spec": { - "pluginVersion": "12.1.0-pre", - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "hideZeros": false, - "mode": "single", - "sort": "none" - } - }, - "fieldConfig": { - "defaults": { - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": 0, - "color": "green" - }, - { - "value": 80, - "color": "red" - } - ] - }, - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - } - }, - "overrides": [] - } - } - } - } - } + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" }, - "layout": { - "kind": "AutoGridLayout", - "spec": { - "maxColumnCount": 3, - "columnWidthMode": "standard", - "rowHeightMode": "standard", - "items": [ + "enable": true, + "hide": false, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + }, + { + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "PD8C576611E62080A" + }, + "enable": true, + "hide": false, + "iconColor": "blue", + "name": "testdata-annos", + "target": { + "lines": 10, + "refId": "Anno", + "scenarioId": "annotations" + } + }, + { + "datasource": { + "type": "prometheus" + }, + "enable": true, + "hide": false, + "iconColor": "blue", + "name": "no-ds-testdata-annos", + "target": { + "lines": 10, + "refId": "Anno", + "scenarioId": "annotations" + } + }, + { + "datasource": { + "type": "prometheus", + "uid": "gdev-prometheus" + }, + "enable": true, + "hide": false, + "iconColor": "yellow", + "name": "prom-annos", + "target": { + "expr": "{action=\"add_client\"}", + "interval": "", + "lines": 10, + "refId": "Anno", + "scenarioId": "annotations" + } + }, + { + "datasource": { + "type": "prometheus" + }, + "enable": true, + "hide": false, + "iconColor": "yellow", + "name": "no-ds-prom-annos", + "target": { + "expr": "{action=\"add_client\"}", + "interval": "", + "lines": 10, + "refId": "Anno", + "scenarioId": "annotations" + } + }, + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "PBBCEC2D313BC06C3" + }, + "enable": true, + "hide": false, + "iconColor": "red", + "name": "postgress-annos", + "target": { + "editorMode": "builder", + "format": "table", + "lines": 10, + "rawSql": "", + "refId": "Anno", + "scenarioId": "annotations", + "sql": { + "columns": [ { - "kind": "AutoGridLayoutItem", - "spec": { - "element": { - "kind": "ElementReference", - "name": "panel-3" - } - } + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + }, + { + "datasource": { + "type": "elasticsearch", + "uid": "gdev-elasticsearch" + }, + "enable": true, + "hide": false, + "iconColor": "red", + "name": "elastic - annos", + "tagsField": "asd", + "target": { + "lines": 10, + "query": "test query", + "refId": "Anno", + "scenarioId": "annotations" + }, + "textField": "asd", + "timeEndField": "asdas", + "timeField": "asd" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "liveNow": false, + "panels": [ + { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 }, { - "kind": "AutoGridLayoutItem", - "spec": { - "element": { - "kind": "ElementReference", - "name": "panel-4" - } - } - }, - { - "kind": "AutoGridLayoutItem", - "spec": { - "element": { - "kind": "ElementReference", - "name": "panel-2" - } - } - }, - { - "kind": "AutoGridLayoutItem", - "spec": { - "element": { - "kind": "ElementReference", - "name": "panel-1" - } - } - }, - { - "kind": "AutoGridLayoutItem", - "spec": { - "element": { - "kind": "ElementReference", - "name": "panel-5" - } - } - }, - { - "kind": "AutoGridLayoutItem", - "spec": { - "element": { - "kind": "ElementReference", - "name": "panel-6" - } - } + "color": "red", + "value": 80 } ] } - }, - "links": [], - "liveNow": false, - "preload": false, - "tags": [], - "timeSettings": { - "timezone": "browser", - "from": "now-6h", - "to": "now", - "autoRefresh": "", - "autoRefreshIntervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "hideTimepicker": false, - "fiscalYearStartMonth": 0 - }, - "title": "Test: V2alpha1 dashboard with annotations", - "variables": [ - { - "kind": "QueryVariable", - "spec": { - "name": "variable-ds-prometheus", - "current": { - "text": "", - "value": "" - }, - "hide": "dontHide", - "refresh": "", - "skipUrlSync": false, - "datasource": { - "type": "prometheus", - "uid": "gdev-prometheus" - }, - "query": { - "kind": "prometheus", - "spec": { - "expr": "up" - } - }, - "regex": "", - "sort": "", - "options": null, - "multi": false, - "includeAll": false, - "allowCustomValue": false - } - }, - { - "kind": "QueryVariable", - "spec": { - "name": "variable-no-ds", - "current": { - "text": "", - "value": "" - }, - "hide": "dontHide", - "refresh": "", - "skipUrlSync": false, - "query": { - "kind": "grafana-testdata-datasource", - "spec": { - "csv": "1,2,3,4", - "scenarioId": "csv_metric_values" - } - }, - "regex": "", - "sort": "", - "options": null, - "multi": false, - "includeAll": false, - "allowCustomValue": false - } - }, - { - "kind": "QueryVariable", - "spec": { - "name": "variable-no-ds-empty-query", - "current": { - "text": "", - "value": "" - }, - "hide": "dontHide", - "refresh": "", - "skipUrlSync": false, - "query": { - "kind": "grafana-testdata-datasource", - "spec": {} - }, - "regex": "", - "sort": "", - "options": null, - "multi": false, - "includeAll": false, - "allowCustomValue": false - } - }, - { - "kind": "QueryVariable", - "spec": { - "name": "variable-no-default-ds", - "current": { - "text": "", - "value": "" - }, - "hide": "dontHide", - "refresh": "", - "skipUrlSync": false, - "query": { - "kind": "prometheus", - "spec": { - "expr": "up" - } - }, - "regex": "", - "sort": "", - "options": null, - "multi": false, - "includeAll": false, - "allowCustomValue": false - } - } - ] - }, - "status": { - "conversion": { - "failed": false, - "storedVersion": "v2beta1" } - } + }, + "gridPos": { + "h": 9, + "w": 8, + "x": 0, + "y": 0 + }, + "id": 3, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.1.0-pre", + "targets": [ + { + "datasource": { + "type": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "rate(counters_requests[$__rate_interval])", + "fullMetaSearch": false, + "includeNullMetadata": false, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Panel with NO REF to gdev-prometheus", + "type": "timeseries" + }, + { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + } + }, + "gridPos": { + "h": 9, + "w": 8, + "x": 8, + "y": 0 + }, + "id": 4, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.1.0-pre", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "gdev-prometheus" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "rate(counters_requests[$__rate_interval])", + "fullMetaSearch": false, + "includeNullMetadata": false, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Panel with ref to gdev-prometheus", + "type": "timeseries" + }, + { + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + } + }, + "gridPos": { + "h": 9, + "w": 8, + "x": 16, + "y": 0 + }, + "id": 2, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "12.1.0-pre", + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "refId": "A", + "scenarioId": "random_walk", + "seriesCount": 4 + } + ], + "title": "Simple stat (NO DS REF)", + "type": "stat" + }, + { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + } + }, + "gridPos": { + "h": 9, + "w": 8, + "x": 0, + "y": 9 + }, + "id": 1, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.1.0-pre", + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "gdev-testdata" + }, + "refId": "A", + "scenarioId": "random_walk", + "seriesCount": 3 + } + ], + "title": "Simple timeseries (WITH DS REF)", + "type": "timeseries" + }, + { + "datasource": { + "type": "mixed", + "uid": "-- Mixed --" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + } + }, + "gridPos": { + "h": 9, + "w": 8, + "x": 8, + "y": 9 + }, + "id": 5, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.1.0-pre", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "gdev-prometheus" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "rate(counters_requests{server=\"backend-01\"}[$__rate_interval])", + "fullMetaSearch": false, + "includeNullMetadata": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + }, + { + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "gdev-testdata" + }, + "refId": "B" + } + ], + "title": "Mixed DS WITH REFS", + "type": "timeseries" + }, + { + "datasource": { + "type": "mixed", + "uid": "-- Mixed --" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + } + }, + "gridPos": { + "h": 9, + "w": 8, + "x": 16, + "y": 9 + }, + "id": 6, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.1.0-pre", + "targets": [ + { + "datasource": { + "type": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "rate(counters_requests{server=\"backend-01\"}[$__rate_interval])", + "fullMetaSearch": false, + "includeNullMetadata": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + }, + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "refId": "B" + } + ], + "title": "Mixed DS WITHOUT REFS", + "type": "timeseries" } + ], + "preload": false, + "refresh": "", + "schemaVersion": 42, + "templating": { + "list": [ + { + "allowCustomValue": false, + "current": { + "text": "", + "value": "" + }, + "datasource": { + "type": "prometheus", + "uid": "gdev-prometheus" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "variable-ds-prometheus", + "options": [], + "query": { + "expr": "up" + }, + "refresh": 0, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "allowCustomValue": false, + "current": { + "text": "", + "value": "" + }, + "datasource": { + "type": "grafana-testdata-datasource" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "variable-no-ds", + "options": [], + "query": { + "csv": "1,2,3,4", + "scenarioId": "csv_metric_values" + }, + "refresh": 0, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "allowCustomValue": false, + "current": { + "text": "", + "value": "" + }, + "datasource": { + "type": "grafana-testdata-datasource" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "variable-no-ds-empty-query", + "options": [], + "query": {}, + "refresh": 0, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "allowCustomValue": false, + "current": { + "text": "", + "value": "" + }, + "datasource": { + "type": "prometheus" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "variable-no-default-ds", + "options": [], + "query": { + "expr": "up" + }, + "refresh": 0, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "browser", + "title": "Test: V2alpha1 dashboard with annotations" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v2alpha1" } } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/v2alpha1.groupby-adhoc-vars.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/v2alpha1.groupby-adhoc-vars.v0alpha1.json index d03d476b881..cae121bdef7 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/v2alpha1.groupby-adhoc-vars.v0alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/v2alpha1.groupby-adhoc-vars.v0alpha1.json @@ -4,110 +4,91 @@ "metadata": { "name": "test-v2alpha1-groupby-adhoc-vars" }, - "spec": null, - "status": { - "conversion": { - "failed": true, - "error": "backend conversion not yet implemented", - "storedVersion": "v2alpha1", - "source": { - "kind": "Dashboard", - "apiVersion": "dashboard.grafana.app/v2alpha1", - "metadata": { - "name": "test-v2alpha1-groupby-adhoc-vars" - }, - "spec": { - "annotations": [], - "cursorSync": "", - "elements": {}, - "layout": null, - "links": [], - "preload": false, - "tags": null, - "timeSettings": { - "from": "", - "to": "", - "autoRefresh": "", - "autoRefreshIntervals": null, - "hideTimepicker": false, - "fiscalYearStartMonth": 0 + "spec": { + "annotations": { + "list": [] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "preload": false, + "refresh": "", + "schemaVersion": 42, + "templating": { + "list": [ + { + "current": { + "text": "text7", + "value": "value7" }, - "title": "Test: V2alpha1 dashboard with group by and adhoc variables", - "variables": [ + "datasource": { + "type": "prometheus", + "uid": "gdev-prometheus" + }, + "description": "A group by variable", + "hide": 0, + "label": "Group By Variable", + "multi": false, + "name": "", + "options": [], + "skipUrlSync": false, + "type": "groupby" + }, + { + "allowCustomValue": true, + "baseFilters": [ { - "kind": "GroupByVariable", - "spec": { - "name": "", - "datasource": { - "type": "prometheus", - "uid": "gdev-prometheus" - }, - "current": { - "text": "text7", - "value": "value7" - }, - "options": null, - "multi": false, - "label": "Group By Variable", - "hide": "dontHide", - "skipUrlSync": false, - "description": "A group by variable" - } + "condition": "AND", + "key": "key1", + "operator": "=", + "value": "value1" }, { - "kind": "AdhocVariable", - "spec": { - "name": "adhocVar", - "datasource": { - "type": "prometheus", - "uid": "datasource-3" - }, - "baseFilters": [ - { - "key": "key1", - "operator": "=", - "value": "value1", - "condition": "AND" - }, - { - "key": "key2", - "operator": "=", - "value": "value2", - "condition": "OR" - } - ], - "filters": [ - { - "key": "key3", - "operator": "=", - "value": "value3", - "condition": "AND" - } - ], - "defaultKeys": [ - { - "text": "defaultKey1", - "value": "defaultKey1", - "group": "defaultGroup1", - "expandable": true - } - ], - "label": "Adhoc Variable", - "hide": "dontHide", - "skipUrlSync": false, - "description": "An adhoc variable", - "allowCustomValue": true - } + "condition": "OR", + "key": "key2", + "operator": "=", + "value": "value2" } - ] - }, - "status": { - "conversion": { - "failed": false, - "storedVersion": "v2beta1" - } + ], + "datasource": { + "type": "prometheus", + "uid": "datasource-3" + }, + "defaultKeys": [ + { + "expandable": true, + "group": "defaultGroup1", + "text": "defaultKey1", + "value": "defaultKey1" + } + ], + "description": "An adhoc variable", + "filters": [ + { + "condition": "AND", + "key": "key3", + "operator": "=", + "value": "value3" + } + ], + "hide": 0, + "label": "Adhoc Variable", + "name": "adhocVar", + "skipUrlSync": false, + "type": "adhoc" } - } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "title": "Test: V2alpha1 dashboard with group by and adhoc variables" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v2alpha1" } } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/v2alpha1.viz-config.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/v2alpha1.viz-config.v0alpha1.json index c5bd133a7fb..37bbba541cd 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/v2alpha1.viz-config.v0alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/v2alpha1.viz-config.v0alpha1.json @@ -4,185 +4,133 @@ "metadata": { "name": "test-v2alpha1-viz-config" }, - "spec": null, - "status": { - "conversion": { - "failed": true, - "error": "backend conversion not yet implemented", - "storedVersion": "v2alpha1", - "source": { - "kind": "Dashboard", - "apiVersion": "dashboard.grafana.app/v2alpha1", - "metadata": { - "name": "test-v2alpha1-viz-config" - }, - "spec": { - "annotations": [], - "cursorSync": "", - "elements": { - "panel-1": { - "kind": "Panel", - "spec": { - "id": 1, - "title": "Simple timeseries (WITH DS REF)", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "grafana-testdata-datasource", - "spec": { - "scenarioId": "random_walk", - "seriesCount": 3 - } - }, - "datasource": { - "type": "grafana-testdata-datasource", - "uid": "gdev-testdata" - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "timeseries", - "spec": { - "pluginVersion": "12.1.0-pre", - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "hideZeros": false, - "mode": "single", - "sort": "none" - } - }, - "fieldConfig": { - "defaults": { - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": 0, - "color": "green" - }, - { - "value": 80, - "color": "red" - } - ] - }, - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - } - }, - "overrides": [] - } - } - } + "spec": { + "annotations": { + "list": [] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "liveNow": false, + "panels": [ + { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" } - } - }, - "layout": { - "kind": "AutoGridLayout", - "spec": { - "maxColumnCount": 3, - "columnWidthMode": "standard", - "rowHeightMode": "standard", - "items": [ + }, + "thresholds": { + "mode": "absolute", + "steps": [ { - "kind": "AutoGridLayoutItem", - "spec": { - "element": { - "kind": "ElementReference", - "name": "panel-1" - } - } + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 } ] } - }, - "links": [], - "liveNow": false, - "preload": false, - "tags": [], - "timeSettings": { - "timezone": "browser", - "from": "now-6h", - "to": "now", - "autoRefresh": "", - "autoRefreshIntervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "hideTimepicker": false, - "fiscalYearStartMonth": 0 - }, - "title": "Test: V2alpha1 dashboard with viz config", - "variables": [] - }, - "status": { - "conversion": { - "failed": false, - "storedVersion": "v2beta1" } - } + }, + "gridPos": { + "h": 9, + "w": 8, + "x": 0, + "y": 0 + }, + "id": 1, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.1.0-pre", + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "gdev-testdata" + }, + "refId": "A", + "scenarioId": "random_walk", + "seriesCount": 3 + } + ], + "title": "Simple timeseries (WITH DS REF)", + "type": "timeseries" } + ], + "preload": false, + "refresh": "", + "schemaVersion": 42, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "browser", + "title": "Test: V2alpha1 dashboard with viz config" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v2alpha1" } } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/v2beta1.complete.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/v2beta1.complete.v0alpha1.json index 21f71a5b188..b942305c98d 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/v2beta1.complete.v0alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/v2beta1.complete.v0alpha1.json @@ -10,534 +10,415 @@ "description": "Complete example of v2alpha1 dashboard features" } }, - "spec": null, - "status": { - "conversion": { - "failed": true, - "error": "backend conversion not yet implemented", - "storedVersion": "v2beta1", - "source": { - "kind": "Dashboard", - "apiVersion": "dashboard.grafana.app/v2beta1", - "metadata": { - "name": "test-v2alpha1-complete", - "labels": { - "category": "test" + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" }, - "annotations": { - "description": "Complete example of v2alpha1 dashboard features" - } + "enable": true, + "hide": false, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" }, - "spec": { - "annotations": [ - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana", - "version": "v0", - "datasource": { - "name": "-- Grafana --" - }, - "spec": { - "limit": 100, - "matchAny": false, - "tags": [], - "type": "dashboard" - } - }, - "enable": true, - "hide": false, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations \u0026 Alerts", - "builtIn": true - } - }, - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "gdev-prometheus" - }, - "spec": { - "expr": "changes(process_start_time_seconds[1m])", - "refId": "Anno" - } - }, - "enable": true, - "hide": false, - "iconColor": "yellow", - "name": "Prometheus Annotations", - "builtIn": false - } - } - ], - "cursorSync": "Tooltip", - "description": "This dashboard demonstrates all features that need to be converted from v2alpha1 to v2beta1", - "editable": true, - "elements": { - "panel-1": { - "kind": "Panel", - "spec": { - "id": 1, - "title": "Panel with Conditional Rendering", - "description": "This panel demonstrates conditional rendering features", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "gdev-prometheus" - }, - "spec": { - "expr": "up{job=\"grafana\"}" - } - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [ - { - "kind": "reduce", - "spec": { - "id": "reduce", - "options": { - "includeTimeField": false, - "mode": "reduceFields", - "reducers": [ - "mean" - ] - } - } - } - ], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "stat", - "version": "12.1.0-pre", - "spec": { - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "textMode": "auto" - }, - "fieldConfig": { - "defaults": { - "mappings": [ - { - "type": "value", - "options": { - "0": { - "text": "Down", - "color": "red" - }, - "1": { - "text": "Up", - "color": "green" - } - } - } - ], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": 0, - "color": "red" - }, - { - "value": 1, - "color": "green" - } - ] - }, - "color": { - "mode": "thresholds" - } - }, - "overrides": [] - } - } - } - } - } + { + "datasource": { + "type": "prometheus", + "uid": "gdev-prometheus" }, - "layout": { - "kind": "RowsLayout", - "spec": { - "rows": [ - { - "kind": "Row", - "spec": { - "title": "Conditional Row", - "collapse": false, - "hideHeader": false, - "fillScreen": false, - "conditionalRendering": { - "kind": "ConditionalRenderingGroup", - "spec": { - "visibility": "show", - "condition": "and", - "items": [ - { - "kind": "ConditionalRenderingVariable", - "spec": { - "variable": "group_by", - "operator": "includes", - "value": "instance" - } - }, - { - "kind": "ConditionalRenderingData", - "spec": { - "value": true - } - }, - { - "kind": "ConditionalRenderingTimeRangeSize", - "spec": { - "value": "1h" - } - } - ] - } - }, - "layout": { - "kind": "GridLayout", - "spec": { - "items": [ - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 24, - "height": 8, - "element": { - "kind": "ElementReference", - "name": "panel-1" - } - } - } - ] - } - } + "enable": true, + "hide": false, + "iconColor": "yellow", + "name": "Prometheus Annotations", + "target": { + "expr": "changes(process_start_time_seconds[1m])", + "refId": "Anno" + } + } + ] + }, + "description": "This dashboard demonstrates all features that need to be converted from v2alpha1 to v2beta1", + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 2, + "liveNow": true, + "panels": [ + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 0 + }, + "id": -1, + "title": "Conditional Row", + "type": "row" + }, + { + "description": "This panel demonstrates conditional rendering features", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [ + { + "options": { + "0": { + "color": "red", + "text": "Down" + }, + "1": { + "color": "green", + "text": "Up" } + }, + "type": "value" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red", + "value": 0 + }, + { + "color": "green", + "value": 1 } ] } - }, - "links": [], - "liveNow": true, - "preload": true, - "tags": [ - "test", - "example", - "migration" - ], - "timeSettings": { - "timezone": "browser", - "from": "now-6h", - "to": "now", - "autoRefresh": "10s", - "autoRefreshIntervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "hideTimepicker": false, - "weekStart": "monday", - "fiscalYearStartMonth": 0 - }, - "title": "Test: Complete V2alpha1 Dashboard Example", - "variables": [ - { - "kind": "SwitchVariable", - "spec": { - "name": "switch_var", - "current": "false", - "enabledValue": "true", - "disabledValue": "false", - "label": "Enable Feature", - "hide": "dontHide", - "skipUrlSync": false, - "description": "Toggle feature on/off" - } - }, - { - "kind": "QueryVariable", - "spec": { - "name": "prometheus_query", - "current": { - "text": "All", - "value": [ - "$__all" - ] - }, - "label": "Prometheus Query", - "hide": "dontHide", - "refresh": "onDashboardLoad", - "skipUrlSync": false, - "description": "Shows all up metrics", - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "gdev-prometheus" - }, - "spec": { - "expr": "up" - } - }, - "regex": "", - "sort": "alphabetical", - "definition": "up", - "options": null, - "multi": true, - "includeAll": true, - "allowCustomValue": false - } - }, - { - "kind": "TextVariable", - "spec": { - "name": "text_var", - "current": { - "selected": true, - "text": "server1", - "value": "server1" - }, - "query": "server1,server2,server3", - "label": "Text Variable", - "hide": "dontHide", - "skipUrlSync": false, - "description": "A simple text variable" - } - }, - { - "kind": "ConstantVariable", - "spec": { - "name": "constant_var", - "query": "production", - "current": { - "selected": true, - "text": "production", - "value": "production" - }, - "label": "Constant", - "hide": "dontHide", - "skipUrlSync": true, - "description": "A constant value" - } - }, - { - "kind": "DatasourceVariable", - "spec": { - "name": "ds_var", - "pluginId": "prometheus", - "refresh": "load", - "regex": "/^gdev-/", - "current": { - "text": "gdev-prometheus", - "value": "gdev-prometheus" - }, - "options": [ - { - "text": "gdev-prometheus", - "value": "gdev-prometheus" - } - ], - "multi": false, - "includeAll": false, - "label": "Datasource", - "hide": "dontHide", - "skipUrlSync": false, - "description": "Select a datasource", - "allowCustomValue": false - } - }, - { - "kind": "IntervalVariable", - "spec": { - "name": "interval", - "query": "1m,5m,10m,30m,1h,6h,12h,1d", - "current": { - "selected": true, - "text": "5m", - "value": "5m" - }, - "options": [ - { - "text": "1m", - "value": "1m" - }, - { - "text": "5m", - "value": "5m" - }, - { - "text": "10m", - "value": "10m" - }, - { - "text": "30m", - "value": "30m" - }, - { - "text": "1h", - "value": "1h" - }, - { - "text": "6h", - "value": "6h" - }, - { - "text": "12h", - "value": "12h" - }, - { - "text": "1d", - "value": "1d" - } - ], - "auto": true, - "auto_min": "10s", - "auto_count": 30, - "refresh": "onTimeRangeChanged", - "label": "Interval", - "hide": "dontHide", - "skipUrlSync": false, - "description": "Time interval selection" - } - }, - { - "kind": "CustomVariable", - "spec": { - "name": "custom_var", - "query": "prod : Production, staging : Staging, dev : Development", - "current": { - "text": [ - "Production" - ], - "value": [ - "prod" - ] - }, - "options": [ - { - "text": "Production", - "value": "prod" - }, - { - "text": "Staging", - "value": "staging" - }, - { - "text": "Development", - "value": "dev" - } - ], - "multi": true, - "includeAll": true, - "allValue": "*", - "label": "Custom Options", - "hide": "dontHide", - "skipUrlSync": false, - "description": "Custom multi-value variable", - "allowCustomValue": true - } - }, - { - "kind": "GroupByVariable", - "group": "prometheus", - "datasource": { - "name": "gdev-prometheus" - }, - "spec": { - "name": "group_by", - "current": { - "text": "instance", - "value": "instance" - }, - "options": null, - "multi": false, - "label": "Group By", - "hide": "dontHide", - "skipUrlSync": false, - "description": "Group metrics by label" - } - }, - { - "kind": "AdhocVariable", - "group": "prometheus", - "datasource": { - "name": "gdev-prometheus" - }, - "spec": { - "name": "filters", - "baseFilters": [ - { - "key": "job", - "operator": "=", - "value": "grafana", - "condition": "AND" - } - ], - "filters": [], - "defaultKeys": [ - { - "text": "job", - "value": "job", - "expandable": true - }, - { - "text": "instance", - "value": "instance", - "expandable": true - } - ], - "label": "Filters", - "hide": "dontHide", - "skipUrlSync": false, - "allowCustomValue": false - } - } - ] - }, - "status": { - "conversion": { - "failed": false, - "storedVersion": "v2alpha1" } - } + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 1, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "textMode": "auto" + }, + "pluginVersion": "12.1.0-pre", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "gdev-prometheus" + }, + "expr": "up{job=\"grafana\"}", + "refId": "A" + } + ], + "title": "Panel with Conditional Rendering", + "transformations": [ + { + "id": "reduce", + "options": { + "includeTimeField": false, + "mode": "reduceFields", + "reducers": [ + "mean" + ] + } + } + ], + "type": "stat" } + ], + "preload": true, + "refresh": "10s", + "schemaVersion": 42, + "tags": [ + "test", + "example", + "migration" + ], + "templating": { + "list": [ + { + "current": { + "text": "false", + "value": "false" + }, + "description": "Toggle feature on/off", + "hide": 0, + "label": "Enable Feature", + "name": "switch_var", + "options": [ + { + "selected": false, + "text": "true", + "value": "true" + }, + { + "selected": true, + "text": "false", + "value": "false" + } + ], + "query": "", + "skipUrlSync": false, + "type": "switch" + }, + { + "allowCustomValue": false, + "current": { + "text": "All", + "value": [ + "$__all" + ] + }, + "datasource": { + "type": "prometheus", + "uid": "gdev-prometheus" + }, + "definition": "up", + "description": "Shows all up metrics", + "hide": 0, + "includeAll": true, + "label": "Prometheus Query", + "multi": true, + "name": "prometheus_query", + "options": [], + "query": { + "expr": "up" + }, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "current": { + "text": "server1", + "value": "server1" + }, + "description": "A simple text variable", + "hide": 0, + "label": "Text Variable", + "name": "text_var", + "query": "server1,server2,server3", + "skipUrlSync": false, + "type": "textbox" + }, + { + "current": { + "text": "production", + "value": "production" + }, + "description": "A constant value", + "hide": 2, + "label": "Constant", + "name": "constant_var", + "query": "production", + "skipUrlSync": true, + "type": "constant" + }, + { + "allowCustomValue": false, + "current": { + "text": "gdev-prometheus", + "value": "gdev-prometheus" + }, + "description": "Select a datasource", + "hide": 0, + "includeAll": false, + "label": "Datasource", + "multi": false, + "name": "ds_var", + "options": [ + { + "text": "gdev-prometheus", + "value": "gdev-prometheus" + } + ], + "query": "prometheus", + "refresh": 0, + "regex": "/^gdev-/", + "skipUrlSync": false, + "type": "datasource" + }, + { + "auto": true, + "auto_count": 30, + "auto_min": "10s", + "current": { + "text": "5m", + "value": "5m" + }, + "description": "Time interval selection", + "hide": 0, + "label": "Interval", + "name": "interval", + "options": [ + { + "text": "1m", + "value": "1m" + }, + { + "text": "5m", + "value": "5m" + }, + { + "text": "10m", + "value": "10m" + }, + { + "text": "30m", + "value": "30m" + }, + { + "text": "1h", + "value": "1h" + }, + { + "text": "6h", + "value": "6h" + }, + { + "text": "12h", + "value": "12h" + }, + { + "text": "1d", + "value": "1d" + } + ], + "query": "1m,5m,10m,30m,1h,6h,12h,1d", + "skipUrlSync": false, + "type": "interval" + }, + { + "allValue": "*", + "allowCustomValue": true, + "current": { + "text": [ + "Production" + ], + "value": [ + "prod" + ] + }, + "description": "Custom multi-value variable", + "hide": 0, + "includeAll": true, + "label": "Custom Options", + "multi": true, + "name": "custom_var", + "options": [ + { + "text": "Production", + "value": "prod" + }, + { + "text": "Staging", + "value": "staging" + }, + { + "text": "Development", + "value": "dev" + } + ], + "query": "prod : Production, staging : Staging, dev : Development", + "skipUrlSync": false, + "type": "custom" + }, + { + "current": { + "text": "instance", + "value": "instance" + }, + "datasource": { + "type": "prometheus", + "uid": "gdev-prometheus" + }, + "description": "Group metrics by label", + "hide": 0, + "label": "Group By", + "multi": false, + "name": "group_by", + "options": [], + "skipUrlSync": false, + "type": "groupby" + }, + { + "allowCustomValue": false, + "baseFilters": [ + { + "condition": "AND", + "key": "job", + "operator": "=", + "value": "grafana" + } + ], + "datasource": { + "type": "prometheus", + "uid": "gdev-prometheus" + }, + "defaultKeys": [ + { + "expandable": true, + "text": "job", + "value": "job" + }, + { + "expandable": true, + "text": "instance", + "value": "instance" + } + ], + "hide": 0, + "label": "Filters", + "name": "filters", + "skipUrlSync": false, + "type": "adhoc" + } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "browser", + "title": "Test: Complete V2alpha1 Dashboard Example", + "weekStart": "monday" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v2beta1" } } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/v2beta1.dashboard-properties.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/v2beta1.dashboard-properties.v0alpha1.json index 94507deec93..8ed346c0bc2 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/v2beta1.dashboard-properties.v0alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/v2beta1.dashboard-properties.v0alpha1.json @@ -7,221 +7,158 @@ "test": "dashboard-properties" } }, - "spec": null, + "spec": { + "annotations": { + "list": [ + { + "enable": true, + "hide": false, + "iconColor": "blue", + "name": "Simple Annotation", + "target": { + "refId": "Anno" + } + } + ] + }, + "description": "Testing dashboard-level property transformations including time settings, cursor sync, and links", + "editable": true, + "fiscalYearStartMonth": 4, + "graphTooltip": 2, + "links": [ + { + "asDropdown": false, + "icon": "external link", + "includeVars": true, + "keepTime": true, + "tags": [ + "monitoring", + "alerts" + ], + "targetBlank": true, + "title": "External Monitoring System", + "tooltip": "View in external system", + "type": "link", + "url": "https://monitoring.example.com/dashboard?from=${__from}\u0026to=${__to}" + }, + { + "asDropdown": true, + "icon": "dashboard", + "includeVars": false, + "keepTime": false, + "tags": [ + "grafana", + "internal" + ], + "targetBlank": false, + "title": "Related Dashboards", + "tooltip": "Navigate to related dashboards", + "type": "dashboards", + "url": "" + }, + { + "asDropdown": false, + "icon": "info", + "includeVars": false, + "keepTime": false, + "placement": "inControlsMenu", + "tags": [], + "targetBlank": true, + "title": "Documentation", + "tooltip": "View documentation", + "type": "link", + "url": "https://docs.example.com/dashboard-guide" + }, + { + "asDropdown": false, + "icon": "tag", + "includeVars": true, + "keepTime": true, + "tags": [ + "alerts" + ], + "targetBlank": false, + "title": "Tag-based Link", + "tooltip": "Filtered by tags", + "type": "tag", + "url": "/dashboards/tag/alerts" + } + ], + "liveNow": true, + "panels": [ + { + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 1, + "options": {}, + "pluginVersion": "", + "targets": [ + { + "refId": "A" + } + ], + "title": "Simple Panel", + "type": "stat" + } + ], + "preload": true, + "refresh": "30s", + "schemaVersion": 42, + "tags": [ + "test", + "properties", + "metadata" + ], + "templating": { + "list": [ + { + "allowCustomValue": true, + "current": { + "text": "value1", + "value": "value1" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "simple_var", + "options": [], + "query": "value1,value2,value3", + "skipUrlSync": false, + "type": "custom" + } + ] + }, + "time": { + "from": "now-12h", + "to": "now" + }, + "timepicker": { + "nowDelay": "1m", + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "America/New_York", + "title": "Dashboard Properties Test", + "weekStart": "sunday" + }, "status": { "conversion": { - "failed": true, - "error": "backend conversion not yet implemented", - "storedVersion": "v2beta1", - "source": { - "kind": "Dashboard", - "apiVersion": "dashboard.grafana.app/v2beta1", - "metadata": { - "name": "dashboard-properties-test", - "labels": { - "test": "dashboard-properties" - } - }, - "spec": { - "annotations": [ - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "", - "version": "v0", - "spec": { - "refId": "Anno" - } - }, - "enable": true, - "hide": false, - "iconColor": "blue", - "name": "Simple Annotation", - "builtIn": false - } - } - ], - "cursorSync": "Tooltip", - "description": "Testing dashboard-level property transformations including time settings, cursor sync, and links", - "editable": true, - "elements": { - "panel-1": { - "kind": "Panel", - "spec": { - "id": 1, - "title": "Simple Panel", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "", - "version": "v0", - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "stat", - "version": "", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - } - }, - "layout": { - "kind": "GridLayout", - "spec": { - "items": [ - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 12, - "height": 8, - "element": { - "kind": "ElementReference", - "name": "panel-1" - } - } - } - ] - } - }, - "links": [ - { - "title": "External Monitoring System", - "type": "link", - "icon": "external link", - "tooltip": "View in external system", - "url": "https://monitoring.example.com/dashboard?from=${__from}\u0026to=${__to}", - "tags": [ - "monitoring", - "alerts" - ], - "asDropdown": false, - "targetBlank": true, - "includeVars": true, - "keepTime": true - }, - { - "title": "Related Dashboards", - "type": "dashboards", - "icon": "dashboard", - "tooltip": "Navigate to related dashboards", - "url": "", - "tags": [ - "grafana", - "internal" - ], - "asDropdown": true, - "targetBlank": false, - "includeVars": false, - "keepTime": false - }, - { - "title": "Documentation", - "type": "link", - "icon": "info", - "tooltip": "View documentation", - "url": "https://docs.example.com/dashboard-guide", - "tags": [], - "asDropdown": false, - "targetBlank": true, - "includeVars": false, - "keepTime": false, - "placement": "inControlsMenu" - }, - { - "title": "Tag-based Link", - "type": "tag", - "icon": "tag", - "tooltip": "Filtered by tags", - "url": "/dashboards/tag/alerts", - "tags": [ - "alerts" - ], - "asDropdown": false, - "targetBlank": false, - "includeVars": true, - "keepTime": true - } - ], - "liveNow": true, - "preload": true, - "tags": [ - "test", - "properties", - "metadata" - ], - "timeSettings": { - "timezone": "America/New_York", - "from": "now-12h", - "to": "now", - "autoRefresh": "30s", - "autoRefreshIntervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "hideTimepicker": false, - "weekStart": "sunday", - "fiscalYearStartMonth": 4, - "nowDelay": "1m" - }, - "title": "Dashboard Properties Test", - "variables": [ - { - "kind": "CustomVariable", - "spec": { - "name": "simple_var", - "query": "value1,value2,value3", - "current": { - "text": "value1", - "value": "value1" - }, - "options": [], - "multi": false, - "includeAll": false, - "hide": "dontHide", - "skipUrlSync": false, - "allowCustomValue": true - } - } - ] - }, - "status": {} - } + "failed": false, + "storedVersion": "v2beta1" } } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/v2beta1.datasource-resolution.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/v2beta1.datasource-resolution.v0alpha1.json index eaa7c1c2549..c1066edc211 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/v2beta1.datasource-resolution.v0alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/v2beta1.datasource-resolution.v0alpha1.json @@ -4,813 +4,575 @@ "metadata": { "name": "test-v2beta1-datasource-resolution" }, - "spec": null, - "status": { - "conversion": { - "failed": true, - "error": "backend conversion not yet implemented", - "storedVersion": "v2beta1", - "source": { - "kind": "Dashboard", - "apiVersion": "dashboard.grafana.app/v2beta1", - "metadata": { - "name": "test-v2beta1-datasource-resolution" - }, - "spec": { - "annotations": [ - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana", - "version": "v0", - "datasource": { - "name": "-- Grafana --" - }, - "spec": { - "limit": 100, - "matchAny": false, - "tags": [], - "type": "dashboard" - } - }, - "enable": true, - "hide": false, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations \u0026 Alerts", - "builtIn": true, - "legacyOptions": { - "type": "dashboard" - } - } - }, - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "existing-ref-uid" - }, - "spec": { - "expr": "ALERTS{alertstate=\"firing\"}", - "interval": "1m", - "refId": "Anno", - "step": 60 - } - }, - "enable": true, - "hide": false, - "iconColor": "red", - "name": "Annotation with DS UID defined", - "builtIn": false, - "legacyOptions": { - "type": "prometheus" - } - } - }, - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "elasticsearch", - "version": "v0", - "spec": { - "query": "tags:deployment", - "refId": "Anno", - "timeField": "@timestamp" - } - }, - "enable": true, - "hide": false, - "iconColor": "yellow", - "name": "Annotations only with group defined", - "builtIn": false, - "legacyOptions": { - "tagsField": "tags", - "textField": "message", - "timeEndField": "end_time", - "timeField": "@timestamp", - "type": "elasticsearch" - } - } - }, - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "", - "version": "v0", - "spec": { - "format": "table", - "rawSql": "SELECT time, title, text, tags FROM annotations WHERE $__timeFilter(time)", - "refId": "Anno" - } - }, - "enable": true, - "hide": false, - "iconColor": "green", - "name": "Annotation without group or UID defined ", - "builtIn": false - } - } - ], - "cursorSync": "Tooltip", - "elements": { - "panel-1": { - "kind": "Panel", - "spec": { - "id": 1, - "title": "Panel with group and name defined", - "description": "This should resolve to the grafana-testdata-datasource datasource", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana-testdata-datasource", - "version": "v0", - "datasource": { - "name": "gdev-testdata" - }, - "spec": { - "scenarioId": "random_walk", - "seriesCount": 3 - } - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "12.1.0-pre", - "spec": { - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "hideZeros": false, - "mode": "single", - "sort": "none" - } - }, - "fieldConfig": { - "defaults": { - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": 0, - "color": "green" - }, - { - "value": 80, - "color": "red" - } - ] - }, - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - } - }, - "overrides": [] - } - } - } - } - }, - "panel-2": { - "kind": "Panel", - "spec": { - "id": 2, - "title": "Panel with group defined but no name", - "description": "This should resolve to the first elasticsearch datasource", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "elasticsearch", - "version": "v0", - "spec": { - "alias": "", - "bucketAggs": [], - "metrics": [], - "query": "", - "timeField": "@timestamp" - } - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "12.1.0-pre", - "spec": { - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "hideZeros": false, - "mode": "single", - "sort": "none" - } - }, - "fieldConfig": { - "defaults": { - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": 0, - "color": "green" - }, - { - "value": 80, - "color": "red" - } - ] - }, - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - } - }, - "overrides": [] - } - } - } - } - }, - "panel-3": { - "kind": "Panel", - "spec": { - "id": 3, - "title": "Panel with no group defined", - "description": "This should resolve to the default datasource", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "", - "version": "v0", - "spec": { - "scenarioId": "random_walk", - "seriesCount": 3 - } - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "12.1.0-pre", - "spec": { - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "hideZeros": false, - "mode": "single", - "sort": "none" - } - }, - "fieldConfig": { - "defaults": { - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": 0, - "color": "green" - }, - { - "value": 80, - "color": "red" - } - ] - }, - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - } - }, - "overrides": [] - } - } - } - } - }, - "panel-4": { - "kind": "Panel", - "spec": { - "id": 4, - "title": "Panel mixed Datasource and Group defined", - "description": "This should resolve to a mixed datasource", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "existing-ref-uid" - }, - "spec": { - "query": "up" - } - }, - "refId": "A", - "hidden": false - } - }, - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "elasticsearch", - "version": "v0", - "spec": { - "bucketAggs": [ - { - "field": "@host", - "id": "2", - "type": "terms" - } - ], - "query": "*", - "size": 100 - } - }, - "refId": "B", - "hidden": false - } - }, - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "", - "version": "v0", - "spec": { - "scenarioId": "random_walk", - "seriesCount": 3 - } - }, - "refId": "C", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "12.1.0-pre", - "spec": { - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "hideZeros": false, - "mode": "single", - "sort": "none" - } - }, - "fieldConfig": { - "defaults": { - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": 0, - "color": "green" - }, - { - "value": 80, - "color": "red" - } - ] - }, - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - } - }, - "overrides": [] - } - } - } - } - } + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" }, - "layout": { - "kind": "RowsLayout", - "spec": { - "rows": [ + "enable": true, + "hide": false, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + }, + { + "datasource": { + "type": "prometheus", + "uid": "existing-ref-uid" + }, + "enable": true, + "hide": false, + "iconColor": "red", + "name": "Annotation with DS UID defined", + "target": { + "expr": "ALERTS{alertstate=\"firing\"}", + "interval": "1m", + "refId": "Anno", + "step": 60 + }, + "type": "prometheus" + }, + { + "datasource": { + "type": "elasticsearch" + }, + "enable": true, + "hide": false, + "iconColor": "yellow", + "name": "Annotations only with group defined", + "tagsField": "tags", + "target": { + "query": "tags:deployment", + "refId": "Anno", + "timeField": "@timestamp" + }, + "textField": "message", + "timeEndField": "end_time", + "timeField": "@timestamp", + "type": "elasticsearch" + }, + { + "enable": true, + "hide": false, + "iconColor": "green", + "name": "Annotation without group or UID defined ", + "target": { + "format": "table", + "rawSql": "SELECT time, title, text, tags FROM annotations WHERE $__timeFilter(time)", + "refId": "Anno" + } + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 2, + "liveNow": false, + "panels": [ + { + "description": "This should resolve to the grafana-testdata-datasource datasource", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "thresholds": { + "mode": "absolute", + "steps": [ { - "kind": "Row", - "spec": { - "title": "", - "collapse": false, - "hideHeader": true, - "fillScreen": false, - "layout": { - "kind": "GridLayout", - "spec": { - "items": [ - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 8, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-1" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 3, - "width": 8, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-2" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 6, - "width": 8, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-3" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 9, - "width": 8, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-4" - } - } - } - ] - } - } - } + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 } ] } - }, - "links": [], - "liveNow": false, - "preload": false, - "tags": [], - "timeSettings": { - "timezone": "browser", - "from": "now-6h", - "to": "now", - "autoRefresh": "", - "autoRefreshIntervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "hideTimepicker": false, - "fiscalYearStartMonth": 0 - }, - "title": "Test: V2beta1 dashboard with datasource resolution", - "variables": [ - { - "kind": "QueryVariable", - "spec": { - "name": "group_and_uid_defined", - "current": { - "text": "legacy_value", - "value": "legacy_string_content" - }, - "label": "Group and UID defined", - "hide": "dontHide", - "refresh": "onDashboardLoad", - "skipUrlSync": false, - "description": "Variable with group and UID defined", - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "existing-ref-uid" - }, - "spec": { - "__legacyStringValue": "up" - } - }, - "regex": "", - "sort": "disabled", - "options": [], - "multi": false, - "includeAll": false, - "allowCustomValue": true - } - }, - { - "kind": "QueryVariable", - "spec": { - "name": "group_defined_but_no_name", - "current": { - "text": "legacy_value", - "value": "legacy_string_content" - }, - "label": "Group defined but no name", - "hide": "dontHide", - "refresh": "onDashboardLoad", - "skipUrlSync": false, - "description": "Variable with group defined but no name", - "query": { - "kind": "DataQuery", - "group": "elasticsearch", - "version": "v0", - "spec": { - "bucketAggs": [ - { - "field": "@host", - "id": "2", - "type": "terms" - } - ], - "query": "*", - "size": 100 - } - }, - "regex": "", - "sort": "disabled", - "options": [], - "multi": false, - "includeAll": false, - "allowCustomValue": true - } - }, - { - "kind": "QueryVariable", - "spec": { - "name": "no_group_defined", - "current": { - "text": "legacy_value", - "value": "legacy_string_content" - }, - "label": "No group defined", - "hide": "dontHide", - "refresh": "onDashboardLoad", - "skipUrlSync": false, - "description": "Variable with no group defined", - "query": { - "kind": "DataQuery", - "group": "", - "version": "v0", - "spec": { - "scenarioId": "random_walk", - "seriesCount": 3 - } - }, - "regex": "", - "sort": "disabled", - "options": [], - "multi": false, - "includeAll": false, - "allowCustomValue": true - } - } - ] - }, - "status": { - "conversion": { - "failed": false, - "storedVersion": "v2beta1" } - } + }, + "gridPos": { + "h": 3, + "w": 8, + "x": 0, + "y": 0 + }, + "id": 1, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.1.0-pre", + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "gdev-testdata" + }, + "refId": "A", + "scenarioId": "random_walk", + "seriesCount": 3 + } + ], + "title": "Panel with group and name defined", + "type": "timeseries" + }, + { + "description": "This should resolve to the first elasticsearch datasource", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + } + }, + "gridPos": { + "h": 3, + "w": 8, + "x": 0, + "y": 3 + }, + "id": 2, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.1.0-pre", + "targets": [ + { + "alias": "", + "bucketAggs": [], + "datasource": { + "type": "elasticsearch" + }, + "metrics": [], + "query": "", + "refId": "A", + "timeField": "@timestamp" + } + ], + "title": "Panel with group defined but no name", + "type": "timeseries" + }, + { + "description": "This should resolve to the default datasource", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + } + }, + "gridPos": { + "h": 3, + "w": 8, + "x": 0, + "y": 6 + }, + "id": 3, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.1.0-pre", + "targets": [ + { + "refId": "A", + "scenarioId": "random_walk", + "seriesCount": 3 + } + ], + "title": "Panel with no group defined", + "type": "timeseries" + }, + { + "datasource": { + "type": "mixed", + "uid": "-- Mixed --" + }, + "description": "This should resolve to a mixed datasource", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + } + }, + "gridPos": { + "h": 3, + "w": 8, + "x": 0, + "y": 9 + }, + "id": 4, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.1.0-pre", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "existing-ref-uid" + }, + "query": "up", + "refId": "A" + }, + { + "bucketAggs": [ + { + "field": "@host", + "id": "2", + "type": "terms" + } + ], + "datasource": { + "type": "elasticsearch" + }, + "query": "*", + "refId": "B", + "size": 100 + }, + { + "refId": "C", + "scenarioId": "random_walk", + "seriesCount": 3 + } + ], + "title": "Panel mixed Datasource and Group defined", + "type": "timeseries" } + ], + "preload": false, + "refresh": "", + "schemaVersion": 42, + "templating": { + "list": [ + { + "allowCustomValue": true, + "current": { + "text": "legacy_value", + "value": "legacy_string_content" + }, + "datasource": { + "type": "prometheus", + "uid": "existing-ref-uid" + }, + "description": "Variable with group and UID defined", + "hide": 0, + "includeAll": false, + "label": "Group and UID defined", + "multi": false, + "name": "group_and_uid_defined", + "options": [], + "query": "up", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "allowCustomValue": true, + "current": { + "text": "legacy_value", + "value": "legacy_string_content" + }, + "datasource": { + "type": "elasticsearch" + }, + "description": "Variable with group defined but no name", + "hide": 0, + "includeAll": false, + "label": "Group defined but no name", + "multi": false, + "name": "group_defined_but_no_name", + "options": [], + "query": { + "bucketAggs": [ + { + "field": "@host", + "id": "2", + "type": "terms" + } + ], + "query": "*", + "size": 100 + }, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "allowCustomValue": true, + "current": { + "text": "legacy_value", + "value": "legacy_string_content" + }, + "description": "Variable with no group defined", + "hide": 0, + "includeAll": false, + "label": "No group defined", + "multi": false, + "name": "no_group_defined", + "options": [], + "query": { + "scenarioId": "random_walk", + "seriesCount": 3 + }, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "browser", + "title": "Test: V2beta1 dashboard with datasource resolution" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v2beta1" } } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/v2beta1.ds-data-query.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/v2beta1.ds-data-query.v0alpha1.json index 121b020c0f0..6a418f1e21d 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/v2beta1.ds-data-query.v0alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/v2beta1.ds-data-query.v0alpha1.json @@ -4,1141 +4,825 @@ "metadata": { "name": "test-v2alpha1-annotations" }, - "spec": null, - "status": { - "conversion": { - "failed": true, - "error": "backend conversion not yet implemented", - "storedVersion": "v2beta1", - "source": { - "kind": "Dashboard", - "apiVersion": "dashboard.grafana.app/v2beta1", - "metadata": { - "name": "test-v2alpha1-annotations" - }, - "spec": { - "annotations": [ - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana", - "version": "v0", - "datasource": { - "name": "-- Grafana --" - }, - "spec": { - "limit": 100, - "matchAny": false, - "tags": [], - "type": "dashboard" - } - }, - "enable": true, - "hide": false, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations \u0026 Alerts", - "builtIn": true - } - }, - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana-testdata-datasource", - "version": "v0", - "datasource": { - "name": "PD8C576611E62080A" - }, - "spec": { - "lines": 10, - "refId": "Anno", - "scenarioId": "annotations" - } - }, - "enable": true, - "hide": false, - "iconColor": "blue", - "name": "testdata-annos", - "builtIn": false - } - }, - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "spec": { - "lines": 10, - "refId": "Anno", - "scenarioId": "annotations" - } - }, - "enable": true, - "hide": false, - "iconColor": "blue", - "name": "no-ds-testdata-annos", - "builtIn": false - } - }, - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "gdev-prometheus" - }, - "spec": { - "expr": "{action=\"add_client\"}", - "interval": "", - "lines": 10, - "refId": "Anno", - "scenarioId": "annotations" - } - }, - "enable": true, - "hide": false, - "iconColor": "yellow", - "name": "prom-annos", - "builtIn": false - } - }, - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "spec": { - "expr": "{action=\"add_client\"}", - "interval": "", - "lines": 10, - "refId": "Anno", - "scenarioId": "annotations" - } - }, - "enable": true, - "hide": false, - "iconColor": "yellow", - "name": "no-ds-prom-annos", - "builtIn": false - } - }, - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana-postgresql-datasource", - "version": "v0", - "datasource": { - "name": "PBBCEC2D313BC06C3" - }, - "spec": { - "editorMode": "builder", - "format": "table", - "lines": 10, - "rawSql": "", - "refId": "Anno", - "scenarioId": "annotations", - "sql": { - "columns": [ - { - "parameters": [], - "type": "function" - } - ], - "groupBy": [ - { - "property": { - "type": "string" - }, - "type": "groupBy" - } - ], - "limit": 50 - } - } - }, - "enable": true, - "hide": false, - "iconColor": "red", - "name": "postgress-annos", - "builtIn": false - } - }, - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "elasticsearch", - "version": "v0", - "datasource": { - "name": "gdev-elasticsearch" - }, - "spec": { - "lines": 10, - "query": "test query", - "refId": "Anno", - "scenarioId": "annotations" - } - }, - "enable": true, - "hide": false, - "iconColor": "red", - "name": "elastic - annos", - "builtIn": false, - "legacyOptions": { - "tagsField": "asd", - "textField": "asd", - "timeEndField": "asdas", - "timeField": "asd" - } - } - } - ], - "cursorSync": "Off", - "editable": true, - "elements": { - "panel-1": { - "kind": "Panel", - "spec": { - "id": 1, - "title": "Simple timeseries (WITH DS REF)", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana-testdata-datasource", - "version": "v0", - "datasource": { - "name": "gdev-testdata" - }, - "spec": { - "scenarioId": "random_walk", - "seriesCount": 3 - } - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "12.1.0-pre", - "spec": { - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "hideZeros": false, - "mode": "single", - "sort": "none" - } - }, - "fieldConfig": { - "defaults": { - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": 0, - "color": "green" - }, - { - "value": 80, - "color": "red" - } - ] - }, - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - } - }, - "overrides": [] - } - } - } - } - }, - "panel-2": { - "kind": "Panel", - "spec": { - "id": 2, - "title": "Simple stat (NO DS REF)", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana-testdata-datasource", - "version": "v0", - "spec": { - "scenarioId": "random_walk", - "seriesCount": 4 - } - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "stat", - "version": "12.1.0-pre", - "spec": { - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "percentChangeColorMode": "standard", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "fieldConfig": { - "defaults": { - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": 0, - "color": "green" - }, - { - "value": 80, - "color": "red" - } - ] - }, - "color": { - "mode": "thresholds" - } - }, - "overrides": [] - } - } - } - } - }, - "panel-3": { - "kind": "Panel", - "spec": { - "id": 3, - "title": "Panel with NO REF to gdev-prometheus", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "spec": { - "disableTextWrap": false, - "editorMode": "builder", - "expr": "rate(counters_requests[$__rate_interval])", - "fullMetaSearch": false, - "includeNullMetadata": false, - "instant": false, - "legendFormat": "__auto", - "range": true, - "useBackend": false - } - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "12.1.0-pre", - "spec": { - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "hideZeros": false, - "mode": "single", - "sort": "none" - } - }, - "fieldConfig": { - "defaults": { - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": 0, - "color": "green" - }, - { - "value": 80, - "color": "red" - } - ] - }, - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - } - }, - "overrides": [] - } - } - } - } - }, - "panel-4": { - "kind": "Panel", - "spec": { - "id": 4, - "title": "Panel with ref to gdev-prometheus", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "gdev-prometheus" - }, - "spec": { - "disableTextWrap": false, - "editorMode": "builder", - "expr": "rate(counters_requests[$__rate_interval])", - "fullMetaSearch": false, - "includeNullMetadata": false, - "instant": false, - "legendFormat": "__auto", - "range": true, - "useBackend": false - } - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "12.1.0-pre", - "spec": { - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "hideZeros": false, - "mode": "single", - "sort": "none" - } - }, - "fieldConfig": { - "defaults": { - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": 0, - "color": "green" - }, - { - "value": 80, - "color": "red" - } - ] - }, - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - } - }, - "overrides": [] - } - } - } - } - }, - "panel-5": { - "kind": "Panel", - "spec": { - "id": 5, - "title": "Mixed DS WITH REFS", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "gdev-prometheus" - }, - "spec": { - "disableTextWrap": false, - "editorMode": "builder", - "expr": "rate(counters_requests{server=\"backend-01\"}[$__rate_interval])", - "fullMetaSearch": false, - "includeNullMetadata": false, - "legendFormat": "__auto", - "range": true, - "useBackend": false - } - }, - "refId": "A", - "hidden": false - } - }, - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana-testdata-datasource", - "version": "v0", - "datasource": { - "name": "gdev-testdata" - }, - "spec": {} - }, - "refId": "B", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "12.1.0-pre", - "spec": { - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "hideZeros": false, - "mode": "single", - "sort": "none" - } - }, - "fieldConfig": { - "defaults": { - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": 0, - "color": "green" - }, - { - "value": 80, - "color": "red" - } - ] - }, - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - } - }, - "overrides": [] - } - } - } - } - }, - "panel-6": { - "kind": "Panel", - "spec": { - "id": 6, - "title": "Mixed DS WITHOUT REFS", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "spec": { - "disableTextWrap": false, - "editorMode": "builder", - "expr": "rate(counters_requests{server=\"backend-01\"}[$__rate_interval])", - "fullMetaSearch": false, - "includeNullMetadata": false, - "legendFormat": "__auto", - "range": true, - "useBackend": false - } - }, - "refId": "A", - "hidden": false - } - }, - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana-testdata-datasource", - "version": "v0", - "spec": {} - }, - "refId": "B", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "12.1.0-pre", - "spec": { - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "hideZeros": false, - "mode": "single", - "sort": "none" - } - }, - "fieldConfig": { - "defaults": { - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": 0, - "color": "green" - }, - { - "value": 80, - "color": "red" - } - ] - }, - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - } - }, - "overrides": [] - } - } - } - } - } + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" }, - "layout": { - "kind": "RowsLayout", - "spec": { - "rows": [ + "enable": true, + "hide": false, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + }, + { + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "PD8C576611E62080A" + }, + "enable": true, + "hide": false, + "iconColor": "blue", + "name": "testdata-annos", + "target": { + "lines": 10, + "refId": "Anno", + "scenarioId": "annotations" + } + }, + { + "datasource": { + "type": "prometheus" + }, + "enable": true, + "hide": false, + "iconColor": "blue", + "name": "no-ds-testdata-annos", + "target": { + "lines": 10, + "refId": "Anno", + "scenarioId": "annotations" + } + }, + { + "datasource": { + "type": "prometheus", + "uid": "gdev-prometheus" + }, + "enable": true, + "hide": false, + "iconColor": "yellow", + "name": "prom-annos", + "target": { + "expr": "{action=\"add_client\"}", + "interval": "", + "lines": 10, + "refId": "Anno", + "scenarioId": "annotations" + } + }, + { + "datasource": { + "type": "prometheus" + }, + "enable": true, + "hide": false, + "iconColor": "yellow", + "name": "no-ds-prom-annos", + "target": { + "expr": "{action=\"add_client\"}", + "interval": "", + "lines": 10, + "refId": "Anno", + "scenarioId": "annotations" + } + }, + { + "datasource": { + "type": "grafana-postgresql-datasource", + "uid": "PBBCEC2D313BC06C3" + }, + "enable": true, + "hide": false, + "iconColor": "red", + "name": "postgress-annos", + "target": { + "editorMode": "builder", + "format": "table", + "lines": 10, + "rawSql": "", + "refId": "Anno", + "scenarioId": "annotations", + "sql": { + "columns": [ { - "kind": "Row", - "spec": { - "title": "", - "collapse": false, - "hideHeader": true, - "fillScreen": false, - "layout": { - "kind": "GridLayout", - "spec": { - "items": [ - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 8, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-3" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 8, - "y": 0, - "width": 8, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-4" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 16, - "y": 0, - "width": 8, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-2" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 3, - "width": 8, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-1" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 8, - "y": 3, - "width": 8, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-5" - } - } - }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 16, - "y": 3, - "width": 8, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-6" - } - } - } - ] - } - } - } + "parameters": [], + "type": "function" + } + ], + "groupBy": [ + { + "property": { + "type": "string" + }, + "type": "groupBy" + } + ], + "limit": 50 + } + } + }, + { + "datasource": { + "type": "elasticsearch", + "uid": "gdev-elasticsearch" + }, + "enable": true, + "hide": false, + "iconColor": "red", + "name": "elastic - annos", + "tagsField": "asd", + "target": { + "lines": 10, + "query": "test query", + "refId": "Anno", + "scenarioId": "annotations" + }, + "textField": "asd", + "timeEndField": "asdas", + "timeField": "asd" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "liveNow": false, + "panels": [ + { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 } ] } - }, - "links": [], - "liveNow": false, - "preload": false, - "tags": [], - "timeSettings": { - "timezone": "browser", - "from": "now-6h", - "to": "now", - "autoRefresh": "", - "autoRefreshIntervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "hideTimepicker": false, - "fiscalYearStartMonth": 0 - }, - "title": "Test: V2alpha1 dashboard with annotations", - "variables": [ - { - "kind": "QueryVariable", - "spec": { - "name": "variable-ds-prometheus", - "current": { - "text": "", - "value": "" - }, - "hide": "dontHide", - "refresh": "", - "skipUrlSync": false, - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "datasource": { - "name": "gdev-prometheus" - }, - "spec": { - "expr": "up" - } - }, - "regex": "", - "sort": "", - "options": null, - "multi": false, - "includeAll": false, - "allowCustomValue": false - } - }, - { - "kind": "QueryVariable", - "spec": { - "name": "variable-no-ds", - "current": { - "text": "", - "value": "" - }, - "hide": "dontHide", - "refresh": "", - "skipUrlSync": false, - "query": { - "kind": "DataQuery", - "group": "grafana-testdata-datasource", - "version": "v0", - "spec": { - "csv": "1,2,3,4", - "scenarioId": "csv_metric_values" - } - }, - "regex": "", - "sort": "", - "options": null, - "multi": false, - "includeAll": false, - "allowCustomValue": false - } - }, - { - "kind": "QueryVariable", - "spec": { - "name": "variable-no-ds-empty-query", - "current": { - "text": "", - "value": "" - }, - "hide": "dontHide", - "refresh": "", - "skipUrlSync": false, - "query": { - "kind": "DataQuery", - "group": "grafana-testdata-datasource", - "version": "v0", - "spec": {} - }, - "regex": "", - "sort": "", - "options": null, - "multi": false, - "includeAll": false, - "allowCustomValue": false - } - }, - { - "kind": "QueryVariable", - "spec": { - "name": "variable-no-default-ds", - "current": { - "text": "", - "value": "" - }, - "hide": "dontHide", - "refresh": "", - "skipUrlSync": false, - "query": { - "kind": "DataQuery", - "group": "prometheus", - "version": "v0", - "spec": { - "expr": "up" - } - }, - "regex": "", - "sort": "", - "options": null, - "multi": false, - "includeAll": false, - "allowCustomValue": false - } - } - ] - }, - "status": { - "conversion": { - "failed": false, - "storedVersion": "v2alpha1" } - } + }, + "gridPos": { + "h": 3, + "w": 8, + "x": 0, + "y": 0 + }, + "id": 3, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.1.0-pre", + "targets": [ + { + "datasource": { + "type": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "rate(counters_requests[$__rate_interval])", + "fullMetaSearch": false, + "includeNullMetadata": false, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Panel with NO REF to gdev-prometheus", + "type": "timeseries" + }, + { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + } + }, + "gridPos": { + "h": 3, + "w": 8, + "x": 8, + "y": 0 + }, + "id": 4, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.1.0-pre", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "gdev-prometheus" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "rate(counters_requests[$__rate_interval])", + "fullMetaSearch": false, + "includeNullMetadata": false, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Panel with ref to gdev-prometheus", + "type": "timeseries" + }, + { + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + } + }, + "gridPos": { + "h": 3, + "w": 8, + "x": 16, + "y": 0 + }, + "id": 2, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "12.1.0-pre", + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "refId": "A", + "scenarioId": "random_walk", + "seriesCount": 4 + } + ], + "title": "Simple stat (NO DS REF)", + "type": "stat" + }, + { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + } + }, + "gridPos": { + "h": 3, + "w": 8, + "x": 0, + "y": 3 + }, + "id": 1, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.1.0-pre", + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "gdev-testdata" + }, + "refId": "A", + "scenarioId": "random_walk", + "seriesCount": 3 + } + ], + "title": "Simple timeseries (WITH DS REF)", + "type": "timeseries" + }, + { + "datasource": { + "type": "mixed", + "uid": "-- Mixed --" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + } + }, + "gridPos": { + "h": 3, + "w": 8, + "x": 8, + "y": 3 + }, + "id": 5, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.1.0-pre", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "gdev-prometheus" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "rate(counters_requests{server=\"backend-01\"}[$__rate_interval])", + "fullMetaSearch": false, + "includeNullMetadata": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + }, + { + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "gdev-testdata" + }, + "refId": "B" + } + ], + "title": "Mixed DS WITH REFS", + "type": "timeseries" + }, + { + "datasource": { + "type": "mixed", + "uid": "-- Mixed --" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + } + }, + "gridPos": { + "h": 3, + "w": 8, + "x": 16, + "y": 3 + }, + "id": 6, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.1.0-pre", + "targets": [ + { + "datasource": { + "type": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "rate(counters_requests{server=\"backend-01\"}[$__rate_interval])", + "fullMetaSearch": false, + "includeNullMetadata": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + }, + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "refId": "B" + } + ], + "title": "Mixed DS WITHOUT REFS", + "type": "timeseries" } + ], + "preload": false, + "refresh": "", + "schemaVersion": 42, + "templating": { + "list": [ + { + "allowCustomValue": false, + "current": { + "text": "", + "value": "" + }, + "datasource": { + "type": "prometheus", + "uid": "gdev-prometheus" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "variable-ds-prometheus", + "options": [], + "query": { + "expr": "up" + }, + "refresh": 0, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "allowCustomValue": false, + "current": { + "text": "", + "value": "" + }, + "datasource": { + "type": "grafana-testdata-datasource" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "variable-no-ds", + "options": [], + "query": { + "csv": "1,2,3,4", + "scenarioId": "csv_metric_values" + }, + "refresh": 0, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "allowCustomValue": false, + "current": { + "text": "", + "value": "" + }, + "datasource": { + "type": "grafana-testdata-datasource" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "variable-no-ds-empty-query", + "options": [], + "query": {}, + "refresh": 0, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "allowCustomValue": false, + "current": { + "text": "", + "value": "" + }, + "datasource": { + "type": "prometheus" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "variable-no-default-ds", + "options": [], + "query": { + "expr": "up" + }, + "refresh": 0, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "browser", + "title": "Test: V2alpha1 dashboard with annotations" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v2beta1" } } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/v2beta1.groupby-adhoc-vars.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/v2beta1.groupby-adhoc-vars.v0alpha1.json index 9de5535160d..c165c9de4e6 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/v2beta1.groupby-adhoc-vars.v0alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/v2beta1.groupby-adhoc-vars.v0alpha1.json @@ -4,115 +4,91 @@ "metadata": { "name": "test-v2alpha1-groupby-adhoc-vars" }, - "spec": null, - "status": { - "conversion": { - "failed": true, - "error": "backend conversion not yet implemented", - "storedVersion": "v2beta1", - "source": { - "kind": "Dashboard", - "apiVersion": "dashboard.grafana.app/v2beta1", - "metadata": { - "name": "test-v2alpha1-groupby-adhoc-vars" + "spec": { + "annotations": { + "list": [] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "preload": false, + "refresh": "", + "schemaVersion": 42, + "templating": { + "list": [ + { + "current": { + "text": "text7", + "value": "value7" + }, + "datasource": { + "type": "prometheus", + "uid": "gdev-prometheus" + }, + "description": "A group by variable", + "hide": 0, + "label": "Group By Variable", + "multi": false, + "name": "", + "options": [], + "skipUrlSync": false, + "type": "groupby" }, - "spec": { - "annotations": [], - "cursorSync": "", - "elements": {}, - "layout": { - "kind": "RowsLayout", - "spec": { - "rows": [] - } - }, - "links": [], - "preload": false, - "tags": [], - "timeSettings": { - "from": "", - "to": "", - "autoRefresh": "", - "autoRefreshIntervals": null, - "hideTimepicker": false, - "fiscalYearStartMonth": 0 - }, - "title": "Test: V2alpha1 dashboard with group by and adhoc variables", - "variables": [ + { + "allowCustomValue": true, + "baseFilters": [ { - "kind": "GroupByVariable", - "group": "prometheus", - "datasource": { - "name": "gdev-prometheus" - }, - "spec": { - "name": "", - "current": { - "text": "text7", - "value": "value7" - }, - "options": null, - "multi": false, - "label": "Group By Variable", - "hide": "dontHide", - "skipUrlSync": false, - "description": "A group by variable" - } + "condition": "AND", + "key": "key1", + "operator": "=", + "value": "value1" }, { - "kind": "AdhocVariable", - "group": "prometheus", - "datasource": { - "name": "datasource-3" - }, - "spec": { - "name": "adhocVar", - "baseFilters": [ - { - "key": "key1", - "operator": "=", - "value": "value1", - "condition": "AND" - }, - { - "key": "key2", - "operator": "=", - "value": "value2", - "condition": "OR" - } - ], - "filters": [ - { - "key": "key3", - "operator": "=", - "value": "value3", - "condition": "AND" - } - ], - "defaultKeys": [ - { - "text": "defaultKey1", - "value": "defaultKey1", - "group": "defaultGroup1", - "expandable": true - } - ], - "label": "Adhoc Variable", - "hide": "dontHide", - "skipUrlSync": false, - "description": "An adhoc variable", - "allowCustomValue": true - } + "condition": "OR", + "key": "key2", + "operator": "=", + "value": "value2" } - ] - }, - "status": { - "conversion": { - "failed": false, - "storedVersion": "v2alpha1" - } + ], + "datasource": { + "type": "prometheus", + "uid": "datasource-3" + }, + "defaultKeys": [ + { + "expandable": true, + "group": "defaultGroup1", + "text": "defaultKey1", + "value": "defaultKey1" + } + ], + "description": "An adhoc variable", + "filters": [ + { + "condition": "AND", + "key": "key3", + "operator": "=", + "value": "value3" + } + ], + "hide": 0, + "label": "Adhoc Variable", + "name": "adhocVar", + "skipUrlSync": false, + "type": "adhoc" } - } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "title": "Test: V2alpha1 dashboard with group by and adhoc variables" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v2beta1" } } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/v2beta1.rows-with-nested-tabs.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/v2beta1.rows-with-nested-tabs.v0alpha1.json index fc54e3d14ff..f81abd723dc 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/v2beta1.rows-with-nested-tabs.v0alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/v2beta1.rows-with-nested-tabs.v0alpha1.json @@ -17,567 +17,416 @@ "grafana.app/saved-from-ui": "Grafana Cloud" } }, - "spec": null, - "status": { - "conversion": { - "failed": true, - "error": "backend conversion not yet implemented", - "storedVersion": "v2beta1", - "source": { - "kind": "Dashboard", - "apiVersion": "dashboard.grafana.app/v2beta1", - "metadata": { - "name": "ivx9964", - "namespace": "stacks-5756", - "uid": "627d3b09-32b6-4e0c-b5b7-b94be6fc94b6", - "resourceVersion": "1764589364114772", - "generation": 1, - "creationTimestamp": "2025-12-01T11:42:44Z", - "labels": { - "grafana.app/deprecatedInternalID": "363903738712064" + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" }, - "annotations": { - "grafana.app/createdBy": "user:ff3wylp4sgpa8a", - "grafana.app/folder": "", - "grafana.app/saved-from-ui": "Grafana Cloud" - } + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "description": "", + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "liveNow": false, + "panels": [ + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 0 }, - "spec": { - "annotations": [ - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana", - "version": "v0", - "datasource": { - "name": "-- Grafana --" - }, - "spec": {} - }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations \u0026 Alerts", - "builtIn": true - } - } - ], - "cursorSync": "Off", - "description": "", - "editable": true, - "elements": { - "panel-1": { - "kind": "Panel", - "spec": { - "id": 1, - "title": "New panel", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana-testdata-datasource", - "version": "v0", - "datasource": { - "name": "defin43am9o1sd" - }, - "spec": { - "scenarioId": "random_walk", - "seriesCount": 1 - } - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "12.4.0-19736337744", - "spec": { - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "hideZeros": false, - "mode": "single", - "sort": "none" - } - }, - "fieldConfig": { - "defaults": { - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": 0, - "color": "green" - }, - { - "value": 80, - "color": "red" - } - ] - }, - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "showValues": false, - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - } - }, - "overrides": [] - } - } - } + "id": -1, + "panels": [], + "title": "Row with tabs", + "type": "row" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 1 + }, + "id": -1, + "panels": [], + "title": "Tab with panels", + "type": "row" + }, + { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" } }, - "panel-2": { - "kind": "Panel", - "spec": { - "id": 2, - "title": "New panel", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana-testdata-datasource", - "version": "v0", - "datasource": { - "name": "defin43am9o1sd" - }, - "spec": { - "scenarioId": "random_walk", - "seriesCount": 1 - } - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "12.4.0-19736337744", - "spec": { - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "hideZeros": false, - "mode": "single", - "sort": "none" - } - }, - "fieldConfig": { - "defaults": { - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": 0, - "color": "green" - }, - { - "value": 80, - "color": "red" - } - ] - }, - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "showValues": false, - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - } - }, - "overrides": [] - } - } - } - } - }, - "panel-3": { - "kind": "Panel", - "spec": { - "id": 3, - "title": "New panel", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana-testdata-datasource", - "version": "v0", - "datasource": { - "name": "defin43am9o1sd" - }, - "spec": { - "scenarioId": "random_walk", - "seriesCount": 1 - } - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "12.4.0-19736337744", - "spec": { - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "hideZeros": false, - "mode": "single", - "sort": "none" - } - }, - "fieldConfig": { - "defaults": { - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": 0, - "color": "green" - }, - { - "value": 80, - "color": "red" - } - ] - }, - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "showValues": false, - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - } - }, - "overrides": [] - } - } - } - } - } - }, - "layout": { - "kind": "RowsLayout", - "spec": { - "rows": [ + "thresholds": { + "mode": "absolute", + "steps": [ { - "kind": "RowsLayoutRow", - "spec": { - "title": "Row with tabs", - "collapse": false, - "layout": { - "kind": "TabsLayout", - "spec": { - "tabs": [ - { - "kind": "TabsLayoutTab", - "spec": { - "title": "Tab with panels", - "layout": { - "kind": "GridLayout", - "spec": { - "items": [ - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 12, - "height": 8, - "element": { - "kind": "ElementReference", - "name": "panel-1" - } - } - } - ] - } - } - } - }, - { - "kind": "TabsLayoutTab", - "spec": { - "title": "Empty tab", - "layout": { - "kind": "AutoGridLayout", - "spec": { - "maxColumnCount": 3, - "columnWidthMode": "standard", - "rowHeightMode": "standard", - "items": [] - } - } - } - } - ] - } - } - } + "color": "green", + "value": 0 }, { - "kind": "RowsLayoutRow", - "spec": { - "title": "Empty row", - "collapse": false, - "layout": { - "kind": "AutoGridLayout", - "spec": { - "maxColumnCount": 3, - "columnWidthMode": "standard", - "rowHeightMode": "standard", - "items": [] - } - } - } - }, - { - "kind": "RowsLayoutRow", - "spec": { - "title": "Row with rows", - "collapse": false, - "layout": { - "kind": "RowsLayout", - "spec": { - "rows": [ - { - "kind": "RowsLayoutRow", - "spec": { - "title": "Empty nested row", - "collapse": false, - "layout": { - "kind": "AutoGridLayout", - "spec": { - "maxColumnCount": 3, - "columnWidthMode": "standard", - "rowHeightMode": "standard", - "items": [] - } - } - } - }, - { - "kind": "RowsLayoutRow", - "spec": { - "title": "Row with panels filling screen", - "collapse": false, - "fillScreen": true, - "layout": { - "kind": "AutoGridLayout", - "spec": { - "maxColumnCount": 3, - "columnWidthMode": "standard", - "rowHeightMode": "standard", - "items": [ - { - "kind": "AutoGridLayoutItem", - "spec": { - "element": { - "kind": "ElementReference", - "name": "panel-2" - } - } - }, - { - "kind": "AutoGridLayoutItem", - "spec": { - "element": { - "kind": "ElementReference", - "name": "panel-3" - } - } - } - ] - } - } - } - } - ] - } - } - } + "color": "red", + "value": 80 } ] } - }, - "links": [], - "liveNow": false, - "preload": false, - "tags": [], - "timeSettings": { - "timezone": "browser", - "from": "now-6h", - "to": "now", - "autoRefresh": "", - "autoRefreshIntervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "hideTimepicker": false, - "fiscalYearStartMonth": 0 - }, - "title": "Rows with nested tabs", - "variables": [] + } }, - "status": {} + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 2 + }, + "id": 1, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.4.0-19736337744", + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "defin43am9o1sd" + }, + "refId": "A", + "scenarioId": "random_walk", + "seriesCount": 1 + } + ], + "title": "New panel", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 10 + }, + "id": -1, + "panels": [], + "title": "Empty tab", + "type": "row" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 11 + }, + "id": -1, + "title": "Empty row", + "type": "row" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 11 + }, + "id": -1, + "panels": [], + "title": "Row with rows", + "type": "row" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 12 + }, + "id": -1, + "title": "Empty nested row", + "type": "row" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 12 + }, + "id": -1, + "title": "Row with panels filling screen", + "type": "row" + }, + { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + } + }, + "gridPos": { + "h": 9, + "w": 8, + "x": 0, + "y": 12 + }, + "id": 2, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.4.0-19736337744", + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "defin43am9o1sd" + }, + "refId": "A", + "scenarioId": "random_walk", + "seriesCount": 1 + } + ], + "title": "New panel", + "type": "timeseries" + }, + { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + } + }, + "gridPos": { + "h": 9, + "w": 8, + "x": 8, + "y": 12 + }, + "id": 3, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.4.0-19736337744", + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "defin43am9o1sd" + }, + "refId": "A", + "scenarioId": "random_walk", + "seriesCount": 1 + } + ], + "title": "New panel", + "type": "timeseries" } + ], + "preload": false, + "refresh": "", + "schemaVersion": 42, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "browser", + "title": "Rows with nested tabs" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v2beta1" } } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/v2beta1.tabs-and-rows-repeated.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/v2beta1.tabs-and-rows-repeated.v0alpha1.json index 5a8c0a0965b..ef513d48d97 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/v2beta1.tabs-and-rows-repeated.v0alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/v2beta1.tabs-and-rows-repeated.v0alpha1.json @@ -17,1003 +17,727 @@ "grafana.app/saved-from-ui": "Grafana Cloud" } }, - "spec": null, - "status": { - "conversion": { - "failed": true, - "error": "backend conversion not yet implemented", - "storedVersion": "v2beta1", - "source": { - "kind": "Dashboard", - "apiVersion": "dashboard.grafana.app/v2beta1", - "metadata": { - "name": "ivc5gfp", - "namespace": "stacks-5756", - "uid": "353905c9-661c-47af-89ac-a0e966c5b3c7", - "resourceVersion": "1764588557728998", - "generation": 2, - "creationTimestamp": "2025-12-01T11:29:17Z", - "labels": { - "grafana.app/deprecatedInternalID": "360521534459904" + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" }, - "annotations": { - "grafana.app/createdBy": "user:ff3wylp4sgpa8a", - "grafana.app/folder": "", - "grafana.app/saved-from-ui": "Grafana Cloud" - } + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "description": "", + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "liveNow": false, + "panels": [ + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 0 }, - "spec": { - "annotations": [ - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana", - "version": "v0", - "datasource": { - "name": "-- Grafana --" - }, - "spec": {} - }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations \u0026 Alerts", - "builtIn": true - } - } - ], - "cursorSync": "Off", - "description": "", - "editable": true, - "elements": { - "panel-1": { - "kind": "Panel", - "spec": { - "id": 1, - "title": "New panel", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana-testdata-datasource", - "version": "v0", - "datasource": { - "name": "defin43am9o1sd" - }, - "spec": { - "scenarioId": "random_walk", - "seriesCount": 4 - } - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "12.4.0-19736337744", - "spec": { - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "hideZeros": false, - "mode": "single", - "sort": "none" - } - }, - "fieldConfig": { - "defaults": { - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": 0, - "color": "green" - }, - { - "value": 80, - "color": "red" - } - ] - }, - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "showValues": false, - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - } - }, - "overrides": [] - } - } - } + "id": -1, + "panels": [], + "title": "Tab without rows", + "type": "row" + }, + { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" } }, - "panel-2": { - "kind": "Panel", - "spec": { - "id": 2, - "title": "New panel", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana-testdata-datasource", - "version": "v0", - "datasource": { - "name": "defin43am9o1sd" - }, - "spec": { - "scenarioId": "random_walk", - "seriesCount": 5 - } - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "12.4.0-19736337744", - "spec": { - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "hideZeros": false, - "mode": "single", - "sort": "none" - } - }, - "fieldConfig": { - "defaults": { - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": 0, - "color": "green" - }, - { - "value": 80, - "color": "red" - } - ] - }, - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "showValues": false, - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - } - }, - "overrides": [] - } - } - } - } - }, - "panel-3": { - "kind": "Panel", - "spec": { - "id": 3, - "title": "Panel in collapsed row 1", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana-testdata-datasource", - "version": "v0", - "datasource": { - "name": "defin43am9o1sd" - }, - "spec": { - "scenarioId": "random_walk", - "seriesCount": 1 - } - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "12.4.0-19736337744", - "spec": { - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "hideZeros": false, - "mode": "single", - "sort": "none" - } - }, - "fieldConfig": { - "defaults": { - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": 0, - "color": "green" - }, - { - "value": 80, - "color": "red" - } - ] - }, - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "showValues": false, - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - } - }, - "overrides": [] - } - } - } - } - }, - "panel-4": { - "kind": "Panel", - "spec": { - "id": 4, - "title": "Panel in collapsed row 2", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana-testdata-datasource", - "version": "v0", - "datasource": { - "name": "defin43am9o1sd" - }, - "spec": { - "scenarioId": "random_walk", - "seriesCount": 1 - } - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "12.4.0-19736337744", - "spec": { - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "hideZeros": false, - "mode": "single", - "sort": "none" - } - }, - "fieldConfig": { - "defaults": { - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": 0, - "color": "green" - }, - { - "value": 80, - "color": "red" - } - ] - }, - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "showValues": false, - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - } - }, - "overrides": [] - } - } - } - } - }, - "panel-5": { - "kind": "Panel", - "spec": { - "id": 5, - "title": "Panel in hidden header row", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana-testdata-datasource", - "version": "v0", - "datasource": { - "name": "defin43am9o1sd" - }, - "spec": { - "scenarioId": "random_walk", - "seriesCount": 5 - } - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "12.4.0-19736337744", - "spec": { - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "hideZeros": false, - "mode": "single", - "sort": "none" - } - }, - "fieldConfig": { - "defaults": { - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": 0, - "color": "green" - }, - { - "value": 80, - "color": "red" - } - ] - }, - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "showValues": false, - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - } - }, - "overrides": [] - } - } - } - } - }, - "panel-6": { - "kind": "Panel", - "spec": { - "id": 6, - "title": "Repeated Panel within tab \"$custom_var_panel\"", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "", - "version": "v0", - "spec": {} - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "__unconfigured-panel", - "version": "12.4.0-19736337744", - "spec": { - "options": {}, - "fieldConfig": { - "defaults": {}, - "overrides": [] - } - } - } - } - } - }, - "layout": { - "kind": "TabsLayout", - "spec": { - "tabs": [ + "thresholds": { + "mode": "absolute", + "steps": [ { - "kind": "TabsLayoutTab", - "spec": { - "title": "Tab without rows", - "layout": { - "kind": "GridLayout", - "spec": { - "items": [ - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 12, - "height": 8, - "element": { - "kind": "ElementReference", - "name": "panel-1" - } - } - } - ] - } - } - } + "color": "green", + "value": 0 }, { - "kind": "TabsLayoutTab", - "spec": { - "title": "Tab With Rows", - "layout": { - "kind": "RowsLayout", - "spec": { - "rows": [ - { - "kind": "RowsLayoutRow", - "spec": { - "title": "Row without tabs", - "collapse": true, - "layout": { - "kind": "AutoGridLayout", - "spec": { - "maxColumnCount": 3, - "columnWidthMode": "standard", - "rowHeightMode": "standard", - "items": [ - { - "kind": "AutoGridLayoutItem", - "spec": { - "element": { - "kind": "ElementReference", - "name": "panel-2" - } - } - } - ] - } - } - } - }, - { - "kind": "RowsLayoutRow", - "spec": { - "title": "Collapsed row", - "collapse": true, - "layout": { - "kind": "AutoGridLayout", - "spec": { - "maxColumnCount": 3, - "columnWidthMode": "standard", - "rowHeightMode": "standard", - "items": [ - { - "kind": "AutoGridLayoutItem", - "spec": { - "element": { - "kind": "ElementReference", - "name": "panel-3" - } - } - }, - { - "kind": "AutoGridLayoutItem", - "spec": { - "element": { - "kind": "ElementReference", - "name": "panel-4" - } - } - } - ] - } - } - } - }, - { - "kind": "RowsLayoutRow", - "spec": { - "title": "Empty row", - "collapse": false, - "layout": { - "kind": "AutoGridLayout", - "spec": { - "maxColumnCount": 3, - "columnWidthMode": "standard", - "rowHeightMode": "standard", - "items": [] - } - } - } - }, - { - "kind": "RowsLayoutRow", - "spec": { - "title": "Hide header row", - "collapse": false, - "hideHeader": true, - "layout": { - "kind": "AutoGridLayout", - "spec": { - "maxColumnCount": 3, - "columnWidthMode": "standard", - "rowHeightMode": "standard", - "items": [ - { - "kind": "AutoGridLayoutItem", - "spec": { - "element": { - "kind": "ElementReference", - "name": "panel-5" - } - } - } - ] - } - } - } - } - ] - } - } - } - }, - { - "kind": "TabsLayoutTab", - "spec": { - "title": "Repeated Tab by \"$custom_var_tab\"", - "layout": { - "kind": "RowsLayout", - "spec": { - "rows": [ - { - "kind": "RowsLayoutRow", - "spec": { - "title": "Repeated by \"$custom_var_row\"", - "collapse": false, - "repeat": { - "mode": "variable", - "value": "custom_var_row" - }, - "layout": { - "kind": "AutoGridLayout", - "spec": { - "maxColumnCount": 3, - "columnWidthMode": "standard", - "rowHeightMode": "standard", - "items": [ - { - "kind": "AutoGridLayoutItem", - "spec": { - "element": { - "kind": "ElementReference", - "name": "panel-6" - }, - "repeat": { - "mode": "variable", - "value": "custom_var_panel" - } - } - } - ] - } - } - } - } - ] - } - }, - "repeat": { - "mode": "variable", - "value": "custom_var_tab" - } - } + "color": "red", + "value": 80 } ] } - }, - "links": [], - "liveNow": false, - "preload": false, - "tags": [], - "timeSettings": { - "timezone": "browser", - "from": "now-6h", - "to": "now", - "autoRefresh": "", - "autoRefreshIntervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "hideTimepicker": false, - "fiscalYearStartMonth": 0 - }, - "title": "Tabs and rows repeated", - "variables": [ - { - "kind": "CustomVariable", - "spec": { - "name": "custom_var_tab", - "query": "option 1, option 2, option 3", - "current": { - "text": [ - "All" - ], - "value": [ - "$__all" - ] - }, - "options": [ - { - "selected": false, - "text": "option 1", - "value": "option 1" - }, - { - "selected": false, - "text": "option 2", - "value": "option 2" - }, - { - "selected": false, - "text": "option 3", - "value": "option 3" - } - ], - "multi": true, - "includeAll": true, - "hide": "dontHide", - "skipUrlSync": false, - "allowCustomValue": true - } - }, - { - "kind": "CustomVariable", - "spec": { - "name": "custom_var_row", - "query": "option 1 1, Option 1 2, Option 1 3", - "current": { - "text": [ - "All" - ], - "value": [ - "$__all" - ] - }, - "options": [ - { - "selected": false, - "text": "option 1 1", - "value": "option 1 1" - }, - { - "selected": false, - "text": "Option 1 2", - "value": "Option 1 2" - }, - { - "selected": false, - "text": "Option 1 3", - "value": "Option 1 3" - } - ], - "multi": true, - "includeAll": true, - "hide": "dontHide", - "skipUrlSync": false, - "allowCustomValue": true - } - }, - { - "kind": "CustomVariable", - "spec": { - "name": "custom_var_panel", - "query": "Panel 1, Panel 2, Panel 3", - "current": { - "text": [ - "All" - ], - "value": [ - "$__all" - ] - }, - "options": [ - { - "selected": false, - "text": "Panel 1", - "value": "Panel 1" - }, - { - "selected": false, - "text": "Panel 2", - "value": "Panel 2" - }, - { - "selected": false, - "text": "Panel 3", - "value": "Panel 3" - } - ], - "multi": true, - "includeAll": true, - "hide": "dontHide", - "skipUrlSync": false, - "allowCustomValue": true - } - } - ] + } }, - "status": {} + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 1 + }, + "id": 1, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.4.0-19736337744", + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "defin43am9o1sd" + }, + "refId": "A", + "scenarioId": "random_walk", + "seriesCount": 4 + } + ], + "title": "New panel", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 9 + }, + "id": -1, + "panels": [], + "title": "Tab With Rows", + "type": "row" + }, + { + "collapsed": true, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 10 + }, + "id": -1, + "panels": [ + { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + } + }, + "gridPos": { + "h": 9, + "w": 8, + "x": 0, + "y": 11 + }, + "id": 2, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.4.0-19736337744", + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "defin43am9o1sd" + }, + "refId": "A", + "scenarioId": "random_walk", + "seriesCount": 5 + } + ], + "title": "New panel", + "type": "timeseries" + } + ], + "title": "Row without tabs", + "type": "row" + }, + { + "collapsed": true, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 11 + }, + "id": -1, + "panels": [ + { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + } + }, + "gridPos": { + "h": 9, + "w": 8, + "x": 0, + "y": 12 + }, + "id": 3, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.4.0-19736337744", + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "defin43am9o1sd" + }, + "refId": "A", + "scenarioId": "random_walk", + "seriesCount": 1 + } + ], + "title": "Panel in collapsed row 1", + "type": "timeseries" + }, + { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + } + }, + "gridPos": { + "h": 9, + "w": 8, + "x": 8, + "y": 12 + }, + "id": 4, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.4.0-19736337744", + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "defin43am9o1sd" + }, + "refId": "A", + "scenarioId": "random_walk", + "seriesCount": 1 + } + ], + "title": "Panel in collapsed row 2", + "type": "timeseries" + } + ], + "title": "Collapsed row", + "type": "row" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 12 + }, + "id": -1, + "title": "Empty row", + "type": "row" + }, + { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + } + }, + "gridPos": { + "h": 9, + "w": 8, + "x": 0, + "y": 12 + }, + "id": 5, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.4.0-19736337744", + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "defin43am9o1sd" + }, + "refId": "A", + "scenarioId": "random_walk", + "seriesCount": 5 + } + ], + "title": "Panel in hidden header row", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 21 + }, + "id": -1, + "panels": [], + "title": "Repeated Tab by \"$custom_var_tab\"", + "type": "row" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 22 + }, + "id": -1, + "repeat": "custom_var_row", + "title": "Repeated by \"$custom_var_row\"", + "type": "row" + }, + { + "gridPos": { + "h": 9, + "w": 8, + "x": 0, + "y": 22 + }, + "id": 6, + "options": {}, + "pluginVersion": "12.4.0-19736337744", + "targets": [ + { + "refId": "A" + } + ], + "title": "Repeated Panel within tab \"$custom_var_panel\"", + "type": "__unconfigured-panel" } + ], + "preload": false, + "refresh": "", + "schemaVersion": 42, + "templating": { + "list": [ + { + "allowCustomValue": true, + "current": { + "text": [ + "All" + ], + "value": [ + "$__all" + ] + }, + "hide": 0, + "includeAll": true, + "multi": true, + "name": "custom_var_tab", + "options": [ + { + "selected": false, + "text": "option 1", + "value": "option 1" + }, + { + "selected": false, + "text": "option 2", + "value": "option 2" + }, + { + "selected": false, + "text": "option 3", + "value": "option 3" + } + ], + "query": "option 1, option 2, option 3", + "skipUrlSync": false, + "type": "custom" + }, + { + "allowCustomValue": true, + "current": { + "text": [ + "All" + ], + "value": [ + "$__all" + ] + }, + "hide": 0, + "includeAll": true, + "multi": true, + "name": "custom_var_row", + "options": [ + { + "selected": false, + "text": "option 1 1", + "value": "option 1 1" + }, + { + "selected": false, + "text": "Option 1 2", + "value": "Option 1 2" + }, + { + "selected": false, + "text": "Option 1 3", + "value": "Option 1 3" + } + ], + "query": "option 1 1, Option 1 2, Option 1 3", + "skipUrlSync": false, + "type": "custom" + }, + { + "allowCustomValue": true, + "current": { + "text": [ + "All" + ], + "value": [ + "$__all" + ] + }, + "hide": 0, + "includeAll": true, + "multi": true, + "name": "custom_var_panel", + "options": [ + { + "selected": false, + "text": "Panel 1", + "value": "Panel 1" + }, + { + "selected": false, + "text": "Panel 2", + "value": "Panel 2" + }, + { + "selected": false, + "text": "Panel 3", + "value": "Panel 3" + } + ], + "query": "Panel 1, Panel 2, Panel 3", + "skipUrlSync": false, + "type": "custom" + } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "browser", + "title": "Tabs and rows repeated" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v2beta1" } } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/v2beta1.tabs-with-nested-rows.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/v2beta1.tabs-with-nested-rows.v0alpha1.json index b66ff05a9df..6ed7076e121 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/v2beta1.tabs-with-nested-rows.v0alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/v2beta1.tabs-with-nested-rows.v0alpha1.json @@ -17,675 +17,484 @@ "grafana.app/saved-from-ui": "Grafana Cloud" } }, - "spec": null, - "status": { - "conversion": { - "failed": true, - "error": "backend conversion not yet implemented", - "storedVersion": "v2beta1", - "source": { - "kind": "Dashboard", - "apiVersion": "dashboard.grafana.app/v2beta1", - "metadata": { - "name": "ivc5gfp", - "namespace": "stacks-5756", - "uid": "353905c9-661c-47af-89ac-a0e966c5b3c7", - "resourceVersion": "1764588557728998", - "generation": 1, - "creationTimestamp": "2025-12-01T11:29:17Z", - "labels": { - "grafana.app/deprecatedInternalID": "360521534459904" + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" }, - "annotations": { - "grafana.app/createdBy": "user:ff3wylp4sgpa8a", - "grafana.app/folder": "", - "grafana.app/saved-from-ui": "Grafana Cloud" - } + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "description": "", + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "liveNow": false, + "panels": [ + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 0 }, - "spec": { - "annotations": [ - { - "kind": "AnnotationQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana", - "version": "v0", - "datasource": { - "name": "-- Grafana --" - }, - "spec": {} - }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations \u0026 Alerts", - "builtIn": true - } - } - ], - "cursorSync": "Off", - "description": "", - "editable": true, - "elements": { - "panel-1": { - "kind": "Panel", - "spec": { - "id": 1, - "title": "New panel", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana-testdata-datasource", - "version": "v0", - "datasource": { - "name": "defin43am9o1sd" - }, - "spec": { - "scenarioId": "random_walk", - "seriesCount": 4 - } - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "12.4.0-19736337744", - "spec": { - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "hideZeros": false, - "mode": "single", - "sort": "none" - } - }, - "fieldConfig": { - "defaults": { - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": 0, - "color": "green" - }, - { - "value": 80, - "color": "red" - } - ] - }, - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "showValues": false, - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - } - }, - "overrides": [] - } - } - } + "id": -1, + "panels": [], + "title": "Tab without rows", + "type": "row" + }, + { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" } }, - "panel-2": { - "kind": "Panel", - "spec": { - "id": 2, - "title": "New panel", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana-testdata-datasource", - "version": "v0", - "datasource": { - "name": "defin43am9o1sd" - }, - "spec": { - "scenarioId": "random_walk", - "seriesCount": 5 - } - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "12.4.0-19736337744", - "spec": { - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "hideZeros": false, - "mode": "single", - "sort": "none" - } - }, - "fieldConfig": { - "defaults": { - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": 0, - "color": "green" - }, - { - "value": 80, - "color": "red" - } - ] - }, - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "showValues": false, - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - } - }, - "overrides": [] - } - } - } - } - }, - "panel-3": { - "kind": "Panel", - "spec": { - "id": 3, - "title": "New panel", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana-testdata-datasource", - "version": "v0", - "datasource": { - "name": "defin43am9o1sd" - }, - "spec": { - "scenarioId": "random_walk", - "seriesCount": 1 - } - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "12.4.0-19736337744", - "spec": { - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "hideZeros": false, - "mode": "single", - "sort": "none" - } - }, - "fieldConfig": { - "defaults": { - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": 0, - "color": "green" - }, - { - "value": 80, - "color": "red" - } - ] - }, - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "showValues": false, - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - } - }, - "overrides": [] - } - } - } - } - }, - "panel-4": { - "kind": "Panel", - "spec": { - "id": 4, - "title": "New panel", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana-testdata-datasource", - "version": "v0", - "datasource": { - "name": "defin43am9o1sd" - }, - "spec": { - "scenarioId": "random_walk", - "seriesCount": 1 - } - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "12.4.0-19736337744", - "spec": { - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "hideZeros": false, - "mode": "single", - "sort": "none" - } - }, - "fieldConfig": { - "defaults": { - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": 0, - "color": "green" - }, - { - "value": 80, - "color": "red" - } - ] - }, - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "showValues": false, - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - } - }, - "overrides": [] - } - } - } - } - } - }, - "layout": { - "kind": "TabsLayout", - "spec": { - "tabs": [ + "thresholds": { + "mode": "absolute", + "steps": [ { - "kind": "TabsLayoutTab", - "spec": { - "title": "Tab without rows", - "layout": { - "kind": "GridLayout", - "spec": { - "items": [ - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 12, - "height": 8, - "element": { - "kind": "ElementReference", - "name": "panel-1" - } - } - } - ] - } - } - } + "color": "green", + "value": 0 }, { - "kind": "TabsLayoutTab", - "spec": { - "title": "Tab With Rows", - "layout": { - "kind": "RowsLayout", - "spec": { - "rows": [ - { - "kind": "RowsLayoutRow", - "spec": { - "title": "Row without tabs", - "collapse": true, - "layout": { - "kind": "AutoGridLayout", - "spec": { - "maxColumnCount": 3, - "columnWidthMode": "standard", - "rowHeightMode": "standard", - "items": [ - { - "kind": "AutoGridLayoutItem", - "spec": { - "element": { - "kind": "ElementReference", - "name": "panel-2" - } - } - } - ] - } - } - } - }, - { - "kind": "RowsLayoutRow", - "spec": { - "title": "Row with tabs", - "collapse": true, - "layout": { - "kind": "AutoGridLayout", - "spec": { - "maxColumnCount": 3, - "columnWidthMode": "standard", - "rowHeightMode": "standard", - "items": [ - { - "kind": "AutoGridLayoutItem", - "spec": { - "element": { - "kind": "ElementReference", - "name": "panel-3" - } - } - }, - { - "kind": "AutoGridLayoutItem", - "spec": { - "element": { - "kind": "ElementReference", - "name": "panel-4" - } - } - } - ] - } - } - } - }, - { - "kind": "RowsLayoutRow", - "spec": { - "title": "Empty row", - "collapse": false, - "layout": { - "kind": "AutoGridLayout", - "spec": { - "maxColumnCount": 3, - "columnWidthMode": "standard", - "rowHeightMode": "standard", - "items": [] - } - } - } - }, - { - "kind": "RowsLayoutRow", - "spec": { - "title": "Hide header row", - "collapse": false, - "hideHeader": true, - "layout": { - "kind": "AutoGridLayout", - "spec": { - "maxColumnCount": 3, - "columnWidthMode": "standard", - "rowHeightMode": "standard", - "items": [] - } - } - } - } - ] - } - } - } + "color": "red", + "value": 80 } ] } - }, - "links": [], - "liveNow": false, - "preload": false, - "tags": [], - "timeSettings": { - "timezone": "browser", - "from": "now-6h", - "to": "now", - "autoRefresh": "", - "autoRefreshIntervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "hideTimepicker": false, - "fiscalYearStartMonth": 0 - }, - "title": "Dashboard with tabs and rows", - "variables": [] + } }, - "status": {} + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 1 + }, + "id": 1, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.4.0-19736337744", + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "defin43am9o1sd" + }, + "refId": "A", + "scenarioId": "random_walk", + "seriesCount": 4 + } + ], + "title": "New panel", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 9 + }, + "id": -1, + "panels": [], + "title": "Tab With Rows", + "type": "row" + }, + { + "collapsed": true, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 10 + }, + "id": -1, + "panels": [ + { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + } + }, + "gridPos": { + "h": 9, + "w": 8, + "x": 0, + "y": 11 + }, + "id": 2, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.4.0-19736337744", + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "defin43am9o1sd" + }, + "refId": "A", + "scenarioId": "random_walk", + "seriesCount": 5 + } + ], + "title": "New panel", + "type": "timeseries" + } + ], + "title": "Row without tabs", + "type": "row" + }, + { + "collapsed": true, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 11 + }, + "id": -1, + "panels": [ + { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + } + }, + "gridPos": { + "h": 9, + "w": 8, + "x": 0, + "y": 12 + }, + "id": 3, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.4.0-19736337744", + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "defin43am9o1sd" + }, + "refId": "A", + "scenarioId": "random_walk", + "seriesCount": 1 + } + ], + "title": "New panel", + "type": "timeseries" + }, + { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + } + }, + "gridPos": { + "h": 9, + "w": 8, + "x": 8, + "y": 12 + }, + "id": 4, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.4.0-19736337744", + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "defin43am9o1sd" + }, + "refId": "A", + "scenarioId": "random_walk", + "seriesCount": 1 + } + ], + "title": "New panel", + "type": "timeseries" + } + ], + "title": "Row with tabs", + "type": "row" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 12 + }, + "id": -1, + "title": "Empty row", + "type": "row" } + ], + "preload": false, + "refresh": "", + "schemaVersion": 42, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "browser", + "title": "Dashboard with tabs and rows" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v2beta1" } } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/v2beta1.viz-config.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/v2beta1.viz-config.v0alpha1.json index 66354d45ff6..0502ebf6b4c 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/v2beta1.viz-config.v0alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/v2beta1.viz-config.v0alpha1.json @@ -4,204 +4,133 @@ "metadata": { "name": "test-v2beta1-viz-config" }, - "spec": null, - "status": { - "conversion": { - "failed": true, - "error": "backend conversion not yet implemented", - "storedVersion": "v2beta1", - "source": { - "kind": "Dashboard", - "apiVersion": "dashboard.grafana.app/v2beta1", - "metadata": { - "name": "test-v2beta1-viz-config" - }, - "spec": { - "annotations": [], - "cursorSync": "Tooltip", - "elements": { - "panel-1": { - "kind": "Panel", - "spec": { - "id": 1, - "title": "Simple timeseries (WITH DS REF)", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana-testdata-datasource", - "version": "v0", - "datasource": { - "name": "gdev-testdata" - }, - "spec": { - "scenarioId": "random_walk", - "seriesCount": 3 - } - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": {} - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "timeseries", - "version": "12.1.0-pre", - "spec": { - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "hideZeros": false, - "mode": "single", - "sort": "none" - } - }, - "fieldConfig": { - "defaults": { - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": 0, - "color": "green" - }, - { - "value": 80, - "color": "red" - } - ] - }, - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - } - }, - "overrides": [] - } - } - } + "spec": { + "annotations": { + "list": [] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 2, + "liveNow": false, + "panels": [ + { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" } - } - }, - "layout": { - "kind": "RowsLayout", - "spec": { - "rows": [ + }, + "thresholds": { + "mode": "absolute", + "steps": [ { - "kind": "Row", - "spec": { - "title": "", - "collapse": false, - "hideHeader": true, - "fillScreen": false, - "layout": { - "kind": "GridLayout", - "spec": { - "items": [ - { - "kind": "GridLayoutItem", - "spec": { - "x": 0, - "y": 0, - "width": 8, - "height": 3, - "element": { - "kind": "ElementReference", - "name": "panel-1" - } - } - } - ] - } - } - } + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 } ] } - }, - "links": [], - "liveNow": false, - "preload": false, - "tags": [], - "timeSettings": { - "timezone": "browser", - "from": "now-6h", - "to": "now", - "autoRefresh": "", - "autoRefreshIntervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "hideTimepicker": false, - "fiscalYearStartMonth": 0 - }, - "title": "Test: V2alpha1 dashboard with viz config", - "variables": [] - }, - "status": { - "conversion": { - "failed": false, - "storedVersion": "v2alpha1" } - } + }, + "gridPos": { + "h": 3, + "w": 8, + "x": 0, + "y": 0 + }, + "id": 1, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.1.0-pre", + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "gdev-testdata" + }, + "refId": "A", + "scenarioId": "random_walk", + "seriesCount": 3 + } + ], + "title": "Simple timeseries (WITH DS REF)", + "type": "timeseries" } + ], + "preload": false, + "refresh": "", + "schemaVersion": 42, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "browser", + "title": "Test: V2alpha1 dashboard with viz config" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v2beta1" } } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/v2.go b/apps/dashboard/pkg/migration/conversion/v2.go index 09245f991a3..fee798d3ec5 100644 --- a/apps/dashboard/pkg/migration/conversion/v2.go +++ b/apps/dashboard/pkg/migration/conversion/v2.go @@ -11,22 +11,45 @@ import ( "github.com/grafana/grafana/apps/dashboard/pkg/migration/schemaversion" ) -func Convert_V2alpha1_to_V0(in *dashv2alpha1.Dashboard, out *dashv0.Dashboard, scope conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - out.APIVersion = dashv0.APIVERSION - out.Kind = in.Kind - - // TODO: implement V2 to V0 conversion - - out.Status = dashv0.DashboardStatus{ - Conversion: &dashv0.DashboardConversionStatus{ - StoredVersion: ptr.To(dashv2alpha1.VERSION), - Failed: true, - Error: ptr.To("backend conversion not yet implemented"), - Source: in, - }, +func Convert_V2alpha1_to_V0(in *dashv2alpha1.Dashboard, out *dashv0.Dashboard, scope conversion.Scope, dsIndexProvider schemaversion.DataSourceIndexProvider) error { + // Convert v2alpha1 → v1beta1 first, then v1beta1 → v0 + v1beta1 := &dashv1.Dashboard{} + if err := ConvertDashboard_V2alpha1_to_V1beta1(in, v1beta1, scope, dsIndexProvider); err != nil { + out.ObjectMeta = in.ObjectMeta + out.APIVersion = dashv0.APIVERSION + out.Kind = in.Kind + out.Status = dashv0.DashboardStatus{ + Conversion: &dashv0.DashboardConversionStatus{ + StoredVersion: ptr.To(dashv2alpha1.VERSION), + Failed: true, + Error: ptr.To(err.Error()), + Source: in, + }, + } + // For errors, set status but don't return error + return nil } + // Convert v1beta1 → v0 + if err := Convert_V1beta1_to_V0(v1beta1, out, scope); err != nil { + out.ObjectMeta = in.ObjectMeta + out.APIVersion = dashv0.APIVERSION + out.Kind = in.Kind + out.Status = dashv0.DashboardStatus{ + Conversion: &dashv0.DashboardConversionStatus{ + StoredVersion: ptr.To(dashv2alpha1.VERSION), + Failed: true, + Error: ptr.To(err.Error()), + Source: in, + }, + } + // For errors, set status but don't return error + return nil + } + + // Update the stored version to reflect the original source + out.Status.Conversion.StoredVersion = ptr.To(dashv2alpha1.VERSION) + return nil } @@ -46,7 +69,7 @@ func Convert_V2alpha1_to_V1beta1(in *dashv2alpha1.Dashboard, out *dashv1.Dashboa }, } - // For errors, set status but don't return error (matches v1beta1_to_v2alpha1 pattern) + // For errors, set status but don't return error return nil } @@ -91,22 +114,44 @@ func Convert_V2alpha1_to_V2beta1(in *dashv2alpha1.Dashboard, out *dashv2beta1.Da return nil } -func Convert_V2beta1_to_V0(in *dashv2beta1.Dashboard, out *dashv0.Dashboard, scope conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - out.APIVersion = dashv0.APIVERSION - out.Kind = in.Kind - - // TODO: implement v2beta1 to V0 conversion - - out.Status = dashv0.DashboardStatus{ - Conversion: &dashv0.DashboardConversionStatus{ - StoredVersion: ptr.To(dashv2beta1.VERSION), - Failed: true, - Error: ptr.To("backend conversion not yet implemented"), - Source: in, - }, +func Convert_V2beta1_to_V0(in *dashv2beta1.Dashboard, out *dashv0.Dashboard, scope conversion.Scope, dsIndexProvider schemaversion.DataSourceIndexProvider) error { + // Convert v2beta1 → v1beta1 first, then v1beta1 → v0 + v1beta1 := &dashv1.Dashboard{} + if err := Convert_V2beta1_to_V1beta1(in, v1beta1, scope, dsIndexProvider); err != nil { + out.ObjectMeta = in.ObjectMeta + out.APIVersion = dashv0.APIVERSION + out.Kind = in.Kind + out.Status = dashv0.DashboardStatus{ + Conversion: &dashv0.DashboardConversionStatus{ + StoredVersion: ptr.To(dashv2beta1.VERSION), + Failed: true, + Error: ptr.To(err.Error()), + Source: in, + }, + } + // For errors, set status but don't return error + return nil } + // Convert v1beta1 → v0 + if err := Convert_V1beta1_to_V0(v1beta1, out, scope); err != nil { + out.ObjectMeta = in.ObjectMeta + out.APIVersion = dashv0.APIVERSION + out.Kind = in.Kind + out.Status = dashv0.DashboardStatus{ + Conversion: &dashv0.DashboardConversionStatus{ + StoredVersion: ptr.To(dashv2beta1.VERSION), + Failed: true, + Error: ptr.To(err.Error()), + Source: in, + }, + } + return nil + } + + // Update the stored version to reflect the original source + out.Status.Conversion.StoredVersion = ptr.To(dashv2beta1.VERSION) + return nil } @@ -127,7 +172,7 @@ func Convert_V2beta1_to_V1beta1(in *dashv2beta1.Dashboard, out *dashv1.Dashboard Source: in, }, } - // For errors, set status but don't return error (matches v1beta1_to_v2alpha1 pattern) + // For errors, set status but don't return error return nil } @@ -143,7 +188,7 @@ func Convert_V2beta1_to_V1beta1(in *dashv2beta1.Dashboard, out *dashv1.Dashboard Source: in, }, } - // For errors, set status but don't return error (matches v1beta1_to_v2alpha1 pattern) + // For errors, set status but don't return error return nil } diff --git a/pkg/api/dashboard.go b/pkg/api/dashboard.go index 9dfa7c95413..5ebbca411eb 100644 --- a/pkg/api/dashboard.go +++ b/pkg/api/dashboard.go @@ -96,12 +96,6 @@ func (hs *HTTPServer) GetDashboard(c *contextmodel.ReqContext) response.Response return rsp } - // v2 is not supported in /api - if strings.HasPrefix(dash.APIVersion, "v2") { - url := fmt.Sprintf("/apis/dashboard.grafana.app/%s/namespaces/%s/dashboards/%s", dash.APIVersion, hs.namespacer(c.GetOrgID()), dash.UID) - return response.Error(http.StatusNotAcceptable, "dashboard api version not supported, use "+url+" instead", nil) - } - var ( publicDashboardEnabled = false err error diff --git a/pkg/api/dashboard_test.go b/pkg/api/dashboard_test.go index eceebaacdd6..6c140667a37 100644 --- a/pkg/api/dashboard_test.go +++ b/pkg/api/dashboard_test.go @@ -7,7 +7,6 @@ import ( "io" "net/http" "os" - "strconv" "strings" "testing" "time" @@ -868,45 +867,6 @@ func TestIntegrationDashboardAPIEndpoint(t *testing.T) { assert.Equal(t, false, dash.Meta.Provisioned) }, mockSQLStore) }) - - t.Run("v2 dashboards should not be returned in api", func(t *testing.T) { - mockSQLStore := dbtest.NewFakeDB() - dashboardService := dashboards.NewFakeDashboardService(t) - dashboardProvisioningService := dashboards.NewFakeDashboardProvisioning(t) - - dataValue, err := simplejson.NewJson([]byte(`{"id": 1, "apiVersion": "v2"}`)) - require.NoError(t, err) - qResult := &dashboards.Dashboard{ - ID: 1, - UID: "dash", - OrgID: 1, - APIVersion: "v2", - Data: dataValue, - } - dashboardService.On("GetDashboard", mock.Anything, mock.AnythingOfType("*dashboards.GetDashboardQuery")).Return(qResult, nil) - - loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/dashboards/uid/dash", "/api/dashboards/uid/:uid", org.RoleEditor, func(sc *scenarioContext) { - hs := &HTTPServer{ - Cfg: setting.NewCfg(), - LibraryElementService: &libraryelementsfake.LibraryElementService{}, - SQLStore: mockSQLStore, - AccessControl: actest.FakeAccessControl{ExpectedEvaluate: true}, - DashboardService: dashboardService, - Features: featuremgmt.WithFeatures(), - starService: startest.NewStarServiceFake(), - tracer: tracing.InitializeTracerForTest(), - dashboardProvisioningService: dashboardProvisioningService, - folderService: foldertest.NewFakeService(), - log: log.New("test"), - namespacer: func(orgID int64) string { return strconv.FormatInt(orgID, 10) }, - } - hs.callGetDashboard(sc) - - assert.Equal(t, http.StatusNotAcceptable, sc.resp.Code) - result := sc.ToJSON() - assert.Equal(t, "dashboard api version not supported, use /apis/dashboard.grafana.app/v2/namespaces/1/dashboards/dash instead", result.Get("message").MustString()) - }, mockSQLStore) - }) } func TestDashboardVersionsAPIEndpoint(t *testing.T) { diff --git a/pkg/tests/apis/dashboard/dashboards_test.go b/pkg/tests/apis/dashboard/dashboards_test.go index 541398ae620..85f369c2ac6 100644 --- a/pkg/tests/apis/dashboard/dashboards_test.go +++ b/pkg/tests/apis/dashboard/dashboards_test.go @@ -285,12 +285,12 @@ func TestIntegrationLegacySupport(t *testing.T) { require.Equal(t, 200, rsp.Response.StatusCode) require.Equal(t, dashboardV0.VERSION, rsp.Result.Meta.APIVersion) - // V2 should send a not acceptable rsp = apis.DoRequest(helper, apis.RequestParams{ User: helper.Org1.Admin, Path: "/api/dashboards/uid/test-v2", }, &dtos.DashboardFullWithMeta{}) - require.Equal(t, 406, rsp.Response.StatusCode) // not acceptable + require.Equal(t, 200, rsp.Response.StatusCode) + require.Equal(t, dashboardV0.VERSION, rsp.Result.Meta.APIVersion) } func TestIntegrationSearchTypeFiltering(t *testing.T) { diff --git a/public/app/features/dashboard-scene/serialization/transformSaveModelV2ToV1.test.ts b/public/app/features/dashboard-scene/serialization/transformSaveModelV2ToV1.test.ts index 18af17a0572..74208ff8a8f 100644 --- a/public/app/features/dashboard-scene/serialization/transformSaveModelV2ToV1.test.ts +++ b/public/app/features/dashboard-scene/serialization/transformSaveModelV2ToV1.test.ts @@ -1,4 +1,4 @@ -import { readdirSync, readFileSync, statSync } from 'fs'; +import { existsSync, readdirSync, readFileSync, statSync } from 'fs'; import path from 'path'; import { Dashboard } from '@grafana/schema'; @@ -38,42 +38,22 @@ jest.mock('@grafana/runtime', () => { ...jest.requireActual('@grafana/runtime').config, defaultDatasource: 'default-ds-uid', datasources: { - '-- Grafana --': { - type: 'grafana', - uid: '-- Grafana --', - name: 'Grafana', - meta: { id: 'grafana' }, - }, + '-- Grafana --': { type: 'grafana', uid: '-- Grafana --', name: 'Grafana', meta: { id: 'grafana' } }, 'existing-ref-uid': { type: 'prometheus', uid: 'existing-ref-uid', name: 'Prometheus', meta: { id: 'prometheus' }, }, - 'influxdb-uid': { - type: 'influxdb', - uid: 'influxdb-uid', - name: 'InfluxDB', - meta: { id: 'influxdb' }, - }, - 'cloudwatch-uid': { - type: 'cloudwatch', - uid: 'cloudwatch-uid', - name: 'CloudWatch', - meta: { id: 'cloudwatch' }, - }, + 'influxdb-uid': { type: 'influxdb', uid: 'influxdb-uid', name: 'InfluxDB', meta: { id: 'influxdb' } }, + 'cloudwatch-uid': { type: 'cloudwatch', uid: 'cloudwatch-uid', name: 'CloudWatch', meta: { id: 'cloudwatch' } }, 'elasticsearch-uid': { type: 'elasticsearch', uid: 'elasticsearch-uid', name: 'Elasticsearch', meta: { id: 'elasticsearch' }, }, - 'loki-uid': { - type: 'loki', - uid: 'loki-uid', - name: 'Loki', - meta: { id: 'loki' }, - }, + 'loki-uid': { type: 'loki', uid: 'loki-uid', name: 'Loki', meta: { id: 'loki' } }, 'default-ds-uid': { type: 'prometheus', uid: 'default-ds-uid', @@ -92,12 +72,7 @@ jest.mock('@grafana/runtime', () => { name: 'Loki Test', meta: { id: 'loki' }, }, - '-- Mixed --': { - type: 'mixed', - uid: '-- Mixed --', - name: '-- Mixed --', - meta: { id: 'mixed' }, - }, + '-- Mixed --': { type: 'mixed', uid: '-- Mixed --', name: '-- Mixed --', meta: { id: 'mixed' } }, }, featureToggles: { dashboardNewLayouts: true, @@ -122,14 +97,14 @@ jest.mock('@grafana/runtime', () => { /* * V2 to V1 Dashboard Transformation Comparison Test * - * This test ensures that the frontend and backend v2beta1→v1beta1 conversions produce identical outputs + * This test ensures that the frontend and backend v2beta1→v1beta1/v0alpha1 conversions produce identical outputs * after being normalized through the same Scene load/save cycle. * * ## Two Conversion Paths Being Compared: * - * ### BACKEND PATH (simulates: API returns v1beta1, UI loads it, user saves) - * 1. Go conversion: v2beta1 → v1beta1 (output file from backend tests) - * 2. Load into Scene: v1beta1 JSON → DashboardModel → Scene + * ### BACKEND PATH (simulates: API returns v1beta1 or v0alpha1, UI loads it, user saves) + * 1. Go conversion: v2beta1 → v1beta1 or v0alpha1 (output file from backend tests) + * 2. Load into Scene: v1/v0 JSON → DashboardModel → Scene * 3. Serialize back: Scene → v1beta1 JSON (transformSceneToSaveModel) * * ### FRONTEND PATH (simulates: API returns v2beta1, UI loads it, user saves) @@ -138,20 +113,29 @@ jest.mock('@grafana/runtime', () => { * 3. Normalize: v1beta1 JSON → Scene → v1beta1 JSON (same as backend step 2-3) * * ## Why Normalize Both? - * Both paths end with the same Scene load/save cycle to ensure we're comparing apples to apples. - * This simulates what would happen if a user loaded a dashboard and saved it without changes. + * Both paths end with the same Scene load/save cycle (via loadAndSerializeV1SaveModel). + * This simulates what would happen if a user loaded a dashboard + * and saved it without changes. The Scene processing may add default values, reorder fields, or + * normalize data structures - by running both outputs through the same normalization, we eliminate + * these differences and focus on the actual conversion logic. + * + * ## Why Include v0alpha1? + * v0alpha1 and v1beta1 share the same spec structure. The v0alpha1 output from v2beta1→v0alpha1 + * conversion should produce identical results when loaded by the Scene. This validates that + * the backend v2→v0 conversion is consistent with the v2→v1 conversion. * * ## Expected Outcome * Both paths should produce identical v1beta1 JSON after normalization, meaning: - * - The backend Go conversion produces correct v1beta1 that survives Scene load/save unchanged + * - The backend Go conversion produces correct v1beta1/v0alpha1 that survives Scene load/save unchanged * - The frontend v2→v1 conversion produces v1beta1 matching what backend would produce */ +// Target versions to compare (v0alpha1 and v1beta1 share the same spec structure) +const TARGET_VERSIONS = ['v0alpha1', 'v1beta1'] as const; + describe('V2 to V1 Dashboard Transformation Comparison', () => { beforeEach(() => { jest.clearAllMocks(); - - // Mock console methods to avoid test failures from expected warnings (but keep console.log for debugging) jest.spyOn(console, 'error').mockImplementation(() => {}); jest.spyOn(console, 'warn').mockImplementation(() => {}); }); @@ -187,79 +171,64 @@ describe('V2 to V1 Dashboard Transformation Comparison', () => { 'output' ); - // Get all files recursively from input directory - const allFiles = getFilesRecursively(inputDir); - - // Filter to only process v2beta1 input files - const v2beta1Inputs = allFiles.filter(({ relativePath }) => { + // Get v2beta1 input files + const v2beta1Inputs = getFilesRecursively(inputDir).filter(({ relativePath }) => { const fileName = path.basename(relativePath); return fileName.startsWith('v2beta1.') && fileName.endsWith('.json'); }); + // Test each input file against each target version v2beta1Inputs.forEach(({ filePath: inputFilePath, relativePath }) => { - it(`compare ${relativePath} from v2beta1 to v1beta1 backend and frontend conversions`, async () => { - const jsonInput = JSON.parse(readFileSync(inputFilePath, 'utf8')); + TARGET_VERSIONS.forEach((targetVersion) => { + it(`${relativePath} → ${targetVersion}`, () => { + const relativeDir = path.dirname(relativePath); + const fileName = path.basename(relativePath); + const outputFileName = fileName.replace('.json', `.${targetVersion}.json`); + const outputFilePath = + relativeDir === '.' + ? path.join(outputDir, outputFileName) + : path.join(outputDir, relativeDir, outputFileName); - // Find the corresponding v1beta1 output file (preserving subdirectory structure) - const relativeDir = path.dirname(relativePath); - const fileName = path.basename(relativePath); - const outputFileName = fileName.replace('.json', '.v1beta1.json'); - const outputFilePath = - relativeDir === '.' ? path.join(outputDir, outputFileName) : path.join(outputDir, relativeDir, outputFileName); + // Skip if output file doesn't exist + if (!existsSync(outputFilePath)) { + return; + } - // Load the backend output - const backendOutput = JSON.parse(readFileSync(outputFilePath, 'utf8')); + const jsonInput = JSON.parse(readFileSync(inputFilePath, 'utf8')); + const backendOutput = JSON.parse(readFileSync(outputFilePath, 'utf8')); - // BACKEND PATH: - // Go conversion (v2beta1 → v1beta1) → load into Scene → serialize back - // This simulates: API returns v1beta1 → UI loads it → user saves changes - // Note: v1beta1 spec contains the dashboard fields directly (no "dashboard" wrapper) - const backendOutputAfterLoadedByScene = loadAndSerializeV1SaveModel(backendOutput.spec); + // Backend path: Load backend output through Scene + const backendSpec = loadAndSerializeV1SaveModel(backendOutput.spec); - // Transform using frontend path: v2beta1 -> Scene -> v1beta1 - // Extract the spec from v2beta1 format and use it as the dashboard data - // Remove snapshot field to prevent isSnapshot() from returning true - const frontendOutputAfterLoadedByScene = transformV2ToV1UsingFrontendTransformers(jsonInput); + // Frontend path: Transform v2beta1 through Scene + const frontendSpec = transformV2ToV1UsingFrontendTransformers(jsonInput); - // Verify both outputs have valid spec structures - expect(frontendOutputAfterLoadedByScene).toBeDefined(); - expect(backendOutputAfterLoadedByScene).toBeDefined(); - - // Compare only the dashboard spec structures, ignoring metadata differences (uid, version, etc.) - // Remove metadata fields that may differ between backend and frontend transformations - const frontendSpec = { ...frontendOutputAfterLoadedByScene }; - const backendSpec = { ...backendOutputAfterLoadedByScene }; - - // Remove metadata fields that are not part of the core dashboard spec - delete frontendSpec.uid; - delete backendSpec.uid; - delete frontendSpec.version; - delete backendSpec.version; - delete frontendSpec.id; - delete backendSpec.id; - - // Compare only the spec structures - this is the core transformation - expect(backendSpec).toEqual(frontendSpec); + // Compare specs (excluding metadata fields) + expect(removeMetadata(backendSpec)).toEqual(removeMetadata(frontendSpec)); + }); }); }); }); -/* - * Simulate the frontend transformation of a dashboard data object to a v1beta1 dashboard data object - * This is to ensure that the frontend transformation produces the same result as the backend transformation - * when the dashboard data object is loaded by the scene. - */ -/* - * Loads a v1beta1 dashboard into Scene and serializes it back to v1beta1. +/** Remove metadata fields that differ between backend and frontend transformations */ +function removeMetadata(spec: Dashboard): Partial { + const { uid, version, id, ...rest } = spec; + return rest; +} + +/** + * Loads a v1beta1/v0alpha1 dashboard into Scene and serializes it back to v1beta1. * * This simulates the real-world flow when editing a dashboard: * v1beta1 JSON → DashboardModel → Scene → v1beta1 JSON * - * This function is used to normalize both backend and frontend outputs - * through the same Scene load/save cycle, ensuring we compare apples to apples. + * This function is used to normalize both backend and frontend outputs through the same + * Scene load/save cycle. The Scene may add default values, reorder fields, + * or normalize data structures - this function ensures both outputs go through + * identical processing. */ function loadAndSerializeV1SaveModel(dashboard: Dashboard): Dashboard { - const sceneBackend = transformSaveModelToScene({ + const scene = transformSaveModelToScene({ dashboard: dashboard as DashboardDataDTO, meta: { isNew: false, @@ -276,22 +245,19 @@ function loadAndSerializeV1SaveModel(dashboard: Dashboard): Dashboard { }, }); - const backendOutputAfterLoadedByScene = transformSceneToSaveModel(sceneBackend, false); - - return backendOutputAfterLoadedByScene; + return transformSceneToSaveModel(scene, false); } -/* - * FRONTEND PATH: - * Transforms v2beta1 to v1beta1 using the frontend conversion pipeline: - * v2beta1 input → Scene (via transformSaveModelSchemaV2ToScene) → v1beta1 (via transformSceneToSaveModel) +/** + * Transforms v2beta1 to v1beta1 using the frontend conversion pipeline. * - * Then passes through loadAndSerializeV1SaveModel to normalize with the same Scene load/save - * cycle that the backend output goes through. This ensures both paths are compared after - * the same normalization process. + * Pipeline: v2beta1 → Scene → v1beta1 → Scene → v1beta1 (normalized) + * + * The final normalization step (passing through loadAndSerializeV1SaveModel) ensures + * the output goes through the same Scene load/save cycle as the backend output, + * making the comparison fair. */ function transformV2ToV1UsingFrontendTransformers(jsonInput: DashboardWithAccessInfo): Dashboard { - // Step 1: Load v2beta1 into Scene const scene = transformSaveModelSchemaV2ToScene({ spec: jsonInput.spec, metadata: jsonInput.metadata || { @@ -305,12 +271,6 @@ function transformV2ToV1UsingFrontendTransformers(jsonInput: DashboardWithAccess kind: 'DashboardWithAccessInfo', }); - // Step 2: Transform Scene to v1beta1 const frontendOutput = transformSceneToSaveModel(scene, false); - - // Step 3: Normalize by passing through Scene load/save (same as backend path) - // This ensures both paths are compared after identical Scene processing - const frontendOutputAfterLoadedByScene = loadAndSerializeV1SaveModel(frontendOutput); - - return frontendOutputAfterLoadedByScene; + return loadAndSerializeV1SaveModel(frontendOutput); } From dc8bb66a45aee5f75fb95d91836971f442610031 Mon Sep 17 00:00:00 2001 From: Gareth Date: Thu, 4 Dec 2025 17:11:33 +0900 Subject: [PATCH 013/110] OpenTSDB: Move health check to the backend (#114082) * add feature toggle * move health check to backend * add tests --- .../feature-toggles/index.md | 1 + .../src/types/featureToggles.gen.ts | 5 ++ pkg/services/featuremgmt/registry.go | 8 +++ pkg/services/featuremgmt/toggles_gen.csv | 1 + pkg/services/featuremgmt/toggles_gen.go | 4 ++ pkg/services/featuremgmt/toggles_gen.json | 14 +++++ pkg/tsdb/opentsdb/opentsdb.go | 60 +++++++++++++++++++ pkg/tsdb/opentsdb/opentsdb_test.go | 52 ++++++++++++++++ pkg/tsdb/opentsdb/standalone/datasource.go | 7 ++- .../plugins/datasource/opentsdb/datasource.ts | 18 ++++-- 10 files changed, 165 insertions(+), 5 deletions(-) diff --git a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md index f0f12cce1b6..b80fa49f815 100644 --- a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md +++ b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md @@ -68,6 +68,7 @@ Most [generally available](https://grafana.com/docs/release-life-cycle/#general- | `tabularNumbers` | Use fixed-width numbers globally in the UI | | | `azureResourcePickerUpdates` | Enables the updated Azure Monitor resource picker | Yes | | `tempoSearchBackendMigration` | Run search queries through the tempo backend | | +| `opentsdbBackendMigration` | Run queries through the data source backend | | ## Public preview feature toggles diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index 33680ca348d..f95bb2d4356 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -1190,6 +1190,11 @@ export interface FeatureToggles { */ transformationsEmptyPlaceholder?: boolean; /** + * Run queries through the data source backend + * @default false + */ + opentsdbBackendMigration?: boolean; + /** * Enable TTL plugin instance manager */ ttlPluginInstanceManager?: boolean; diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index bda80ace800..b121f58cb42 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -1961,6 +1961,14 @@ var ( FrontendOnly: true, Owner: grafanaDataProSquad, }, + { + Name: "opentsdbBackendMigration", + Description: "Run queries through the data source backend", + Stage: FeatureStageGeneralAvailability, + Owner: grafanaOSSBigTent, + Expression: "false", + RequiresRestart: true, + }, { Name: "ttlPluginInstanceManager", Description: "Enable TTL plugin instance manager", diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index b8323314f48..1287a38870e 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -266,6 +266,7 @@ panelTimeSettings,experimental,@grafana/dashboards-squad,false,false,false kubernetesAnnotations,experimental,@grafana/grafana-backend-services-squad,false,false,false awsDatasourcesHttpProxy,experimental,@grafana/aws-datasources,false,false,false transformationsEmptyPlaceholder,preview,@grafana/datapro,false,false,true +opentsdbBackendMigration,GA,@grafana/oss-big-tent,false,true,false ttlPluginInstanceManager,experimental,@grafana/plugins-platform-backend,false,false,true lokiQueryLimitsContext,experimental,@grafana/observability-logs,false,false,true rudderstackUpgrade,experimental,@grafana/grafana-frontend-platform,false,false,true diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go index 3183c587efd..afc599d4eb8 100644 --- a/pkg/services/featuremgmt/toggles_gen.go +++ b/pkg/services/featuremgmt/toggles_gen.go @@ -762,6 +762,10 @@ const ( // Enables http proxy settings for aws datasources FlagAwsDatasourcesHttpProxy = "awsDatasourcesHttpProxy" + // FlagOpentsdbBackendMigration + // Run queries through the data source backend + FlagOpentsdbBackendMigration = "opentsdbBackendMigration" + // FlagKubernetesAlertingHistorian // Adds support for Kubernetes alerting historian APIs FlagKubernetesAlertingHistorian = "kubernetesAlertingHistorian" diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json index 035edaa9a8d..ec1bce7682b 100644 --- a/pkg/services/featuremgmt/toggles_gen.json +++ b/pkg/services/featuremgmt/toggles_gen.json @@ -2499,6 +2499,20 @@ "expression": "true" } }, + { + "metadata": { + "name": "opentsdbBackendMigration", + "resourceVersion": "1763456634837", + "creationTimestamp": "2025-11-18T09:03:54Z" + }, + "spec": { + "description": "Run queries through the data source backend", + "stage": "GA", + "codeowner": "@grafana/oss-big-tent", + "requiresRestart": true, + "expression": "false" + } + }, { "metadata": { "name": "otelLogsFormatting", diff --git a/pkg/tsdb/opentsdb/opentsdb.go b/pkg/tsdb/opentsdb/opentsdb.go index df614c3f456..8b34b21cbaf 100644 --- a/pkg/tsdb/opentsdb/opentsdb.go +++ b/pkg/tsdb/opentsdb/opentsdb.go @@ -94,6 +94,66 @@ func newInstanceSettings(httpClientProvider *httpclient.Provider) datasource.Ins } } +func (s *Service) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) { + logger := logger.FromContext(ctx) + + dsInfo, err := s.getDSInfo(ctx, req.PluginContext) + if err != nil { + return &backend.CheckHealthResult{ + Status: backend.HealthStatusError, + Message: err.Error(), + }, nil + } + + u, err := url.Parse(dsInfo.URL) + if err != nil { + return &backend.CheckHealthResult{ + Status: backend.HealthStatusError, + Message: err.Error(), + }, nil + } + + u.Path = path.Join(u.Path, "api/suggest") + query := u.Query() + query.Set("q", "cpu") + query.Set("type", "metrics") + u.RawQuery = query.Encode() + + httpReq, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil) + if err != nil { + return &backend.CheckHealthResult{ + Status: backend.HealthStatusError, + Message: err.Error(), + }, nil + } + + res, err := dsInfo.HTTPClient.Do(httpReq) + if err != nil { + return &backend.CheckHealthResult{ + Status: backend.HealthStatusError, + Message: err.Error(), + }, nil + } + + defer func() { + if err := res.Body.Close(); err != nil { + logger.Error("Failed to close response body", "error", err) + } + }() + + if res.StatusCode != 200 { + return &backend.CheckHealthResult{ + Status: backend.HealthStatusError, + Message: fmt.Sprintf("OpenTSDB suggest endpoint returned status %d", res.StatusCode), + }, nil + } + + return &backend.CheckHealthResult{ + Status: backend.HealthStatusOk, + Message: "Data source is working", + }, nil +} + func (s *Service) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { logger := logger.FromContext(ctx) diff --git a/pkg/tsdb/opentsdb/opentsdb_test.go b/pkg/tsdb/opentsdb/opentsdb_test.go index cef8a003301..b959e9efa26 100644 --- a/pkg/tsdb/opentsdb/opentsdb_test.go +++ b/pkg/tsdb/opentsdb/opentsdb_test.go @@ -18,6 +18,58 @@ import ( "github.com/stretchr/testify/require" ) +func TestCheckHealth(t *testing.T) { + tests := []struct { + name string + httpStatusCode int + expectedStatus backend.HealthStatus + expectedMessage string + }{ + { + name: "successful health check", + httpStatusCode: 200, + expectedStatus: backend.HealthStatusOk, + expectedMessage: "Data source is working", + }, + { + name: "http error", + httpStatusCode: 500, + expectedStatus: backend.HealthStatusError, + expectedMessage: "OpenTSDB suggest endpoint returned status 500", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "/api/suggest", r.URL.Path) + assert.Equal(t, "cpu", r.URL.Query().Get("q")) + assert.Equal(t, "metrics", r.URL.Query().Get("type")) + w.WriteHeader(tt.httpStatusCode) + })) + defer server.Close() + + pluginCtx := backend.PluginContext{ + DataSourceInstanceSettings: &backend.DataSourceInstanceSettings{ + URL: server.URL, + JSONData: []byte(`{}`), + }, + } + + im := datasource.NewInstanceManager(newInstanceSettings(httpclient.NewProvider())) + service := &Service{im: im} + ctx := backend.WithPluginContext(context.Background(), pluginCtx) + result, err := service.CheckHealth(ctx, &backend.CheckHealthRequest{ + PluginContext: pluginCtx, + }) + + assert.NoError(t, err) + assert.Equal(t, tt.expectedStatus, result.Status) + assert.Contains(t, result.Message, tt.expectedMessage) + }) + } +} + func TestOpenTsdbExecutor(t *testing.T) { service := &Service{} diff --git a/pkg/tsdb/opentsdb/standalone/datasource.go b/pkg/tsdb/opentsdb/standalone/datasource.go index 7ca315aabb5..c2eacaf1d53 100644 --- a/pkg/tsdb/opentsdb/standalone/datasource.go +++ b/pkg/tsdb/opentsdb/standalone/datasource.go @@ -10,7 +10,8 @@ import ( ) var ( - _ backend.QueryDataHandler = (*Datasource)(nil) + _ backend.QueryDataHandler = (*Datasource)(nil) + _ backend.CheckHealthHandler = (*Datasource)(nil) ) type Datasource struct { @@ -26,3 +27,7 @@ func NewDatasource(context.Context, backend.DataSourceInstanceSettings) (instanc func (d *Datasource) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { return d.Service.QueryData(ctx, req) } + +func (d *Datasource) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) { + return d.Service.CheckHealth(ctx, req) +} diff --git a/public/app/plugins/datasource/opentsdb/datasource.ts b/public/app/plugins/datasource/opentsdb/datasource.ts index 2b70dfb605c..14c24b5c34d 100644 --- a/public/app/plugins/datasource/opentsdb/datasource.ts +++ b/public/app/plugins/datasource/opentsdb/datasource.ts @@ -20,19 +20,25 @@ import { AnnotationEvent, DataQueryRequest, DataQueryResponse, - DataSourceApi, dateMath, DateTime, ScopedVars, toDataFrame, } from '@grafana/data'; -import { FetchResponse, getBackendSrv, getTemplateSrv, TemplateSrv } from '@grafana/runtime'; +import { + config, + DataSourceWithBackend, + FetchResponse, + getBackendSrv, + getTemplateSrv, + TemplateSrv, +} from '@grafana/runtime'; import { AnnotationEditor } from './components/AnnotationEditor'; import { prepareAnnotation } from './migrations'; import { OpenTsdbFilter, OpenTsdbOptions, OpenTsdbQuery } from './types'; -export default class OpenTsDatasource extends DataSourceApi { +export default class OpenTsDatasource extends DataSourceWithBackend { type: 'opentsdb'; url: string; name: string; @@ -397,7 +403,11 @@ export default class OpenTsDatasource extends DataSourceApi { From 1ba57a505a8e232369a92d9ae465802419abc280 Mon Sep 17 00:00:00 2001 From: Dominik Prokop Date: Thu, 4 Dec 2025 09:23:45 +0100 Subject: [PATCH 014/110] v1 to v2 conversion: Fix issue when Grafana datasource wouldn't be resolved correctly (#114779) * Reapply "v1 to v2 conversion: Fix issue when Grafana datasource wouldn't be resolved correctly (#114555)" This reverts commit db9cff8e2d1c478bc0415fd188af0fcda4d1b978. * Update input and fix the failing test --- ...eta1.panel-datasource-type-datasource.json | 153 ++++++++++++ ...l-datasource-type-datasource.v0alpha1.json | 158 ++++++++++++ ...l-datasource-type-datasource.v2alpha1.json | 205 ++++++++++++++++ ...el-datasource-type-datasource.v2beta1.json | 208 ++++++++++++++++ .../conversion/v1beta1_to_v2alpha1.go | 91 +++++-- .../conversion/v1beta1_to_v2alpha1_test.go | 228 ++++++++++++++++++ eslint-suppressions.json | 3 - go.work.sum | 12 + .../transformSceneToSaveModelSchemaV2.ts | 13 +- .../dashboard/api/ResponseTransformers.ts | 14 +- 10 files changed, 1057 insertions(+), 28 deletions(-) create mode 100644 apps/dashboard/pkg/migration/conversion/testdata/input/v1beta1.panel-datasource-type-datasource.json create mode 100644 apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.panel-datasource-type-datasource.v0alpha1.json create mode 100644 apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.panel-datasource-type-datasource.v2alpha1.json create mode 100644 apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.panel-datasource-type-datasource.v2beta1.json create mode 100644 apps/dashboard/pkg/migration/conversion/v1beta1_to_v2alpha1_test.go diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/v1beta1.panel-datasource-type-datasource.json b/apps/dashboard/pkg/migration/conversion/testdata/input/v1beta1.panel-datasource-type-datasource.json new file mode 100644 index 00000000000..6becb06c8f6 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/input/v1beta1.panel-datasource-type-datasource.json @@ -0,0 +1,153 @@ +{ + "apiVersion": "dashboard.grafana.app/v1beta1", + "kind": "Dashboard", + "metadata": { + "name": "ad5vfcn", + "namespace": "default", + "uid": "dlMZZl6GndU8gJLUQSmgZxXBPCNXyXhNBeQJhHXl0r4X", + "resourceVersion": "2", + "generation": 2, + "creationTimestamp": "2025-11-28T10:14:21Z", + "labels": { + "grafana.app/deprecatedInternalID": "288" + }, + "annotations": { + "grafana.app/createdBy": "user:eex2ofwuj0agwd", + "grafana.app/updatedBy": "user:eex2ofwuj0agwd", + "grafana.app/updatedTimestamp": "2025-11-28T10:15:06Z" + } + }, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 288, + "links": [], + "panels": [ + { + "datasource": { + "type": "testdata", + "uid": "gdev-testdata" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 1, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.4.0-pre", + "targets": [ + { + "datasource": {}, + "queryType": "randomWalk", + "refId": "A" + } + ], + "title": "New panel", + "type": "timeseries" + } + ], + "preload": false, + "schemaVersion": 42, + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "browser", + "title": "Panel ds inheritance ", + "uid": "ad5vfcn", + "version": 2 + }, + "status": {} +} diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.panel-datasource-type-datasource.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.panel-datasource-type-datasource.v0alpha1.json new file mode 100644 index 00000000000..3ee27b33fd6 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.panel-datasource-type-datasource.v0alpha1.json @@ -0,0 +1,158 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v0alpha1", + "metadata": { + "name": "ad5vfcn", + "namespace": "default", + "uid": "dlMZZl6GndU8gJLUQSmgZxXBPCNXyXhNBeQJhHXl0r4X", + "resourceVersion": "2", + "generation": 2, + "creationTimestamp": "2025-11-28T10:14:21Z", + "labels": { + "grafana.app/deprecatedInternalID": "288" + }, + "annotations": { + "grafana.app/createdBy": "user:eex2ofwuj0agwd", + "grafana.app/updatedBy": "user:eex2ofwuj0agwd", + "grafana.app/updatedTimestamp": "2025-11-28T10:15:06Z" + } + }, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 288, + "links": [], + "panels": [ + { + "datasource": { + "type": "testdata", + "uid": "gdev-testdata" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 1, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.4.0-pre", + "targets": [ + { + "datasource": {}, + "queryType": "randomWalk", + "refId": "A" + } + ], + "title": "New panel", + "type": "timeseries" + } + ], + "preload": false, + "schemaVersion": 42, + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "browser", + "title": "Panel ds inheritance ", + "uid": "ad5vfcn", + "version": 2 + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.panel-datasource-type-datasource.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.panel-datasource-type-datasource.v2alpha1.json new file mode 100644 index 00000000000..5a0a8e074e6 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.panel-datasource-type-datasource.v2alpha1.json @@ -0,0 +1,205 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2alpha1", + "metadata": { + "name": "ad5vfcn", + "namespace": "default", + "uid": "dlMZZl6GndU8gJLUQSmgZxXBPCNXyXhNBeQJhHXl0r4X", + "resourceVersion": "2", + "generation": 2, + "creationTimestamp": "2025-11-28T10:14:21Z", + "labels": { + "grafana.app/deprecatedInternalID": "288" + }, + "annotations": { + "grafana.app/createdBy": "user:eex2ofwuj0agwd", + "grafana.app/updatedBy": "user:eex2ofwuj0agwd", + "grafana.app/updatedTimestamp": "2025-11-28T10:15:06Z" + } + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "query": { + "kind": "grafana", + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "New panel", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "testdata", + "spec": { + "queryType": "randomWalk" + } + }, + "datasource": { + "type": "testdata", + "uid": "gdev-testdata" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "12.4.0-pre", + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "fieldConfig": { + "defaults": { + "thresholds": { + "mode": "absolute", + "steps": [ + { + "value": 0, + "color": "green" + }, + { + "value": 80, + "color": "red" + } + ] + }, + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + } + }, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 12, + "height": 8, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "browser", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "Panel ds inheritance ", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.panel-datasource-type-datasource.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.panel-datasource-type-datasource.v2beta1.json new file mode 100644 index 00000000000..3a13853c08e --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.panel-datasource-type-datasource.v2beta1.json @@ -0,0 +1,208 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2beta1", + "metadata": { + "name": "ad5vfcn", + "namespace": "default", + "uid": "dlMZZl6GndU8gJLUQSmgZxXBPCNXyXhNBeQJhHXl0r4X", + "resourceVersion": "2", + "generation": 2, + "creationTimestamp": "2025-11-28T10:14:21Z", + "labels": { + "grafana.app/deprecatedInternalID": "288" + }, + "annotations": { + "grafana.app/createdBy": "user:eex2ofwuj0agwd", + "grafana.app/updatedBy": "user:eex2ofwuj0agwd", + "grafana.app/updatedTimestamp": "2025-11-28T10:15:06Z" + } + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana", + "version": "v0", + "datasource": { + "name": "-- Grafana --" + }, + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "New panel", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "testdata", + "version": "v0", + "datasource": { + "name": "gdev-testdata" + }, + "spec": { + "queryType": "randomWalk" + } + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "12.4.0-pre", + "spec": { + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "fieldConfig": { + "defaults": { + "thresholds": { + "mode": "absolute", + "steps": [ + { + "value": 0, + "color": "green" + }, + { + "value": 80, + "color": "red" + } + ] + }, + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + } + }, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 12, + "height": 8, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "browser", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "Panel ds inheritance ", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/v1beta1_to_v2alpha1.go b/apps/dashboard/pkg/migration/conversion/v1beta1_to_v2alpha1.go index da8e8a6ae28..e6599b1d8eb 100644 --- a/apps/dashboard/pkg/migration/conversion/v1beta1_to_v2alpha1.go +++ b/apps/dashboard/pkg/migration/conversion/v1beta1_to_v2alpha1.go @@ -16,6 +16,7 @@ import ( dashv2alpha1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1" schemaversion "github.com/grafana/grafana/apps/dashboard/pkg/migration/schemaversion" "github.com/grafana/grafana/pkg/apimachinery/identity" + "github.com/grafana/grafana/pkg/tsdb/grafanads" ) // getDefaultDatasourceType gets the default datasource type using the datasource provider @@ -52,6 +53,16 @@ func getDatasourceTypeByUID(ctx context.Context, uid string, provider schemavers return getDefaultDatasourceType(ctx, provider) } +// resolveGrafanaDatasourceUID resolves the Grafana datasource UID when type is "datasource" and UID is empty. +// The Grafana datasource has type "datasource" and UID "grafana". When a v1beta1 dashboard has +// datasource: { type: "datasource" } with no UID, it should resolve to uid: "grafana". +func resolveGrafanaDatasourceUID(dsType, dsUID string) string { + if dsType == "datasource" && dsUID == "" { + return grafanads.DatasourceUID + } + return dsUID +} + // prepareV1beta1ConversionContext sets up the context with namespace and service identity // for v1beta1 dashboard conversions. This context is needed to retrieve datasources for // converting dashboard datasource references. @@ -1674,6 +1685,9 @@ func buildGroupByVariable(ctx context.Context, varMap map[string]interface{}, co // If no UID and no type, use default datasourceType = getDefaultDatasourceType(ctx, dsIndexProvider) } + + // Resolve Grafana datasource UID when type is "datasource" and UID is empty + datasourceUID = resolveGrafanaDatasourceUID(datasourceType, datasourceUID) } else { datasourceType = getDefaultDatasourceType(ctx, dsIndexProvider) } @@ -1929,22 +1943,45 @@ func transformPanelQueries(ctx context.Context, panelMap map[string]interface{}, // Get panel datasource var panelDatasource *dashv2alpha1.DashboardDataSourceRef - if ds, ok := panelMap["datasource"].(map[string]interface{}); ok { - dsUID := schemaversion.GetStringValue(ds, "uid") - dsType := schemaversion.GetStringValue(ds, "type") + ds, dsExists := panelMap["datasource"] - // If we have a UID, use it to get the correct type from the datasource service - // BUT: Don't try to resolve types for template variables - if dsUID != "" && dsType == "" && !isTemplateVariable(dsUID) { - dsType = getDatasourceTypeByUID(ctx, dsUID, dsIndexProvider) - } else if dsUID == "" && dsType == "" { - // If no UID and no type, use default - dsType = getDefaultDatasourceType(ctx, dsIndexProvider) - } + if dsExists && ds != nil { + if dsMap, ok := ds.(map[string]interface{}); ok { + // Handle panel datasource as object + dsUID := schemaversion.GetStringValue(dsMap, "uid") + dsType := schemaversion.GetStringValue(dsMap, "type") - panelDatasource = &dashv2alpha1.DashboardDataSourceRef{ - Type: &dsType, - Uid: &dsUID, + // Check if datasource object is effectively empty (no uid and no type) + // Empty objects {} should be preserved as empty, not converted to defaults + isEmpty := dsUID == "" && dsType == "" + + // If we have a UID, use it to get the correct type from the datasource service + // BUT: Don't try to resolve types for template variables + if dsUID != "" && dsType == "" && !isTemplateVariable(dsUID) { + dsType = getDatasourceTypeByUID(ctx, dsUID, dsIndexProvider) + } else if !isEmpty && dsUID == "" && dsType == "" { + // Only set default if datasource is missing (not empty object) + // Empty objects {} should remain empty + dsType = getDefaultDatasourceType(ctx, dsIndexProvider) + } + + // Resolve Grafana datasource UID when type is "datasource" and UID is empty + // Only resolve if we have a type (not for empty objects) + if !isEmpty { + dsUID = resolveGrafanaDatasourceUID(dsType, dsUID) + } + + // Only create panelDatasource if it's not empty after resolution + // Empty objects {} should result in nil panelDatasource + // After resolution, check if we have a type or UID (not just the original isEmpty) + // This ensures that type: "datasource" with empty UID gets resolved to uid: "grafana" + // and panelDatasource is created + if dsType != "" || dsUID != "" { + panelDatasource = &dashv2alpha1.DashboardDataSourceRef{ + Type: &dsType, + Uid: &dsUID, + } + } } } @@ -1971,12 +2008,24 @@ func transformSingleQuery(ctx context.Context, targetMap map[string]interface{}, queryDatasourceUID = schemaversion.GetStringValue(ds, "uid") queryDatasourceType = schemaversion.GetStringValue(ds, "type") - // If we have a UID, use it to get the correct type from the datasource service - // BUT: Don't try to resolve types for template variables - if queryDatasourceUID != "" && queryDatasourceType == "" && !isTemplateVariable(queryDatasourceUID) { - queryDatasourceType = getDatasourceTypeByUID(ctx, queryDatasourceUID, dsIndexProvider) + // If target datasource is empty object {} (no uid and no type), treat it as missing + // and fall through to use panel datasource (matches frontend behavior in v36 migration) + if queryDatasourceUID == "" && queryDatasourceType == "" { + // Empty datasource object - will use panel datasource below + } else { + // If we have a UID, use it to get the correct type from the datasource service + // BUT: Don't try to resolve types for template variables + if queryDatasourceUID != "" && queryDatasourceType == "" && !isTemplateVariable(queryDatasourceUID) { + queryDatasourceType = getDatasourceTypeByUID(ctx, queryDatasourceUID, dsIndexProvider) + } + + // Resolve Grafana datasource UID when type is "datasource" and UID is empty + queryDatasourceUID = resolveGrafanaDatasourceUID(queryDatasourceType, queryDatasourceUID) } - } else if panelDatasource != nil { + } + + // Use panel datasource if target datasource is missing or empty + if queryDatasourceUID == "" && queryDatasourceType == "" && panelDatasource != nil { // Only use panel datasource if it's not a mixed datasource // Mixed datasources should not be propagated to individual queries if panelDatasource.Uid != nil && *panelDatasource.Uid != "-- Mixed --" { @@ -1984,6 +2033,10 @@ func transformSingleQuery(ctx context.Context, targetMap map[string]interface{}, queryDatasourceType = *panelDatasource.Type } queryDatasourceUID = *panelDatasource.Uid + } else if panelDatasource.Type != nil && *panelDatasource.Type == "datasource" { + // Handle case where panel datasource has type "datasource" but no UID + queryDatasourceType = *panelDatasource.Type + queryDatasourceUID = resolveGrafanaDatasourceUID(*panelDatasource.Type, "") } } diff --git a/apps/dashboard/pkg/migration/conversion/v1beta1_to_v2alpha1_test.go b/apps/dashboard/pkg/migration/conversion/v1beta1_to_v2alpha1_test.go new file mode 100644 index 00000000000..6bbdf1ca214 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/v1beta1_to_v2alpha1_test.go @@ -0,0 +1,228 @@ +package conversion + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "k8s.io/apimachinery/pkg/runtime" + + dashv1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1" + dashv2alpha1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1" + "github.com/grafana/grafana/apps/dashboard/pkg/migration" + migrationtestutil "github.com/grafana/grafana/apps/dashboard/pkg/migration/testutil" + "github.com/grafana/grafana/pkg/tsdb/grafanads" +) + +// TestV1beta1ToV2alpha1 tests conversion from v1beta1 to v2alpha1 with various datasource scenarios +func TestV1beta1ToV2alpha1(t *testing.T) { + // Initialize the migrator with test providers + dsProvider := migrationtestutil.NewDataSourceProvider(migrationtestutil.StandardTestConfig) + leProvider := migrationtestutil.NewLibraryElementProvider() + migration.Initialize(dsProvider, leProvider) + + // Set up conversion scheme + scheme := runtime.NewScheme() + err := RegisterConversions(scheme, dsProvider, leProvider) + require.NoError(t, err) + + testCases := []struct { + name string + createV1beta1 func() *dashv1.Dashboard + validateV2alpha1 func(t *testing.T, v2alpha1 *dashv2alpha1.Dashboard) + }{ + { + name: "panel datasource type datasource with no UID - resolves to grafana UID", + createV1beta1: func() *dashv1.Dashboard { + return &dashv1.Dashboard{ + Spec: dashv1.DashboardSpec{ + Object: map[string]interface{}{ + "title": "Test Dashboard", + "panels": []interface{}{ + map[string]interface{}{ + "id": 1, + "type": "bargauge", + // Panel datasource has type: "datasource" but no UID + "datasource": map[string]interface{}{ + "type": "datasource", + // No "uid" field + }, + "targets": []interface{}{ + map[string]interface{}{ + "refId": "A", + "scenarioId": "random_walk", + // Target datasource is empty object {} + "datasource": map[string]interface{}{}, + }, + }, + }, + }, + }, + }, + } + }, + validateV2alpha1: func(t *testing.T, v2alpha1 *dashv2alpha1.Dashboard) { + require.NotNil(t, v2alpha1.Spec.Elements["panel-1"]) + panel := v2alpha1.Spec.Elements["panel-1"].PanelKind + require.NotNil(t, panel) + + // Verify queries have datasource with UID resolved to "grafana" + require.Len(t, panel.Spec.Data.Spec.Queries, 1) + query := panel.Spec.Data.Spec.Queries[0] + require.NotNil(t, query.Spec.Datasource, "Query should have datasource") + + // Verify datasource type is "datasource" + assert.NotNil(t, query.Spec.Datasource.Type) + assert.Equal(t, "datasource", *query.Spec.Datasource.Type) + + // Verify datasource UID is resolved to "grafana" + assert.NotNil(t, query.Spec.Datasource.Uid) + assert.Equal(t, grafanads.DatasourceUID, *query.Spec.Datasource.Uid, "type: 'datasource' with no UID should resolve to uid: 'grafana'") + + // Verify query kind matches datasource type + assert.Equal(t, "datasource", query.Spec.Query.Kind) + }, + }, + { + name: "empty target datasource objects inherit from panel datasource", + createV1beta1: func() *dashv1.Dashboard { + return &dashv1.Dashboard{ + Spec: dashv1.DashboardSpec{ + Object: map[string]interface{}{ + "title": "Test Dashboard", + "panels": []interface{}{ + map[string]interface{}{ + "id": 1, + "type": "bargauge", + // Panel datasource is set + "datasource": map[string]interface{}{ + "type": "prometheus", + "uid": "prometheus-uid", + }, + "targets": []interface{}{ + map[string]interface{}{ + "refId": "A", + "scenarioId": "random_walk", + // Target datasource is empty object {} - should inherit from panel + "datasource": map[string]interface{}{}, + }, + map[string]interface{}{ + "refId": "B", + "scenarioId": "random_walk", + "datasource": map[string]interface{}{}, + }, + }, + }, + }, + }, + }, + } + }, + validateV2alpha1: func(t *testing.T, v2alpha1 *dashv2alpha1.Dashboard) { + require.NotNil(t, v2alpha1.Spec.Elements["panel-1"]) + panel := v2alpha1.Spec.Elements["panel-1"].PanelKind + require.NotNil(t, panel) + + // Verify queries inherit panel datasource + require.Len(t, panel.Spec.Data.Spec.Queries, 2) + for _, query := range panel.Spec.Data.Spec.Queries { + require.NotNil(t, query.Spec.Datasource, "Query should inherit datasource from panel when target datasource is empty") + assert.Equal(t, "prometheus", *query.Spec.Datasource.Type) + assert.Equal(t, "prometheus-uid", *query.Spec.Datasource.Uid) + assert.Equal(t, "prometheus", query.Spec.Query.Kind) + } + }, + }, + { + name: "panel datasource null without empty target datasource objects - no default set", + createV1beta1: func() *dashv1.Dashboard { + return &dashv1.Dashboard{ + Spec: dashv1.DashboardSpec{ + Object: map[string]interface{}{ + "title": "Test Dashboard", + "panels": []interface{}{ + map[string]interface{}{ + "id": 1, + "type": "bargauge", + // Panel datasource is null + "datasource": nil, + "targets": []interface{}{ + map[string]interface{}{ + "refId": "A", + "scenarioId": "random_walk", + // Target has no datasource field at all (not even empty object) + }, + }, + }, + }, + }, + }, + } + }, + validateV2alpha1: func(t *testing.T, v2alpha1 *dashv2alpha1.Dashboard) { + require.NotNil(t, v2alpha1.Spec.Elements["panel-1"]) + panel := v2alpha1.Spec.Elements["panel-1"].PanelKind + require.NotNil(t, panel) + + // Verify queries don't have datasource when panel is null and targets don't have empty datasource objects + require.Len(t, panel.Spec.Data.Spec.Queries, 1) + query := panel.Spec.Data.Spec.Queries[0] + // Query should not have datasource when panel datasource is null and target doesn't have empty datasource object + assert.Nil(t, query.Spec.Datasource, "Query should not have datasource when panel datasource is null and target has no empty datasource object") + }, + }, + { + name: "empty panel datasource object preserved as empty", + createV1beta1: func() *dashv1.Dashboard { + return &dashv1.Dashboard{ + Spec: dashv1.DashboardSpec{ + Object: map[string]interface{}{ + "title": "Test Dashboard", + "panels": []interface{}{ + map[string]interface{}{ + "id": 1, + "type": "bargauge", + // Panel datasource is empty object {} - should be preserved as empty + "datasource": map[string]interface{}{}, + "targets": []interface{}{ + map[string]interface{}{ + "refId": "A", + "scenarioId": "random_walk", + "datasource": map[string]interface{}{}, + }, + }, + }, + }, + }, + }, + } + }, + validateV2alpha1: func(t *testing.T, v2alpha1 *dashv2alpha1.Dashboard) { + require.NotNil(t, v2alpha1.Spec.Elements["panel-1"]) + panel := v2alpha1.Spec.Elements["panel-1"].PanelKind + require.NotNil(t, panel) + + // Verify queries don't have datasource when panel datasource is empty object {} + require.Len(t, panel.Spec.Data.Spec.Queries, 1) + query := panel.Spec.Data.Spec.Queries[0] + // Empty objects {} should be preserved as empty, not converted to defaults + assert.Nil(t, query.Spec.Datasource, "Query should not have datasource when panel datasource is empty object {}") + assert.Equal(t, "", query.Spec.Query.Kind, "Query kind should be empty when datasource is empty object {}") + }, + }, + } + + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + v1beta1Dash := tt.createV1beta1() + + // Convert to v2alpha1 + var v2alpha1Dash dashv2alpha1.Dashboard + err := scheme.Convert(v1beta1Dash, &v2alpha1Dash, nil) + require.NoError(t, err) + + // Validate the conversion result + tt.validateV2alpha1(t, &v2alpha1Dash) + }) + } +} diff --git a/eslint-suppressions.json b/eslint-suppressions.json index 318b3faa31a..d358be67002 100644 --- a/eslint-suppressions.json +++ b/eslint-suppressions.json @@ -2093,9 +2093,6 @@ } }, "public/app/features/dashboard/api/ResponseTransformers.ts": { - "@typescript-eslint/consistent-type-assertions": { - "count": 2 - }, "@typescript-eslint/no-explicit-any": { "count": 1 } diff --git a/go.work.sum b/go.work.sum index aad28b17bf8..36bd9f3510d 100644 --- a/go.work.sum +++ b/go.work.sum @@ -309,6 +309,7 @@ github.com/GoogleCloudPlatform/cloudsql-proxy v1.37.8/go.mod h1:exon/I6I+5u/ab7A github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.3 h1:2afWGsMzkIcN8Qm4mgPJKZWyroE5QBszMiDMYEBrnfw= github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.3/go.mod h1:dppbR7CwXD4pgtV9t3wD1812RaLDcBjtblcDF5f1vI0= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0/go.mod h1:yAZHSGnqScoU556rBOVkwLze6WP5N+U11RHuWaGVxwY= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0/go.mod h1:Cz6ft6Dkn3Et6l2v2a9/RpN7epQ1GtDlO6lj8bEcOvw= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0/go.mod h1:BnBReJLvVYx2CS/UHOgVz2BXKXD9wsQPxZug20nZhd0= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v1.29.0 h1:YVtMlmfRUTaWs3+1acwMBp7rBUo6zrxl6Kn13/R9YW4= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v1.29.0/go.mod h1:rKOFVIPbNs2wZeh7ZeQ0D9p/XLgbNiTr5m7x6KuAshk= @@ -548,6 +549,7 @@ github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe h1:QQ3GSy+MqSHxm/d8nC github.com/cncf/xds/go v0.0.0-20240723142845-024c85f92f20/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/cncf/xds/go v0.0.0-20250326154945-ae57f3c0d45f/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= +github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/cockroachdb/cockroach-go v0.0.0-20181001143604-e0a95dfd547c h1:2zRrJWIt/f9c9HhNHAgrRgq0San5gRRUJTBXLkchal0= github.com/cockroachdb/datadriven v1.0.2/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd h1:qMd81Ts1T2OTKmB4acZcyKaMtRnY5Y44NuXGX2GFJ1w= @@ -703,7 +705,9 @@ github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRr github.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/emicklei/proto v1.10.0/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A= +github.com/envoyproxy/go-control-plane v0.13.4/go.mod h1:kDfuBlDVsSj2MjrLEtRWtHlsWIFcGyB2RMO44Dc5GZA= github.com/envoyproxy/go-control-plane/envoy v1.32.3/go.mod h1:F6hWupPfh75TBXGKA++MCT/CZHFq5r9/uwt/kQYkZfE= +github.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw= github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew= github.com/envoyproxy/protoc-gen-validate v1.1.0/go.mod h1:sXRDRVmzEbkM7CVcM06s9shE/m23dg3wzjl0UWqJ2q4= github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= @@ -750,6 +754,7 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4 h1:WtGNWLvXpe github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ= github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA= github.com/go-jose/go-jose/v4 v4.1.1/go.mod h1:BdsZGqgdO3b6tTc6LSE56wcDbMMLuPsw5d4ZD5f94kA= +github.com/go-jose/go-jose/v4 v4.1.2/go.mod h1:22cg9HWM1pOlnRiY+9cQYJ9XHmya1bYW8OeDM6Ku6Oo= github.com/go-json-experiment/json v0.0.0-20250211171154-1ae217ad3535 h1:yE7argOs92u+sSCRgqqe6eF+cDaVhSPlioy1UkA0p/w= github.com/go-json-experiment/json v0.0.0-20250211171154-1ae217ad3535/go.mod h1:BWmvoE1Xia34f3l/ibJweyhrT+aROb/FQ6d+37F0e2s= github.com/go-kit/kit v0.12.0 h1:e4o3o3IsBfAKQh5Qbbiqyfu97Ku7jrO/JbohvztANh4= @@ -869,6 +874,7 @@ github.com/grafana/grafana-app-sdk v0.41.0 h1:SYHN3U7B1myRKY3UZZDkFsue9TDmAOap0U github.com/grafana/grafana-app-sdk v0.41.0/go.mod h1:Wg/3vEZfok1hhIWiHaaJm+FwkosfO98o8KbeLFEnZpY= github.com/grafana/grafana-app-sdk v0.46.0/go.mod h1:LCTrqR1SwBS13XGVYveBmM7giJDDjzuXK+M9VzPuPWc= github.com/grafana/grafana-app-sdk v0.47.0/go.mod h1:kywXmkppq0oReUMzkjTW8Fq2EBzyN7v914jttTWnWxA= +github.com/grafana/grafana-app-sdk v0.48.2/go.mod h1:LDOvQ7OOyHLcXdSa0InATCa5OMoYAd6E1+rGLrMgHuk= github.com/grafana/grafana-app-sdk/logging v0.38.0/go.mod h1:Y/bvbDhBiV/tkIle9RW49pgfSPIPSON8Q4qjx3pyqDk= github.com/grafana/grafana-app-sdk/logging v0.39.0 h1:3GgN5+dUZYqq74Q+GT9/ET+yo+V54zWQk/Q2/JsJQB4= github.com/grafana/grafana-app-sdk/logging v0.39.0/go.mod h1:WhDENSnaGHtyVVwZGVnAR7YLvh2xlLDYR3D7E6h7XVk= @@ -882,6 +888,7 @@ github.com/grafana/grafana-app-sdk/logging v0.45.0/go.mod h1:Gh/nBWnspK3oDNWtiM5 github.com/grafana/grafana-app-sdk/logging v0.46.0/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA= github.com/grafana/grafana-app-sdk/logging v0.48.0 h1:xolkQxBlA2LQF4hprKIAeu+zUem1DigYZ6XC1TOhFJE= github.com/grafana/grafana-app-sdk/logging v0.48.0/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA= +github.com/grafana/grafana-app-sdk/logging v0.48.1/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA= github.com/grafana/grafana-app-sdk/logging v0.48.2 h1:tI+a9slUvxKUgweXDzUqkca2LWV3g1UdaSvwt8nQNHg= github.com/grafana/grafana-app-sdk/logging v0.48.2/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA= github.com/grafana/grafana-app-sdk/plugin v0.41.0 h1:ShUvGpAVzM3UxcsfwS6l/lwW4ytDeTbCQXf8w2P8Yp8= @@ -1431,6 +1438,7 @@ github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmq github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4= +github.com/spiffe/go-spiffe/v2 v2.5.0/go.mod h1:P+NxobPc6wXhVtINNtFjNWGBTreew1GBUCwT2wPmb7g= github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad h1:fiWzISvDn0Csy5H0iwgAuJGQTUpVfEMJJd4nRFXogbc= github.com/stefanberger/go-pkcs11uri v0.0.0-20230803200340-78284954bff6/go.mod h1:39R/xuhNgVhi+K0/zst4TLrJrVmbm6LVgl4A0+ZFS5M= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= @@ -1786,6 +1794,7 @@ go.opentelemetry.io/contrib/config v0.14.0/go.mod h1:77rDmFPqBae5jtQ2C78RuDTHz4P go.opentelemetry.io/contrib/detectors/aws/ec2 v1.37.0 h1:BJnWw8+FULhuuF/6R6B/JYqAlCTCy9E4J8qmLpo/7KU= go.opentelemetry.io/contrib/detectors/aws/ec2 v1.37.0/go.mod h1:gs3y8jvJscW5D+FzrZvJZEsGj+xlMCF0S1x4R6ktiNo= go.opentelemetry.io/contrib/detectors/gcp v1.36.0/go.mod h1:IbBN8uAIIx734PTonTPxAxnjc2pQTxWNkwfstZ+6H2k= +go.opentelemetry.io/contrib/detectors/gcp v1.37.0/go.mod h1:K5zQ3TT7p2ru9Qkzk0bKtCql0RGkPj9pRjpXgZJZ+rU= go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho v0.59.0 h1:I8k9HW4yl8SRYNmECKKtjhcOvq9lAP9riqYPixBU3qw= go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho v0.59.0/go.mod h1:/vTiuiSKBQAerQeMB3CsVJbXd+cvTbhcdOk5AV5Z5R0= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.45.0/go.mod h1:vsh3ySueQCiKPxFLvjWC4Z135gIa34TQ/NSqkDTZYUM= @@ -2073,6 +2082,7 @@ google.golang.org/genproto/googleapis/api v0.0.0-20250728155136-f173205681a0/go. google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b/go.mod h1:oDOGiMSXHL4sDTJvFvIB9nRQCGdLP1o/iVaqQK8zB+M= google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c/go.mod h1:ea2MjsO70ssTfCjiwHgI0ZFqcw45Ksuk2ckf9G468GA= google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5/go.mod h1:j3QtIyytwqGr1JUDtYXwtMXWPKsEa5LtzIFN1Wn5WvE= +google.golang.org/genproto/googleapis/api v0.0.0-20250908214217-97024824d090/go.mod h1:U8EXRNSd8sUYyDfs/It7KVWodQr+Hf9xtxyxWudSwEw= google.golang.org/genproto/googleapis/api v0.0.0-20250929231259-57b25ae835d4/go.mod h1:NnuHhy+bxcg30o7FnVAZbXsPHUDQ9qKWAQKCD7VxFtk= google.golang.org/genproto/googleapis/bytestream v0.0.0-20250603155806-513f23925822 h1:zWFRixYR5QlotL+Uv3YfsPRENIrQFXiGs+iwqel6fOQ= google.golang.org/genproto/googleapis/bytestream v0.0.0-20250603155806-513f23925822/go.mod h1:h6yxum/C2qRb4txaZRLDHK8RyS0H/o2oEDeKY4onY/Y= @@ -2102,6 +2112,7 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5/go. google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1/go.mod h1:GmFNa4BdJZ2a8G+wCe9Bg3wwThLrJun751XstdJt5Og= google.golang.org/genproto/googleapis/rpc v0.0.0-20250908214217-97024824d090/go.mod h1:GmFNa4BdJZ2a8G+wCe9Bg3wwThLrJun751XstdJt5Og= google.golang.org/genproto/googleapis/rpc v0.0.0-20250929231259-57b25ae835d4/go.mod h1:HSkG/KdJWusxU1F6CNrwNDjBMgisKxGnc5dAZfT0mjQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251002232023-7c0ddcbb5797/go.mod h1:HSkG/KdJWusxU1F6CNrwNDjBMgisKxGnc5dAZfT0mjQ= google.golang.org/genproto/googleapis/rpc v0.0.0-20251014184007-4626949a642f/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= @@ -2123,6 +2134,7 @@ google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7E google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM= google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= +google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0 h1:M1YKkFIboKNieVO5DLUEVzQfGwJD30Nv2jfUgzb5UcE= google.golang.org/grpc/examples v0.0.0-20230224211313-3775f633ce20 h1:MLBCGN1O7GzIx+cBiwfYPwtmZ41U3Mn/cotLJciaArI= google.golang.org/grpc/examples v0.0.0-20230224211313-3775f633ce20/go.mod h1:Nr5H8+MlGWr5+xX/STzdoEqJrO+YteqFbMyCsrb6mH0= diff --git a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.ts b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.ts index e40dcd41d5f..4a63c62af93 100644 --- a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.ts +++ b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.ts @@ -855,11 +855,18 @@ export function getPersistedDSFor { const { refId, hide, datasource, ...query } = t; - const ds = t.datasource || panelDatasource; + // Check if target datasource is empty object {} (no keys), treat it as missing + // and fall through to use panel datasource (matches backend behavior) + const targetDs = t.datasource; + const isEmptyDatasourceObject = targetDs && typeof targetDs === 'object' && Object.keys(targetDs).length === 0; + const ds = isEmptyDatasourceObject ? panelDatasource : targetDs || panelDatasource; const q: PanelQueryKind = { kind: 'PanelQuery', spec: { @@ -525,7 +529,8 @@ export function getPanelQueries(targets: DataQuery[], panelDatasource: DataSourc } export function buildPanelKind(p: Panel): PanelKind { - const queries = getPanelQueries((p.targets as unknown as DataQuery[]) || [], p.datasource ?? { type: '', uid: '' }); + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/consistent-type-assertions + const queries = getPanelQueries((p.targets as any) || [], p.datasource ?? { type: '', uid: '' }); const transformations = getPanelTransformations(p.transformations || []); @@ -541,10 +546,13 @@ export function buildPanelKind(p: Panel): PanelKind { } // match backend conversion behavior + // Only set first threshold step value to null if it's explicitly null or undefined + // Preserve 0 values (0 is falsy but should be kept as 0, not converted to null) if ( fieldConfig.defaults.thresholds?.steps && fieldConfig.defaults.thresholds.steps.length > 0 && - !fieldConfig.defaults.thresholds.steps[0]?.value + (fieldConfig.defaults.thresholds.steps[0]?.value === null || + fieldConfig.defaults.thresholds.steps[0]?.value === undefined) ) { fieldConfig.defaults.thresholds.steps[0]!.value = null; } From f4fbbcc4f41d201df33cd325c5355da9b27430ec Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Thu, 4 Dec 2025 09:36:03 +0100 Subject: [PATCH 015/110] Zanzana: Fix dashboard access evaluation in folders (#114718) * Zanzana: Fix dashboard access evaluation in folders * add negative test * Fix listing --- .../authz/zanzana/server/server_check.go | 22 +++++++++++++ .../authz/zanzana/server/server_check_test.go | 32 +++++++++++++++++++ .../authz/zanzana/server/server_list.go | 19 +++++++++++ .../authz/zanzana/server/server_list_test.go | 11 +++++++ .../authz/zanzana/server/server_test.go | 2 ++ 5 files changed, 86 insertions(+) diff --git a/pkg/services/authz/zanzana/server/server_check.go b/pkg/services/authz/zanzana/server/server_check.go index d8c1a2ec0c9..44d4ea28c4c 100644 --- a/pkg/services/authz/zanzana/server/server_check.go +++ b/pkg/services/authz/zanzana/server/server_check.go @@ -12,6 +12,7 @@ import ( "go.opentelemetry.io/otel/codes" "google.golang.org/protobuf/types/known/structpb" + dashboardV1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1" "github.com/grafana/grafana/pkg/services/authz/zanzana/common" ) @@ -148,6 +149,18 @@ func (s *Server) checkGeneric(ctx context.Context, subject, relation string, res folderRelation = common.SubresourceRelation(relation) ) + if isFolderPermissionBasedResource(resource.GroupResource()) { + // Check if resource inherits permissions from the folder (like dashboards in a folder) + res, err := s.openfgaCheck(ctx, store, subject, relation, folderIdent, contextuals, resourceCtx) + if err != nil { + return nil, err + } + + if res.GetAllowed() { + return &authzv1.CheckResponse{Allowed: res.GetAllowed()}, nil + } + } + if folderIdent != "" && common.IsSubresourceRelation(folderRelation) { // Check if subject has access as a sub resource for the folder res, err := s.openfgaCheck(ctx, store, subject, folderRelation, folderIdent, contextuals, resourceCtx) @@ -196,3 +209,12 @@ func (s *Server) openfgaCheck(ctx context.Context, store *storeInfo, subject, re return res, nil } + +var folderPermissionBasedResources = map[string]bool{ + // dashboard.grafana.app/dashboards + common.FormatGroupResource(dashboardV1.DashboardResourceInfo.GroupResource().Group, dashboardV1.DashboardResourceInfo.GroupResource().Resource, ""): true, +} + +func isFolderPermissionBasedResource(resource string) bool { + return folderPermissionBasedResources[resource] +} diff --git a/pkg/services/authz/zanzana/server/server_check_test.go b/pkg/services/authz/zanzana/server/server_check_test.go index 528eb483187..bd22a4d6d62 100644 --- a/pkg/services/authz/zanzana/server/server_check_test.go +++ b/pkg/services/authz/zanzana/server/server_check_test.go @@ -184,4 +184,36 @@ func testCheck(t *testing.T, server *Server) { require.NoError(t, err) assert.True(t, res.GetAllowed()) }) + + t.Run("user:17 should be able to view dashboards in folder 4 and all subfolders", func(t *testing.T) { + // Check for folders + res, err := server.Check(newContextWithNamespace(), newReq("user:17", utils.VerbGet, folderGroup, folderResource, "", "", "4")) + require.NoError(t, err) + assert.True(t, res.GetAllowed()) + + res, err = server.Check(newContextWithNamespace(), newReq("user:17", utils.VerbGet, folderGroup, folderResource, "", "", "5")) + require.NoError(t, err) + assert.True(t, res.GetAllowed()) + + res, err = server.Check(newContextWithNamespace(), newReq("user:17", utils.VerbGet, folderGroup, folderResource, "", "", "6")) + require.NoError(t, err) + assert.True(t, res.GetAllowed()) + + // Check for dashboards + res, err = server.Check(newContextWithNamespace(), newReq("user:17", utils.VerbGet, dashboardGroup, dashboardResource, "", "4", "1")) + require.NoError(t, err) + assert.True(t, res.GetAllowed(), "user should be able to view dashboards in folder 4") + + res, err = server.Check(newContextWithNamespace(), newReq("user:17", utils.VerbGet, dashboardGroup, dashboardResource, "", "5", "1")) + require.NoError(t, err) + assert.True(t, res.GetAllowed(), "user should be able to view dashboards in folder 5") + + res, err = server.Check(newContextWithNamespace(), newReq("user:17", utils.VerbGet, dashboardGroup, dashboardResource, "", "6", "1")) + require.NoError(t, err) + assert.True(t, res.GetAllowed(), "user should be able to view dashboards in folder 6") + + res, err = server.Check(newContextWithNamespace(), newReq("user:17", utils.VerbGet, "foo.grafana.app", "bar", "", "4", "1")) + require.NoError(t, err) + assert.False(t, res.GetAllowed(), "user should not be able to view other resources in folder 4") + }) } diff --git a/pkg/services/authz/zanzana/server/server_list.go b/pkg/services/authz/zanzana/server/server_list.go index 46f7a9167ad..216e8df933e 100644 --- a/pkg/services/authz/zanzana/server/server_list.go +++ b/pkg/services/authz/zanzana/server/server_list.go @@ -153,6 +153,25 @@ func (s *Server) listGeneric(ctx context.Context, subject, relation string, reso folders = res.GetObjects() } + // Special case for folder permission based resources (like dashboards in a folder) + if isFolderPermissionBasedResource(resource.GroupResource()) { + res, err := s.listObjects(ctx, &openfgav1.ListObjectsRequest{ + StoreId: store.ID, + AuthorizationModelId: store.ModelID, + Type: common.TypeFolder, + Relation: relation, + User: subject, + Context: resourceCtx, + ContextualTuples: contextuals, + }) + + if err != nil { + return nil, err + } + + folders = append(folders, res.GetObjects()...) + } + // 2. List all resource directly assigned to subject var objects []string if resource.IsValidRelation(relation) { diff --git a/pkg/services/authz/zanzana/server/server_list_test.go b/pkg/services/authz/zanzana/server/server_list_test.go index 2979e4118d4..d105e9827d3 100644 --- a/pkg/services/authz/zanzana/server/server_list_test.go +++ b/pkg/services/authz/zanzana/server/server_list_test.go @@ -155,4 +155,15 @@ func testList(t *testing.T, server *Server) { assert.Contains(t, res.GetItems(), "1") }) + + t.Run("user:17 should be able to list all dashboards in folder 4 and all subfolders", func(t *testing.T) { + res, err := server.List(newContextWithNamespace(), newList("user:17", dashboardGroup, dashboardResource, "")) + require.NoError(t, err) + assert.Len(t, res.GetItems(), 0) + assert.Len(t, res.GetFolders(), 3) + + assert.Contains(t, res.GetFolders(), "4") + assert.Contains(t, res.GetFolders(), "5") + assert.Contains(t, res.GetFolders(), "6") + }) } diff --git a/pkg/services/authz/zanzana/server/server_test.go b/pkg/services/authz/zanzana/server/server_test.go index 14e8f629729..63cf8ee2a50 100644 --- a/pkg/services/authz/zanzana/server/server_test.go +++ b/pkg/services/authz/zanzana/server/server_test.go @@ -57,6 +57,7 @@ func setup(t *testing.T, srv *Server) *Server { common.NewFolderResourceTuple("user:5", common.RelationSetEdit, dashboardGroup, dashboardResource, "", "1"), common.NewFolderTuple("user:6", common.RelationGet, "1"), common.NewGroupResourceTuple("user:7", common.RelationGet, folderGroup, folderResource, ""), + // folder-4 -> folder-5 -> folder-6 common.NewFolderParentTuple("5", "4"), common.NewFolderParentTuple("6", "5"), common.NewFolderResourceTuple("user:8", common.RelationSetEdit, dashboardGroup, dashboardResource, "", "5"), @@ -69,6 +70,7 @@ func setup(t *testing.T, srv *Server) *Server { common.NewTypedResourceTuple("user:14", common.RelationGet, common.TypeTeam, teamGroup, teamResource, statusSubresource, "1"), common.NewTypedResourceTuple("user:15", common.RelationGet, common.TypeUser, userGroup, userResource, statusSubresource, "1"), common.NewTypedResourceTuple("user:16", common.RelationGet, common.TypeServiceAccount, serviceAccountGroup, serviceAccountResource, statusSubresource, "1"), + common.NewFolderTuple("user:17", common.RelationSetView, "4"), } return setupOpenFGADatabase(t, srv, tuples) From 73b9a8c3af1b8742215829641d2e185e90a77755 Mon Sep 17 00:00:00 2001 From: Santiago Date: Thu, 4 Dec 2025 10:01:12 +0100 Subject: [PATCH 016/110] Alerting: Add datasource_uid query param to search for rules (#114697) * Alerting: Add datasource param to BE search * use array for param, datasource -> datasources * tests * remove comments * tests, short-circuit request if all data source names are invalid * rephrase comment * update some tests... * make linter happy * datasource_uid -> datasource_uids * added test * datasource_uids -> datasource_uid --------- Co-authored-by: Sonia Aguilar --- .../alerting/unified/api/prometheusApi.ts | 3 + .../rule-list/hooks/filterPredicates.ts | 2 +- .../rule-list/hooks/grafanaFilter.test.ts | 102 ++++++++++++++---- .../unified/rule-list/hooks/grafanaFilter.ts | 16 ++- .../hooks/useFilteredRulesIterator.ts | 7 +- .../rule-list/paginationLimits.test.ts | 22 ++-- 6 files changed, 117 insertions(+), 35 deletions(-) diff --git a/public/app/features/alerting/unified/api/prometheusApi.ts b/public/app/features/alerting/unified/api/prometheusApi.ts index a8e4279de7f..3c4fe219dd0 100644 --- a/public/app/features/alerting/unified/api/prometheusApi.ts +++ b/public/app/features/alerting/unified/api/prometheusApi.ts @@ -37,6 +37,7 @@ type PromRulesOptions = WithNotificationOptions<{ export type GrafanaPromRulesOptions = Omit & { folderUid?: string; dashboardUid?: string; + datasources?: string[]; panelId?: number; limitAlerts?: number; ruleLimit?: number; @@ -98,6 +99,7 @@ export const prometheusApi = alertingApi.injectEndpoints({ limitAlerts, groupNextToken, title, + datasources, searchGroupName, dashboardUid, }) => ({ @@ -114,6 +116,7 @@ export const prometheusApi = alertingApi.injectEndpoints({ rule_limit: ruleLimit?.toFixed(0), group_limit: groupLimit?.toFixed(0), group_next_token: groupNextToken, + datasource_uid: datasources, 'search.rule_name': title, 'search.rule_group': searchGroupName, dashboard_uid: dashboardUid, diff --git a/public/app/features/alerting/unified/rule-list/hooks/filterPredicates.ts b/public/app/features/alerting/unified/rule-list/hooks/filterPredicates.ts index d268a336f08..c9639f71030 100644 --- a/public/app/features/alerting/unified/rule-list/hooks/filterPredicates.ts +++ b/public/app/features/alerting/unified/rule-list/hooks/filterPredicates.ts @@ -259,7 +259,7 @@ function looseParseMatcher(matcherQuery: string): Matcher | undefined { } // Memoize the function to avoid calling getDatasourceAPIUid for the filter values multiple times -const mapDataSourceNamesToUids = memoize( +export const mapDataSourceNamesToUids = memoize( (names: string[]): string[] => { return names.map((name) => attempt(getDatasourceAPIUid, name)).filter(isString); }, diff --git a/public/app/features/alerting/unified/rule-list/hooks/grafanaFilter.test.ts b/public/app/features/alerting/unified/rule-list/hooks/grafanaFilter.test.ts index 6a714382a96..12a46a87bdd 100644 --- a/public/app/features/alerting/unified/rule-list/hooks/grafanaFilter.test.ts +++ b/public/app/features/alerting/unified/rule-list/hooks/grafanaFilter.test.ts @@ -257,12 +257,55 @@ describe('grafana-managed rules', () => { expect(backendFilter.health).toEqual([]); expect(backendFilter.contactPoint).toBeUndefined(); }); + + it('should not set hasInvalidDataSourceNames flag when no data source names are provided', () => { + const { hasInvalidDataSourceNames } = getGrafanaFilter(getFilter({})); + + expect(hasInvalidDataSourceNames).toBe(false); + }); }); describe('backend filtering with alertingUIUseBackendFilters feature toggle', () => { describe('when alertingUIUseBackendFilters is enabled', () => { testWithFeatureToggles({ enable: ['alertingUIUseBackendFilters'] }); + it('should include datasources in backend filter when valid data source names are provided', () => { + const { backendFilter, hasInvalidDataSourceNames } = getGrafanaFilter( + getFilter({ dataSourceNames: ['prometheus', 'loki'] }) + ); + + expect(backendFilter.datasources).toEqual(['datasource-uid-1', 'datasource-uid-3']); + expect(hasInvalidDataSourceNames).toBe(false); + }); + + it('should detect invalid data source names and set hasInvalidDataSourceNames flag', () => { + const { backendFilter, hasInvalidDataSourceNames } = getGrafanaFilter( + getFilter({ dataSourceNames: ['non-existent-datasource'] }) + ); + + expect(backendFilter.datasources).toEqual([]); + expect(hasInvalidDataSourceNames).toBe(true); + }); + + it('should include only valid datasource UIDs when some names are invalid', () => { + const { backendFilter, hasInvalidDataSourceNames } = getGrafanaFilter( + getFilter({ dataSourceNames: ['prometheus', 'non-existent-datasource'] }) + ); + + expect(backendFilter.datasources).toEqual(['datasource-uid-1']); + expect(hasInvalidDataSourceNames).toBe(false); // Not all are invalid + }); + + it('should skip dataSourceNames filtering on frontend when backend filtering is enabled', () => { + const rule = mockGrafanaPromAlertingRule({ + queriedDatasourceUIDs: ['datasource-uid-1'], + }); + + const { frontendFilter } = getGrafanaFilter(getFilter({ dataSourceNames: ['loki'] })); + // Should return true because dataSourceNames filter is null (handled by backend). + expect(frontendFilter.ruleMatches(rule)).toBe(true); + }); + it('should include title in backend filter when freeFormWords are provided', () => { const { backendFilter } = getGrafanaFilter(getFilter({ freeFormWords: ['cpu', 'usage'] })); @@ -556,25 +599,32 @@ describe('grafana-managed rules', () => { const { frontendFilter: groupNoMatch } = getGrafanaFilter(getFilter({ groupName: 'memory' })); expect(groupNoMatch.groupMatches(group)).toBe(false); - // Always-frontend filters (labels, dataSourceNames, namespace) should work + // Always-frontend filters (labels, namespace) should work. const { frontendFilter: labelsMatch } = getGrafanaFilter(getFilter({ labels: ['severity=critical'] })); expect(labelsMatch.ruleMatches(alertingRule)).toBe(true); const { frontendFilter: labelsNoMatch } = getGrafanaFilter(getFilter({ labels: ['severity=warning'] })); expect(labelsNoMatch.ruleMatches(alertingRule)).toBe(false); - const { frontendFilter: dsMatch } = getGrafanaFilter(getFilter({ dataSourceNames: ['prometheus'] })); - expect(dsMatch.ruleMatches(alertingRule)).toBe(true); - - const { frontendFilter: dsNoMatch } = getGrafanaFilter(getFilter({ dataSourceNames: ['loki'] })); - expect(dsNoMatch.ruleMatches(alertingRule)).toBe(false); - const { frontendFilter: nsMatch } = getGrafanaFilter(getFilter({ namespace: 'production' })); expect(nsMatch.groupMatches(group)).toBe(true); const { frontendFilter: nsNoMatch } = getGrafanaFilter(getFilter({ namespace: 'staging' })); expect(nsNoMatch.groupMatches(group)).toBe(false); }); + + it('should skip dataSourceNames filtering on frontend (handled by backend)', () => { + const alertingRule = mockGrafanaPromAlertingRule({ + queriedDatasourceUIDs: ['datasource-uid-1'], + }); + + // DataSourceNames is backend-filtered when feature toggle is enabled. + const { frontendFilter: dsMatch } = getGrafanaFilter(getFilter({ dataSourceNames: ['prometheus'] })); + expect(dsMatch.ruleMatches(alertingRule)).toBe(true); + + const { frontendFilter: dsNoMatch } = getGrafanaFilter(getFilter({ dataSourceNames: ['loki'] })); + expect(dsNoMatch.ruleMatches(alertingRule)).toBe(true); + }); }); describe('when both alertingUIUseBackendFilters and alertingUIUseFullyCompatBackendFilters are enabled', () => { @@ -631,11 +681,10 @@ describe('grafana-managed rules', () => { expect(frontendFilter.groupMatches(group)).toBe(true); }); - it('should still apply always-frontend filters (labels, dataSourceNames, namespace)', () => { + it('should still apply always-frontend filters (labels, namespace)', () => { const rule = mockGrafanaPromAlertingRule({ name: 'High CPU Usage', labels: { severity: 'critical' }, - queriedDatasourceUIDs: ['datasource-uid-1'], alerts: [], }); @@ -646,13 +695,6 @@ describe('grafana-managed rules', () => { const { frontendFilter: labelFilter2 } = getGrafanaFilter(getFilter({ labels: ['severity=critical'] })); expect(labelFilter2.ruleMatches(rule)).toBe(true); - // DataSourceNames filter should still work - const { frontendFilter: dsFilter } = getGrafanaFilter(getFilter({ dataSourceNames: ['prometheus'] })); - expect(dsFilter.ruleMatches(rule)).toBe(true); - - const { frontendFilter: dsFilter2 } = getGrafanaFilter(getFilter({ dataSourceNames: ['loki'] })); - expect(dsFilter2.ruleMatches(rule)).toBe(false); - // Namespace filter should still work const group: PromRuleGroupDTO = { name: 'Test Group', @@ -667,6 +709,19 @@ describe('grafana-managed rules', () => { const { frontendFilter: nsFilter2 } = getGrafanaFilter(getFilter({ namespace: 'staging' })); expect(nsFilter2.groupMatches(group)).toBe(false); }); + + it('should skip dataSourceNames filtering on frontend (handled by backend)', () => { + const rule = mockGrafanaPromAlertingRule({ + queriedDatasourceUIDs: ['datasource-uid-1'], + }); + + // DataSourceNames is backend-filtered when both feature toggles are enabled. + const { frontendFilter: dsFilter } = getGrafanaFilter(getFilter({ dataSourceNames: ['prometheus'] })); + expect(dsFilter.ruleMatches(rule)).toBe(true); + + const { frontendFilter: dsFilter2 } = getGrafanaFilter(getFilter({ dataSourceNames: ['loki'] })); + expect(dsFilter2.ruleMatches(rule)).toBe(true); + }); }); }); @@ -732,9 +787,12 @@ describe('grafana-managed rules', () => { expect(hasGrafanaClientSideFilters(getFilter({ groupName: 'test-group' }))).toBe(false); }); + it('should return false for dataSourceNames (handled by backend when feature toggle is enabled)', () => { + expect(hasGrafanaClientSideFilters(getFilter({ dataSourceNames: ['prometheus'] }))).toBe(false); + }); + it('should return true for client-side only filters', () => { expect(hasGrafanaClientSideFilters(getFilter({ namespace: 'production' }))).toBe(true); - expect(hasGrafanaClientSideFilters(getFilter({ dataSourceNames: ['prometheus'] }))).toBe(true); expect(hasGrafanaClientSideFilters(getFilter({ labels: ['severity=critical'] }))).toBe(true); }); @@ -749,10 +807,11 @@ describe('grafana-managed rules', () => { testWithFeatureToggles({ enable: ['alertingUIUseFullyCompatBackendFilters'] }); it('should return correct values for all filter types', () => { - // Should return false for: empty, backend-handled (ruleType, dashboardUid), and backend-only filters + // Should return false for: empty, backend-handled (ruleType, dashboardUid, dataSourceNames), and backend-only filters expect(hasGrafanaClientSideFilters(getFilter({}))).toBe(false); expect(hasGrafanaClientSideFilters(getFilter({ ruleType: PromRuleType.Alerting }))).toBe(false); expect(hasGrafanaClientSideFilters(getFilter({ dashboardUid: 'test-dashboard' }))).toBe(false); + expect(hasGrafanaClientSideFilters(getFilter({ dataSourceNames: ['prometheus'] }))).toBe(false); expect(hasGrafanaClientSideFilters(getFilter({ ruleState: PromAlertingRuleState.Firing }))).toBe(false); expect(hasGrafanaClientSideFilters(getFilter({ ruleHealth: RuleHealth.Ok }))).toBe(false); expect(hasGrafanaClientSideFilters(getFilter({ contactPoint: 'my-contact-point' }))).toBe(false); @@ -762,7 +821,6 @@ describe('grafana-managed rules', () => { expect(hasGrafanaClientSideFilters(getFilter({ ruleName: 'alert' }))).toBe(true); expect(hasGrafanaClientSideFilters(getFilter({ groupName: 'test-group' }))).toBe(true); expect(hasGrafanaClientSideFilters(getFilter({ namespace: 'production' }))).toBe(true); - expect(hasGrafanaClientSideFilters(getFilter({ dataSourceNames: ['prometheus'] }))).toBe(true); expect(hasGrafanaClientSideFilters(getFilter({ labels: ['severity=critical'] }))).toBe(true); }); }); @@ -782,10 +840,12 @@ describe('grafana-managed rules', () => { expect(hasGrafanaClientSideFilters(getFilter({ ruleHealth: RuleHealth.Ok }))).toBe(false); expect(hasGrafanaClientSideFilters(getFilter({ contactPoint: 'my-contact-point' }))).toBe(false); - // Should return true for: always-frontend filters only + // Should return true for: always-frontend filters only (namespace, labels) expect(hasGrafanaClientSideFilters(getFilter({ namespace: 'production' }))).toBe(true); - expect(hasGrafanaClientSideFilters(getFilter({ dataSourceNames: ['prometheus'] }))).toBe(true); expect(hasGrafanaClientSideFilters(getFilter({ labels: ['severity=critical'] }))).toBe(true); + + // Should return false for: backend-handled dataSourceNames when feature toggles are enabled + expect(hasGrafanaClientSideFilters(getFilter({ dataSourceNames: ['prometheus'] }))).toBe(false); }); }); }); diff --git a/public/app/features/alerting/unified/rule-list/hooks/grafanaFilter.ts b/public/app/features/alerting/unified/rule-list/hooks/grafanaFilter.ts index 0cc89ceafcf..c0fd9fea4d8 100644 --- a/public/app/features/alerting/unified/rule-list/hooks/grafanaFilter.ts +++ b/public/app/features/alerting/unified/rule-list/hooks/grafanaFilter.ts @@ -14,6 +14,7 @@ import { groupMatches, groupNameFilter, labelsFilter, + mapDataSourceNamesToUids, namespaceFilter, pluginsFilter, ruleMatches, @@ -63,6 +64,17 @@ export function getGrafanaFilter(filterState: Partial) { // Build title search for backend filtering const titleSearch = buildTitleSearch(normalizedFilterState); + // Check if data source names were provided but none are valid. + let hasInvalidDataSourceNames = false; + let datasourceUids: string[] | undefined = undefined; + + // Only map datasources if data source filter should be applied on backend (when ruleFilterConfig.dataSourceNames is null). + if (ruleFilterConfig.dataSourceNames === null && normalizedFilterState.dataSourceNames.length > 0) { + datasourceUids = mapDataSourceNamesToUids(normalizedFilterState.dataSourceNames); + // If names were provided but no valid UIDs were found, all names are invalid. + hasInvalidDataSourceNames = datasourceUids.length === 0; + } + const backendFilter: GrafanaPromRulesOptions = { state: normalizedFilterState.ruleState ? [normalizedFilterState.ruleState] : [], health: normalizedFilterState.ruleHealth ? [normalizedFilterState.ruleHealth] : [], @@ -72,6 +84,7 @@ export function getGrafanaFilter(filterState: Partial) { type: ruleFilterConfig.ruleType ? undefined : normalizedFilterState.ruleType, dashboardUid: ruleFilterConfig.dashboardUid ? undefined : normalizedFilterState.dashboardUid, searchGroupName: groupFilterConfig.groupName ? undefined : normalizedFilterState.groupName, + datasources: ruleFilterConfig.dataSourceNames ? undefined : datasourceUids, }; return { @@ -80,6 +93,7 @@ export function getGrafanaFilter(filterState: Partial) { groupMatches: (group: PromRuleGroupDTO) => groupMatches(group, normalizedFilterState, groupFilterConfig), ruleMatches: (rule: PromRuleDTO) => ruleMatches(rule, normalizedFilterState, ruleFilterConfig), }, + hasInvalidDataSourceNames, }; } @@ -100,7 +114,7 @@ function buildGrafanaFilterConfigs() { ruleName: useBackendFilters ? null : ruleNameFilter, ruleState: null, ruleType: useBackendFilters || useFullyCompatibleBackendFilters ? null : ruleTypeFilter, - dataSourceNames: dataSourceNamesFilter, + dataSourceNames: useBackendFilters || useFullyCompatibleBackendFilters ? null : dataSourceNamesFilter, labels: labelsFilter, ruleHealth: null, dashboardUid: useBackendFilters || useFullyCompatibleBackendFilters ? null : dashboardUidFilter, diff --git a/public/app/features/alerting/unified/rule-list/hooks/useFilteredRulesIterator.ts b/public/app/features/alerting/unified/rule-list/hooks/useFilteredRulesIterator.ts index 7c5533beea5..5202c580542 100644 --- a/public/app/features/alerting/unified/rule-list/hooks/useFilteredRulesIterator.ts +++ b/public/app/features/alerting/unified/rule-list/hooks/useFilteredRulesIterator.ts @@ -84,7 +84,12 @@ export function useFilteredRulesIteratorProvider() { const hasDataSourceFilterActive = Boolean(filterState.dataSourceNames.length); - const { backendFilter, frontendFilter } = getGrafanaFilter(filterState); + const { backendFilter, frontendFilter, hasInvalidDataSourceNames } = getGrafanaFilter(filterState); + + // Short-circuit: if all provided data source names are invalid, return empty results (no rules can match). + if (hasInvalidDataSourceNames) { + return { iterable: empty(), abortController }; + } const grafanaRulesGenerator: AsyncIterableX = from( grafanaGroupsGenerator(options.grafanaManagedLimit, backendFilter) diff --git a/public/app/features/alerting/unified/rule-list/paginationLimits.test.ts b/public/app/features/alerting/unified/rule-list/paginationLimits.test.ts index 5ef8431aced..2e1db699883 100644 --- a/public/app/features/alerting/unified/rule-list/paginationLimits.test.ts +++ b/public/app/features/alerting/unified/rule-list/paginationLimits.test.ts @@ -73,6 +73,7 @@ describe('paginationLimits', () => { { ruleState: PromAlertingRuleState.Firing }, { ruleHealth: RuleHealth.Ok }, { contactPoint: 'slack' }, + { dataSourceNames: ['prometheus'] }, ])( 'should return rule limit for grafana + large limit for datasource when only backend filters are used: %p', (filterState) => { @@ -85,7 +86,6 @@ describe('paginationLimits', () => { it.each>([ { namespace: 'production' }, - { dataSourceNames: ['prometheus'] }, { labels: ['severity=critical'] }, { ruleState: PromAlertingRuleState.Firing, namespace: 'production' }, ])('should return large limits for both when frontend filters are used: %p', (filterState) => { @@ -112,6 +112,7 @@ describe('paginationLimits', () => { { ruleState: PromAlertingRuleState.Firing }, { ruleHealth: RuleHealth.Ok }, { contactPoint: 'slack' }, + { dataSourceNames: ['prometheus'] }, ])( 'should return rule limit for grafana + large limit for datasource when only backend filters are used: %p', (filterState) => { @@ -127,7 +128,6 @@ describe('paginationLimits', () => { { ruleName: 'alert' }, { groupName: 'test-group' }, { namespace: 'production' }, - { dataSourceNames: ['prometheus'] }, { labels: ['severity=critical'] }, ])('should return large limits for both when frontend filters are used: %p', (filterState) => { const { grafanaManagedLimit, datasourceManagedLimit } = getFilteredRulesLimits(getFilter(filterState)); @@ -156,6 +156,7 @@ describe('paginationLimits', () => { { ruleState: PromAlertingRuleState.Firing }, { ruleHealth: RuleHealth.Ok }, { contactPoint: 'slack' }, + { dataSourceNames: ['prometheus'] }, ])( 'should return rule limit for grafana + large limit for datasource when only backend filters are used: %p', (filterState) => { @@ -166,16 +167,15 @@ describe('paginationLimits', () => { } ); - it.each>([ - { namespace: 'production' }, - { dataSourceNames: ['prometheus'] }, - { labels: ['severity=critical'] }, - ])('should return large limits for both when frontend filters are used: %p', (filterState) => { - const { grafanaManagedLimit, datasourceManagedLimit } = getFilteredRulesLimits(getFilter(filterState)); + it.each>([{ namespace: 'production' }, { labels: ['severity=critical'] }])( + 'should return large limits for both when frontend filters are used: %p', + (filterState) => { + const { grafanaManagedLimit, datasourceManagedLimit } = getFilteredRulesLimits(getFilter(filterState)); - expect(grafanaManagedLimit).toEqual({ groupLimit: FILTERED_GROUPS_LARGE_API_PAGE_SIZE }); - expect(datasourceManagedLimit).toEqual({ groupLimit: FILTERED_GROUPS_LARGE_API_PAGE_SIZE }); - }); + expect(grafanaManagedLimit).toEqual({ groupLimit: FILTERED_GROUPS_LARGE_API_PAGE_SIZE }); + expect(datasourceManagedLimit).toEqual({ groupLimit: FILTERED_GROUPS_LARGE_API_PAGE_SIZE }); + } + ); }); }); }); From ed91ada3c0add49b517e68691fd43350c3e692df Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Thu, 4 Dec 2025 11:27:59 +0100 Subject: [PATCH 017/110] Zanzana: Allow resources to derive permissions from folders by default (#114820) --- pkg/services/authz/zanzana/server/server_check.go | 10 ++++------ pkg/services/authz/zanzana/server/server_check_test.go | 4 ---- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/pkg/services/authz/zanzana/server/server_check.go b/pkg/services/authz/zanzana/server/server_check.go index 44d4ea28c4c..916c84e5c0a 100644 --- a/pkg/services/authz/zanzana/server/server_check.go +++ b/pkg/services/authz/zanzana/server/server_check.go @@ -12,7 +12,6 @@ import ( "go.opentelemetry.io/otel/codes" "google.golang.org/protobuf/types/known/structpb" - dashboardV1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1" "github.com/grafana/grafana/pkg/services/authz/zanzana/common" ) @@ -149,7 +148,7 @@ func (s *Server) checkGeneric(ctx context.Context, subject, relation string, res folderRelation = common.SubresourceRelation(relation) ) - if isFolderPermissionBasedResource(resource.GroupResource()) { + if folderIdent != "" && isFolderPermissionBasedResource(resource.GroupResource()) { // Check if resource inherits permissions from the folder (like dashboards in a folder) res, err := s.openfgaCheck(ctx, store, subject, relation, folderIdent, contextuals, resourceCtx) if err != nil { @@ -210,11 +209,10 @@ func (s *Server) openfgaCheck(ctx context.Context, store *storeInfo, subject, re return res, nil } -var folderPermissionBasedResources = map[string]bool{ - // dashboard.grafana.app/dashboards - common.FormatGroupResource(dashboardV1.DashboardResourceInfo.GroupResource().Group, dashboardV1.DashboardResourceInfo.GroupResource().Resource, ""): true, +var folderPermissionBasedResourceExceptions = map[string]bool{ + // allow all resources to inherit permissions from the folder } func isFolderPermissionBasedResource(resource string) bool { - return folderPermissionBasedResources[resource] + return !folderPermissionBasedResourceExceptions[resource] } diff --git a/pkg/services/authz/zanzana/server/server_check_test.go b/pkg/services/authz/zanzana/server/server_check_test.go index bd22a4d6d62..59a192fe6a0 100644 --- a/pkg/services/authz/zanzana/server/server_check_test.go +++ b/pkg/services/authz/zanzana/server/server_check_test.go @@ -211,9 +211,5 @@ func testCheck(t *testing.T, server *Server) { res, err = server.Check(newContextWithNamespace(), newReq("user:17", utils.VerbGet, dashboardGroup, dashboardResource, "", "6", "1")) require.NoError(t, err) assert.True(t, res.GetAllowed(), "user should be able to view dashboards in folder 6") - - res, err = server.Check(newContextWithNamespace(), newReq("user:17", utils.VerbGet, "foo.grafana.app", "bar", "", "4", "1")) - require.NoError(t, err) - assert.False(t, res.GetAllowed(), "user should not be able to view other resources in folder 4") }) } From 5c49dbf4c475b6b44515af99426ada7498ba1a54 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Thu, 4 Dec 2025 11:28:09 +0100 Subject: [PATCH 018/110] Zanzana: Non-blocking shadow compile (#114774) --- .../authz/zanzana/client/shadow_client.go | 31 +++++++++++++------ 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/pkg/services/authz/zanzana/client/shadow_client.go b/pkg/services/authz/zanzana/client/shadow_client.go index 93fbfd10b4d..a7946b53864 100644 --- a/pkg/services/authz/zanzana/client/shadow_client.go +++ b/pkg/services/authz/zanzana/client/shadow_client.go @@ -2,6 +2,7 @@ package client import ( "context" + "sync" "github.com/prometheus/client_golang/prometheus" @@ -71,6 +72,9 @@ func (c *ShadowClient) Check(ctx context.Context, id authlib.AuthInfo, req authl func (c *ShadowClient) Compile(ctx context.Context, id authlib.AuthInfo, req authlib.ListRequest) (authlib.ItemChecker, authlib.Zookie, error) { zanzanaItemCheckerChan := make(chan authlib.ItemChecker, 1) + var zanzanaItemChecker authlib.ItemChecker + var once sync.Once + go func() { if c.zanzanaClient == nil { zanzanaItemCheckerChan <- nil @@ -93,19 +97,26 @@ func (c *ShadowClient) Compile(ctx context.Context, id authlib.AuthInfo, req aut return nil, authlib.NoopZookie{}, err } - zanzanaItemChecker := <-zanzanaItemCheckerChan - shadowItemChecker := func(name, folder string) bool { rbacRes := rbacItemChecker(name, folder) - if zanzanaItemChecker != nil { - zanzanaRes := zanzanaItemChecker(name, folder) - if zanzanaRes != rbacRes { - c.metrics.evaluationStatusTotal.WithLabelValues("error").Inc() - c.logger.Warn("Zanzana compile result does not match", "expected", rbacRes, "actual", zanzanaRes, "name", name, "folder", folder) - } else { - c.metrics.evaluationStatusTotal.WithLabelValues("success").Inc() + + go func() { + // Wait for zanzana result to be ready and then use it to compare against RBAC + once.Do(func() { + zanzanaItemChecker = <-zanzanaItemCheckerChan + }) + + if zanzanaItemChecker != nil { + zanzanaRes := zanzanaItemChecker(name, folder) + if zanzanaRes != rbacRes { + c.metrics.evaluationStatusTotal.WithLabelValues("error").Inc() + c.logger.Warn("Zanzana compile result does not match", "expected", rbacRes, "actual", zanzanaRes, "name", name, "folder", folder) + } else { + c.metrics.evaluationStatusTotal.WithLabelValues("success").Inc() + } } - } + }() + return rbacRes } From 7f4a94a6bd4f044cf38196073611678c2a957ba4 Mon Sep 17 00:00:00 2001 From: Samarth Bagga Date: Thu, 4 Dec 2025 16:30:44 +0530 Subject: [PATCH 019/110] Explore: Use new Table component (#111463) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update TableContainer.tsx Use PanelRenderer in TableContainer instead of Table * Passing OnCellFilterAdded * Fix lint * Fix * Update tests * Update tests --------- Co-authored-by: Piotr Jamróz --- public/app/features/explore/Explore.tsx | 3 +- .../explore/Table/TableContainer.test.tsx | 49 ++----------------- .../features/explore/Table/TableContainer.tsx | 49 ++++++++++++++----- 3 files changed, 44 insertions(+), 57 deletions(-) diff --git a/public/app/features/explore/Explore.tsx b/public/app/features/explore/Explore.tsx index 39c5b09dc6c..9aa7bc1904b 100644 --- a/public/app/features/explore/Explore.tsx +++ b/public/app/features/explore/Explore.tsx @@ -410,7 +410,7 @@ export class Explore extends PureComponent { } renderTablePanel(width: number) { - const { exploreId, timeZone } = this.props; + const { exploreId, timeZone, eventBus } = this.props; return ( { onCellFilterAdded={this.onCellFilterAdded} timeZone={timeZone} splitOpenFn={this.onSplitOpen('table')} + eventBus={eventBus} /> ); diff --git a/public/app/features/explore/Table/TableContainer.test.tsx b/public/app/features/explore/Table/TableContainer.test.tsx index af987261ead..bb579840017 100644 --- a/public/app/features/explore/Table/TableContainer.test.tsx +++ b/public/app/features/explore/Table/TableContainer.test.tsx @@ -1,22 +1,11 @@ -import { render, screen, within } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; import { DataFrame, FieldType, getDefaultTimeRange, InternalTimeZones, toDataFrame } from '@grafana/data'; import { TableContainerWithTheme } from './TableContainer'; -function getTables(): HTMLElement[] { - return screen.getAllByRole('table'); -} - -function getRowsData(rows: HTMLElement[]): Object[] { - let content = []; - for (let i = 1; i < rows.length; i++) { - content.push({ - time: within(rows[i]).getByText(/2021*/).textContent, - text: within(rows[i]).getByText(/test_string_*/).textContent, - }); - } - return content; +function getPanels(): HTMLElement[] { + return screen.getAllByText(/PanelRenderer/); } const dataFrame = toDataFrame({ @@ -60,17 +49,9 @@ describe('TableContainerWithTheme', () => { describe('With one main frame', () => { it('should render component', () => { render(); - const tables = getTables(); + const tables = getPanels(); expect(tables.length).toBe(1); expect(tables[0]).toBeInTheDocument(); - const rows = within(tables[0]).getAllByRole('row'); - expect(rows).toHaveLength(5); - expect(getRowsData(rows)).toEqual([ - { time: '2021-01-01 00:00:00', text: 'test_string_1' }, - { time: '2021-01-01 03:00:00', text: 'test_string_2' }, - { time: '2021-01-01 01:00:00', text: 'test_string_3' }, - { time: '2021-01-01 02:00:00', text: 'test_string_4' }, - ]); }); it('should render 0 series returned on no items', () => { @@ -85,26 +66,6 @@ describe('TableContainerWithTheme', () => { expect(screen.getByText('0 series returned')).toBeInTheDocument(); }); - it('should update time when timezone changes', () => { - const { rerender } = render(); - const rowsBeforeChange = within(getTables()[0]).getAllByRole('row'); - expect(getRowsData(rowsBeforeChange)).toEqual([ - { time: '2021-01-01 00:00:00', text: 'test_string_1' }, - { time: '2021-01-01 03:00:00', text: 'test_string_2' }, - { time: '2021-01-01 01:00:00', text: 'test_string_3' }, - { time: '2021-01-01 02:00:00', text: 'test_string_4' }, - ]); - - rerender(); - const rowsAfterChange = within(getTables()[0]).getAllByRole('row'); - expect(getRowsData(rowsAfterChange)).toEqual([ - { time: '2020-12-31 19:00:00', text: 'test_string_1' }, - { time: '2020-12-31 22:00:00', text: 'test_string_2' }, - { time: '2020-12-31 20:00:00', text: 'test_string_3' }, - { time: '2020-12-31 21:00:00', text: 'test_string_4' }, - ]); - }); - it('should render table title with Prometheus query', () => { const dataFrames = [{ ...dataFrame, name: 'metric{label="value"}' }]; const tableProps = { ...defaultProps, tableResult: dataFrames }; @@ -118,7 +79,7 @@ describe('TableContainerWithTheme', () => { const dataFrames = [dataFrame, dataFrame]; const multiDefaultProps = { ...defaultProps, tableResult: dataFrames }; render(); - const tables = getTables(); + const tables = getPanels(); expect(tables.length).toBe(2); expect(tables[0]).toBeInTheDocument(); expect(tables[1]).toBeInTheDocument(); diff --git a/public/app/features/explore/Table/TableContainer.tsx b/public/app/features/explore/Table/TableContainer.tsx index b0a203bd22a..2c1614e539d 100644 --- a/public/app/features/explore/Table/TableContainer.tsx +++ b/public/app/features/explore/Table/TableContainer.tsx @@ -2,11 +2,20 @@ import { css } from '@emotion/css'; import { PureComponent } from 'react'; import { connect, ConnectedProps } from 'react-redux'; -import { applyFieldOverrides, SplitOpen, DataFrame, LoadingState, FieldType } from '@grafana/data'; +import { + applyFieldOverrides, + SplitOpen, + DataFrame, + LoadingState, + FieldType, + DataLinksContext, + EventBus, + EventBusSrv, +} from '@grafana/data'; import { Trans, t } from '@grafana/i18n'; -import { getTemplateSrv } from '@grafana/runtime'; +import { getTemplateSrv, PanelRenderer } from '@grafana/runtime'; import { TimeZone } from '@grafana/schema'; -import { Table, AdHocFilterItem, PanelChrome, withTheme2, Themeable2 } from '@grafana/ui'; +import { AdHocFilterItem, PanelChrome, withTheme2, Themeable2, PanelContextProvider } from '@grafana/ui'; import { config } from 'app/core/config'; import { hasDeprecatedParentRowIndex, @@ -23,12 +32,13 @@ import { exploreDataLinkPostProcessorFactory } from '../utils/links'; const MAX_NUMBER_OF_COLUMNS = 20; interface TableContainerProps extends Themeable2 { - ariaLabel?: string; exploreId: string; width: number; timeZone: TimeZone; onCellFilterAdded?: (filter: AdHocFilterItem) => void; splitOpenFn: SplitOpen; + eventBus?: EventBus; + ariaLabel?: string; } function mapStateToProps(state: StoreState, { exploreId }: TableContainerProps) { @@ -79,7 +89,7 @@ export class TableContainer extends PureComponent { } render() { - const { loading, onCellFilterAdded, tableResult, width, splitOpenFn, range, ariaLabel, timeZone, theme } = + const { loading, onCellFilterAdded, tableResult, width, splitOpenFn, range, timeZone, theme, eventBus } = this.props; const { showAll } = this.state; @@ -153,13 +163,28 @@ export class TableContainer extends PureComponent { loadingState={loading ? LoadingState.Loading : undefined} > {(innerWidth, innerHeight) => ( -
+ + + + + )} ))} From 45cc410ab878837ce39d6a5d22c0719098131170 Mon Sep 17 00:00:00 2001 From: David Harris Date: Thu, 4 Dec 2025 11:08:57 +0000 Subject: [PATCH 020/110] feat: add new feature highlights for tempo and pyroscope (#114761) * add new ds feature highlights * fix translation * fix translation again --- .../datasources/components/CloudInfoBox.tsx | 23 +++++++++++-------- public/locales/en-US/grafana.json | 4 ++-- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/public/app/features/datasources/components/CloudInfoBox.tsx b/public/app/features/datasources/components/CloudInfoBox.tsx index 9982973f4d4..3fa29d4a7bc 100644 --- a/public/app/features/datasources/components/CloudInfoBox.tsx +++ b/public/app/features/datasources/components/CloudInfoBox.tsx @@ -10,8 +10,7 @@ export interface Props { } export function CloudInfoBox({ dataSource }: Props) { - let mainDS = ''; - let extraDS = ''; + let dsName = ''; // don't show for already configured data sources or provisioned data sources if (dataSource.readOnly || (dataSource.version ?? 0) > 2) { @@ -25,12 +24,16 @@ export function CloudInfoBox({ dataSource }: Props) { switch (dataSource.type) { case 'prometheus': - mainDS = 'Prometheus'; - extraDS = 'Loki'; + dsName = 'Prometheus'; break; case 'loki': - mainDS = 'Loki'; - extraDS = 'Prometheus'; + dsName = 'Loki'; + break; + case 'tempo': + dsName = 'Tempo'; + break; + case 'grafana-pyroscope-datasource': + dsName = 'Pyroscope'; break; default: return null; @@ -44,8 +47,8 @@ export function CloudInfoBox({ dataSource }: Props) { } return ( - Or skip the effort and get {{ mainDS }} (and {{ extraDS }}) as fully-managed, scalable, and hosted data - sources from Grafana Labs with the{' '} + Or skip the effort and get {{ dsName }} as fully-managed, scalable, and hosted data source from Grafana + Labs with the{' '} free-forever Grafana Cloud plan.", - "title-alert": "Configure your {{mainDS}} data source below" + "body-alert": "Or skip the effort and get {{dsName}} as fully-managed, scalable, and hosted data source from Grafana Labs with the <4>free-forever Grafana Cloud plan.", + "title-alert": "Configure your {{dsName}} data source below" }, "dashboards-table": { "aria-label-delete-dashboard": "Delete dashboard", From b71ed229f15fb6a0d0d55c23e71466714da77229 Mon Sep 17 00:00:00 2001 From: Steve Simpson Date: Thu, 4 Dec 2025 12:52:24 +0100 Subject: [PATCH 021/110] Alerting: Minor refactor to historan app RegisterAppInstaller. (#114828) Splits the function in two so that NewAppInstaller can be used stanalone. --- pkg/registry/apps/alerting/historian/register.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pkg/registry/apps/alerting/historian/register.go b/pkg/registry/apps/alerting/historian/register.go index 78312c326aa..6125469ce10 100644 --- a/pkg/registry/apps/alerting/historian/register.go +++ b/pkg/registry/apps/alerting/historian/register.go @@ -26,7 +26,6 @@ func RegisterAppInstaller( cfg *setting.Cfg, ng *ngalert.AlertNG, ) (*AlertingHistorianAppInstaller, error) { - installer := &AlertingHistorianAppInstaller{} appSpecificConfig := historianAppConfig.RuntimeConfig{} // If we're provided an AlertNG, then call back into that for things we need. @@ -43,6 +42,12 @@ func RegisterAppInstaller( appSpecificConfig.GetAlertStateHistoryHandler = handlers.GetAlertStateHistoryHandler } + return NewAppInstaller(appSpecificConfig) +} + +func NewAppInstaller(appSpecificConfig historianAppConfig.RuntimeConfig) (*AlertingHistorianAppInstaller, error) { + installer := &AlertingHistorianAppInstaller{} + provider := simple.NewAppProvider(apis.LocalManifest(), appSpecificConfig, historianApp.New) appConfig := app.Config{ From 94b7d6f7b8fd660ee30e05fd0e2cf980e5c81cd0 Mon Sep 17 00:00:00 2001 From: Andreas Christou Date: Thu, 4 Dec 2025 13:22:23 +0100 Subject: [PATCH 022/110] Azure: Include aggregate columns in logs builder (#114684) * Include groupBy and aggregate columns * Order by tests --- .../LogsQueryBuilder/OrderBySection.test.tsx | 373 ++++++++++++++++++ .../LogsQueryBuilder/OrderBySection.tsx | 37 +- 2 files changed, 401 insertions(+), 9 deletions(-) create mode 100644 public/app/plugins/datasource/azuremonitor/components/LogsQueryBuilder/OrderBySection.test.tsx diff --git a/public/app/plugins/datasource/azuremonitor/components/LogsQueryBuilder/OrderBySection.test.tsx b/public/app/plugins/datasource/azuremonitor/components/LogsQueryBuilder/OrderBySection.test.tsx new file mode 100644 index 00000000000..471470aed86 --- /dev/null +++ b/public/app/plugins/datasource/azuremonitor/components/LogsQueryBuilder/OrderBySection.test.tsx @@ -0,0 +1,373 @@ +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; + +import { + AzureQueryType, + BuilderQueryEditorExpressionType, + BuilderQueryEditorOrderByExpression, + BuilderQueryEditorOrderByOptions, + BuilderQueryEditorPropertyType, +} from '../../dataquery.gen'; +import { AzureMonitorQuery } from '../../types/query'; + +import { OrderBySection } from './OrderBySection'; + +describe('OrderBySection', () => { + const mockAllColumns = [ + { name: 'TimeGenerated', type: 'datetime' }, + { name: 'Level', type: 'string' }, + { name: 'Count', type: 'int' }, + { name: 'Duration', type: 'real' }, + ]; + + const createMockQuery = (orderBy?: BuilderQueryEditorOrderByExpression[]): AzureMonitorQuery => ({ + refId: 'A', + queryType: AzureQueryType.LogAnalytics, + azureLogAnalytics: { + builderQuery: { + from: { + type: BuilderQueryEditorExpressionType.Property, + property: { type: BuilderQueryEditorPropertyType.String, name: 'AppRequests' }, + }, + columns: { + type: BuilderQueryEditorExpressionType.Property, + columns: ['TimeGenerated', 'Level', 'Count'], + }, + orderBy: { + type: BuilderQueryEditorExpressionType.Order_by, + expressions: orderBy || [], + }, + reduce: { + type: BuilderQueryEditorExpressionType.Reduce, + expressions: [], + }, + groupBy: { + type: BuilderQueryEditorExpressionType.Group_by, + expressions: [], + }, + where: { + type: BuilderQueryEditorExpressionType.And, + expressions: [], + }, + }, + }, + }); + + const defaultProps = { + query: createMockQuery(), + allColumns: mockAllColumns, + buildAndUpdateQuery: jest.fn(), + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('renders the order by section', () => { + render(); + expect(screen.getByText('Order By')).toBeInTheDocument(); + }); + + it('renders add button when no order by exists', () => { + render(); + const addButton = screen.getByLabelText('Add order by'); + expect(addButton).toBeInTheDocument(); + }); + + it('renders existing order by expressions', () => { + const existingOrderBy: BuilderQueryEditorOrderByExpression[] = [ + { + property: { name: 'TimeGenerated', type: BuilderQueryEditorPropertyType.String }, + order: BuilderQueryEditorOrderByOptions.Desc, + type: BuilderQueryEditorExpressionType.Order_by, + }, + { + property: { name: 'Level', type: BuilderQueryEditorPropertyType.String }, + order: BuilderQueryEditorOrderByOptions.Asc, + type: BuilderQueryEditorExpressionType.Order_by, + }, + ]; + + const queryWithOrderBy = createMockQuery(existingOrderBy); + render(); + + expect(screen.getAllByLabelText('Order by column')).toHaveLength(2); + expect(screen.getAllByLabelText('Order Direction')).toHaveLength(2); + expect(screen.getByText('TimeGenerated')).toBeInTheDocument(); + expect(screen.getByText('Level')).toBeInTheDocument(); + }); + + it('calls buildAndUpdateQuery when order by is added', async () => { + render(); + + const addButton = screen.getByLabelText('Add order by'); + await userEvent.click(addButton); + + expect(defaultProps.buildAndUpdateQuery).toHaveBeenCalledWith({ + orderBy: expect.arrayContaining([ + expect.objectContaining({ + property: expect.objectContaining({ name: '' }), + order: BuilderQueryEditorOrderByOptions.Asc, + }), + ]), + }); + }); + + it('calls buildAndUpdateQuery when column is changed', async () => { + const existingOrderBy: BuilderQueryEditorOrderByExpression[] = [ + { + property: { name: 'TimeGenerated', type: BuilderQueryEditorPropertyType.String }, + order: BuilderQueryEditorOrderByOptions.Desc, + type: BuilderQueryEditorExpressionType.Order_by, + }, + ]; + + const queryWithOrderBy = createMockQuery(existingOrderBy); + render(); + + const columnSelect = screen.getByLabelText('Order by column'); + await userEvent.click(columnSelect); + + const levelOption = await screen.getByText('Level'); + await userEvent.click(levelOption); + + expect(defaultProps.buildAndUpdateQuery).toHaveBeenCalledWith({ + orderBy: expect.arrayContaining([ + expect.objectContaining({ + property: expect.objectContaining({ name: 'Level' }), + }), + ]), + }); + }); + + it('calls buildAndUpdateQuery when order direction is changed', async () => { + const existingOrderBy: BuilderQueryEditorOrderByExpression[] = [ + { + property: { name: 'TimeGenerated', type: BuilderQueryEditorPropertyType.String }, + order: BuilderQueryEditorOrderByOptions.Asc, + type: BuilderQueryEditorExpressionType.Order_by, + }, + ]; + + const queryWithOrderBy = createMockQuery(existingOrderBy); + render(); + + const orderSelect = screen.getByLabelText('Order Direction'); + await userEvent.click(orderSelect); + + const descOption = await screen.getByText('Descending'); + await userEvent.click(descOption); + + expect(defaultProps.buildAndUpdateQuery).toHaveBeenCalledWith({ + orderBy: expect.arrayContaining([ + expect.objectContaining({ + order: BuilderQueryEditorOrderByOptions.Desc, + }), + ]), + }); + }); + + it('calls buildAndUpdateQuery when order by is deleted', async () => { + const existingOrderBy: BuilderQueryEditorOrderByExpression[] = [ + { + property: { name: 'TimeGenerated', type: BuilderQueryEditorPropertyType.String }, + order: BuilderQueryEditorOrderByOptions.Desc, + type: BuilderQueryEditorExpressionType.Order_by, + }, + { + property: { name: 'Level', type: BuilderQueryEditorPropertyType.String }, + order: BuilderQueryEditorOrderByOptions.Asc, + type: BuilderQueryEditorExpressionType.Order_by, + }, + ]; + + const queryWithOrderBy = createMockQuery(existingOrderBy); + render(); + + const removeButtons = screen.getAllByLabelText('Remove order by'); + await userEvent.click(removeButtons[0]); + + expect(defaultProps.buildAndUpdateQuery).toHaveBeenCalledWith({ + orderBy: [ + expect.objectContaining({ + property: expect.objectContaining({ name: 'Level' }), + }), + ], + }); + }); + + it('uses group by columns when available', async () => { + const query = createMockQuery(); + query.azureLogAnalytics!.builderQuery!.groupBy = { + type: BuilderQueryEditorExpressionType.Group_by, + expressions: [ + { + property: { name: 'Level', type: BuilderQueryEditorPropertyType.String }, + type: BuilderQueryEditorExpressionType.Group_by, + }, + ], + }; + + render(); + + const addButton = screen.getByLabelText('Add order by'); + await userEvent.click(addButton); + + const columnSelect = screen.getByLabelText('Order by column'); + await userEvent.click(columnSelect); + + expect(await screen.getByText('Level')).toBeInTheDocument(); + }); + + it('uses aggregate columns when available', async () => { + const query = createMockQuery(); + query.azureLogAnalytics!.builderQuery!.reduce = { + type: BuilderQueryEditorExpressionType.Reduce, + expressions: [ + { + reduce: { + name: 'sum', + type: BuilderQueryEditorPropertyType.Function, + }, + property: { name: 'Count', type: BuilderQueryEditorPropertyType.String }, + }, + ], + }; + + render(); + + const addButton = screen.getByLabelText('Add order by'); + await userEvent.click(addButton); + + const columnSelect = screen.getByLabelText('Order by column'); + await userEvent.click(columnSelect); + + expect(await screen.getByText('Count')).toBeInTheDocument(); + }); + + it('uses both group by and aggregate columns when available', async () => { + const query = createMockQuery(); + query.azureLogAnalytics!.builderQuery!.groupBy = { + type: BuilderQueryEditorExpressionType.Group_by, + expressions: [ + { + property: { name: 'Level', type: BuilderQueryEditorPropertyType.String }, + type: BuilderQueryEditorExpressionType.Group_by, + }, + ], + }; + query.azureLogAnalytics!.builderQuery!.reduce = { + type: BuilderQueryEditorExpressionType.Reduce, + expressions: [ + { + reduce: { + name: 'sum', + type: BuilderQueryEditorPropertyType.Function, + }, + property: { name: 'Count', type: BuilderQueryEditorPropertyType.String }, + }, + ], + }; + + render(); + + const addButton = screen.getByLabelText('Add order by'); + await userEvent.click(addButton); + + const columnSelect = screen.getByLabelText('Order by column'); + await userEvent.click(columnSelect); + + expect(await screen.getByText('Level')).toBeInTheDocument(); + expect(await screen.getByText('Count')).toBeInTheDocument(); + }); + + it('does not duplicate available columns', async () => { + const query = createMockQuery(); + query.azureLogAnalytics!.builderQuery!.groupBy = { + type: BuilderQueryEditorExpressionType.Group_by, + expressions: [ + { + property: { name: 'Level', type: BuilderQueryEditorPropertyType.String }, + type: BuilderQueryEditorExpressionType.Group_by, + }, + ], + }; + query.azureLogAnalytics!.builderQuery!.reduce = { + type: BuilderQueryEditorExpressionType.Reduce, + expressions: [ + { + reduce: { + name: 'sum', + type: BuilderQueryEditorPropertyType.Function, + }, + property: { name: 'Level', type: BuilderQueryEditorPropertyType.String }, + }, + ], + }; + + render(); + + const addButton = screen.getByLabelText('Add order by'); + await userEvent.click(addButton); + + const columnSelect = screen.getByLabelText('Order by column'); + await userEvent.click(columnSelect); + + expect(await screen.getByText('Level')).toBeInTheDocument(); + }); + + it('uses selected columns when no group by or aggregates', async () => { + const query = createMockQuery(); + query.azureLogAnalytics!.builderQuery!.columns!.columns = ['TimeGenerated', 'Level']; + + render(); + + const addButton = screen.getByLabelText('Add order by'); + await userEvent.click(addButton); + + const columnSelect = screen.getByLabelText('Order by column'); + await userEvent.click(columnSelect); + + expect(await screen.getByText('TimeGenerated')).toBeInTheDocument(); + expect(await screen.getByText('Level')).toBeInTheDocument(); + }); + + it('falls back to all columns when no other columns available', async () => { + const query = createMockQuery(); + query.azureLogAnalytics!.builderQuery!.columns!.columns = []; + + render(); + + const addButton = screen.getByLabelText('Add order by'); + await userEvent.click(addButton); + + const columnSelect = screen.getByLabelText('Order by column'); + await userEvent.click(columnSelect); + + expect(await screen.getByText('TimeGenerated')).toBeInTheDocument(); + expect(await screen.getByText('Level')).toBeInTheDocument(); + expect(await screen.getByText('Count')).toBeInTheDocument(); + expect(await screen.getByText('Duration')).toBeInTheDocument(); + }); + + it('resets order by when table changes', () => { + const existingOrderBy: BuilderQueryEditorOrderByExpression[] = [ + { + property: { name: 'TimeGenerated', type: BuilderQueryEditorPropertyType.String }, + order: BuilderQueryEditorOrderByOptions.Desc, + type: BuilderQueryEditorExpressionType.Order_by, + }, + ]; + + const queryWithOrderBy = createMockQuery(existingOrderBy); + const { rerender } = render(); + + const newQuery = createMockQuery(existingOrderBy); + newQuery.azureLogAnalytics!.builderQuery!.from!.property.name = 'AppEvents'; + + rerender(); + + const addButton = screen.getByLabelText('Add order by'); + expect(addButton).toBeInTheDocument(); + }); +}); diff --git a/public/app/plugins/datasource/azuremonitor/components/LogsQueryBuilder/OrderBySection.tsx b/public/app/plugins/datasource/azuremonitor/components/LogsQueryBuilder/OrderBySection.tsx index 98587b1401f..ed8cde5ab39 100644 --- a/public/app/plugins/datasource/azuremonitor/components/LogsQueryBuilder/OrderBySection.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/LogsQueryBuilder/OrderBySection.tsx @@ -45,16 +45,35 @@ export const OrderBySection: React.FC = ({ query, allColumn const aggregateColumns = builderQuery?.reduce?.expressions?.map((r) => r.property?.name) || []; const selectedColumns = builderQuery?.columns?.columns || []; - const allAvailableColumns = - groupByColumns.length > 0 - ? groupByColumns - : aggregateColumns.length > 0 - ? aggregateColumns - : selectedColumns.length > 0 - ? selectedColumns - : allColumns.map((col) => col.name); + const allAvailableColumns = new Set(); + if (groupByColumns.length > 0) { + groupByColumns.forEach((col) => { + if (col) { + allAvailableColumns.add(col); + } + }); + } + if (aggregateColumns.length > 0) { + aggregateColumns.forEach((col) => { + if (col) { + allAvailableColumns.add(col); + } + }); + } + if (allAvailableColumns.size === 0 && selectedColumns.length > 0) { + selectedColumns.forEach((col) => { + if (col) { + allAvailableColumns.add(col); + } + }); + } + if (allAvailableColumns.size === 0) { + allColumns.forEach((col) => { + allAvailableColumns.add(col.name); + }); + } - const columnOptions = allAvailableColumns.map((col) => ({ + const columnOptions = Array.from(allAvailableColumns).map((col) => ({ label: col, value: col, })); From 7aa77af7c41c775c29cec79fd75f822a31f019c3 Mon Sep 17 00:00:00 2001 From: Steve Simpson Date: Thu, 4 Dec 2025 13:26:09 +0100 Subject: [PATCH 023/110] Alerting: Implement notification history query endpoint. (#114736) Implements the /notification/query endpoint on the historian app. Note that it does not apply any RBAC right now, that will be a follow up commit. We have to use a go-kit logger in grafana/alerting, so an adapter is needed. Going from go-kit to slog is a bit hairy but works well enough. --- apps/advisor/go.mod | 1 + apps/alerting/historian/go.mod | 98 ++- apps/alerting/historian/go.sum | 628 ++++++++++++++++++ apps/alerting/historian/pkg/app/app.go | 12 +- .../historian/pkg/app/config/config.go | 7 + .../historian/pkg/app/logutil/logging.go | 62 ++ .../historian/pkg/app/logutil/logging_test.go | 101 +++ .../pkg/app/notification/lokireader.go | 232 +++++++ .../pkg/app/notification/lokireader_test.go | 596 +++++++++++++++++ .../pkg/app/notification/notification.go | 77 +++ .../historian/pkg/app/notification/types.go | 28 + apps/iam/go.mod | 1 + go.mod | 1 - .../apps/alerting/historian/register.go | 19 + 14 files changed, 1856 insertions(+), 7 deletions(-) create mode 100644 apps/alerting/historian/pkg/app/logutil/logging.go create mode 100644 apps/alerting/historian/pkg/app/logutil/logging_test.go create mode 100644 apps/alerting/historian/pkg/app/notification/lokireader.go create mode 100644 apps/alerting/historian/pkg/app/notification/lokireader_test.go create mode 100644 apps/alerting/historian/pkg/app/notification/notification.go create mode 100644 apps/alerting/historian/pkg/app/notification/types.go diff --git a/apps/advisor/go.mod b/apps/advisor/go.mod index 53531280f4b..c6b283dd48d 100644 --- a/apps/advisor/go.mod +++ b/apps/advisor/go.mod @@ -288,6 +288,7 @@ require ( golang.org/x/text v0.31.0 // indirect golang.org/x/time v0.14.0 // indirect golang.org/x/tools v0.39.0 // indirect + golang.org/x/tools/godoc v0.1.0-deprecated // indirect golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect gonum.org/v1/gonum v0.16.0 // indirect diff --git a/apps/alerting/historian/go.mod b/apps/alerting/historian/go.mod index 11e0d0f2406..d14fc9ad55e 100644 --- a/apps/alerting/historian/go.mod +++ b/apps/alerting/historian/go.mod @@ -3,69 +3,151 @@ module github.com/grafana/grafana/apps/alerting/historian go 1.25.5 require ( + github.com/go-kit/log v0.2.1 + github.com/grafana/alerting v0.0.0-20251202151018-58fa500f3232 + github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4 github.com/grafana/grafana-app-sdk v0.48.4 + github.com/grafana/grafana-app-sdk/logging v0.48.3 + github.com/prometheus/client_golang v1.23.2 + github.com/stretchr/testify v1.11.1 + go.opentelemetry.io/otel v1.38.0 + go.opentelemetry.io/otel/trace v1.38.0 k8s.io/apimachinery v0.34.2 k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 ) require ( + dario.cat/mergo v1.0.2 // indirect + github.com/HdrHistogram/hdrhistogram-go v1.1.2 // indirect + github.com/Masterminds/goutils v1.1.1 // indirect + github.com/Masterminds/semver/v3 v3.4.0 // indirect + github.com/Masterminds/sprig/v3 v3.3.0 // indirect + github.com/armon/go-metrics v0.4.1 // indirect + github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect + github.com/aws/aws-sdk-go v1.55.7 // indirect + github.com/benbjohnson/clock v1.3.5 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/emicklei/go-restful/v3 v3.13.0 // indirect github.com/evanphx/json-patch v5.9.11+incompatible // indirect github.com/fxamacker/cbor/v2 v2.9.0 // indirect github.com/getkin/kin-openapi v0.133.0 // indirect + github.com/go-logfmt/logfmt v0.6.1 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-openapi/errors v0.22.3 // indirect github.com/go-openapi/jsonpointer v0.22.1 // indirect github.com/go-openapi/jsonreference v0.21.2 // indirect + github.com/go-openapi/strfmt v0.24.0 // indirect github.com/go-openapi/swag v0.23.1 // indirect github.com/go-openapi/swag/jsonname v0.25.1 // indirect github.com/go-test/deep v1.1.1 // indirect + github.com/go-viper/mapstructure/v2 v2.4.0 // indirect + github.com/gofrs/uuid v4.4.0+incompatible // indirect + github.com/gogo/googleapis v1.4.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect + github.com/gogo/status v1.1.1 // indirect + github.com/golang-jwt/jwt/v5 v5.3.0 // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/golang/snappy v1.0.0 // indirect + github.com/google/btree v1.1.3 // indirect github.com/google/gnostic-models v0.7.0 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/grafana/grafana-app-sdk/logging v0.48.3 // indirect + github.com/grafana/loki/pkg/push v0.0.0-20250823105456-332df2b20000 // indirect + github.com/grafana/otel-profiling-go v0.5.1 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-immutable-radix v1.3.1 // indirect + github.com/hashicorp/go-metrics v0.5.4 // indirect + github.com/hashicorp/go-msgpack/v2 v2.1.2 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/hashicorp/go-sockaddr v1.0.7 // indirect + github.com/hashicorp/go-uuid v1.0.3 // indirect + github.com/hashicorp/golang-lru v1.0.2 // indirect + github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect + github.com/hashicorp/memberlist v0.5.2 // indirect + github.com/huandu/xstrings v1.5.0 // indirect + github.com/jaegertracing/jaeger-idl v0.5.0 // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/josharian/intern v1.0.0 // indirect + github.com/jpillora/backoff v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/mailru/easyjson v0.9.0 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect + github.com/mdlayher/socket v0.4.1 // indirect + github.com/mdlayher/vsock v1.2.1 // indirect + github.com/miekg/dns v1.1.63 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect + github.com/oklog/run v1.1.0 // indirect + github.com/oklog/ulid v1.3.1 // indirect github.com/onsi/ginkgo/v2 v2.22.2 // indirect github.com/onsi/gomega v1.36.2 // indirect + github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/perimeterx/marshmallow v1.1.5 // indirect + github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/prometheus/client_golang v1.23.2 // indirect + github.com/prometheus/alertmanager v0.28.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.67.3 // indirect + github.com/prometheus/common/sigv4 v0.1.0 // indirect + github.com/prometheus/exporter-toolkit v0.14.0 // indirect github.com/prometheus/procfs v0.16.1 // indirect github.com/puzpuzpuz/xsync/v2 v2.5.1 // indirect + github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect + github.com/shopspring/decimal v1.4.0 // indirect + github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c // indirect + github.com/shurcooL/vfsgen v0.0.0-20230704071429-0000e147ea92 // indirect + github.com/spf13/cast v1.10.0 // indirect github.com/spf13/pflag v1.0.10 // indirect + github.com/stretchr/objx v0.5.2 // indirect + github.com/uber/jaeger-client-go v2.30.0+incompatible // indirect + github.com/uber/jaeger-lib v2.4.1+incompatible // indirect github.com/woodsbury/decimal128 v1.3.0 // indirect github.com/x448/float16 v0.8.4 // indirect + go.mongodb.org/mongo-driver v1.17.4 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/otel v1.38.0 // indirect + go.opentelemetry.io/contrib/bridges/prometheus v0.61.0 // indirect + go.opentelemetry.io/contrib/exporters/autoexport v0.61.0 // indirect + go.opentelemetry.io/contrib/propagators/jaeger v1.38.0 // indirect + go.opentelemetry.io/contrib/samplers/jaegerremote v0.32.0 // indirect + go.opentelemetry.io/otel/exporters/jaeger v1.17.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.12.2 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.12.2 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/prometheus v0.59.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.12.2 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.37.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 // indirect + go.opentelemetry.io/otel/log v0.12.2 // indirect go.opentelemetry.io/otel/metric v1.38.0 // indirect go.opentelemetry.io/otel/sdk v1.38.0 // indirect - go.opentelemetry.io/otel/trace v1.38.0 // indirect + go.opentelemetry.io/otel/sdk/log v0.12.2 // indirect + go.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect go.opentelemetry.io/proto/otlp v1.9.0 // indirect + go.uber.org/atomic v1.11.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/crypto v0.45.0 // indirect + golang.org/x/mod v0.30.0 // indirect golang.org/x/net v0.47.0 // indirect golang.org/x/oauth2 v0.33.0 // indirect golang.org/x/sync v0.18.0 // indirect @@ -73,12 +155,17 @@ require ( golang.org/x/term v0.37.0 // indirect golang.org/x/text v0.31.0 // indirect golang.org/x/time v0.14.0 // indirect + golang.org/x/tools v0.39.0 // indirect + golang.org/x/tools/godoc v0.1.0-deprecated // indirect gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba // indirect google.golang.org/grpc v1.77.0 // indirect google.golang.org/protobuf v1.36.10 // indirect + gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/mail.v2 v2.3.1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/api v0.34.2 // indirect k8s.io/apiextensions-apiserver v0.34.2 // indirect @@ -90,3 +177,6 @@ require ( sigs.k8s.io/structured-merge-diff/v6 v6.3.1 // indirect sigs.k8s.io/yaml v1.6.0 // indirect ) + +// Use our fork of the upstream Alertmanager. +replace github.com/prometheus/alertmanager => github.com/grafana/prometheus-alertmanager v0.25.1-0.20250911094103-5456b6e45604 diff --git a/apps/alerting/historian/go.sum b/apps/alerting/historian/go.sum index c447da4e325..5420cd9d519 100644 --- a/apps/alerting/historian/go.sum +++ b/apps/alerting/historian/go.sum @@ -1,83 +1,326 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= +dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/HdrHistogram/hdrhistogram-go v1.1.2 h1:5IcZpTvzydCQeHzK4Ef/D5rrSqwxob0t8PQPMybUNFM= +github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= +github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= +github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= +github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs= +github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0= +github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA= +github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= +github.com/aws/aws-sdk-go v1.38.35/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= +github.com/aws/aws-sdk-go v1.55.7 h1:UJrkFq7es5CShfBwlWAC8DA077vp8PyVbQd3lqLiztE= +github.com/aws/aws-sdk-go v1.55.7/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= +github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o= +github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 h1:N7oVaKyGp8bttX0bfZGmcGkjz7DLQXhAn3DNd3T0ous= github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= +github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes= github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch v5.9.11+incompatible h1:ixHHqfcGvxhWkniF1tWxBHA0yb4Z+d1UQi45df52xW8= github.com/evanphx/json-patch v5.9.11+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/getkin/kin-openapi v0.133.0 h1:pJdmNohVIJ97r4AUFtEXRXwESr8b0bD721u/Tz6k8PQ= github.com/getkin/kin-openapi v0.133.0/go.mod h1:boAciF6cXk5FhPqe/NQeBTeenbjqU4LhWBf09ILVvWE= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= +github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logfmt/logfmt v0.6.1 h1:4hvbpePJKnIzH1B+8OR/JPbTx37NktoI9LE2QZBBkvE= +github.com/go-logfmt/logfmt v0.6.1/go.mod h1:EV2pOAQoZaT1ZXZbqDl5hrymndi4SY9ED9/z6CO0XAk= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-openapi/errors v0.22.3 h1:k6Hxa5Jg1TUyZnOwV2Lh81j8ayNw5VVYLvKrp4zFKFs= +github.com/go-openapi/errors v0.22.3/go.mod h1:+WvbaBBULWCOna//9B9TbLNGSFOfF8lY9dw4hGiEiKQ= github.com/go-openapi/jsonpointer v0.22.1 h1:sHYI1He3b9NqJ4wXLoJDKmUmHkWy/L7rtEo92JUxBNk= github.com/go-openapi/jsonpointer v0.22.1/go.mod h1:pQT9OsLkfz1yWoMgYFy4x3U5GY5nUlsOn1qSBH5MkCM= github.com/go-openapi/jsonreference v0.21.2 h1:Wxjda4M/BBQllegefXrY/9aq1fxBA8sI5M/lFU6tSWU= github.com/go-openapi/jsonreference v0.21.2/go.mod h1:pp3PEjIsJ9CZDGCNOyXIQxsNuroxm8FAJ/+quA0yKzQ= +github.com/go-openapi/strfmt v0.24.0 h1:dDsopqbI3wrrlIzeXRbqMihRNnjzGC+ez4NQaAAJLuc= +github.com/go-openapi/strfmt v0.24.0/go.mod h1:Lnn1Bk9rZjXxU9VMADbEEOo7D7CDyKGLsSKekhFr7s4= github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU= github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0= github.com/go-openapi/swag/jsonname v0.25.1 h1:Sgx+qbwa4ej6AomWC6pEfXrA6uP2RkaNjA9BR8a1RJU= github.com/go-openapi/swag/jsonname v0.25.1/go.mod h1:71Tekow6UOLBD3wS7XhdT98g5J5GR13NOTQ9/6Q11Zo= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U= github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= +github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= +github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= +github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gogo/googleapis v0.0.0-20180223154316-0cd9801be74a/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= +github.com/gogo/googleapis v1.4.1 h1:1Yx4Myt7BxzvUr5ldGSbwYiZG6t9wGBZ+8/fX3Wvtq0= +github.com/gogo/googleapis v1.4.1/go.mod h1:2lpHqI5OcWCtVElxXnPt+s8oJvMpySlOyM6xDCrzib4= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/gogo/status v1.1.1 h1:DuHXlSFHNKqTQ+/ACf5Vs6r4X/dH2EgIzR9Vr+H65kg= +github.com/gogo/status v1.1.1/go.mod h1:jpG3dM5QPcqu19Hg8lkUhBFBa3TcLs1DG7+2Jqci7oU= +github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= +github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs= +github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= +github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8= github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/grafana/alerting v0.0.0-20251202151018-58fa500f3232 h1:I9l/BxoqxTlPUVx05t8OsqbdP/qwqOeD2E5makeeIz0= +github.com/grafana/alerting v0.0.0-20251202151018-58fa500f3232/go.mod h1:l7v67cgP7x72ajB9UPZlumdrHqNztpKoqQ52cU8T3LU= +github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4 h1:jSojuc7njleS3UOz223WDlXOinmuLAIPI0z2vtq8EgI= +github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4/go.mod h1:VahT+GtfQIM+o8ht2StR6J9g+Ef+C2Vokh5uuSmOD/4= github.com/grafana/grafana-app-sdk v0.48.4 h1:t9r+Y6E7D832ZxQ2c1n0lp6cvsYKhhrAodVYzE1y0s0= github.com/grafana/grafana-app-sdk v0.48.4/go.mod h1:HJsMOSBmt/D/Ihs1SvagOwmXKi0coBMVHlfvdd+qe9Y= github.com/grafana/grafana-app-sdk/logging v0.48.3 h1:72NUpGNiJXCNQz/on++YSsl38xuVYYBKv5kKQaOClX4= github.com/grafana/grafana-app-sdk/logging v0.48.3/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA= +github.com/grafana/loki/pkg/push v0.0.0-20250823105456-332df2b20000 h1:/5LKSYgLmAhwA4m6iGUD4w1YkydEWWjazn9qxCFT8W0= +github.com/grafana/loki/pkg/push v0.0.0-20250823105456-332df2b20000/go.mod h1:/ZklAgE1i4f3Z8uriXwESmCr1VLF8lBGaJspuaGuf78= +github.com/grafana/otel-profiling-go v0.5.1 h1:stVPKAFZSa7eGiqbYuG25VcqYksR6iWvF3YH66t4qL8= +github.com/grafana/otel-profiling-go v0.5.1/go.mod h1:ftN/t5A/4gQI19/8MoWurBEtC6gFw8Dns1sJZ9W4Tls= +github.com/grafana/prometheus-alertmanager v0.25.1-0.20250911094103-5456b6e45604 h1:aXfUhVN/Ewfpbko2CCtL65cIiGgwStOo4lWH2b6gw2U= +github.com/grafana/prometheus-alertmanager v0.25.1-0.20250911094103-5456b6e45604/go.mod h1:O/QP1BCm0HHIzbKvgMzqb5sSyH88rzkFk84F4TfJjBU= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= +github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-metrics v0.5.4 h1:8mmPiIJkTPPEbAiV97IxdAGNdRdaWwVap1BU6elejKY= +github.com/hashicorp/go-metrics v0.5.4/go.mod h1:CG5yz4NZ/AI/aQt9Ucm/vdBnbh7fvmv4lxZ350i+QQI= +github.com/hashicorp/go-msgpack/v2 v2.1.2 h1:4Ee8FTp834e+ewB71RDrQ0VKpyFdrKOjvYtnQ/ltVj0= +github.com/hashicorp/go-msgpack/v2 v2.1.2/go.mod h1:upybraOAblm4S7rx0+jeNy+CWWhzywQsSRV5033mMu4= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= +github.com/hashicorp/go-sockaddr v1.0.7 h1:G+pTkSO01HpR5qCxg7lxfsFEZaG+C0VssTy/9dbT+Fw= +github.com/hashicorp/go-sockaddr v1.0.7/go.mod h1:FZQbEYa1pxkQ7WLpyXJ6cbjpT8q0YgQaK/JakXqGyWw= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= +github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= +github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/hashicorp/memberlist v0.5.2 h1:rJoNPWZ0juJBgqn48gjy59K5H4rNgvUoM1kUD7bXiuI= +github.com/hashicorp/memberlist v0.5.2/go.mod h1:Ri9p/tRShbjYnpNf4FFPXG7wxEGY4Nrcn6E7jrVa//4= +github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= +github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/jaegertracing/jaeger-idl v0.5.0 h1:zFXR5NL3Utu7MhPg8ZorxtCBjHrL3ReM1VoB65FOFGE= +github.com/jaegertracing/jaeger-idl v0.5.0/go.mod h1:ON90zFo9eoyXrt9F/KN8YeF3zxcnujaisMweFY/rg5k= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= +github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U= +github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA= +github.com/mdlayher/vsock v1.2.1 h1:pC1mTJTvjo1r9n9fbm7S1j04rCgCzhCOS5DY0zqHlnQ= +github.com/mdlayher/vsock v1.2.1/go.mod h1:NRfCibel++DgeMD8z/hP+PPTjlNJsdPOmxcnENvE+SE= +github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY= +github.com/miekg/dns v1.1.63/go.mod h1:6NGHfjhpmr5lt3XPLuyfDJi5AXbNIPM9PY6H6sF1Nfs= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= @@ -85,137 +328,518 @@ github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9 github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY= github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw= github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletIKwUIt4x3t8n2SxavmoclizMb8c= github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o= +github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= +github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= +github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/onsi/ginkgo/v2 v2.22.2 h1:/3X8Panh8/WwhU/3Ssa6rCKqPLuAkVY2I0RoyDLySlU= github.com/onsi/ginkgo/v2 v2.22.2/go.mod h1:oeMosUL+8LtarXBHu/c0bx2D/K9zyQ6uX3cTyztHwsk= github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8= github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY= +github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= +github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= +github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= +github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/common v0.29.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/common v0.67.3 h1:shd26MlnwTw5jksTDhC7rTQIteBxy+ZZDr3t7F2xN2Q= github.com/prometheus/common v0.67.3/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= +github.com/prometheus/common/sigv4 v0.1.0 h1:qoVebwtwwEhS85Czm2dSROY5fTo2PAPEVdDeppTwGX4= +github.com/prometheus/common/sigv4 v0.1.0/go.mod h1:2Jkxxk9yYvCkE5G1sQT7GuEXm57JrvHu9k5YwTjsNtI= +github.com/prometheus/exporter-toolkit v0.14.0 h1:NMlswfibpcZZ+H0sZBiTjrA3/aBFHkNZqE+iCj5EmRg= +github.com/prometheus/exporter-toolkit v0.14.0/go.mod h1:Gu5LnVvt7Nr/oqTBUC23WILZepW0nffNo10XdhQcwWA= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= github.com/puzpuzpuz/xsync/v2 v2.5.1 h1:mVGYAvzDSu52+zaGyNjC+24Xw2bQi3kTr4QJ6N9pIIU= github.com/puzpuzpuz/xsync/v2 v2.5.1/go.mod h1:gD2H2krq/w52MfPLE+Uy64TzJDVY7lP2znR9qmR35kU= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= +github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= +github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c h1:aqg5Vm5dwtvL+YgDpBcK1ITf3o96N/K7/wsRXQnUTEs= +github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c/go.mod h1:owqhoLW1qZoYLZzLnBw+QkPP9WZnjlSWihhxAJC1+/M= +github.com/shurcooL/vfsgen v0.0.0-20230704071429-0000e147ea92 h1:OfRzdxCzDhp+rsKWXuOO2I/quKMJ/+TQwVbIP/gltZg= +github.com/shurcooL/vfsgen v0.0.0-20230704071429-0000e147ea92/go.mod h1:7/OT02F6S6I7v6WXb+IjhMuZEYfH/RJ5RwEWnEo5BMg= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= +github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= +github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaOOb6ThwMmTEbhRwtKR97o= +github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= +github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg= +github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/woodsbury/decimal128 v1.3.0 h1:8pffMNWIlC0O5vbyHWFZAt5yWvWcrHA+3ovIIjVWss0= github.com/woodsbury/decimal128 v1.3.0/go.mod h1:C5UTmyTjW3JftjUFzOVhC20BEQa2a4ZKOB5I6Zjb+ds= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.mongodb.org/mongo-driver v1.17.4 h1:jUorfmVzljjr0FLzYQsGP8cgN/qzzxlY9Vh0C9KFXVw= +go.mongodb.org/mongo-driver v1.17.4/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/contrib/bridges/prometheus v0.61.0 h1:RyrtJzu5MAmIcbRrwg75b+w3RlZCP0vJByDVzcpAe3M= +go.opentelemetry.io/contrib/bridges/prometheus v0.61.0/go.mod h1:tirr4p9NXbzjlbruiRGp53IzlYrDk5CO2fdHj0sSSaY= +go.opentelemetry.io/contrib/exporters/autoexport v0.61.0 h1:XfzKtKSrbtYk9TNCF8dkO0Y9M7IOfb4idCwBOTwGBiI= +go.opentelemetry.io/contrib/exporters/autoexport v0.61.0/go.mod h1:N6otC+qXTD5bAnbK2O1f/1SXq3cX+3KYSWrkBUqG0cw= +go.opentelemetry.io/contrib/propagators/jaeger v1.38.0 h1:nXGeLvT1QtCAhkASkP/ksjkTKZALIaQBIW+JSIw1KIc= +go.opentelemetry.io/contrib/propagators/jaeger v1.38.0/go.mod h1:oMvOXk78ZR3KEuPMBgp/ThAMDy9ku/eyUVztr+3G6Wo= +go.opentelemetry.io/contrib/samplers/jaegerremote v0.32.0 h1:oPW/SRFyHgIgxrvNhSBzqvZER2N5kRlci3/rGTOuyWo= +go.opentelemetry.io/contrib/samplers/jaegerremote v0.32.0/go.mod h1:B9Oka5QVD0bnmZNO6gBbBta6nohD/1Z+f9waH2oXyBs= +go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo= go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= +go.opentelemetry.io/otel/exporters/jaeger v1.17.0 h1:D7UpUy2Xc2wsi1Ras6V40q806WM07rqoCWzXu7Sqy+4= +go.opentelemetry.io/otel/exporters/jaeger v1.17.0/go.mod h1:nPCqOnEH9rNLKqH/+rrUjiMzHJdV1BlpKcTwRTyKkKI= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.12.2 h1:06ZeJRe5BnYXceSM9Vya83XXVaNGe3H1QqsvqRANQq8= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.12.2/go.mod h1:DvPtKE63knkDVP88qpatBj81JxN+w1bqfVbsbCbj1WY= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.12.2 h1:tPLwQlXbJ8NSOfZc4OkgU5h2A38M4c9kfHSVc4PFQGs= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.12.2/go.mod h1:QTnxBwT/1rBIgAG1goq6xMydfYOBKU6KTiYF4fp5zL8= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 h1:vl9obrcoWVKp/lwl8tRE33853I8Xru9HFbw/skNeLs8= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0/go.mod h1:GAXRxmLJcVM3u22IjTg74zWBrRCKq8BnOqUVLodpcpw= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 h1:Oe2z/BCg5q7k4iXC3cqJxKYg0ieRiOqF0cecFYdPTwk= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0/go.mod h1:ZQM5lAJpOsKnYagGg/zV2krVqTtaVdYdDkhMoX6Oalg= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4= +go.opentelemetry.io/otel/exporters/prometheus v0.59.0 h1:HHf+wKS6o5++XZhS98wvILrLVgHxjA/AMjqHKes+uzo= +go.opentelemetry.io/otel/exporters/prometheus v0.59.0/go.mod h1:R8GpRXTZrqvXHDEGVH5bF6+JqAZcK8PjJcZ5nGhEWiE= +go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.12.2 h1:12vMqzLLNZtXuXbJhSENRg+Vvx+ynNilV8twBLBsXMY= +go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.12.2/go.mod h1:ZccPZoPOoq8x3Trik/fCsba7DEYDUnN6yX79pgp2BUQ= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.37.0 h1:6VjV6Et+1Hd2iLZEPtdV7vie80Yyqf7oikJLjQ/myi0= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.37.0/go.mod h1:u8hcp8ji5gaM/RfcOo8z9NMnf1pVLfVY7lBY2VOGuUU= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 h1:kJxSDN4SgWWTjG/hPp3O7LCGLcHXFlvS2/FFOrwL+SE= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0/go.mod h1:mgIOzS7iZeKJdeB8/NYHrJ48fdGc71Llo5bJ1J4DWUE= +go.opentelemetry.io/otel/log v0.12.2 h1:yob9JVHn2ZY24byZeaXpTVoPS6l+UrrxmxmPKohXTwc= +go.opentelemetry.io/otel/log v0.12.2/go.mod h1:ShIItIxSYxufUMt+1H5a2wbckGli3/iCfuEbVZi/98E= +go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM= go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= +go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E= go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= +go.opentelemetry.io/otel/sdk/log v0.12.2 h1:yNoETvTByVKi7wHvYS6HMcZrN5hFLD7I++1xIZ/k6W0= +go.opentelemetry.io/otel/sdk/log v0.12.2/go.mod h1:DcpdmUXHJgSqN/dh+XMWa7Vf89u9ap0/AAk/XGLnEzY= +go.opentelemetry.io/otel/sdk/log/logtest v0.0.0-20250521073539-a85ae98dcedc h1:uqxdywfHqqCl6LmZzI3pUnXT1RGFYyUgxj0AkWPFxi0= +go.opentelemetry.io/otel/sdk/log/logtest v0.0.0-20250521073539-a85ae98dcedc/go.mod h1:TY/N/FT7dmFrP/r5ym3g0yysP1DefqGpAZr4f82P0dE= go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= +go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ= go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= +golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= +golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk= +golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo= golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= +golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ= golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= +golang.org/x/tools/godoc v0.1.0-deprecated h1:o+aZ1BOj6Hsx/GBdJO/s815sqftjSnrZZwyYTHODvtk= +golang.org/x/tools/godoc v0.1.0-deprecated/go.mod h1:qM63CriJ961IHWmnWa9CjZnBndniPt4a3CK0PVB9bIg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0= gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= +gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= +gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= +gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180518175338-11a468237815/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba h1:B14OtaXuMaCQsl2deSvNkyPKIzq3BjfxQp8d00QyWx4= google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:G5IanEx8/PgI9w6CFcYQf7jMtHQhZruvfM1i3qOqk5U= google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba h1:UKgtfRM7Yh93Sya0Fo8ZzhDP4qBckrrxEr2oF5UIVb8= google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk= +gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/mail.v2 v2.3.1 h1:WYFn/oANrAGP2C0dcV6/pbkPzv8yGzqTjPmTeO7qoXk= +gopkg.in/mail.v2 v2.3.1/go.mod h1:htwXN1Qh09vZJ1NVKxQqHPBaCBbzKhp5GzuJEA4VJWw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= k8s.io/api v0.34.2 h1:fsSUNZhV+bnL6Aqrp6O7lMTy6o5x2C4XLjnh//8SLYY= k8s.io/api v0.34.2/go.mod h1:MMBPaWlED2a8w4RSeanD76f7opUoypY8TFYkSM+3XHw= k8s.io/apiextensions-apiserver v0.34.2 h1:WStKftnGeoKP4AZRz/BaAAEJvYp4mlZGN0UCv+uvsqo= @@ -230,6 +854,10 @@ k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZ k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y= k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= diff --git a/apps/alerting/historian/pkg/app/app.go b/apps/alerting/historian/pkg/app/app.go index 2996a21a231..5ef3a0412d8 100644 --- a/apps/alerting/historian/pkg/app/app.go +++ b/apps/alerting/historian/pkg/app/app.go @@ -5,22 +5,30 @@ import ( "net/http" "github.com/grafana/grafana-app-sdk/app" + "github.com/grafana/grafana-app-sdk/logging" "github.com/grafana/grafana-app-sdk/simple" + "github.com/prometheus/client_golang/prometheus" + "go.opentelemetry.io/otel" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/grafana/grafana/apps/alerting/historian/pkg/apis/alertinghistorian/v0alpha1" "github.com/grafana/grafana/apps/alerting/historian/pkg/app/config" + "github.com/grafana/grafana/apps/alerting/historian/pkg/app/notification" ) func New(cfg app.Config) (app.App, error) { + reg := prometheus.DefaultRegisterer + tracer := otel.GetTracerProvider().Tracer("historian.alerting.app") + logger := logging.DefaultLogger.With("app", "historian.alerting.app") + runtimeConfig := cfg.SpecificConfig.(config.RuntimeConfig) alertStateHandler := runtimeConfig.GetAlertStateHistoryHandler if alertStateHandler == nil { alertStateHandler = NewErrorHandler("no alert state handler") } - notificationHandler := NewErrorHandler("unimplemented") + notificationHandler := notification.New(runtimeConfig.Notification, reg, logger, tracer) simpleConfig := simple.AppConfig{ Name: "alerting.historian", @@ -36,7 +44,7 @@ func New(cfg app.Config) (app.App, error) { Namespaced: true, Path: "/notification/query", Method: "POST", - }: notificationHandler, + }: notificationHandler.QueryHandler, }, }, // TODO: Remove when SDK is fixed. diff --git a/apps/alerting/historian/pkg/app/config/config.go b/apps/alerting/historian/pkg/app/config/config.go index 5a40503dea8..dffb672b2ef 100644 --- a/apps/alerting/historian/pkg/app/config/config.go +++ b/apps/alerting/historian/pkg/app/config/config.go @@ -1,9 +1,16 @@ package config import ( + "github.com/grafana/alerting/notify/historian/lokiclient" "github.com/grafana/grafana-app-sdk/simple" ) +type NotificationConfig struct { + Enabled bool + Loki lokiclient.LokiConfig +} + type RuntimeConfig struct { GetAlertStateHistoryHandler simple.AppCustomRouteHandler + Notification NotificationConfig } diff --git a/apps/alerting/historian/pkg/app/logutil/logging.go b/apps/alerting/historian/pkg/app/logutil/logging.go new file mode 100644 index 00000000000..e3315159355 --- /dev/null +++ b/apps/alerting/historian/pkg/app/logutil/logging.go @@ -0,0 +1,62 @@ +package logutil + +import ( + "github.com/go-kit/log" + "github.com/go-kit/log/level" + "github.com/grafana/grafana-app-sdk/logging" +) + +func ToGoKitLogger(logger logging.Logger) log.Logger { + return &sdk2gkLogger{logger: logger} +} + +type sdk2gkLogger struct { + logger logging.Logger +} + +func (s *sdk2gkLogger) Log(keyvals ...interface{}) error { + var ( + outMsg = "" + outLevel = interface{}(level.InfoValue()) + outKeyvals = []interface{}{} + ) + + if len(keyvals) == 0 { + s.logger.Info("") + return nil + } + + if len(keyvals)%2 == 1 { + keyvals = append(keyvals, nil) + } + + for i := 0; i < len(keyvals); i += 2 { + k, v := keyvals[i], keyvals[i+1] + + if keyvals[i] == "msg" { + outMsg = v.(string) + continue + } + + if k == level.Key() { + outLevel = v + continue + } + + outKeyvals = append(outKeyvals, k) + outKeyvals = append(outKeyvals, v) + } + + switch outLevel { + case level.DebugValue(): + s.logger.Debug(outMsg, outKeyvals...) + case level.InfoValue(): + s.logger.Info(outMsg, outKeyvals...) + case level.WarnValue(): + s.logger.Warn(outMsg, outKeyvals...) + case level.ErrorValue(): + s.logger.Error(outMsg, outKeyvals...) + } + + return nil +} diff --git a/apps/alerting/historian/pkg/app/logutil/logging_test.go b/apps/alerting/historian/pkg/app/logutil/logging_test.go new file mode 100644 index 00000000000..516f68c7233 --- /dev/null +++ b/apps/alerting/historian/pkg/app/logutil/logging_test.go @@ -0,0 +1,101 @@ +package logutil + +import ( + "context" + "testing" + + "github.com/go-kit/log" + "github.com/go-kit/log/level" + "github.com/grafana/grafana-app-sdk/logging" + "github.com/stretchr/testify/require" +) + +type ent struct { + level string + msg string + kvs []any +} +type fakeLogger struct { + logs []ent +} + +func (f *fakeLogger) Debug(msg string, args ...any) { + f.logs = append(f.logs, ent{"debug", msg, args}) +} +func (f *fakeLogger) Info(msg string, args ...any) { + f.logs = append(f.logs, ent{"info", msg, args}) +} +func (f *fakeLogger) Warn(msg string, args ...any) { + f.logs = append(f.logs, ent{"warn", msg, args}) +} +func (f *fakeLogger) Error(msg string, args ...any) { + f.logs = append(f.logs, ent{"error", msg, args}) +} +func (f *fakeLogger) With(args ...any) logging.Logger { + return nil +} +func (f *fakeLogger) WithContext(context.Context) logging.Logger { + return nil +} + +func setup() (*fakeLogger, log.Logger) { + fake := &fakeLogger{} + return fake, ToGoKitLogger(fake) +} + +func TestToGoKitLogger(t *testing.T) { + t.Run("debug / 1 args", func(t *testing.T) { + fake, gk := setup() + require.NoError(t, level.Debug(gk).Log("msg", "hello world", "foo")) + require.Equal(t, "debug", fake.logs[0].level) + require.Equal(t, "hello world", fake.logs[0].msg) + require.Len(t, fake.logs[0].kvs, 2) + require.Equal(t, "foo", fake.logs[0].kvs[0]) + require.Equal(t, "(MISSING)", fake.logs[0].kvs[1].(error).Error()) + }) + t.Run("debug / 2 args", func(t *testing.T) { + fake, gk := setup() + require.NoError(t, level.Debug(gk).Log("msg", "hello world", "foo", "bar")) + require.Equal(t, []ent{{"debug", "hello world", []any{"foo", "bar"}}}, fake.logs) + }) + t.Run("debug / 4 args", func(t *testing.T) { + fake, gk := setup() + require.NoError(t, level.Debug(gk).Log("msg", "hello world", "foo", "bar", "baz", 1)) + require.Equal(t, []ent{{"debug", "hello world", []any{"foo", "bar", "baz", 1}}}, fake.logs) + }) + t.Run("debug / no args", func(t *testing.T) { + fake, gk := setup() + require.NoError(t, level.Debug(gk).Log("msg", "hello world")) + require.Equal(t, []ent{{"debug", "hello world", []any{}}}, fake.logs) + }) + t.Run("info / no args", func(t *testing.T) { + fake, gk := setup() + require.NoError(t, level.Info(gk).Log("msg", "hello world")) + require.Equal(t, []ent{{"info", "hello world", []any{}}}, fake.logs) + }) + t.Run("warn / no args", func(t *testing.T) { + fake, gk := setup() + require.NoError(t, level.Warn(gk).Log("msg", "hello world")) + require.Equal(t, []ent{{"warn", "hello world", []any{}}}, fake.logs) + }) + t.Run("error / no args", func(t *testing.T) { + fake, gk := setup() + require.NoError(t, level.Error(gk).Log("msg", "hello world")) + require.Equal(t, []ent{{"error", "hello world", []any{}}}, fake.logs) + }) + t.Run("no level / no args", func(t *testing.T) { + fake, gk := setup() + require.NoError(t, gk.Log("msg", "hello world")) + require.Equal(t, []ent{{"info", "hello world", []any{}}}, fake.logs) + }) + t.Run("no level / 2 args / no msg", func(t *testing.T) { + fake, gk := setup() + require.NoError(t, gk.Log("foo", "bar")) + require.Equal(t, []ent{{"info", "", []any{"foo", "bar"}}}, fake.logs) + }) + t.Run("no level / no args / no msg", func(t *testing.T) { + fake, gk := setup() + require.NoError(t, gk.Log()) + require.Equal(t, []ent{{"info", "", nil}}, fake.logs) + }) +} diff --git a/apps/alerting/historian/pkg/app/notification/lokireader.go b/apps/alerting/historian/pkg/app/notification/lokireader.go new file mode 100644 index 00000000000..e8cea23dda7 --- /dev/null +++ b/apps/alerting/historian/pkg/app/notification/lokireader.go @@ -0,0 +1,232 @@ +package notification + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "regexp" + "sort" + "strings" + "time" + + "github.com/grafana/alerting/notify/historian" + "github.com/grafana/alerting/notify/historian/lokiclient" + "github.com/grafana/dskit/instrument" + "github.com/grafana/grafana-app-sdk/logging" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" + "go.opentelemetry.io/otel/trace" + + "github.com/grafana/grafana/apps/alerting/historian/pkg/apis/alertinghistorian/v0alpha1" + "github.com/grafana/grafana/apps/alerting/historian/pkg/app/logutil" +) + +const ( + LokiClientSpanName = "grafana.apps.alerting.historian.client" + defaultQueryRange = 6 * time.Hour + defaultLimit = 100 + maxLimit = 1000 + Namespace = "grafana" + Subsystem = "alerting" +) + +var ( + // ErrInvalidQuery is returned if the query is invalid. + ErrInvalidQuery = errors.New("invalid query") + + validLabelKeyRegex = regexp.MustCompile("^[a-zA-Z_][a-zA-Z0-9_]*$") +) + +type lokiClient interface { + RangeQuery(ctx context.Context, logQL string, start, end, limit int64) (lokiclient.QueryRes, error) +} + +type LokiReader struct { + client lokiClient + logger logging.Logger +} + +func NewLokiReader(cfg lokiclient.LokiConfig, reg prometheus.Registerer, logger logging.Logger, tracer trace.Tracer) *LokiReader { + duration := instrument.NewHistogramCollector(promauto.With(reg).NewHistogramVec(prometheus.HistogramOpts{ + Namespace: Namespace, + Subsystem: Subsystem, + Name: "notification_history_read_request_duration_seconds", + Help: "Histogram of read request durations to the notification history store.", + Buckets: instrument.DefBuckets, + }, instrument.HistogramCollectorBuckets)) + + gkLogger := logutil.ToGoKitLogger(logger) + return &LokiReader{ + client: lokiclient.NewLokiClient(cfg, lokiclient.NewRequester(), nil, duration, gkLogger, tracer, LokiClientSpanName), + logger: logger, + } +} + +// Query retrieves notification history entries from an external Loki instance. +func (h *LokiReader) Query(ctx context.Context, query Query) (QueryResult, error) { + logql, err := buildQuery(query) + if err != nil { + return QueryResult{}, err + } + + now := time.Now().UTC() + from := now.Add(-defaultQueryRange) + if query.From != nil { + from = *query.From + } + to := now + if query.To != nil { + to = *query.To + } + + limit := int64(defaultLimit) + if query.Limit != nil { + limit = *query.Limit + } + + if limit > maxLimit { + return QueryResult{}, fmt.Errorf("%w: limit (%d) over maximum allowed (%d)", ErrInvalidQuery, limit, maxLimit) + } + + entries, err := h.runQuery(ctx, logql, from, to, limit) + if err != nil { + return QueryResult{}, err + } + + return QueryResult{ + Entries: entries, + }, nil +} + +// buildQuery creates the LogQL to perform the requested query. +func buildQuery(query Query) (string, error) { + selectors := []string{ + fmt.Sprintf(`%s=%q`, historian.LabelFrom, historian.LabelFromValue), + } + + if query.RuleUID != nil { + selectors = append(selectors, + fmt.Sprintf(`%s=%q`, historian.LabelRuleUID, *query.RuleUID)) + } + + logql := fmt.Sprintf(`{%s} | json`, strings.Join(selectors, `,`)) + + // Add receiver filter if specified. + if query.Receiver != nil && *query.Receiver != "" { + logql += fmt.Sprintf(` | receiver = %q`, *query.Receiver) + } + + // Add status filter if specified. + if query.Status != nil && *query.Status != "" { + logql += fmt.Sprintf(` | status = %q`, *query.Status) + } + + // Add group labels filter if specified. + if query.GroupLabels != nil { + for _, matcher := range *query.GroupLabels { + // Validate the matcher close to where it is used to form the query, + // to reduce the risk of introducing a query injection bug. + if !validLabelKeyRegex.MatchString(matcher.Label) { + return "", fmt.Errorf("%w: group label: %q", ErrInvalidQuery, matcher.Label) + } + switch matcher.Type { + case "=", "!=", "=~", "!~": + default: + return "", fmt.Errorf("%w: matcher type: %s", ErrInvalidQuery, matcher.Type) + } + logql += fmt.Sprintf(` | groupLabels_%s %s %q`, matcher.Label, matcher.Type, matcher.Value) + } + } + + // Add outcome filter if specified. + if query.Outcome != nil && *query.Outcome != "" { + switch *query.Outcome { + case v0alpha1.CreateNotificationqueryRequestNotificationOutcomeSuccess: + logql += ` | error = ""` + case v0alpha1.CreateNotificationqueryRequestNotificationOutcomeError: + logql += ` | error != ""` + } + } + + return logql, nil +} + +// runQuery runs the query and collects results. +func (l *LokiReader) runQuery(ctx context.Context, logql string, from, to time.Time, limit int64) ([]Entry, error) { + entries := make([]Entry, 0) + r, err := l.client.RangeQuery(ctx, logql, from.UnixNano(), to.UnixNano(), limit) + if err != nil { + return nil, fmt.Errorf("loki range query: %w", err) + } + + for _, stream := range r.Data.Result { + for _, s := range stream.Values { + entry, err := parseLokiEntry(s) + if err != nil { + l.logger.Warn("Ignoring notification history entry", "err", err) + continue + } + entries = append(entries, entry) + } + } + + // We need to sort as results might be from a combination of streams. + sort.Slice(entries, func(i, j int) bool { + return entries[i].Timestamp.After(entries[j].Timestamp) + }) + + l.logger.Debug("Notification history query complete", "entries", len(entries)) + + return entries, nil +} + +// parseLokiEntry unmarshals the JSON stored in the entry. +func parseLokiEntry(s lokiclient.Sample) (Entry, error) { + var lokiEntry historian.NotificationHistoryLokiEntry + err := json.Unmarshal([]byte(s.V), &lokiEntry) + if err != nil { + return Entry{}, fmt.Errorf("failed to unmarshal entry [%s]: %w", s.T, err) + } + + if lokiEntry.SchemaVersion != 1 { + return Entry{}, fmt.Errorf("unsupported schema version [%s]: %d", s.T, lokiEntry.SchemaVersion) + } + + outcome := OutcomeSuccess + var entryError *string + if lokiEntry.Error != "" { + outcome = OutcomeError + entryError = &lokiEntry.Error + } + + groupLabels := lokiEntry.GroupLabels + if groupLabels == nil { + groupLabels = make(map[string]string) + } + + alerts := make([]EntryAlert, len(lokiEntry.Alerts)) + for i, a := range lokiEntry.Alerts { + alerts[i] = EntryAlert{ + Status: a.Status, + Labels: a.Labels, + Annotations: a.Annotations, + StartsAt: a.StartsAt, + EndsAt: a.EndsAt, + } + } + + return Entry{ + Timestamp: s.T, + Receiver: lokiEntry.Receiver, + Status: Status(lokiEntry.Status), + Outcome: outcome, + GroupKey: lokiEntry.GroupKey, + GroupLabels: groupLabels, + Alerts: alerts, + Retry: lokiEntry.Retry, + Error: entryError, + Duration: lokiEntry.Duration, + PipelineTime: lokiEntry.PipelineTime, + }, nil +} diff --git a/apps/alerting/historian/pkg/app/notification/lokireader_test.go b/apps/alerting/historian/pkg/app/notification/lokireader_test.go new file mode 100644 index 00000000000..708c9d10df1 --- /dev/null +++ b/apps/alerting/historian/pkg/app/notification/lokireader_test.go @@ -0,0 +1,596 @@ +package notification + +import ( + "context" + "encoding/json" + "fmt" + "testing" + "time" + + "github.com/grafana/alerting/notify/historian" + "github.com/grafana/alerting/notify/historian/lokiclient" + "github.com/grafana/grafana-app-sdk/logging" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + + "github.com/grafana/grafana/apps/alerting/historian/pkg/apis/alertinghistorian/v0alpha1" +) + +// mockLokiClient implements the lokiClient interface for testing +type mockLokiClient struct { + mock.Mock +} + +func (m *mockLokiClient) RangeQuery(ctx context.Context, logQL string, start, end, limit int64) (lokiclient.QueryRes, error) { + args := m.Called(ctx, logQL, start, end, limit) + return args.Get(0).(lokiclient.QueryRes), args.Error(1) +} + +func TestLokiReader_Query(t *testing.T) { + now := time.Now().UTC() + testTimestamp := now.Add(-1 * time.Hour) + + tests := []struct { + name string + query Query + lokiResponse lokiclient.QueryRes + responseError error + experr error + validateFn func(t *testing.T, result QueryResult) + }{ + { + name: "successful query with results", + query: Query{ + RuleUID: stringPtr("test-rule-uid"), + }, + lokiResponse: createMockLokiResponse(testTimestamp), + validateFn: func(t *testing.T, result QueryResult) { + assert.Len(t, result.Entries, 1) + assert.Equal(t, "test-receiver", result.Entries[0].Receiver) + assert.Equal(t, Status("firing"), result.Entries[0].Status) + assert.Equal(t, OutcomeSuccess, result.Entries[0].Outcome) + }, + }, + { + name: "query with custom time range", + query: Query{ + RuleUID: stringPtr("test-rule-uid"), + From: timePtr(now.Add(-2 * time.Hour)), + To: timePtr(now), + }, + lokiResponse: createMockLokiResponse(testTimestamp), + }, + { + name: "query with custom limit", + query: Query{ + RuleUID: stringPtr("test-rule-uid"), + Limit: int64Ptr(100), + }, + lokiResponse: createMockLokiResponse(testTimestamp), + }, + { + name: "query with max limit", + query: Query{ + RuleUID: stringPtr("test-rule-uid"), + Limit: int64Ptr(1000), + }, + lokiResponse: createMockLokiResponse(testTimestamp), + }, + { + name: "query with over max limit", + query: Query{ + RuleUID: stringPtr("test-rule-uid"), + Limit: int64Ptr(1001), + }, + lokiResponse: createMockLokiResponse(testTimestamp), + experr: ErrInvalidQuery, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockClient := &mockLokiClient{} + mockClient.On("RangeQuery", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything). + Return(tt.lokiResponse, tt.responseError) + + reader := &LokiReader{ + client: mockClient, + logger: &logging.NoOpLogger{}, + } + + result, err := reader.Query(context.Background(), tt.query) + if tt.experr != nil { + assert.ErrorIs(t, err, ErrInvalidQuery) + return + } + + require.NoError(t, err) + if tt.validateFn != nil { + tt.validateFn(t, result) + } + + mockClient.AssertExpectations(t) + }) + } +} + +func TestBuildQuery(t *testing.T) { + tests := []struct { + name string + query Query + expected string + experr error + }{ + { + name: "query with no filters", + query: Query{}, + expected: fmt.Sprintf(`{%s=%q} | json`, + historian.LabelFrom, historian.LabelFromValue), + }, + { + name: "query with rule uid filter", + query: Query{ + RuleUID: stringPtr("test-rule-uid"), + }, + expected: fmt.Sprintf(`{%s=%q,%s=%q} | json`, + historian.LabelFrom, historian.LabelFromValue, + historian.LabelRuleUID, "test-rule-uid"), + }, + { + name: "query with receiver filter", + query: Query{ + RuleUID: stringPtr("test-rule-uid"), + Receiver: stringPtr("email-receiver"), + }, + expected: fmt.Sprintf(`{%s=%q,%s=%q} | json | receiver = "email-receiver"`, + historian.LabelFrom, historian.LabelFromValue, + historian.LabelRuleUID, "test-rule-uid"), + }, + { + name: "query with status filter", + query: Query{ + RuleUID: stringPtr("test-rule-uid"), + Status: createStatusPtr(v0alpha1.CreateNotificationqueryRequestNotificationStatusFiring), + }, + expected: fmt.Sprintf(`{%s=%q,%s=%q} | json | status = "firing"`, + historian.LabelFrom, historian.LabelFromValue, + historian.LabelRuleUID, "test-rule-uid"), + }, + { + name: "query with success outcome filter", + query: Query{ + RuleUID: stringPtr("test-rule-uid"), + Outcome: outcomePtr(v0alpha1.CreateNotificationqueryRequestNotificationOutcomeSuccess), + }, + expected: fmt.Sprintf(`{%s=%q,%s=%q} | json | error = ""`, + historian.LabelFrom, historian.LabelFromValue, + historian.LabelRuleUID, "test-rule-uid"), + }, + { + name: "query with error outcome filter", + query: Query{ + RuleUID: stringPtr("test-rule-uid"), + Outcome: outcomePtr(v0alpha1.CreateNotificationqueryRequestNotificationOutcomeError), + }, + expected: fmt.Sprintf(`{%s=%q,%s=%q} | json | error != ""`, + historian.LabelFrom, historian.LabelFromValue, + historian.LabelRuleUID, "test-rule-uid"), + }, + { + name: "query with many filters", + query: Query{ + RuleUID: stringPtr("test-rule-uid"), + Receiver: stringPtr("email-receiver"), + Status: createStatusPtr(v0alpha1.CreateNotificationqueryRequestNotificationStatusResolved), + Outcome: outcomePtr(v0alpha1.CreateNotificationqueryRequestNotificationOutcomeSuccess), + }, + expected: fmt.Sprintf(`{%s=%q,%s=%q} | json | receiver = "email-receiver" | status = "resolved" | error = ""`, + historian.LabelFrom, historian.LabelFromValue, + historian.LabelRuleUID, "test-rule-uid"), + }, + { + name: "query with group label matcher", + query: Query{ + GroupLabels: &Matchers{{Type: "=", Label: "foo", Value: "bar"}}, + }, + expected: fmt.Sprintf(`{%s=%q} | json | groupLabels_foo = "bar"`, + historian.LabelFrom, historian.LabelFromValue), + }, + { + name: "query with many group label matchers", + query: Query{ + GroupLabels: &Matchers{ + {Type: "=", Label: "f1", Value: "b1"}, + {Type: "!=", Label: "f2", Value: "b2"}, + {Type: "=~", Label: "f3", Value: "b3"}, + {Type: "!~", Label: "f4", Value: "b4"}, + }, + }, + expected: fmt.Sprintf(`{%s=%q} | json | groupLabels_f1 = "b1" | groupLabels_f2 != "b2"`+ + ` | groupLabels_f3 =~ "b3" | groupLabels_f4 !~ "b4"`, + historian.LabelFrom, historian.LabelFromValue), + }, + { + name: "query with invalid group label with space", + query: Query{ + GroupLabels: &Matchers{{Type: "=", Label: "fo o", Value: "bar"}}, + }, + experr: ErrInvalidQuery, + }, + { + name: "query with invalid group label starting with number", + query: Query{ + GroupLabels: &Matchers{{Type: "=", Label: "1foo", Value: "bar"}}, + }, + experr: ErrInvalidQuery, + }, + { + name: "query with invalid group label with attempted injection", + query: Query{ + GroupLabels: &Matchers{{Type: "=", Label: "\" = \"ship\"", Value: "bar"}}, + }, + experr: ErrInvalidQuery, + }, + { + name: "query with invalid group operator", + query: Query{ + GroupLabels: &Matchers{{Type: "|=", Label: "foo", Value: "bar"}}, + }, + experr: ErrInvalidQuery, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result, err := buildQuery(tt.query) + if tt.experr != nil { + require.ErrorIs(t, err, tt.experr) + } else { + require.NoError(t, err) + assert.Equal(t, tt.expected, result) + } + }) + } +} + +func TestParseLokiEntry(t *testing.T) { + now := time.Now().UTC() + timestamp := now + + tests := []struct { + name string + sample lokiclient.Sample + wantErr bool + want Entry + }{ + { + name: "valid entry with success outcome", + sample: lokiclient.Sample{ + T: timestamp, + V: createLokiEntryJSON(t, historian.NotificationHistoryLokiEntry{ + SchemaVersion: 1, + Receiver: "test-receiver", + Status: "firing", + Error: "", + GroupKey: "key:thing", + GroupLabels: map[string]string{ + "alertname": "test-alert", + }, + Alerts: []historian.NotificationHistoryLokiEntryAlert{ + { + Status: "firing", + Labels: map[string]string{ + "severity": "critical", + }, + Annotations: map[string]string{ + "summary": "Test alert", + }, + StartsAt: now, + EndsAt: now.Add(1 * time.Hour), + }, + }, + Retry: false, + Duration: 100, + PipelineTime: now, + }), + }, + wantErr: false, + want: Entry{ + Timestamp: timestamp, + Receiver: "test-receiver", + Status: Status("firing"), + Outcome: OutcomeSuccess, + GroupKey: "key:thing", + GroupLabels: map[string]string{ + "alertname": "test-alert", + }, + Alerts: []EntryAlert{ + { + Status: "firing", + Labels: map[string]string{ + "severity": "critical", + }, + Annotations: map[string]string{ + "summary": "Test alert", + }, + StartsAt: now, + EndsAt: now.Add(1 * time.Hour), + }, + }, + Retry: false, + Error: nil, + Duration: 100, + PipelineTime: now, + }, + }, + { + name: "valid entry with error outcome", + sample: lokiclient.Sample{ + T: timestamp, + V: createLokiEntryJSON(t, historian.NotificationHistoryLokiEntry{ + SchemaVersion: 1, + Receiver: "test-receiver", + Status: "firing", + Error: "notification failed", + GroupKey: "key:thing", + GroupLabels: map[string]string{}, + Alerts: []historian.NotificationHistoryLokiEntryAlert{}, + PipelineTime: now, + }), + }, + wantErr: false, + want: Entry{ + Timestamp: timestamp, + Receiver: "test-receiver", + Status: Status("firing"), + Outcome: OutcomeError, + GroupKey: "key:thing", + GroupLabels: map[string]string{}, + Alerts: []EntryAlert{}, + Error: stringPtr("notification failed"), + PipelineTime: now, + }, + }, + { + name: "entry with nil group labels", + sample: lokiclient.Sample{ + T: timestamp, + V: createLokiEntryJSONWithNilLabels(t, now), + }, + wantErr: false, + want: Entry{ + Timestamp: timestamp, + Receiver: "test-receiver", + Status: Status("firing"), + Outcome: OutcomeSuccess, + GroupLabels: map[string]string{}, + Alerts: []EntryAlert{}, + PipelineTime: now, + }, + }, + { + name: "invalid JSON", + sample: lokiclient.Sample{ + T: timestamp, + V: "invalid json", + }, + wantErr: true, + }, + { + name: "unsupported schema version", + sample: lokiclient.Sample{ + T: timestamp, + V: createLokiEntryJSON(t, historian.NotificationHistoryLokiEntry{ + SchemaVersion: 99, + Receiver: "test-receiver", + PipelineTime: now, + }), + }, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := parseLokiEntry(tt.sample) + if tt.wantErr { + assert.Error(t, err) + return + } + + require.NoError(t, err) + assert.Equal(t, tt.want.Timestamp, got.Timestamp) + assert.Equal(t, tt.want.Receiver, got.Receiver) + assert.Equal(t, tt.want.Status, got.Status) + assert.Equal(t, tt.want.Outcome, got.Outcome) + assert.Equal(t, tt.want.GroupKey, got.GroupKey) + assert.Equal(t, tt.want.GroupLabels, got.GroupLabels) + assert.Equal(t, tt.want.Retry, got.Retry) + assert.Equal(t, tt.want.Duration, got.Duration) + assert.Equal(t, tt.want.PipelineTime, got.PipelineTime) + + if tt.want.Error != nil { + require.NotNil(t, got.Error) + assert.Equal(t, *tt.want.Error, *got.Error) + } else { + assert.Nil(t, got.Error) + } + + assert.Equal(t, len(tt.want.Alerts), len(got.Alerts)) + for i := range tt.want.Alerts { + assert.Equal(t, tt.want.Alerts[i].Status, got.Alerts[i].Status) + assert.Equal(t, tt.want.Alerts[i].Labels, got.Alerts[i].Labels) + assert.Equal(t, tt.want.Alerts[i].Annotations, got.Alerts[i].Annotations) + assert.Equal(t, tt.want.Alerts[i].StartsAt, got.Alerts[i].StartsAt) + assert.Equal(t, tt.want.Alerts[i].EndsAt, got.Alerts[i].EndsAt) + } + }) + } +} + +func TestLokiReader_RunQuery(t *testing.T) { + now := time.Now().UTC() + + entry1Time := now.Add(-3 * time.Hour) + entry2Time := now.Add(-2 * time.Hour) + entry3Time := now.Add(-1 * time.Hour) + + mockResponse := lokiclient.QueryRes{ + Data: lokiclient.QueryData{ + Result: []lokiclient.Stream{ + { + Values: []lokiclient.Sample{ + { + T: entry1Time, + V: createLokiEntryJSON(t, historian.NotificationHistoryLokiEntry{ + SchemaVersion: 1, + Receiver: "receiver-1", + Status: "firing", + GroupLabels: map[string]string{}, + Alerts: []historian.NotificationHistoryLokiEntryAlert{}, + PipelineTime: now, + }), + }, + { + T: entry3Time, + V: createLokiEntryJSON(t, historian.NotificationHistoryLokiEntry{ + SchemaVersion: 1, + Receiver: "receiver-3", + Status: "firing", + GroupLabels: map[string]string{}, + Alerts: []historian.NotificationHistoryLokiEntryAlert{}, + PipelineTime: now, + }), + }, + }, + }, + { + Values: []lokiclient.Sample{ + { + T: entry2Time, + V: createLokiEntryJSON(t, historian.NotificationHistoryLokiEntry{ + SchemaVersion: 1, + Receiver: "receiver-2", + Status: "firing", + GroupLabels: map[string]string{}, + Alerts: []historian.NotificationHistoryLokiEntryAlert{}, + PipelineTime: now, + }), + }, + }, + }, + }, + }, + } + + mockClient := &mockLokiClient{} + mockClient.On("RangeQuery", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything). + Return(mockResponse, nil) + + reader := &LokiReader{ + client: mockClient, + logger: &logging.NoOpLogger{}, + } + + entries, err := reader.runQuery(context.Background(), "test query", now.Add(-6*time.Hour), now, 1000) + require.NoError(t, err) + require.Len(t, entries, 3) + + mockClient.AssertExpectations(t) + + assert.Equal(t, "receiver-3", entries[0].Receiver) + assert.Equal(t, "receiver-2", entries[1].Receiver) + assert.Equal(t, "receiver-1", entries[2].Receiver) + assert.Equal(t, entries[0].Timestamp, entry3Time) + assert.Equal(t, entries[1].Timestamp, entry2Time) + assert.Equal(t, entries[2].Timestamp, entry1Time) +} + +// Helper functions + +func stringPtr(s string) *string { + return &s +} + +func int64Ptr(i int64) *int64 { + return &i +} + +func timePtr(t time.Time) *time.Time { + return &t +} + +func createStatusPtr(s v0alpha1.CreateNotificationqueryRequestNotificationStatus) *v0alpha1.CreateNotificationqueryRequestNotificationStatus { + return &s +} + +func outcomePtr(o v0alpha1.CreateNotificationqueryRequestNotificationOutcome) *v0alpha1.CreateNotificationqueryRequestNotificationOutcome { + return &o +} + +func createMockLokiResponse(timestamp time.Time) lokiclient.QueryRes { + return lokiclient.QueryRes{ + Data: lokiclient.QueryData{ + Result: []lokiclient.Stream{ + { + Values: []lokiclient.Sample{ + { + T: timestamp, + V: createLokiEntryJSON(nil, historian.NotificationHistoryLokiEntry{ + SchemaVersion: 1, + Receiver: "test-receiver", + Status: "firing", + Error: "", + GroupKey: "key:thing", + GroupLabels: map[string]string{ + "alertname": "test-alert", + }, + Alerts: []historian.NotificationHistoryLokiEntryAlert{ + { + Status: "firing", + Labels: map[string]string{ + "severity": "critical", + }, + Annotations: map[string]string{ + "summary": "Test alert", + }, + StartsAt: timestamp, + EndsAt: timestamp.Add(1 * time.Hour), + }, + }, + Retry: false, + Duration: 100, + PipelineTime: timestamp, + }), + }, + }, + }, + }, + }, + } +} + +func createLokiEntryJSON(t *testing.T, entry historian.NotificationHistoryLokiEntry) string { + data, err := json.Marshal(entry) + if t != nil && err != nil { + t.Fatalf("failed to marshal entry: %v", err) + } + return string(data) +} + +func createLokiEntryJSONWithNilLabels(t *testing.T, timestamp time.Time) string { + // Create JSON with explicit null for group_labels + jsonStr := fmt.Sprintf(`{ + "schemaVersion": 1, + "receiver": "test-receiver", + "status": "firing", + "error": "", + "groupLabels": null, + "alerts": [], + "retry": false, + "duration": 0, + "pipelineTime": "%s" + }`, timestamp.Format(time.RFC3339Nano)) + return jsonStr +} diff --git a/apps/alerting/historian/pkg/app/notification/notification.go b/apps/alerting/historian/pkg/app/notification/notification.go new file mode 100644 index 00000000000..2a28e258de2 --- /dev/null +++ b/apps/alerting/historian/pkg/app/notification/notification.go @@ -0,0 +1,77 @@ +package notification + +import ( + "context" + "encoding/json" + "errors" + "net/http" + + "github.com/grafana/grafana-app-sdk/app" + "github.com/grafana/grafana-app-sdk/logging" + "github.com/prometheus/client_golang/prometheus" + "go.opentelemetry.io/otel/trace" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/grafana/grafana/apps/alerting/historian/pkg/apis/alertinghistorian/v0alpha1" + "github.com/grafana/grafana/apps/alerting/historian/pkg/app/config" +) + +type Notification struct { + loki *LokiReader + logger logging.Logger +} + +func New(cfg config.NotificationConfig, reg prometheus.Registerer, logger logging.Logger, tracer trace.Tracer) *Notification { + if !cfg.Enabled { + return &Notification{} + } + return &Notification{ + loki: NewLokiReader(cfg.Loki, reg, logger, tracer), + logger: logger, + } +} + +func (n *Notification) QueryHandler(ctx context.Context, writer app.CustomRouteResponseWriter, request *app.CustomRouteRequest) error { + if n.loki == nil { + return &apierrors.StatusError{ + ErrStatus: metav1.Status{ + Status: metav1.StatusFailure, + Code: http.StatusUnprocessableEntity, + Message: "notification history disabled", + }} + } + + var body v0alpha1.CreateNotificationqueryRequestBody + err := json.NewDecoder(request.Body).Decode(&body) + if err != nil { + return &apierrors.StatusError{ + ErrStatus: metav1.Status{ + Status: metav1.StatusFailure, + Code: http.StatusBadRequest, + Message: err.Error(), + }} + } + + response, err := n.loki.Query(ctx, body) + if err != nil { + if errors.Is(err, ErrInvalidQuery) { + return &apierrors.StatusError{ + ErrStatus: metav1.Status{ + Status: metav1.StatusFailure, + Code: http.StatusBadRequest, + Message: err.Error(), + }} + } + return &apierrors.StatusError{ + ErrStatus: metav1.Status{ + Status: metav1.StatusFailure, + Code: http.StatusInternalServerError, + Message: err.Error(), + }} + } + + writer.Header().Add("Content-Type", "application/json") + writer.WriteHeader(http.StatusOK) + return json.NewEncoder(writer).Encode(response) +} diff --git a/apps/alerting/historian/pkg/app/notification/types.go b/apps/alerting/historian/pkg/app/notification/types.go new file mode 100644 index 00000000000..4a784a5ac25 --- /dev/null +++ b/apps/alerting/historian/pkg/app/notification/types.go @@ -0,0 +1,28 @@ +package notification + +import ( + "github.com/grafana/grafana/apps/alerting/historian/pkg/apis/alertinghistorian/v0alpha1" +) + +// Aliases to shorten names. +// In the future, we may have to make these distinct types from the API, +// to handle differences in API versions, but that's not necessary for now. + +type Query = v0alpha1.CreateNotificationqueryRequestBody + +type Matchers = v0alpha1.CreateNotificationqueryRequestMatchers + +type QueryResult = v0alpha1.CreateNotificationquery + +type Status = v0alpha1.NotificationStatus + +type Outcome = v0alpha1.NotificationOutcome + +const ( + OutcomeSuccess = v0alpha1.NotificationOutcomeSuccess + OutcomeError = v0alpha1.NotificationOutcomeError +) + +type Entry = v0alpha1.NotificationEntry + +type EntryAlert = v0alpha1.NotificationEntryAlert diff --git a/apps/iam/go.mod b/apps/iam/go.mod index 78da5f7ed9a..784b98bba4e 100644 --- a/apps/iam/go.mod +++ b/apps/iam/go.mod @@ -426,6 +426,7 @@ require ( golang.org/x/text v0.31.0 // indirect golang.org/x/time v0.14.0 // indirect golang.org/x/tools v0.39.0 // indirect + golang.org/x/tools/godoc v0.1.0-deprecated // indirect golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect gonum.org/v1/gonum v0.16.0 // indirect diff --git a/go.mod b/go.mod index 1c6dd24b962..57cebdcede4 100644 --- a/go.mod +++ b/go.mod @@ -633,7 +633,6 @@ require ( golang.org/x/sys v0.38.0 // indirect golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54 // indirect golang.org/x/term v0.37.0 // indirect - golang.org/x/tools/godoc v0.1.0-deprecated // indirect golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect google.golang.org/genproto v0.0.0-20250715232539-7130f93afb79 // indirect diff --git a/pkg/registry/apps/alerting/historian/register.go b/pkg/registry/apps/alerting/historian/register.go index 6125469ce10..7fc2176d758 100644 --- a/pkg/registry/apps/alerting/historian/register.go +++ b/pkg/registry/apps/alerting/historian/register.go @@ -11,6 +11,7 @@ import ( historianAppConfig "github.com/grafana/grafana/apps/alerting/historian/pkg/app/config" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/services/ngalert" + "github.com/grafana/grafana/pkg/services/ngalert/lokiconfig" "github.com/grafana/grafana/pkg/setting" ) @@ -28,6 +29,24 @@ func RegisterAppInstaller( ) (*AlertingHistorianAppInstaller, error) { appSpecificConfig := historianAppConfig.RuntimeConfig{} + // If we're provided some config, then we can enable some things. + if cfg != nil { + nhCfg := cfg.UnifiedAlerting.NotificationHistory + + // Only parse config if enabled. + if nhCfg.Enabled { + lokiConfig, err := lokiconfig.NewLokiConfig(cfg.UnifiedAlerting.NotificationHistory.LokiSettings) + if err != nil { + return nil, err + } + + appSpecificConfig.Notification = historianAppConfig.NotificationConfig{ + Enabled: nhCfg.Enabled, + Loki: lokiConfig, + } + } + } + // If we're provided an AlertNG, then call back into that for things we need. // This is a temporary whilst building out the app; we should not depend on it. if ng != nil { From d92898888ce67597c6bca3431c32cbea83b1ff5b Mon Sep 17 00:00:00 2001 From: Gareth Date: Thu, 4 Dec 2025 21:34:52 +0900 Subject: [PATCH 024/110] OpenTSDB: Fix metric dropdown autocomplete (#114825) --- .../plugins/datasource/opentsdb/components/MetricSection.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/plugins/datasource/opentsdb/components/MetricSection.tsx b/public/app/plugins/datasource/opentsdb/components/MetricSection.tsx index 5c62f2ecf06..2ca39e4a19d 100644 --- a/public/app/plugins/datasource/opentsdb/components/MetricSection.tsx +++ b/public/app/plugins/datasource/opentsdb/components/MetricSection.tsx @@ -31,7 +31,7 @@ export function MetricSection({ query, onChange, onRunQuery, suggestMetrics, agg placeholder="Metric name" allowCustomValue loadOptions={metricSearch} - defaultOptions={[]} + defaultOptions={true} onChange={({ value }) => { if (value) { onChange({ ...query, metric: value }); From ff4228bd58aeb76158d8a76983a55014f08886b3 Mon Sep 17 00:00:00 2001 From: Andreas Christou Date: Thu, 4 Dec 2025 13:48:55 +0100 Subject: [PATCH 025/110] Azure: Improved column handling in logs query builder (#114667) * Add parameter type field * Use parameterType to filter columns for aggregation funcs * Add tests for aggregate components --- .../x/AzureMonitorDataQuery_types.gen.ts | 9 + .../kinds/dataquery/types_dataquery_gen.go | 9 + .../LogsQueryBuilder/AggregateItem.test.tsx | 191 ++++++++++++ .../LogsQueryBuilder/AggregateItem.tsx | 11 +- .../AggregationSection.test.tsx | 293 ++++++++++++++++++ .../LogsQueryBuilder/AggregationSection.tsx | 22 +- .../components/LogsQueryBuilder/utils.ts | 22 +- .../datasource/azuremonitor/dataquery.cue | 3 + .../datasource/azuremonitor/dataquery.gen.ts | 9 + 9 files changed, 556 insertions(+), 13 deletions(-) create mode 100644 public/app/plugins/datasource/azuremonitor/components/LogsQueryBuilder/AggregateItem.test.tsx create mode 100644 public/app/plugins/datasource/azuremonitor/components/LogsQueryBuilder/AggregationSection.test.tsx diff --git a/packages/grafana-schema/src/raw/composable/azuremonitor/dataquery/x/AzureMonitorDataQuery_types.gen.ts b/packages/grafana-schema/src/raw/composable/azuremonitor/dataquery/x/AzureMonitorDataQuery_types.gen.ts index 55ad5313063..d889801bfb4 100644 --- a/packages/grafana-schema/src/raw/composable/azuremonitor/dataquery/x/AzureMonitorDataQuery_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/azuremonitor/dataquery/x/AzureMonitorDataQuery_types.gen.ts @@ -329,8 +329,17 @@ export enum BuilderQueryEditorOrderByOptions { Desc = 'desc', } +export enum BuilderQueryEditorReduceParameterTypes { + Generic = 'generic', + Numeric = 'numeric', +} + export interface BuilderQueryEditorProperty { name: string; + /** + * Optional parameter type for function properties + */ + parameterType?: BuilderQueryEditorReduceParameterTypes; type: BuilderQueryEditorPropertyType; } diff --git a/pkg/tsdb/azuremonitor/kinds/dataquery/types_dataquery_gen.go b/pkg/tsdb/azuremonitor/kinds/dataquery/types_dataquery_gen.go index e1deb1f008b..47594fbd55c 100644 --- a/pkg/tsdb/azuremonitor/kinds/dataquery/types_dataquery_gen.go +++ b/pkg/tsdb/azuremonitor/kinds/dataquery/types_dataquery_gen.go @@ -223,6 +223,8 @@ func NewBuilderQueryEditorPropertyExpression() *BuilderQueryEditorPropertyExpres type BuilderQueryEditorProperty struct { Type BuilderQueryEditorPropertyType `json:"type"` Name string `json:"name"` + // Optional parameter type for function properties + ParameterType *BuilderQueryEditorReduceParameterTypes `json:"parameterType,omitempty"` } // NewBuilderQueryEditorProperty creates a new BuilderQueryEditorProperty object. @@ -242,6 +244,13 @@ const ( BuilderQueryEditorPropertyTypeInterval BuilderQueryEditorPropertyType = "interval" ) +type BuilderQueryEditorReduceParameterTypes string + +const ( + BuilderQueryEditorReduceParameterTypesGeneric BuilderQueryEditorReduceParameterTypes = "generic" + BuilderQueryEditorReduceParameterTypesNumeric BuilderQueryEditorReduceParameterTypes = "numeric" +) + type BuilderQueryEditorExpressionType string const ( diff --git a/public/app/plugins/datasource/azuremonitor/components/LogsQueryBuilder/AggregateItem.test.tsx b/public/app/plugins/datasource/azuremonitor/components/LogsQueryBuilder/AggregateItem.test.tsx new file mode 100644 index 00000000000..3c472fcb69e --- /dev/null +++ b/public/app/plugins/datasource/azuremonitor/components/LogsQueryBuilder/AggregateItem.test.tsx @@ -0,0 +1,191 @@ +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; + +import { + BuilderQueryEditorExpressionType, + BuilderQueryEditorPropertyType, + BuilderQueryEditorReduceExpression, + BuilderQueryEditorReduceParameterTypes, +} from '../../dataquery.gen'; + +import AggregateItem from './AggregateItem'; + +describe('AggregateItem', () => { + const mockColumns = [ + { label: 'TimeGenerated', value: 'TimeGenerated' }, + { label: 'Level', value: 'Level' }, + { label: 'Message', value: 'Message' }, + ]; + + const mockTemplateVariables = { label: '$variable', value: '$variable' }; + + const defaultAggregate: BuilderQueryEditorReduceExpression = { + reduce: { + name: 'sum', + type: BuilderQueryEditorPropertyType.Function, + parameterType: BuilderQueryEditorReduceParameterTypes.Numeric, + }, + property: { + name: 'TimeGenerated', + type: BuilderQueryEditorPropertyType.String, + }, + }; + + const defaultProps = { + aggregate: defaultAggregate, + columns: mockColumns, + onChange: jest.fn(), + onDelete: jest.fn(), + templateVariableOptions: mockTemplateVariables, + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('renders aggregate function select (with column field)', () => { + render(); + expect(screen.getByLabelText('Aggregate function')).toBeInTheDocument(); + expect(screen.getByLabelText('Column')).toBeInTheDocument(); + }); + + it('does not render column select for count aggregates', () => { + const countAggregate = { + ...defaultAggregate, + reduce: { + name: 'count', + type: BuilderQueryEditorPropertyType.Function, + parameterType: BuilderQueryEditorReduceParameterTypes.Generic, + }, + }; + render(); + expect(screen.queryByLabelText('Column')).not.toBeInTheDocument(); + }); + + it('renders percentile input and OF label for percentile aggregate', () => { + const percentileAggregate: BuilderQueryEditorReduceExpression = { + reduce: { + name: 'percentile', + type: BuilderQueryEditorPropertyType.Function, + parameterType: BuilderQueryEditorReduceParameterTypes.Numeric, + }, + parameters: [ + { + type: BuilderQueryEditorExpressionType.Function_parameter, + fieldType: BuilderQueryEditorPropertyType.Number, + value: '95', + }, + { + type: BuilderQueryEditorExpressionType.Function_parameter, + fieldType: BuilderQueryEditorPropertyType.String, + value: 'TimeGenerated', + }, + ], + property: { + name: 'TimeGenerated', + type: BuilderQueryEditorPropertyType.String, + }, + }; + render(); + expect(screen.getByDisplayValue('95')).toBeInTheDocument(); + expect(screen.getByText('OF')).toBeInTheDocument(); + }); + + it('calls onChange when aggregate function changes', async () => { + render(); + + const select = screen.getByLabelText('Aggregate function'); + await userEvent.click(select); + + const avgOption = await screen.findByText('avg'); + await userEvent.click(avgOption); + + expect(defaultProps.onChange).toHaveBeenCalledWith( + expect.objectContaining({ + reduce: expect.objectContaining({ + name: 'avg', + parameterType: BuilderQueryEditorReduceParameterTypes.Numeric, + }), + }) + ); + }); + + it('calls onChange when column changes', async () => { + render(); + + const columnSelect = screen.getByLabelText('Column'); + await userEvent.click(columnSelect); + const levelOption = await screen.findByText('Level'); + await userEvent.click(levelOption); + + expect(defaultProps.onChange).toHaveBeenCalledWith( + expect.objectContaining({ + property: expect.objectContaining({ + name: 'Level', + }), + }) + ); + }); + + it('calls onChange when percentile value changes', async () => { + const percentileAggregate: BuilderQueryEditorReduceExpression = { + reduce: { + name: 'percentile', + type: BuilderQueryEditorPropertyType.Function, + parameterType: BuilderQueryEditorReduceParameterTypes.Numeric, + }, + parameters: [ + { + type: BuilderQueryEditorExpressionType.Function_parameter, + fieldType: BuilderQueryEditorPropertyType.Number, + value: '95', + }, + { + type: BuilderQueryEditorExpressionType.Function_parameter, + fieldType: BuilderQueryEditorPropertyType.String, + value: 'TimeGenerated', + }, + ], + }; + render(); + + const percentileInput = screen.getByDisplayValue('95'); + await userEvent.clear(percentileInput); + await userEvent.type(percentileInput, '99'); + + expect(defaultProps.onChange).toHaveBeenCalledWith( + expect.objectContaining({ + parameters: expect.arrayContaining([expect.objectContaining({ value: '99' })]), + }) + ); + }); + + it('calls onDelete when delete button clicked', async () => { + render(); + + const deleteButton = screen.getByLabelText('Remove'); + await userEvent.click(deleteButton); + expect(defaultProps.onDelete).toHaveBeenCalledTimes(1); + }); + + it('includes template variables in column options', async () => { + render(); + + const columnSelect = screen.getByLabelText('Column'); + await userEvent.click(columnSelect); + expect(await screen.findByText('$variable')).toBeInTheDocument(); + }); + + it('handles array of template variables', async () => { + const arrayTemplateVars = [ + { label: '$var1', value: '$var1' }, + { label: '$var2', value: '$var2' }, + ]; + render(); + + const columnSelect = screen.getByLabelText('Column'); + await userEvent.click(columnSelect); + + expect(await screen.findByText('$var1')).toBeInTheDocument(); + }); +}); diff --git a/public/app/plugins/datasource/azuremonitor/components/LogsQueryBuilder/AggregateItem.tsx b/public/app/plugins/datasource/azuremonitor/components/LogsQueryBuilder/AggregateItem.tsx index 867098f7574..2eacf01b8bd 100644 --- a/public/app/plugins/datasource/azuremonitor/components/LogsQueryBuilder/AggregateItem.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/LogsQueryBuilder/AggregateItem.tsx @@ -9,6 +9,7 @@ import { BuilderQueryEditorExpressionType, BuilderQueryEditorPropertyType, BuilderQueryEditorReduceExpression, + BuilderQueryEditorReduceParameterTypes, } from '../../dataquery.gen'; import { aggregateOptions, inputFieldSize } from './utils'; @@ -65,8 +66,16 @@ const AggregateItem: React.FC = ({ }; const handleAggregateChange = (funcName?: string) => { + const functionParameterType = + aggregateOptions.find((option) => option.value === (funcName || ''))?.parameterType || + BuilderQueryEditorReduceParameterTypes.Generic; + updateAggregate({ - reduce: { name: funcName || '', type: BuilderQueryEditorPropertyType.Function }, + reduce: { + name: funcName || '', + type: BuilderQueryEditorPropertyType.Function, + parameterType: functionParameterType, + }, }); }; diff --git a/public/app/plugins/datasource/azuremonitor/components/LogsQueryBuilder/AggregationSection.test.tsx b/public/app/plugins/datasource/azuremonitor/components/LogsQueryBuilder/AggregationSection.test.tsx new file mode 100644 index 00000000000..ea073e1e8e5 --- /dev/null +++ b/public/app/plugins/datasource/azuremonitor/components/LogsQueryBuilder/AggregationSection.test.tsx @@ -0,0 +1,293 @@ +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; + +import { + AzureQueryType, + BuilderQueryEditorExpressionType, + BuilderQueryEditorPropertyType, + BuilderQueryEditorReduceExpression, + BuilderQueryEditorReduceParameterTypes, +} from '../../dataquery.gen'; +import { AzureMonitorQuery } from '../../types/query'; + +import { AggregateSection } from './AggregationSection'; + +describe('AggregationSection', () => { + const mockAllColumns = [ + { name: 'TimeGenerated', type: 'datetime' }, + { name: 'Level', type: 'string' }, + { name: 'Count', type: 'int' }, + { name: 'Duration', type: 'real' }, + ]; + + const mockTemplateVariables = { label: '$variable', value: '$variable' }; + + const createMockQuery = (reduce?: BuilderQueryEditorReduceExpression[]): AzureMonitorQuery => ({ + refId: 'A', + queryType: AzureQueryType.LogAnalytics, + azureLogAnalytics: { + builderQuery: { + from: { + type: BuilderQueryEditorExpressionType.Property, + property: { type: BuilderQueryEditorPropertyType.String, name: 'AppRequests' }, + }, + columns: { + type: BuilderQueryEditorExpressionType.Property, + columns: [], + }, + reduce: { + type: BuilderQueryEditorExpressionType.Reduce, + expressions: reduce || [], + }, + where: { + type: BuilderQueryEditorExpressionType.And, + expressions: [], + }, + groupBy: { + type: BuilderQueryEditorExpressionType.Group_by, + expressions: [], + }, + }, + }, + }); + + const defaultProps = { + query: createMockQuery(), + allColumns: mockAllColumns, + templateVariableOptions: mockTemplateVariables, + buildAndUpdateQuery: jest.fn(), + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('renders the aggregate section', () => { + render(); + expect(screen.getByTestId('aggregate-section')).toBeInTheDocument(); + }); + + it('renders empty list when no aggregates exist', () => { + render(); + const addButton = screen.getByRole('button', { name: /add/i }); + expect(addButton).toBeInTheDocument(); + }); + + it('renders existing aggregates', () => { + const existingAggregates: BuilderQueryEditorReduceExpression[] = [ + { + reduce: { + name: 'sum', + type: BuilderQueryEditorPropertyType.Function, + parameterType: BuilderQueryEditorReduceParameterTypes.Numeric, + }, + property: { + name: 'Count', + type: BuilderQueryEditorPropertyType.String, + }, + }, + { + reduce: { + name: 'avg', + type: BuilderQueryEditorPropertyType.Function, + parameterType: BuilderQueryEditorReduceParameterTypes.Numeric, + }, + property: { + name: 'Duration', + type: BuilderQueryEditorPropertyType.String, + }, + }, + ]; + + const queryWithAggregates = createMockQuery(existingAggregates); + render(); + + expect(screen.getAllByLabelText('Aggregate function')).toHaveLength(2); + }); + + it('calls buildAndUpdateQuery when aggregate is added', async () => { + render(); + + const addButton = screen.getByRole('button', { name: /add/i }); + await userEvent.click(addButton); + + expect(defaultProps.buildAndUpdateQuery).toHaveBeenCalledWith({ + reduce: expect.arrayContaining([expect.objectContaining({})]), + }); + }); + + it('calls buildAndUpdateQuery when aggregate is deleted', async () => { + const existingAggregates: BuilderQueryEditorReduceExpression[] = [ + { + reduce: { + name: 'sum', + type: BuilderQueryEditorPropertyType.Function, + parameterType: BuilderQueryEditorReduceParameterTypes.Numeric, + }, + property: { + name: 'Count', + type: BuilderQueryEditorPropertyType.String, + }, + }, + ]; + const avgAggregate = { + reduce: { + name: 'avg', + type: BuilderQueryEditorPropertyType.Function, + parameterType: BuilderQueryEditorReduceParameterTypes.Numeric, + }, + property: { + name: 'Duration', + type: BuilderQueryEditorPropertyType.String, + }, + }; + existingAggregates.push(avgAggregate); + + const queryWithAggregates = createMockQuery(existingAggregates); + render(); + + const deleteButton = (await screen.findAllByLabelText('Remove'))[0]; + await userEvent.click(deleteButton); + + expect(defaultProps.buildAndUpdateQuery).toHaveBeenCalledWith({ + reduce: [avgAggregate], + }); + expect(screen.getAllByLabelText('Aggregate function')).toHaveLength(1); + }); + + it('provides numeric columns for numeric aggregate functions', async () => { + const numericAggregate: BuilderQueryEditorReduceExpression[] = [ + { + reduce: { + name: 'sum', + type: BuilderQueryEditorPropertyType.Function, + parameterType: BuilderQueryEditorReduceParameterTypes.Numeric, + }, + }, + ]; + + const queryWithAggregate = createMockQuery(numericAggregate); + render(); + + const columnSelect = screen.getByLabelText('Column'); + await userEvent.click(columnSelect); + + expect(await screen.getByText('Count')).toBeInTheDocument(); + expect(await screen.getByText('Duration')).toBeInTheDocument(); + + expect(screen.queryByText('Level')).not.toBeInTheDocument(); + }); + + it('provides all columns for generic aggregate functions', async () => { + const genericAggregate: BuilderQueryEditorReduceExpression[] = [ + { + reduce: { + name: 'min', + type: BuilderQueryEditorPropertyType.Function, + parameterType: BuilderQueryEditorReduceParameterTypes.Generic, + }, + }, + ]; + + const queryWithAggregate = createMockQuery(genericAggregate); + render(); + + const columnSelect = screen.getByLabelText('Column'); + await userEvent.click(columnSelect); + + expect(await screen.getByText('TimeGenerated')).toBeInTheDocument(); + expect(await screen.getByText('Level')).toBeInTheDocument(); + expect(await screen.getByText('Count')).toBeInTheDocument(); + expect(await screen.getByText('Duration')).toBeInTheDocument(); + }); + + it('resets aggregates when table changes', () => { + const existingAggregates: BuilderQueryEditorReduceExpression[] = [ + { + reduce: { + name: 'sum', + type: BuilderQueryEditorPropertyType.Function, + parameterType: BuilderQueryEditorReduceParameterTypes.Numeric, + }, + property: { + name: 'Count', + type: BuilderQueryEditorPropertyType.String, + }, + }, + ]; + + const queryWithAggregates = createMockQuery(existingAggregates); + const { rerender } = render(); + + const newQuery = createMockQuery(existingAggregates); + newQuery.azureLogAnalytics!.builderQuery!.from!.property.name = 'AppEvents'; + + rerender(); + + const addButton = screen.getByRole('button', { name: /add/i }); + expect(addButton).toBeInTheDocument(); + }); + + it('uses selected columns when available', async () => { + const aggregate: BuilderQueryEditorReduceExpression[] = [ + { + reduce: { + name: 'sum', + type: BuilderQueryEditorPropertyType.Function, + parameterType: BuilderQueryEditorReduceParameterTypes.Generic, + }, + }, + ]; + + const query = createMockQuery(aggregate); + query.azureLogAnalytics!.builderQuery = { + ...query.azureLogAnalytics!.builderQuery, + columns: { + columns: ['TimeGenerated', 'Level'], + type: BuilderQueryEditorExpressionType.Property, + }, + }; + + render(); + + const columnSelect = screen.getByLabelText('Column'); + await userEvent.click(columnSelect); + + expect(await screen.getByText('TimeGenerated')).toBeInTheDocument(); + expect(await screen.getByText('Level')).toBeInTheDocument(); + + expect(screen.queryByText('Count')).not.toBeInTheDocument(); + expect(screen.queryByText('Duration')).not.toBeInTheDocument(); + }); + + it('falls back to all columns when no columns selected', async () => { + const aggregate: BuilderQueryEditorReduceExpression[] = [ + { + reduce: { + name: 'sum', + type: BuilderQueryEditorPropertyType.Function, + parameterType: BuilderQueryEditorReduceParameterTypes.Generic, + }, + }, + ]; + + const query = createMockQuery(aggregate); + query.azureLogAnalytics!.builderQuery = { + ...query.azureLogAnalytics!.builderQuery, + columns: { + columns: [], + type: BuilderQueryEditorExpressionType.Property, + }, + }; + + render(); + + const columnSelect = screen.getByLabelText('Column'); + await userEvent.click(columnSelect); + + expect(await screen.getByText('TimeGenerated')).toBeInTheDocument(); + expect(await screen.getByText('Level')).toBeInTheDocument(); + expect(await screen.getByText('Count')).toBeInTheDocument(); + expect(await screen.getByText('Duration')).toBeInTheDocument(); + }); +}); diff --git a/public/app/plugins/datasource/azuremonitor/components/LogsQueryBuilder/AggregationSection.tsx b/public/app/plugins/datasource/azuremonitor/components/LogsQueryBuilder/AggregationSection.tsx index 4f3a1050a14..6910792685d 100644 --- a/public/app/plugins/datasource/azuremonitor/components/LogsQueryBuilder/AggregationSection.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/LogsQueryBuilder/AggregationSection.tsx @@ -4,12 +4,12 @@ import { SelectableValue } from '@grafana/data'; import { t } from '@grafana/i18n'; import { EditorField, EditorFieldGroup, EditorList, EditorRow } from '@grafana/plugin-ui'; -import { BuilderQueryEditorReduceExpression } from '../../dataquery.gen'; +import { BuilderQueryEditorReduceExpression, BuilderQueryEditorReduceParameterTypes } from '../../dataquery.gen'; import { AzureLogAnalyticsMetadataColumn } from '../../types/logAnalyticsMetadata'; import { AzureMonitorQuery } from '../../types/query'; import AggregateItem from './AggregateItem'; -import { BuildAndUpdateOptions } from './utils'; +import { BuildAndUpdateOptions, isNumericColumn } from './utils'; interface AggregateSectionProps { query: AzureMonitorQuery; @@ -43,6 +43,10 @@ export const AggregateSection: React.FC = ({ const availableColumns: Array> = builderQuery?.columns?.columns?.length ? builderQuery.columns.columns.map((col) => ({ label: col, value: col })) : allColumns.map((col) => ({ label: col.name, value: col.name })); + const numericColumns: Array> = allColumns.filter(isNumericColumn).map((col) => ({ + label: col.name, + value: col.name, + })); const onChange = (newItems: Array>) => { setAggregates(newItems); @@ -82,7 +86,12 @@ export const AggregateSection: React.FC = ({ @@ -93,6 +102,7 @@ export const AggregateSection: React.FC = ({ function makeRenderAggregate( availableColumns: Array>, + numericColumns: Array>, onDeleteAggregate: (aggregate: BuilderQueryEditorReduceExpression) => void, templateVariableOptions: SelectableValue ) { @@ -105,7 +115,11 @@ function makeRenderAggregate( aggregate={item} onChange={onChange} onDelete={() => onDeleteAggregate(item)} - columns={availableColumns} + columns={ + item.reduce?.name && item.reduce.parameterType === BuilderQueryEditorReduceParameterTypes.Numeric + ? numericColumns + : availableColumns + } templateVariableOptions={templateVariableOptions} /> ); diff --git a/public/app/plugins/datasource/azuremonitor/components/LogsQueryBuilder/utils.ts b/public/app/plugins/datasource/azuremonitor/components/LogsQueryBuilder/utils.ts index 062dac286f9..cb660fb98e9 100644 --- a/public/app/plugins/datasource/azuremonitor/components/LogsQueryBuilder/utils.ts +++ b/public/app/plugins/datasource/azuremonitor/components/LogsQueryBuilder/utils.ts @@ -9,6 +9,7 @@ import { BuilderQueryEditorPropertyExpression, BuilderQueryEditorPropertyType, BuilderQueryEditorReduceExpression, + BuilderQueryEditorReduceParameterTypes, BuilderQueryEditorWhereExpression, BuilderQueryExpression, } from '../../dataquery.gen'; @@ -93,12 +94,17 @@ export interface BuildAndUpdateOptions { } export const aggregateOptions = [ - { label: 'sum', value: 'sum' }, - { label: 'avg', value: 'avg' }, - { label: 'percentile', value: 'percentile' }, - { label: 'count', value: 'count' }, - { label: 'min', value: 'min' }, - { label: 'max', value: 'max' }, - { label: 'dcount', value: 'dcount' }, - { label: 'stdev', value: 'stdev' }, + { label: 'sum', value: 'sum', parameterType: BuilderQueryEditorReduceParameterTypes.Numeric }, + { label: 'avg', value: 'avg', parameterType: BuilderQueryEditorReduceParameterTypes.Numeric }, + { label: 'percentile', value: 'percentile', parameterType: BuilderQueryEditorReduceParameterTypes.Numeric }, + { label: 'stdev', value: 'stdev', parameterType: BuilderQueryEditorReduceParameterTypes.Numeric }, + { label: 'min', value: 'min', parameterType: BuilderQueryEditorReduceParameterTypes.Generic }, + { label: 'max', value: 'max', parameterType: BuilderQueryEditorReduceParameterTypes.Generic }, + { label: 'count', value: 'count', parameterType: BuilderQueryEditorReduceParameterTypes.Generic }, + { label: 'dcount', value: 'dcount', parameterType: BuilderQueryEditorReduceParameterTypes.Generic }, ]; + +export const isNumericColumn = (column: AzureLogAnalyticsMetadataColumn): boolean => { + const numericTypes = ['decimal', 'int', 'long', 'real']; + return numericTypes.includes(column.type); +}; diff --git a/public/app/plugins/datasource/azuremonitor/dataquery.cue b/public/app/plugins/datasource/azuremonitor/dataquery.cue index 4fd52d5f297..403fd1ea72e 100644 --- a/public/app/plugins/datasource/azuremonitor/dataquery.cue +++ b/public/app/plugins/datasource/azuremonitor/dataquery.cue @@ -175,10 +175,13 @@ composableKinds: DataQuery: { #BuilderQueryEditorExpressionType: "property" | "operator" | "reduce" | "function_parameter" | "group_by" | "or" | "and" | "order_by" @cuetsy(kind="enum", memberNames:"Property|Operator|Reduce|FunctionParameter|GroupBy|Or|And|OrderBy") #BuilderQueryEditorPropertyType: "number" | "string" | "boolean" | "datetime" | "time_span" | "function" | "interval" @cuetsy(kind="enum", memberNames:"Number|String|Boolean|Datetime|TimeSpan|Function|Interval") #BuilderQueryEditorOrderByOptions: "asc" | "desc" @cuetsy(kind="enum", memberNames:"Asc|Desc") + #BuilderQueryEditorReduceParameterTypes: "generic" | "numeric" @cuetsy(kind="enum", memberNames:"Asc|Desc") #BuilderQueryEditorProperty: { type: #BuilderQueryEditorPropertyType name: string + // Optional parameter type for function properties + parameterType?: #BuilderQueryEditorReduceParameterTypes } @cuetsy(kind="interface") #BuilderQueryEditorPropertyExpression: { diff --git a/public/app/plugins/datasource/azuremonitor/dataquery.gen.ts b/public/app/plugins/datasource/azuremonitor/dataquery.gen.ts index c4b986970ca..2ed7b87a5ce 100644 --- a/public/app/plugins/datasource/azuremonitor/dataquery.gen.ts +++ b/public/app/plugins/datasource/azuremonitor/dataquery.gen.ts @@ -327,8 +327,17 @@ export enum BuilderQueryEditorOrderByOptions { Desc = 'desc', } +export enum BuilderQueryEditorReduceParameterTypes { + Generic = 'generic', + Numeric = 'numeric', +} + export interface BuilderQueryEditorProperty { name: string; + /** + * Optional parameter type for function properties + */ + parameterType?: BuilderQueryEditorReduceParameterTypes; type: BuilderQueryEditorPropertyType; } From c88314296b7c31a7b547e3891cb2c483b806d326 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Irene=20Rodr=C3=ADguez?= Date: Thu, 4 Dec 2025 14:51:59 +0100 Subject: [PATCH 026/110] Add Zabbix to the list of unsupported data sources (#114823) Fixes: https://github.com/grafana/support-escalations/issues/19739 --- .../share-dashboards-panels/shared-dashboards/index.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/sources/visualizations/dashboards/share-dashboards-panels/shared-dashboards/index.md b/docs/sources/visualizations/dashboards/share-dashboards-panels/shared-dashboards/index.md index 789d6617c18..a96efda3edf 100644 --- a/docs/sources/visualizations/dashboards/share-dashboards-panels/shared-dashboards/index.md +++ b/docs/sources/visualizations/dashboards/share-dashboards-panels/shared-dashboards/index.md @@ -250,6 +250,7 @@ guaranteed because plugin developers can override this functionality. The follow - Graphite - Google Sheets - Tempo +- Zabbix ### Unconfirmed From 994e1dd58fb523d6a0fa4fa5b38d70c3c858114c Mon Sep 17 00:00:00 2001 From: Will Assis <35489495+gassiss@users.noreply.github.com> Date: Thu, 4 Dec 2025 09:21:17 -0500 Subject: [PATCH 027/110] unified-storage: sqlkv migrations (#114790) * unified-storage: create resource_events table and add key_path column to resource_history * Update resource_history_insert template * update test snapshots * use latin encoding for key_path and bump size to 2048 --- .../unified/sql/data/resource_history_insert.sql | 6 ++++-- .../unified/sql/db/migrations/resource_mig.go | 13 +++++++++++++ pkg/storage/unified/sql/queries.go | 1 + ..._history_insert-insert into resource_history.sql | 6 ++++-- ..._history_insert-insert into resource_history.sql | 6 ++++-- ..._history_insert-insert into resource_history.sql | 6 ++++-- 6 files changed, 30 insertions(+), 8 deletions(-) diff --git a/pkg/storage/unified/sql/data/resource_history_insert.sql b/pkg/storage/unified/sql/data/resource_history_insert.sql index 4ac3cc03547..5a968ef9033 100644 --- a/pkg/storage/unified/sql/data/resource_history_insert.sql +++ b/pkg/storage/unified/sql/data/resource_history_insert.sql @@ -12,7 +12,8 @@ INSERT INTO {{ .Ident "resource_history" }} {{ .Ident "previous_resource_version"}}, {{ .Ident "generation"}}, {{ .Ident "value" }}, - {{ .Ident "action" }} + {{ .Ident "action" }}, + {{ .Ident "key_path" }} ) VALUES ( @@ -28,6 +29,7 @@ INSERT INTO {{ .Ident "resource_history" }} {{ .Arg .WriteEvent.PreviousRV }}, {{ .Arg .Generation }}, {{ .Arg .WriteEvent.Value }}, - {{ .Arg .WriteEvent.Type }} + {{ .Arg .WriteEvent.Type }}, + {{ .Arg .KeyPath }} ) ; diff --git a/pkg/storage/unified/sql/db/migrations/resource_mig.go b/pkg/storage/unified/sql/db/migrations/resource_mig.go index 315ff2b5b40..c8a6d980104 100644 --- a/pkg/storage/unified/sql/db/migrations/resource_mig.go +++ b/pkg/storage/unified/sql/db/migrations/resource_mig.go @@ -185,5 +185,18 @@ func initResourceTables(mg *migrator.Migrator) string { Name: "UQE_resource_last_import_time_last_import_time", })) + mg.AddMigration("Add key_path column to resource_history", migrator.NewAddColumnMigration(resource_history_table, &migrator.Column{ + Name: "key_path", Type: migrator.DB_NVarchar, Length: 2048, Nullable: false, Default: "", IsLatin: true, + })) + + resource_events_table := migrator.Table{ + Name: "resource_events", + Columns: []*migrator.Column{ + {Name: "key_path", Type: migrator.DB_NVarchar, Length: 2048, Nullable: false, IsPrimaryKey: true, IsLatin: true}, + {Name: "value", Type: migrator.DB_MediumText, Nullable: false}, + }, + } + mg.AddMigration("create table "+resource_events_table.Name, migrator.NewAddTableMigration(resource_events_table)) + return marker } diff --git a/pkg/storage/unified/sql/queries.go b/pkg/storage/unified/sql/queries.go index e6b9b1615bf..e51cf943041 100644 --- a/pkg/storage/unified/sql/queries.go +++ b/pkg/storage/unified/sql/queries.go @@ -84,6 +84,7 @@ type sqlResourceRequest struct { WriteEvent resource.WriteEvent Generation int64 Folder string + KeyPath string // Useful when batch writing ResourceVersion int64 diff --git a/pkg/storage/unified/sql/testdata/mysql--resource_history_insert-insert into resource_history.sql b/pkg/storage/unified/sql/testdata/mysql--resource_history_insert-insert into resource_history.sql index 6eae2b07b7a..7b82bce294d 100755 --- a/pkg/storage/unified/sql/testdata/mysql--resource_history_insert-insert into resource_history.sql +++ b/pkg/storage/unified/sql/testdata/mysql--resource_history_insert-insert into resource_history.sql @@ -9,7 +9,8 @@ INSERT INTO `resource_history` `previous_resource_version`, `generation`, `value`, - `action` + `action`, + `key_path` ) VALUES ( '', @@ -21,6 +22,7 @@ INSERT INTO `resource_history` 1234, 789, '[]', - 'UNKNOWN' + 'UNKNOWN', + '' ) ; diff --git a/pkg/storage/unified/sql/testdata/postgres--resource_history_insert-insert into resource_history.sql b/pkg/storage/unified/sql/testdata/postgres--resource_history_insert-insert into resource_history.sql index 11f6b18c01b..6375c88407e 100755 --- a/pkg/storage/unified/sql/testdata/postgres--resource_history_insert-insert into resource_history.sql +++ b/pkg/storage/unified/sql/testdata/postgres--resource_history_insert-insert into resource_history.sql @@ -9,7 +9,8 @@ INSERT INTO "resource_history" "previous_resource_version", "generation", "value", - "action" + "action", + "key_path" ) VALUES ( '', @@ -21,6 +22,7 @@ INSERT INTO "resource_history" 1234, 789, '[]', - 'UNKNOWN' + 'UNKNOWN', + '' ) ; diff --git a/pkg/storage/unified/sql/testdata/sqlite--resource_history_insert-insert into resource_history.sql b/pkg/storage/unified/sql/testdata/sqlite--resource_history_insert-insert into resource_history.sql index 11f6b18c01b..6375c88407e 100755 --- a/pkg/storage/unified/sql/testdata/sqlite--resource_history_insert-insert into resource_history.sql +++ b/pkg/storage/unified/sql/testdata/sqlite--resource_history_insert-insert into resource_history.sql @@ -9,7 +9,8 @@ INSERT INTO "resource_history" "previous_resource_version", "generation", "value", - "action" + "action", + "key_path" ) VALUES ( '', @@ -21,6 +22,7 @@ INSERT INTO "resource_history" 1234, 789, '[]', - 'UNKNOWN' + 'UNKNOWN', + '' ) ; From 64a3c298cf28052e822276c05097c651c97a7843 Mon Sep 17 00:00:00 2001 From: Mariell Hoversholm Date: Thu, 4 Dec 2025 15:25:59 +0100 Subject: [PATCH 028/110] docs: use clearly invalid tokens (#114842) --- .../plan-rbac-rollout-strategy/index.md | 22 +++++++++---------- .../administration/service-accounts/_index.md | 4 ++-- .../service-accounts/migrate-api-keys.md | 4 ++-- .../api-reference/http-api/team.md | 18 +++++++-------- .../api-reference/http-api/team_sync.md | 6 ++--- pkg/api/dtos/apikey.go | 2 +- pkg/components/satokengen/tokengen_test.go | 8 +++---- public/api-enterprise-spec.json | 2 +- public/api-merged.json | 2 +- public/openapi3.json | 2 +- 10 files changed, 35 insertions(+), 35 deletions(-) diff --git a/docs/sources/administration/roles-and-permissions/access-control/plan-rbac-rollout-strategy/index.md b/docs/sources/administration/roles-and-permissions/access-control/plan-rbac-rollout-strategy/index.md index 1924ab84149..f9c43317585 100644 --- a/docs/sources/administration/roles-and-permissions/access-control/plan-rbac-rollout-strategy/index.md +++ b/docs/sources/administration/roles-and-permissions/access-control/plan-rbac-rollout-strategy/index.md @@ -162,7 +162,7 @@ The following request creates a custom role that includes permissions to access ``` curl --location --request POST '/api/access-control/roles/' \ ---header 'Authorization: Bearer glsa_kcVxDhZtu5ISOZIEt' \ +--header 'Authorization: Bearer glsa_iNValIdinValiDinvalidinvalidinva_5b582697' \ --header 'Content-Type: application/json' \ --data-raw '{ "version": 1, @@ -208,13 +208,13 @@ By default, only a Grafana Server Admin can create and manage custom roles. If y ```bash # Fetch the role, modify it to add the desired permissions and increment its version - curl -H 'Authorization: Bearer glsa_kcVxDhZtu5ISOZIEt' \ + curl -H 'Authorization: Bearer glsa_iNValIdinValiDinvalidinvalidinva_5b582697' \ -X GET '/api/access-control/roles/basic_editor' | \ jq 'del(.created)| del(.updated) | del(.permissions[].created) | del(.permissions[].updated) | .version += 1' | \ jq '.permissions += [{"action": "roles:read", "scope": "roles:*"}, {"action": "roles:write", "scope": "permissions:type:delegate"}, {"action": "roles:delete", "scope": "permissions:type:delegate"}]' > /tmp/basic_editor.json # Update the role - curl -H 'Authorization: Bearer glsa_kcVxDhZtu5ISOZIEt' -H 'Content-Type: application/json' \ + curl -H 'Authorization: Bearer glsa_iNValIdinValiDinvalidinvalidinva_5b582697' -H 'Content-Type: application/json' \ -X PUT-d @/tmp/basic_editor.json '/api/access-control/roles/basic_editor' ``` @@ -253,13 +253,13 @@ If you want your `Viewers` to create reports, [update the `Viewer` basic role pe ```bash # Fetch the role, modify it to add the desired permissions and increment its version - curl -H 'Authorization: Bearer glsa_kcVxDhZtu5ISOZIEt' \ + curl -H 'Authorization: Bearer glsa_iNValIdinValiDinvalidinvalidinva_5b582697' \ -X GET '/api/access-control/roles/basic_viewer' | \ jq 'del(.created)| del(.updated) | del(.permissions[].created) | del(.permissions[].updated) | .version += 1' | \ jq '.permissions += [{"action": "reports:create"}, {"action": "reports:read", "scope": "reports:*"}, {"action": "reports:write", "scope": "reports:*"}, {"action": "reports:send", "scope": "reports:*"}]' > /tmp/basic_viewer.json # Update the role - curl -H 'Authorization: Bearer glsa_kcVxDhZtu5ISOZIEt' -H 'Content-Type: application/json' \ + curl -H 'Authorization: Bearer glsa_iNValIdinValiDinvalidinvalidinva_5b582697' -H 'Content-Type: application/json' \ -X PUT-d @/tmp/basic_viewer.json '/api/access-control/roles/basic_viewer' ``` @@ -299,13 +299,13 @@ There are two ways to achieve this: ```bash # Fetch the role, modify it to remove the undesired permissions and increment its version - curl -H 'Authorization: Bearer glsa_kcVxDhZtu5ISOZIEt' \ + curl -H 'Authorization: Bearer glsa_iNValIdinValiDinvalidinvalidinva_5b582697' \ -X GET '/api/access-control/roles/basic_grafana_admin' | \ jq 'del(.created)| del(.updated) | del(.permissions[].created) | del(.permissions[].updated) | .version += 1' | \ jq 'del(.permissions[] | select (.action == "users:create")) | del(.permissions[] | select (.action == "org.users:add" and .scope == "users:*"))' > /tmp/basic_grafana_admin.json # Update the role - curl -H 'Authorization: Bearer glsa_kcVxDhZtu5ISOZIEt' -H 'Content-Type: application/json' \ + curl -H 'Authorization: Bearer glsa_iNValIdinValiDinvalidinvalidinva_5b582697' -H 'Content-Type: application/json' \ -X PUT-d @/tmp/basic_grafana_admin.json '/api/access-control/roles/basic_grafana_admin' ``` @@ -361,14 +361,14 @@ Here are two ways to achieve this: ```bash # Fetch the role, modify it to remove the undesired permissions, add the new permission and increment its version - curl -H 'Authorization: Bearer glsa_kcVxDhZtu5ISOZIEt' \ + curl -H 'Authorization: Bearer glsa_iNValIdinValiDinvalidinvalidinva_5b582697' \ -X GET '/api/access-control/roles/basic_viewer' | \ jq 'del(.created)| del(.updated) | del(.permissions[].created) | del(.permissions[].updated) | .version += 1' | \ jq 'del(.permissions[] | select (.action == "plugins.app:access" and .scope == "plugins:*"))' | \ jq '.permissions += [{"action": "plugins.app:access", "scope": "plugins:id:kentik-connect-app"}]' > /tmp/basic_viewer.json # Update the role - curl -H 'Authorization: Bearer glsa_kcVxDhZtu5ISOZIEt' -H 'Content-Type: application/json' \ + curl -H 'Authorization: Bearer glsa_iNValIdinValiDinvalidinvalidinva_5b582697' -H 'Content-Type: application/json' \ -X PUT -d @/tmp/basic_viewer.json '/api/access-control/roles/basic_viewer' ``` @@ -400,13 +400,13 @@ Here are two ways to achieve this: ```bash # Fetch the role, modify it to remove permissions to kentik-connect-app and increment role version - curl -H 'Authorization: Bearer glsa_kcVxDhZtu5ISOZIEt' \ + curl -H 'Authorization: Bearer glsa_iNValIdinValiDinvalidinvalidinva_5b582697' \ -X GET '/api/access-control/roles/basic_viewer' | \ jq 'del(.created)| del(.updated) | del(.permissions[].created) | del(.permissions[].updated) | .version += 1' | \ jq 'del(.permissions[] | select (.action == "plugins.app:access" and .scope == "plugins:id:kentik-connect-app"))' # Update the role - curl -H 'Authorization: Bearer glsa_kcVxDhZtu5ISOZIEt' -H 'Content-Type: application/json' \ + curl -H 'Authorization: Bearer glsa_iNValIdinValiDinvalidinvalidinva_5b582697' -H 'Content-Type: application/json' \ -X PUT -d @/tmp/basic_viewer.json '/api/access-control/roles/basic_viewer' ``` diff --git a/docs/sources/administration/service-accounts/_index.md b/docs/sources/administration/service-accounts/_index.md index 34bbd916610..dcc7983b735 100644 --- a/docs/sources/administration/service-accounts/_index.md +++ b/docs/sources/administration/service-accounts/_index.md @@ -243,7 +243,7 @@ Authorize your request with the token whose permissions you want to check. {{< /admonition >}} ```bash -curl -H "Authorization: Bearer glsa_HOruNAb7SOiCdshU9algkrq7FDsNSLAa_54e2f8be" -X GET '/api/access-control/user/permissions' | jq +curl -H "Authorization: Bearer glsa_iNValIdinValiDinvalidinvalidinva_5b582697" -X GET '/api/access-control/user/permissions' | jq ``` The output lists the token's permissions: @@ -268,7 +268,7 @@ To list which dashboards a token can view, you can filter the `/api/access-contr #### Example ```bash -curl -H "Authorization: Bearer glsa_HOruNAb7SOiCdshU9algkrq7FDsNSLAa_54e2f8be" -X GET '/api/access-control/user/permissions' | jq '."dashboards:read"' +curl -H "Authorization: Bearer glsa_iNValIdinValiDinvalidinvalidinva_5b582697" -X GET '/api/access-control/user/permissions' | jq '."dashboards:read"' ``` The output lists the dashboards a token can view and the folders a token can view dashboards from, diff --git a/docs/sources/administration/service-accounts/migrate-api-keys.md b/docs/sources/administration/service-accounts/migrate-api-keys.md index d30b3bddcdb..5db442bbe42 100644 --- a/docs/sources/administration/service-accounts/migrate-api-keys.md +++ b/docs/sources/administration/service-accounts/migrate-api-keys.md @@ -136,10 +136,10 @@ curl -X POST -H "Content-Type: application/json" -d '{"name": "my-service-accoun curl -X POST -H "Content-Type: application/json" -d '{"name": "my-service-account-token"}' http://admin:admin@localhost:3000/api/serviceaccounts/1/tokens # response with the created SAT id,name and key. -{"id":2,"name":"my-service-account-token","key":"glsa_9244xlVFZK0j8Lh4fU8Cz6Z5tO664zIi_7a762939"}% +{"id":2,"name":"my-service-account-token","key":"glsa_iNValIdinValiDinvalidinvalidinva_5b582697"}% # now you can authenticate the same way as you did with the API key -curl --request GET --url http://localhost:3000/api/folders --header 'Authorization: Bearer glsa_9244xlVFZK0j8Lh4fU8Cz6Z5tO664zIi_7a762939' +curl --request GET --url http://localhost:3000/api/folders --header 'Authorization: Bearer glsa_iNValIdinValiDinvalidinvalidinva_5b582697' # response [{"id":1,"uid":"a5261a84-eebc-4733-83a9-61f4713561d1","title":"gdev dashboards"}]% diff --git a/docs/sources/developer-resources/api-reference/http-api/team.md b/docs/sources/developer-resources/api-reference/http-api/team.md index 25578435ba6..d45ebe64940 100644 --- a/docs/sources/developer-resources/api-reference/http-api/team.md +++ b/docs/sources/developer-resources/api-reference/http-api/team.md @@ -53,7 +53,7 @@ See note in the [introduction](#team-api) for an explanation. GET /api/teams/search?perpage=10&page=1&query=mytestteam HTTP/1.1 Accept: application/json Content-Type: application/json -Authorization: Bearer glsa_kcVxDhZtu5ISOZIEt +Authorization: Bearer glsa_iNValIdinValiDinvalidinvalidinva_5b582697 ``` **Example Response**: @@ -119,7 +119,7 @@ See note in the [introduction](#team-api) for an explanation. GET /api/teams/1 HTTP/1.1 Accept: application/json Content-Type: application/json -Authorization: Bearer glsa_kcVxDhZtu5ISOZIEt +Authorization: Bearer glsa_iNValIdinValiDinvalidinvalidinva_5b582697 ``` **Example Response**: @@ -165,7 +165,7 @@ See note in the [introduction](#team-api) for an explanation. POST /api/teams HTTP/1.1 Accept: application/json Content-Type: application/json -Authorization: Bearer glsa_kcVxDhZtu5ISOZIEt +Authorization: Bearer glsa_iNValIdinValiDinvalidinvalidinva_5b582697 { "name": "MyTestTeam", @@ -209,7 +209,7 @@ See note in the [introduction](#team-api) for an explanation. PUT /api/teams/2 HTTP/1.1 Accept: application/json Content-Type: application/json -Authorization: Bearer glsa_kcVxDhZtu5ISOZIEt +Authorization: Bearer glsa_iNValIdinValiDinvalidinvalidinva_5b582697 { "name": "MyTestTeam", @@ -252,7 +252,7 @@ See note in the [introduction](#team-api) for an explanation. DELETE /api/teams/2 HTTP/1.1 Accept: application/json Content-Type: application/json -Authorization: Bearer glsa_kcVxDhZtu5ISOZIEt +Authorization: Bearer glsa_iNValIdinValiDinvalidinvalidinva_5b582697 ``` **Example Response**: @@ -289,7 +289,7 @@ See note in the [introduction](#team-api) for an explanation. GET /api/teams/1/members HTTP/1.1 Accept: application/json Content-Type: application/json -Authorization: Bearer glsa_kcVxDhZtu5ISOZIEt +Authorization: Bearer glsa_iNValIdinValiDinvalidinvalidinva_5b582697 ``` **Example Response**: @@ -342,7 +342,7 @@ See note in the [introduction](#team-api) for an explanation. POST /api/teams/1/members HTTP/1.1 Accept: application/json Content-Type: application/json -Authorization: Bearer glsa_kcVxDhZtu5ISOZIEt +Authorization: Bearer glsa_iNValIdinValiDinvalidinvalidinva_5b582697 { "userId": 2 @@ -384,7 +384,7 @@ See note in the [introduction](#team-api) for an explanation. DELETE /api/teams/2/members/3 HTTP/1.1 Accept: application/json Content-Type: application/json -Authorization: Bearer glsa_kcVxDhZtu5ISOZIEt +Authorization: Bearer glsa_iNValIdinValiDinvalidinvalidinva_5b582697 ``` **Example Response**: @@ -424,7 +424,7 @@ See note in the [introduction](#team-api) for an explanation. PUT /api/teams/1/members HTTP/1.1 Accept: application/json Content-Type: application/json -Authorization: Bearer glsa_kcVxDhZtu5ISOZIEt +Authorization: Bearer glsa_iNValIdinValiDinvalidinvalidinva_5b582697 { "members": ["user1@email.com", "user2@email.com"] diff --git a/docs/sources/developer-resources/api-reference/http-api/team_sync.md b/docs/sources/developer-resources/api-reference/http-api/team_sync.md index fc0a41769cc..2cd48a801d2 100644 --- a/docs/sources/developer-resources/api-reference/http-api/team_sync.md +++ b/docs/sources/developer-resources/api-reference/http-api/team_sync.md @@ -47,7 +47,7 @@ See note in the [introduction](#external-group-synchronization-api) for an expla GET /api/teams/1/groups HTTP/1.1 Accept: application/json Content-Type: application/json -Authorization: Bearer glsa_kcVxDhZtu5ISOZIEt +Authorization: Bearer glsa_iNValIdinValiDinvalidinvalidinva_5b582697 ``` **Example Response**: @@ -131,7 +131,7 @@ See note in the [introduction](#external-group-synchronization-api) for an expla DELETE /api/teams/1/groups?groupId=cn%3Deditors%2Cou%3Dgroups%2Cdc%3Dgrafana%2Cdc%3Dorg HTTP/1.1 Accept: application/json Content-Type: application/json -Authorization: Bearer glsa_kcVxDhZtu5ISOZIEt +Authorization: Bearer glsa_iNValIdinValiDinvalidinvalidinva_5b582697 ``` **Example Response**: @@ -168,7 +168,7 @@ Search for team groups with pagination support. GET /api/teams/1/groups/search?name=editors&query=group&page=1&perpage=10 HTTP/1.1 Accept: application/json Content-Type: application/json -Authorization: Bearer glsa_kcVxDhZtu5ISOZIEt +Authorization: Bearer glsa_iNValIdinValiDinvalidinvalidinva_5b582697 ``` **Example Response**: diff --git a/pkg/api/dtos/apikey.go b/pkg/api/dtos/apikey.go index ec77c18a550..8499a77b6fa 100644 --- a/pkg/api/dtos/apikey.go +++ b/pkg/api/dtos/apikey.go @@ -5,6 +5,6 @@ type NewApiKeyResult struct { ID int64 `json:"id"` // example: grafana Name string `json:"name"` - // example: glsa_yscW25imSKJIuav8zF37RZmnbiDvB05G_fcaaf58a + // example: glsa_iNValIdinValiDinvalidinvalidinva_5b582697 Key string `json:"key"` } diff --git a/pkg/components/satokengen/tokengen_test.go b/pkg/components/satokengen/tokengen_test.go index 1b75fdc5b5f..fbc71023367 100644 --- a/pkg/components/satokengen/tokengen_test.go +++ b/pkg/components/satokengen/tokengen_test.go @@ -9,15 +9,15 @@ import ( func TestApiKeyValidation(t *testing.T) { result := KeyGenResult{ - ClientSecret: "glsa_yscW25imSKJIuav8zF37RZmnbiDvB05G_fcaaf58a", - HashedKey: "26cd2524985150529dc5f32109f544860512b999766e11bc8f3d5711bf0ba6e7020099f9f21538b5df94d577782f7431dd27", + ClientSecret: "glsa_iNValIdinValiDinvalidinvalidinva_5b582697", + HashedKey: "c59a6e547944ef768df51d1fc8b2a9810bc777a0bd2e5daa9ef8590f300c884e0ab9470c22c6f789414fdb6485b531166ded", } keyInfo, err := Decode(result.ClientSecret) require.NoError(t, err) require.Equal(t, "sa", keyInfo.ServiceID) - require.Equal(t, "yscW25imSKJIuav8zF37RZmnbiDvB05G", keyInfo.Secret) - require.Equal(t, "fcaaf58a", keyInfo.Checksum) + require.Equal(t, "iNValIdinValiDinvalidinvalidinva", keyInfo.Secret) + require.Equal(t, "5b582697", keyInfo.Checksum) hash, err := keyInfo.Hash() require.NoError(t, err) diff --git a/public/api-enterprise-spec.json b/public/api-enterprise-spec.json index 54935d3c47a..d2fdd854168 100644 --- a/public/api-enterprise-spec.json +++ b/public/api-enterprise-spec.json @@ -5905,7 +5905,7 @@ }, "key": { "type": "string", - "example": "glsa_yscW25imSKJIuav8zF37RZmnbiDvB05G_fcaaf58a" + "example": "glsa_iNValIdinValiDinvalidinvalidinva_5b582697" }, "name": { "type": "string", diff --git a/public/api-merged.json b/public/api-merged.json index da189e2d50e..6effd7054fa 100644 --- a/public/api-merged.json +++ b/public/api-merged.json @@ -18080,7 +18080,7 @@ }, "key": { "type": "string", - "example": "glsa_yscW25imSKJIuav8zF37RZmnbiDvB05G_fcaaf58a" + "example": "glsa_iNValIdinValiDinvalidinvalidinva_5b582697" }, "name": { "type": "string", diff --git a/public/openapi3.json b/public/openapi3.json index e775ea14d2c..546f15a7a86 100644 --- a/public/openapi3.json +++ b/public/openapi3.json @@ -7612,7 +7612,7 @@ "type": "integer" }, "key": { - "example": "glsa_yscW25imSKJIuav8zF37RZmnbiDvB05G_fcaaf58a", + "example": "glsa_iNValIdinValiDinvalidinvalidinva_5b582697", "type": "string" }, "name": { From ae4d2324d63061319fcb6cb3f954e353b9b83bc8 Mon Sep 17 00:00:00 2001 From: Will Assis <35489495+gassiss@users.noreply.github.com> Date: Thu, 4 Dec 2025 09:26:50 -0500 Subject: [PATCH 029/110] Unified storage kvstore bulk import support (#113791) * implement batchdelete in datastore * implement bulkprocess in kv storage_backend * convert bulkRVs to snowflake --- pkg/storage/unified/resource/bulk.go | 85 ++++++++ pkg/storage/unified/resource/datastore.go | 21 ++ .../unified/resource/datastore_test.go | 36 ++++ .../unified/resource/storage_backend.go | 184 ++++++++++++++++++ 4 files changed, 326 insertions(+) diff --git a/pkg/storage/unified/resource/bulk.go b/pkg/storage/unified/resource/bulk.go index 1ac0ff2032f..e665485dc3c 100644 --- a/pkg/storage/unified/resource/bulk.go +++ b/pkg/storage/unified/resource/bulk.go @@ -6,10 +6,14 @@ import ( "fmt" "io" "net/http" + "sync" + "time" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" "google.golang.org/grpc/metadata" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" authlib "github.com/grafana/authlib/types" @@ -328,3 +332,84 @@ func (b *batchRunner) RollbackRequested() bool { } return false } + +type bulkRV struct { + max int64 + counter int64 +} + +// Used when executing a bulk import so that we can generate snowflake RVs in the past +func newBulkRV() *bulkRV { + t := snowflakeFromTime(time.Now()) + return &bulkRV{ + max: t, + counter: 0, + } +} + +func (x *bulkRV) next(obj metav1.Object) int64 { + ts := snowflakeFromTime(obj.GetCreationTimestamp().Time) + anno := obj.GetAnnotations() + if anno != nil { + v := anno[utils.AnnoKeyUpdatedTimestamp] + t, err := time.Parse(time.RFC3339, v) + if err == nil { + ts = snowflakeFromTime(t) + } + } + if ts > x.max || ts < 0 { + ts = x.max + } + + x.counter++ + return ts + x.counter +} + +type BulkLock struct { + running map[string]bool + mu sync.Mutex +} + +func NewBulkLock() *BulkLock { + return &BulkLock{ + running: make(map[string]bool), + } +} + +func (x *BulkLock) Start(keys []*resourcepb.ResourceKey) error { + x.mu.Lock() + defer x.mu.Unlock() + + // First verify that it is not already running + ids := make([]string, len(keys)) + for i, k := range keys { + id := NSGR(k) + if x.running[id] { + return &apierrors.StatusError{ErrStatus: metav1.Status{ + Code: http.StatusPreconditionFailed, + Message: "bulk export is already running", + }} + } + ids[i] = id + } + + // Then add the keys to the lock + for _, k := range ids { + x.running[k] = true + } + return nil +} + +func (x *BulkLock) Finish(keys []*resourcepb.ResourceKey) { + x.mu.Lock() + defer x.mu.Unlock() + for _, k := range keys { + delete(x.running, NSGR(k)) + } +} + +func (x *BulkLock) Active() bool { + x.mu.Lock() + defer x.mu.Unlock() + return len(x.running) > 0 +} diff --git a/pkg/storage/unified/resource/datastore.go b/pkg/storage/unified/resource/datastore.go index 82f1e1e59d5..8a930492420 100644 --- a/pkg/storage/unified/resource/datastore.go +++ b/pkg/storage/unified/resource/datastore.go @@ -537,6 +537,27 @@ func (d *dataStore) Delete(ctx context.Context, key DataKey) error { return d.kv.Delete(ctx, dataSection, key.String()) } +func (n *dataStore) batchDelete(ctx context.Context, keys []DataKey) error { + for len(keys) > 0 { + batch := keys + if len(batch) > dataBatchSize { + batch = batch[:dataBatchSize] + } + + keys = keys[len(batch):] + stringKeys := make([]string, len(batch)) + for _, dataKey := range batch { + stringKeys = append(stringKeys, dataKey.String()) + } + + if err := n.kv.BatchDelete(ctx, dataSection, stringKeys); err != nil { + return err + } + } + + return nil +} + // ParseKey parses a string key into a DataKey struct func ParseKey(key string) (DataKey, error) { parts := strings.Split(key, "/") diff --git a/pkg/storage/unified/resource/datastore_test.go b/pkg/storage/unified/resource/datastore_test.go index fbfb828f133..8f167c2e16e 100644 --- a/pkg/storage/unified/resource/datastore_test.go +++ b/pkg/storage/unified/resource/datastore_test.go @@ -2950,6 +2950,42 @@ func TestDataStore_getGroupResources(t *testing.T) { } } +func TestDataStore_BatchDelete(t *testing.T) { + ds := setupTestDataStore(t) + ctx := context.Background() + + keys := make([]DataKey, 95) + for i := 0; i < 95; i++ { + rv := node.Generate().Int64() + keys[i] = DataKey{ + Namespace: "test-namespace", + Group: "test-group", + Resource: "test-resource", + Name: fmt.Sprintf("test-name-%d", i), + ResourceVersion: rv, + Action: DataActionCreated, + Folder: "test-folder", + } + content := fmt.Sprintf("test-value-%d", i) + err := ds.Save(ctx, keys[i], bytes.NewReader([]byte(content))) + require.NoError(t, err) + } + + err := ds.batchDelete(ctx, keys) + require.NoError(t, err) + + // Verify all events were deleted + for i := 0; i < 95; i++ { + _, err := ds.Get(ctx, DataKey{ + Namespace: "test-namespace", + Group: "test-group", + Resource: "test-resource", + Name: fmt.Sprintf("test-name-%d", i), + }) + require.Error(t, err, "Resource should have been deleted") + } +} + func TestDataStore_BatchGet(t *testing.T) { ds := setupTestDataStore(t) ctx := context.Background() diff --git a/pkg/storage/unified/resource/storage_backend.go b/pkg/storage/unified/resource/storage_backend.go index 0f65867f351..0de97b0355e 100644 --- a/pkg/storage/unified/resource/storage_backend.go +++ b/pkg/storage/unified/resource/storage_backend.go @@ -18,6 +18,7 @@ import ( "github.com/prometheus/client_golang/prometheus" "go.opentelemetry.io/otel/trace" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "github.com/grafana/grafana/pkg/apimachinery/utils" "github.com/grafana/grafana/pkg/storage/unified/resourcepb" @@ -54,6 +55,7 @@ func convertEmptyToClusterNamespace(namespace string, withExperimentalClusterSco type kvStorageBackend struct { snowflake *snowflake.Node kv KV + bulkLock *BulkLock dataStore *dataStore eventStore *eventStore notifier *notifier @@ -102,6 +104,7 @@ func NewKVStorageBackend(opts KVBackendOptions) (StorageBackend, error) { backend := &kvStorageBackend{ kv: kv, + bulkLock: NewBulkLock(), dataStore: newDataStore(kv), eventStore: eventStore, notifier: newNotifier(eventStore, notifierOptions{}), @@ -1236,6 +1239,187 @@ func (k *kvStorageBackend) GetResourceLastImportTimes(ctx context.Context) iter. } } +func (b *kvStorageBackend) ProcessBulk(ctx context.Context, setting BulkSettings, iter BulkRequestIterator) *resourcepb.BulkResponse { + // TODO cross-node lock + err := b.bulkLock.Start(setting.Collection) + if err != nil { + return &resourcepb.BulkResponse{ + Error: AsErrorResult(err), + } + } + defer b.bulkLock.Finish(setting.Collection) + + bulkRvGenerator := newBulkRV() + summaries := make(map[string]*resourcepb.BulkResponse_Summary, len(setting.Collection)) + rsp := &resourcepb.BulkResponse{} + + if setting.RebuildCollection { + for _, key := range setting.Collection { + events := make([]string, 0) + for evtKeyStr, err := range b.eventStore.ListKeysSince(ctx, 1) { + if err != nil { + b.log.Error("failed to list event: %s", err) + return rsp + } + + evtKey, err := ParseEventKey(evtKeyStr) + if err != nil { + b.log.Error("error parsing event key: %s", err) + return rsp + } + + if evtKey.Group != key.Group || evtKey.Resource != key.Resource || evtKey.Namespace != key.Namespace { + continue + } + + events = append(events, evtKeyStr) + } + + if err := b.eventStore.batchDelete(ctx, events); err != nil { + b.log.Error("failed to delete events: %s", err) + return rsp + } + + historyKeys := make([]DataKey, 0) + + for dataKey, err := range b.dataStore.Keys(ctx, ListRequestKey{ + Namespace: key.Namespace, + Group: key.Group, + Resource: key.Resource, + }, SortOrderAsc) { + if err != nil { + b.log.Error("failed to list collection before delete: %s", err) + return rsp + } + + historyKeys = append(historyKeys, dataKey) + } + + previousCount := int64(len(historyKeys)) + if err := b.dataStore.batchDelete(ctx, historyKeys); err != nil { + b.log.Error("failed to delete collection: %s", err) + return rsp + } + summaries[NSGR(key)] = &resourcepb.BulkResponse_Summary{ + Namespace: key.Namespace, + Group: key.Group, + Resource: key.Resource, + PreviousCount: previousCount, + } + } + } else { + for _, key := range setting.Collection { + summaries[NSGR(key)] = &resourcepb.BulkResponse_Summary{ + Namespace: key.Namespace, + Group: key.Group, + Resource: key.Resource, + } + } + } + + obj := &unstructured.Unstructured{} + + saved := make([]DataKey, 0) + rollback := func() { + // we don't have transactions in the kv store, so we simply delete everything we created + err = b.dataStore.batchDelete(ctx, saved) + if err != nil { + b.log.Error("failed to delete during rollback: %s", err) + } + } + + for iter.Next() { + if iter.RollbackRequested() { + rollback() + break + } + + req := iter.Request() + if req == nil { + rollback() + rsp.Error = AsErrorResult(fmt.Errorf("missing request")) + break + } + + rsp.Processed++ + + var action DataAction + switch resourcepb.WatchEvent_Type(req.Action) { + case resourcepb.WatchEvent_ADDED: + action = DataActionCreated + // Check if resource already exists for create operations + _, err := b.dataStore.GetLatestResourceKey(ctx, GetRequestKey{ + Group: req.Key.Group, + Resource: req.Key.Resource, + Namespace: req.Key.Namespace, + Name: req.Key.Name, + }) + if err == nil { + rsp.Rejected = append(rsp.Rejected, &resourcepb.BulkResponse_Rejected{ + Key: req.Key, + Action: req.Action, + Error: "resource already exists", + }) + continue + } + if !errors.Is(err, ErrNotFound) { + rsp.Rejected = append(rsp.Rejected, &resourcepb.BulkResponse_Rejected{ + Key: req.Key, + Action: req.Action, + Error: fmt.Sprintf("failed to check if resource exists: %s", err), + }) + continue + } + case resourcepb.WatchEvent_MODIFIED: + action = DataActionUpdated + case resourcepb.WatchEvent_DELETED: + action = DataActionDeleted + default: + rsp.Rejected = append(rsp.Rejected, &resourcepb.BulkResponse_Rejected{ + Key: req.Key, + Action: req.Action, + Error: "invalid event type", + }) + continue + } + + err := obj.UnmarshalJSON(req.Value) + if err != nil { + rsp.Rejected = append(rsp.Rejected, &resourcepb.BulkResponse_Rejected{ + Key: req.Key, + Action: req.Action, + Error: "unable to unmarshal json", + }) + continue + } + + dataKey := DataKey{ + Group: req.Key.Group, + Resource: req.Key.Resource, + Namespace: req.Key.Namespace, + Name: req.Key.Name, + ResourceVersion: bulkRvGenerator.next(obj), + Action: action, + Folder: req.Folder, + } + err = b.dataStore.Save(ctx, dataKey, bytes.NewReader(req.Value)) + if err != nil { + rsp.Rejected = append(rsp.Rejected, &resourcepb.BulkResponse_Rejected{ + Key: req.Key, + Action: req.Action, + Error: fmt.Sprintf("failed to save resource: %s", err), + }) + continue + } + + saved = append(saved, dataKey) + } + + // TODO update last import time + + return rsp +} + // readAndClose reads all data from a ReadCloser and ensures it's closed, // combining any errors from both operations. func readAndClose(r io.ReadCloser) ([]byte, error) { From 3c5d905e0fcefa3d5c83d306ac83655e197e1c1c Mon Sep 17 00:00:00 2001 From: mohammad-hamid Date: Thu, 4 Dec 2025 10:04:23 -0500 Subject: [PATCH 030/110] `AuthZ`: Redirect legacy resource permissions handler to k8s (part I) (#114199) * Add K8s API redirect for GET resource permissions * wire * move restconfig to options * address comments * fix helper after adding RestConfigProvider * Revert K8s redirect changes for service accounts, teams, and receivers Keep only dashboard and folder redirect functionality for this PR. Service accounts, teams, and receivers will be handled in a separate PR. * address comments * lint --- pkg/api/folder_bench_test.go | 4 +- pkg/server/wire_gen.go | 8 +- .../ossaccesscontrol/dashboard.go | 12 +- .../accesscontrol/ossaccesscontrol/folder.go | 11 +- .../ossaccesscontrol/testutil/testutil.go | 1 + .../accesscontrol/resourcepermissions/api.go | 35 +++- .../resourcepermissions/api_adapter.go | 164 ++++++++++++++++++ .../resourcepermissions/options.go | 6 + .../resourcepermissions/service.go | 2 +- 9 files changed, 222 insertions(+), 21 deletions(-) create mode 100644 pkg/services/accesscontrol/resourcepermissions/api_adapter.go diff --git a/pkg/api/folder_bench_test.go b/pkg/api/folder_bench_test.go index 167aa242c1e..6d9b2afec9f 100644 --- a/pkg/api/folder_bench_test.go +++ b/pkg/api/folder_bench_test.go @@ -442,7 +442,7 @@ func setupServer(b testing.TB, sc benchScenario, features featuremgmt.FeatureTog features, tracing.InitializeTracerForTest(), sc.db, permreg.ProvidePermissionRegistry(), nil, ) folderPermissions, err := ossaccesscontrol.ProvideFolderPermissions( - cfg, features, routing.NewRouteRegister(), sc.db, ac, license, folderServiceWithFlagOn, acSvc, sc.teamSvc, sc.userSvc, actionSets) + cfg, features, routing.NewRouteRegister(), sc.db, ac, license, folderServiceWithFlagOn, acSvc, sc.teamSvc, sc.userSvc, actionSets, apiserver.WithoutRestConfig) require.NoError(b, err) dashboardSvc, err := dashboardservice.ProvideDashboardServiceImpl( sc.cfg, @@ -474,7 +474,7 @@ func setupServer(b testing.TB, sc benchScenario, features featuremgmt.FeatureTog require.NoError(b, err) _, err = ossaccesscontrol.ProvideDashboardPermissions( - cfg, features, routing.NewRouteRegister(), sc.db, ac, license, dashboardSvc, folderServiceWithFlagOn, acSvc, sc.teamSvc, sc.userSvc, actionSets, dashboardSvc) + cfg, features, routing.NewRouteRegister(), sc.db, ac, license, dashboardSvc, folderServiceWithFlagOn, acSvc, sc.teamSvc, sc.userSvc, actionSets, dashboardSvc, apiserver.WithoutRestConfig) require.NoError(b, err) starSvc := startest.NewStarServiceFake() diff --git a/pkg/server/wire_gen.go b/pkg/server/wire_gen.go index a9dc71d4d3b..e920bdbec61 100644 --- a/pkg/server/wire_gen.go +++ b/pkg/server/wire_gen.go @@ -584,7 +584,7 @@ func Initialize(ctx context.Context, cfg *setting.Cfg, opts Options, apiOpts api } filestoreService := filestore.ProvideService(inMemory) fileStoreManager := dashboards.ProvideFileStoreManager(pluginstoreService, filestoreService) - folderPermissionsService, err := ossaccesscontrol.ProvideFolderPermissions(cfg, featureToggles, routeRegisterImpl, sqlStore, accessControl, ossLicensingService, folderimplService, acimplService, teamService, userService, actionSetService) + folderPermissionsService, err := ossaccesscontrol.ProvideFolderPermissions(cfg, featureToggles, routeRegisterImpl, sqlStore, accessControl, ossLicensingService, folderimplService, acimplService, teamService, userService, actionSetService, eventualRestConfigProvider) if err != nil { return nil, err } @@ -715,7 +715,7 @@ func Initialize(ctx context.Context, cfg *setting.Cfg, opts Options, apiOpts api pluginassetsService := pluginassets2.ProvideService(pluginManagementCfg, pluginscdnService, signatureSignature, pluginstoreService) avatarCacheServer := avatar.ProvideAvatarCacheServer(cfg) prefService := prefimpl.ProvideService(sqlStore, cfg) - dashboardPermissionsService, err := ossaccesscontrol.ProvideDashboardPermissions(cfg, featureToggles, routeRegisterImpl, sqlStore, accessControl, ossLicensingService, dashboardService, folderimplService, acimplService, teamService, userService, actionSetService, dashboardServiceImpl) + dashboardPermissionsService, err := ossaccesscontrol.ProvideDashboardPermissions(cfg, featureToggles, routeRegisterImpl, sqlStore, accessControl, ossLicensingService, dashboardService, folderimplService, acimplService, teamService, userService, actionSetService, dashboardServiceImpl, eventualRestConfigProvider) if err != nil { return nil, err } @@ -1235,7 +1235,7 @@ func InitializeForTest(ctx context.Context, t sqlutil.ITestDB, testingT interfac } filestoreService := filestore.ProvideService(inMemory) fileStoreManager := dashboards.ProvideFileStoreManager(pluginstoreService, filestoreService) - folderPermissionsService, err := ossaccesscontrol.ProvideFolderPermissions(cfg, featureToggles, routeRegisterImpl, sqlStore, accessControl, ossLicensingService, folderimplService, acimplService, teamService, userService, actionSetService) + folderPermissionsService, err := ossaccesscontrol.ProvideFolderPermissions(cfg, featureToggles, routeRegisterImpl, sqlStore, accessControl, ossLicensingService, folderimplService, acimplService, teamService, userService, actionSetService, eventualRestConfigProvider) if err != nil { return nil, err } @@ -1368,7 +1368,7 @@ func InitializeForTest(ctx context.Context, t sqlutil.ITestDB, testingT interfac pluginassetsService := pluginassets2.ProvideService(pluginManagementCfg, pluginscdnService, signatureSignature, pluginstoreService) avatarCacheServer := avatar.ProvideAvatarCacheServer(cfg) prefService := prefimpl.ProvideService(sqlStore, cfg) - dashboardPermissionsService, err := ossaccesscontrol.ProvideDashboardPermissions(cfg, featureToggles, routeRegisterImpl, sqlStore, accessControl, ossLicensingService, dashboardService, folderimplService, acimplService, teamService, userService, actionSetService, dashboardServiceImpl) + dashboardPermissionsService, err := ossaccesscontrol.ProvideDashboardPermissions(cfg, featureToggles, routeRegisterImpl, sqlStore, accessControl, ossLicensingService, dashboardService, folderimplService, acimplService, teamService, userService, actionSetService, dashboardServiceImpl, eventualRestConfigProvider) if err != nil { return nil, err } diff --git a/pkg/services/accesscontrol/ossaccesscontrol/dashboard.go b/pkg/services/accesscontrol/ossaccesscontrol/dashboard.go index 317585ef8ac..cd4a5a02cd3 100644 --- a/pkg/services/accesscontrol/ossaccesscontrol/dashboard.go +++ b/pkg/services/accesscontrol/ossaccesscontrol/dashboard.go @@ -4,12 +4,14 @@ import ( "context" "errors" + dashboardv1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1" "github.com/grafana/grafana/pkg/api/routing" "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/metrics" "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol/resourcepermissions" + "github.com/grafana/grafana/pkg/services/apiserver" "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/folder" @@ -99,7 +101,7 @@ func ProvideDashboardPermissions( cfg *setting.Cfg, features featuremgmt.FeatureToggles, router routing.RouteRegister, sql db.DB, ac accesscontrol.AccessControl, license licensing.Licensing, dashboardService dashboards.DashboardService, folderService folder.Service, service accesscontrol.Service, teamService team.Service, userService user.Service, actionSetService resourcepermissions.ActionSetService, - dashboardPermissionsRegistration dashboards.PermissionsRegistrationService, + dashboardPermissionsRegistration dashboards.PermissionsRegistrationService, restConfigProvider apiserver.RestConfigProvider, ) (*DashboardPermissionsService, error) { getDashboard := func(ctx context.Context, orgID int64, resourceID string) (*dashboards.Dashboard, error) { query := &dashboards.GetDashboardQuery{UID: resourceID, OrgID: orgID} @@ -117,6 +119,7 @@ func ProvideDashboardPermissions( options := resourcepermissions.Options{ Resource: "dashboards", ResourceAttribute: "uid", + APIGroup: dashboardv1.APIGroup, ResourceValidator: func(ctx context.Context, orgID int64, resourceID string) error { ctx, span := tracer.Start(ctx, "accesscontrol.ossaccesscontrol.ProvideDashboardPermissions.ResourceValidator") defer span.End() @@ -166,9 +169,10 @@ func ProvideDashboardPermissions( "Edit": getDashboardEditActions(features), "Admin": getDashboardAdminActions(features), }, - ReaderRoleName: "Permission reader", - WriterRoleName: "Permission writer", - RoleGroup: "Dashboards", + ReaderRoleName: "Permission reader", + WriterRoleName: "Permission writer", + RoleGroup: "Dashboards", + RestConfigProvider: restConfigProvider, } srv, err := resourcepermissions.New(cfg, options, features, router, license, ac, service, sql, teamService, userService, actionSetService) diff --git a/pkg/services/accesscontrol/ossaccesscontrol/folder.go b/pkg/services/accesscontrol/ossaccesscontrol/folder.go index f62b67ce027..3f307146cab 100644 --- a/pkg/services/accesscontrol/ossaccesscontrol/folder.go +++ b/pkg/services/accesscontrol/ossaccesscontrol/folder.go @@ -4,12 +4,14 @@ import ( "context" "errors" + folderv1 "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1" "github.com/grafana/grafana/pkg/api/routing" "github.com/grafana/grafana/pkg/apimachinery/errutil" "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol/resourcepermissions" + "github.com/grafana/grafana/pkg/services/apiserver" "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/folder" @@ -90,6 +92,7 @@ func ProvideFolderPermissions( cfg *setting.Cfg, features featuremgmt.FeatureToggles, router routing.RouteRegister, sql db.DB, accesscontrol accesscontrol.AccessControl, license licensing.Licensing, folderService folder.Service, service accesscontrol.Service, teamService team.Service, userService user.Service, actionSetService resourcepermissions.ActionSetService, + restConfigProvider apiserver.RestConfigProvider, ) (*FolderPermissionsService, error) { if err := registerFolderRoles(cfg, features, service); err != nil { return nil, err @@ -98,6 +101,7 @@ func ProvideFolderPermissions( options := resourcepermissions.Options{ Resource: "folders", ResourceAttribute: "uid", + APIGroup: folderv1.APIGroup, ResourceValidator: func(ctx context.Context, orgID int64, resourceID string) error { ctx, span := tracer.Start(ctx, "accesscontrol.ossaccesscontrol.ProvideFolderPermissions.ResourceValidator") defer span.End() @@ -139,9 +143,10 @@ func ProvideFolderPermissions( "Edit": append(getDashboardEditActions(features), FolderEditActions...), "Admin": append(getDashboardAdminActions(features), FolderAdminActions...), }, - ReaderRoleName: "Permission reader", - WriterRoleName: "Permission writer", - RoleGroup: "Folders", + ReaderRoleName: "Permission reader", + WriterRoleName: "Permission writer", + RoleGroup: "Folders", + RestConfigProvider: restConfigProvider, } srv, err := resourcepermissions.New(cfg, options, features, router, license, accesscontrol, service, sql, teamService, userService, actionSetService) if err != nil { diff --git a/pkg/services/accesscontrol/ossaccesscontrol/testutil/testutil.go b/pkg/services/accesscontrol/ossaccesscontrol/testutil/testutil.go index 920d7b826d0..2bffa06e546 100644 --- a/pkg/services/accesscontrol/ossaccesscontrol/testutil/testutil.go +++ b/pkg/services/accesscontrol/ossaccesscontrol/testutil/testutil.go @@ -93,5 +93,6 @@ func ProvideFolderPermissions( teamSvc, userSvc, actionSets, + apiserver.WithoutRestConfig, ) } diff --git a/pkg/services/accesscontrol/resourcepermissions/api.go b/pkg/services/accesscontrol/resourcepermissions/api.go index ac8c30a0d26..981a99f5189 100644 --- a/pkg/services/accesscontrol/resourcepermissions/api.go +++ b/pkg/services/accesscontrol/resourcepermissions/api.go @@ -11,8 +11,11 @@ import ( "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/api/routing" + "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/services/accesscontrol" + "github.com/grafana/grafana/pkg/services/apiserver" contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" + "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/team" "github.com/grafana/grafana/pkg/services/user" @@ -23,20 +26,22 @@ import ( var tracer = otel.Tracer("github.com/grafana/grafana/pkg/accesscontrol/resourcepermissions") type api struct { - cfg *setting.Cfg - ac accesscontrol.AccessControl - router routing.RouteRegister - service *Service - permissions []string + cfg *setting.Cfg + ac accesscontrol.AccessControl + router routing.RouteRegister + service *Service + permissions []string + features featuremgmt.FeatureToggles + restConfigProvider apiserver.RestConfigProvider } -func newApi(cfg *setting.Cfg, ac accesscontrol.AccessControl, router routing.RouteRegister, manager *Service) *api { +func newApi(cfg *setting.Cfg, ac accesscontrol.AccessControl, router routing.RouteRegister, manager *Service, features featuremgmt.FeatureToggles, restConfigProvider apiserver.RestConfigProvider) *api { permissions := make([]string, 0, len(manager.permissions)) // reverse the permissions order for display for i := len(manager.permissions) - 1; i >= 0; i-- { permissions = append(permissions, manager.permissions[i]) } - return &api{cfg, ac, router, manager, permissions} + return &api{cfg, ac, router, manager, permissions, features, restConfigProvider} } func (a *api) registerEndpoints() { @@ -176,6 +181,22 @@ func (a *api) getPermissions(c *contextmodel.ReqContext) response.Response { resourceID := web.Params(c.Req)[":resourceID"] + //nolint:staticcheck // not yet migrated to OpenFeature + if a.features.IsEnabledGlobally(featuremgmt.FlagKubernetesAuthZHandlerRedirect) && + a.features.IsEnabledGlobally(featuremgmt.FlagKubernetesAuthzResourcePermissionApis) { + k8sPermissions, err := a.getResourcePermissionsFromK8s(c.Req.Context(), c.Namespace, resourceID) + if err == nil { + return response.JSON(http.StatusOK, k8sPermissions) + } + span.RecordError(err) + logger := log.New("resource-permissions-api") + if errors.Is(err, ErrRestConfigNotAvailable) { + logger.Debug("k8s API not available for resource permissions, falling back to legacy", "error", err, "resourceID", resourceID, "resource", a.service.options.Resource) + } else { + logger.Warn("Failed to get resource permissions from k8s API, falling back to legacy", "error", err, "resourceID", resourceID, "resource", a.service.options.Resource) + } + } + permissions, err := a.service.GetPermissions(c.Req.Context(), c.SignedInUser, resourceID) if err != nil { return response.ErrOrFallback(http.StatusInternalServerError, "Failed to get permissions", err) diff --git a/pkg/services/accesscontrol/resourcepermissions/api_adapter.go b/pkg/services/accesscontrol/resourcepermissions/api_adapter.go new file mode 100644 index 00000000000..868ae1a32b5 --- /dev/null +++ b/pkg/services/accesscontrol/resourcepermissions/api_adapter.go @@ -0,0 +1,164 @@ +package resourcepermissions + +import ( + "context" + "errors" + "fmt" + + "github.com/grafana/authlib/types" + "golang.org/x/text/cases" + "golang.org/x/text/language" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/dynamic" + + iamv0 "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1" + "github.com/grafana/grafana/pkg/api/dtos" + "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/services/team" + "github.com/grafana/grafana/pkg/services/user" +) + +var ErrRestConfigNotAvailable = errors.New("k8s rest config provider not available") + +func (a *api) getDynamicClient(ctx context.Context) (dynamic.Interface, error) { + if a.restConfigProvider == nil { + return nil, ErrRestConfigNotAvailable + } + + restConfig, err := a.restConfigProvider.GetRestConfig(ctx) + if err != nil { + return nil, fmt.Errorf("failed to get rest config: %w", err) + } + + dynamicClient, err := dynamic.NewForConfig(restConfig) + if err != nil { + return nil, fmt.Errorf("failed to create dynamic client: %w", err) + } + + return dynamicClient, nil +} + +func (a *api) getResourcePermissionsFromK8s(ctx context.Context, namespace string, resourceID string) (getResourcePermissionsResponse, error) { + dynamicClient, err := a.getDynamicClient(ctx) + if err != nil { + return nil, err + } + + resourcePermName := a.buildResourcePermissionName(resourceID) + + resourcePermResource := dynamicClient.Resource(iamv0.ResourcePermissionInfo.GroupVersionResource()).Namespace(namespace) + unstructuredObj, err := resourcePermResource.Get(ctx, resourcePermName, metav1.GetOptions{}) + if err != nil { + if k8serrors.IsNotFound(err) { + return getResourcePermissionsResponse{}, nil + } + return nil, fmt.Errorf("failed to get resource permission from k8s: %w", err) + } + + var resourcePerm iamv0.ResourcePermission + if err := runtime.DefaultUnstructuredConverter.FromUnstructured(unstructuredObj.Object, &resourcePerm); err != nil { + return nil, fmt.Errorf("failed to convert to typed resource permission: %w", err) + } + + return a.convertK8sResourcePermissionToDTO(&resourcePerm, namespace) +} + +func (a *api) convertK8sResourcePermissionToDTO(resourcePerm *iamv0.ResourcePermission, namespace string) (getResourcePermissionsResponse, error) { + permissions := resourcePerm.Spec.Permissions + if len(permissions) == 0 { + return getResourcePermissionsResponse{}, nil + } + + namespaceInfo, err := types.ParseNamespace(namespace) + if err != nil { + return nil, fmt.Errorf("failed to parse namespace %q: %w", namespace, err) + } + orgID := namespaceInfo.OrgID + + dto := make(getResourcePermissionsResponse, 0, len(permissions)) + + for _, perm := range permissions { + kind := perm.Kind + name := perm.Name + verb := perm.Verb + + if name == "" || verb == "" { + continue + } + + permission := cases.Title(language.Und).String(verb) + actions, exists := a.service.options.PermissionsToActions[permission] + if !exists { + log.New("resource-permissions-api").Warn( + "Permission not found in PermissionsToActions map", + "permission", permission, + "resource", a.service.options.Resource, + "availablePermissions", fmt.Sprintf("%v", getMapKeys(a.service.options.PermissionsToActions)), + ) + actions = []string{} + } + + permDTO := resourcePermissionDTO{ + Permission: permission, + Actions: actions, + IsManaged: true, + IsInherited: false, + } + + switch kind { + case iamv0.ResourcePermissionSpecPermissionKindUser, iamv0.ResourcePermissionSpecPermissionKindServiceAccount: + userDetails, err := a.service.userService.GetByUID(context.Background(), &user.GetUserByUIDQuery{UID: name}) + if err == nil { + permDTO.UserID = userDetails.ID + permDTO.UserUID = userDetails.UID + permDTO.UserLogin = userDetails.Login + permDTO.UserAvatarUrl = dtos.GetGravatarUrl(a.cfg, userDetails.Email) + permDTO.IsServiceAccount = userDetails.IsServiceAccount + permDTO.RoleName = fmt.Sprintf("managed:users:%d:permissions", userDetails.ID) + } + case iamv0.ResourcePermissionSpecPermissionKindTeam: + teamDetails, err := a.service.teamService.GetTeamByID(context.Background(), &team.GetTeamByIDQuery{ + UID: name, + OrgID: orgID, + }) + if err == nil { + permDTO.Team = teamDetails.Name + permDTO.TeamID = teamDetails.ID + permDTO.TeamUID = teamDetails.UID + permDTO.TeamAvatarUrl = dtos.GetGravatarUrlWithDefault(a.cfg, teamDetails.Email, teamDetails.Name) + permDTO.RoleName = fmt.Sprintf("managed:teams:%d:permissions", teamDetails.ID) + } else { + permDTO.TeamUID = name + permDTO.Team = name + } + case iamv0.ResourcePermissionSpecPermissionKindBasicRole: + permDTO.BuiltInRole = name + permDTO.RoleName = fmt.Sprintf("managed:builtins:%s:permissions", name) + } + + dto = append(dto, permDTO) + } + + return dto, nil +} + +func (a *api) getAPIGroup() string { + if a.service.options.APIGroup != "" { + return a.service.options.APIGroup + } + return fmt.Sprintf("%s.grafana.app", a.service.options.Resource) +} + +func getMapKeys(m map[string][]string) []string { + keys := make([]string, 0, len(m)) + for k := range m { + keys = append(keys, k) + } + return keys +} + +func (a *api) buildResourcePermissionName(resourceID string) string { + return fmt.Sprintf("%s-%s-%s", a.getAPIGroup(), a.service.options.Resource, resourceID) +} diff --git a/pkg/services/accesscontrol/resourcepermissions/options.go b/pkg/services/accesscontrol/resourcepermissions/options.go index 01d40b5834e..51654764620 100644 --- a/pkg/services/accesscontrol/resourcepermissions/options.go +++ b/pkg/services/accesscontrol/resourcepermissions/options.go @@ -5,6 +5,7 @@ import ( "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/services/accesscontrol" + "github.com/grafana/grafana/pkg/services/apiserver" "github.com/grafana/grafana/pkg/web" ) @@ -16,6 +17,9 @@ type Options struct { Resource string // ResourceAttribute is the attribute the scope should be based on (e.g. id or uid) ResourceAttribute string + // APIGroup is the Kubernetes API group for the resource (e.g. "folder.grafana.app") + // If not set, defaults to "{Resource}.grafana.app" + APIGroup string // OnlyManaged will tell the service to return all permissions if set to false and only managed permissions if set to true OnlyManaged bool // ResourceTranslator is a translator function that will be called before each action, it can be used to translate a resource id to a different format. @@ -45,4 +49,6 @@ type Options struct { InheritedScopesSolver InheritedScopesSolver // LicenseMV if configured is applied to endpoints that can modify permissions LicenseMW web.Handler + // RestConfigProvider if configured enables K8s API redirect for resource permissions + RestConfigProvider apiserver.RestConfigProvider } diff --git a/pkg/services/accesscontrol/resourcepermissions/service.go b/pkg/services/accesscontrol/resourcepermissions/service.go index 0866967bdd4..f3a8a8d30b4 100644 --- a/pkg/services/accesscontrol/resourcepermissions/service.go +++ b/pkg/services/accesscontrol/resourcepermissions/service.go @@ -104,7 +104,7 @@ func New(cfg *setting.Cfg, actionSetSvc: actionSetService, } - s.api = newApi(cfg, ac, router, s) + s.api = newApi(cfg, ac, router, s, features, s.options.RestConfigProvider) if err := s.declareFixedRoles(); err != nil { return nil, err From e5259c2ad43d62049246597f77543068e63da317 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Jim=C3=A9nez=20S=C3=A1nchez?= Date: Thu, 4 Dec 2025 16:05:15 +0100 Subject: [PATCH 031/110] Dashboards: Prevent memory leak in CUE validation by reusing context only for 100 validations (#114818) * fix(dashboard): prevent memory leak in CUE validation by using fresh contexts Fixes #114344 The CUE validation was reusing a single cue.Context across all validations, which caused unbounded memory growth due to CUE's internal caching of intermediate computation results (disjunctions, unifications, etc.). Root Cause: - A single cue.Context was created and reused via getValidator() - Each validation added entries to the context's internal caches - These caches grew unboundedly over time - Memory could not be garbage collected because the context held references Solution: - Store the schema source string instead of a compiled cue.Value - Create a fresh cuecontext.New() for each validation - This allows the context and its caches to be garbage collected after each validation completes Performance Impact: - ~2x slower due to schema recompilation per validation - Acceptable trade-off to prevent memory leaks - Memory usage stays bounded instead of growing unboundedly * fix(dashboard): use periodic context recreation to prevent CUE memory leaks Replace fresh context creation with periodic context recreation approach. The context is reused for up to 100 validations, then recreated to allow garbage collection of cached values while maintaining good performance. This balances performance (only 19% slower than leaky approach) with memory safety (stable at ~5 MB vs ~2 GB leak). See https://github.com/grafana/grafana/issues/114344#issuecomment-3605562491 * refactor(cuevalidator): simplify to use mutex instead of atomic counter Since CUE is not thread-safe, we need the mutex for the entire validation operation anyway. Using a regular int counter protected by the mutex is simpler and cleaner than using atomic operations. --- .../apis/dashboard/cuevalidator/validator.go | 51 ++++++++++++++++--- .../pkg/apis/dashboard/v0alpha1/validation.go | 9 ++-- .../pkg/apis/dashboard/v1beta1/validation.go | 9 ++-- .../pkg/apis/dashboard/v2alpha1/validation.go | 9 ++-- .../pkg/apis/dashboard/v2beta1/validation.go | 9 ++-- 5 files changed, 65 insertions(+), 22 deletions(-) diff --git a/apps/dashboard/pkg/apis/dashboard/cuevalidator/validator.go b/apps/dashboard/pkg/apis/dashboard/cuevalidator/validator.go index 38792ade026..2a3c075eb56 100644 --- a/apps/dashboard/pkg/apis/dashboard/cuevalidator/validator.go +++ b/apps/dashboard/pkg/apis/dashboard/cuevalidator/validator.go @@ -4,26 +4,65 @@ import ( "sync" "cuelang.org/go/cue" + "cuelang.org/go/cue/cuecontext" cuejson "cuelang.org/go/encoding/json" ) -// Validator provides thread-safe CUE schema validation. +const ( + // maxValidations limits how many validations can use the same context before it's recreated. + // This prevents unbounded memory growth while still allowing schema reuse for performance. + // After this many validations, the context is discarded and a new one is created. + maxValidations = 100 +) + +// Validator provides thread-safe CUE schema validation with periodic context recreation. // // CUE is not safe for concurrent use: https://github.com/cue-lang/cue/discussions/1205#discussioncomment-1189238 // This validator uses a mutex to protect concurrent access to the underlying CUE validation. +// +// To prevent memory leaks from CUE's internal caching, we reuse a context for up to maxValidations +// validations, then recreate it. This balances performance (schema reuse) with memory safety +// (periodic garbage collection of cached values). +// +// See https://github.com/grafana/grafana/issues/114344#issuecomment-3605562491 for details +// about the memory leak issue and this fix. type Validator struct { - schema cue.Value - mu sync.Mutex + schemaSource string + schemaPath cue.Path + mu sync.Mutex + ctx *cue.Context + compiledSchema cue.Value + validationCount int } -func NewValidator(schema cue.Value) *Validator { +// NewValidatorFromSource creates a new validator from a schema source string and path. +// This prevents memory leaks by periodically recreating the CUE context after maxValidations uses. +func NewValidatorFromSource(schemaSource string, schemaPath cue.Path) *Validator { + cueCtx := cuecontext.New() + compiledSchema := cueCtx.CompileString(schemaSource).LookupPath(schemaPath) return &Validator{ - schema: schema, + schemaSource: schemaSource, + schemaPath: schemaPath, + ctx: cueCtx, + compiledSchema: compiledSchema, } } func (v *Validator) Validate(data []byte) error { v.mu.Lock() defer v.mu.Unlock() - return cuejson.Validate(data, v.schema) + + // Increment validation count + v.validationCount++ + + // If we've reached the maximum number of validations, recreate the context + if v.validationCount >= maxValidations { + // Recreate context to allow GC to reclaim cached values + v.ctx = cuecontext.New() + v.compiledSchema = v.ctx.CompileString(v.schemaSource).LookupPath(v.schemaPath) + v.validationCount = 0 + } + + // Validate using the current compiled schema + return cuejson.Validate(data, v.compiledSchema) } diff --git a/apps/dashboard/pkg/apis/dashboard/v0alpha1/validation.go b/apps/dashboard/pkg/apis/dashboard/v0alpha1/validation.go index a90eab95126..295797a2fb5 100644 --- a/apps/dashboard/pkg/apis/dashboard/v0alpha1/validation.go +++ b/apps/dashboard/pkg/apis/dashboard/v0alpha1/validation.go @@ -12,7 +12,6 @@ import ( "k8s.io/apimachinery/pkg/util/validation/field" "cuelang.org/go/cue" - "cuelang.org/go/cue/cuecontext" "cuelang.org/go/cue/errors" ) @@ -83,11 +82,13 @@ var schemaSource string func getValidator() *cuevalidator.Validator { getSchemaOnce.Do(func() { - cueCtx := cuecontext.New() - compiledSchema := cueCtx.CompileString(schemaSource).LookupPath( + // The validator uses periodic context recreation to prevent memory leaks. + // The context is reused for up to 100 validations, then recreated to allow + // garbage collection of cached values while maintaining good performance. + validator = cuevalidator.NewValidatorFromSource( + schemaSource, cue.ParsePath("lineage.schemas[0].schema.spec"), ) - validator = cuevalidator.NewValidator(compiledSchema) }) return validator diff --git a/apps/dashboard/pkg/apis/dashboard/v1beta1/validation.go b/apps/dashboard/pkg/apis/dashboard/v1beta1/validation.go index 248dc27111c..0b57f702409 100644 --- a/apps/dashboard/pkg/apis/dashboard/v1beta1/validation.go +++ b/apps/dashboard/pkg/apis/dashboard/v1beta1/validation.go @@ -10,7 +10,6 @@ import ( "k8s.io/apimachinery/pkg/util/validation/field" "cuelang.org/go/cue" - "cuelang.org/go/cue/cuecontext" "cuelang.org/go/cue/errors" "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/cuevalidator" @@ -84,11 +83,13 @@ var schemaSource string func getValidator() *cuevalidator.Validator { getSchemaOnce.Do(func() { - cueCtx := cuecontext.New() - compiledSchema := cueCtx.CompileString(schemaSource).LookupPath( + // The validator uses periodic context recreation to prevent memory leaks. + // The context is reused for up to 100 validations, then recreated to allow + // garbage collection of cached values while maintaining good performance. + validator = cuevalidator.NewValidatorFromSource( + schemaSource, cue.ParsePath("lineage.schemas[0].schema.spec"), ) - validator = cuevalidator.NewValidator(compiledSchema) }) return validator diff --git a/apps/dashboard/pkg/apis/dashboard/v2alpha1/validation.go b/apps/dashboard/pkg/apis/dashboard/v2alpha1/validation.go index ca9dcd3e514..71f2e6d08e1 100644 --- a/apps/dashboard/pkg/apis/dashboard/v2alpha1/validation.go +++ b/apps/dashboard/pkg/apis/dashboard/v2alpha1/validation.go @@ -10,7 +10,6 @@ import ( "k8s.io/apimachinery/pkg/util/validation/field" "cuelang.org/go/cue" - "cuelang.org/go/cue/cuecontext" "cuelang.org/go/cue/errors" "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/cuevalidator" @@ -133,11 +132,13 @@ var schemaSource string func getValidator() *cuevalidator.Validator { getSchemaOnce.Do(func() { - cueCtx := cuecontext.New() - compiledSchema := cueCtx.CompileString(schemaSource).LookupPath( + // The validator uses periodic context recreation to prevent memory leaks. + // The context is reused for up to 100 validations, then recreated to allow + // garbage collection of cached values while maintaining good performance. + validator = cuevalidator.NewValidatorFromSource( + schemaSource, cue.ParsePath("DashboardSpec"), ) - validator = cuevalidator.NewValidator(compiledSchema) }) return validator diff --git a/apps/dashboard/pkg/apis/dashboard/v2beta1/validation.go b/apps/dashboard/pkg/apis/dashboard/v2beta1/validation.go index 518c133bcd7..875be7b4beb 100644 --- a/apps/dashboard/pkg/apis/dashboard/v2beta1/validation.go +++ b/apps/dashboard/pkg/apis/dashboard/v2beta1/validation.go @@ -10,7 +10,6 @@ import ( "k8s.io/apimachinery/pkg/util/validation/field" "cuelang.org/go/cue" - "cuelang.org/go/cue/cuecontext" "cuelang.org/go/cue/errors" "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/cuevalidator" @@ -133,11 +132,13 @@ var schemaSource string func getValidator() *cuevalidator.Validator { getSchemaOnce.Do(func() { - cueCtx := cuecontext.New() - compiledSchema := cueCtx.CompileString(schemaSource).LookupPath( + // The validator uses periodic context recreation to prevent memory leaks. + // The context is reused for up to 100 validations, then recreated to allow + // garbage collection of cached values while maintaining good performance. + validator = cuevalidator.NewValidatorFromSource( + schemaSource, cue.ParsePath("DashboardSpec"), ) - validator = cuevalidator.NewValidator(compiledSchema) }) return validator From 4c5d9cb95f32055cd4a68e30d70e0607b3047b59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mustafa=20Sencer=20=C3=96zcan?= <32759850+mustafasencer@users.noreply.github.com> Date: Thu, 4 Dec 2025 16:15:00 +0100 Subject: [PATCH 032/110] feat: add unified storage data migration step for playlists (#114582) * fix: add type * feat: register step * feat: add playlist support * test: add test case * fix: gen mock * fix: go gen * fix: lint * fix: lint * fix: tests * fix: add resource * fix: readd * fix: address comments * fix: independent playlist query for migrations * fix: remove lock logic for sqlite * fix: handle creation and update datetimes * fix: query templating * fix: simply resources and address comments --- .../datamigrations/to_unified_storage.go | 38 ++-- .../migration_dashboard_accessor_mock.go | 61 ++++++ pkg/registry/apis/dashboard/legacy/queries.go | 27 +++ .../apis/dashboard/legacy/queries_test.go | 14 ++ .../apis/dashboard/legacy/query_playlists.sql | 18 ++ .../apis/dashboard/legacy/sql_dashboards.go | 177 +++++++++++++++++- .../testdata/mysql--query_playlists-list.sql | 18 ++ .../postgres--query_playlists-list.sql | 18 ++ .../testdata/sqlite--query_playlists-list.sql | 18 ++ pkg/registry/apis/dashboard/legacy/types.go | 1 + pkg/setting/setting_unified_storage.go | 9 +- pkg/storage/unified/migrations/migrator.go | 82 ++------ .../unified/migrations/migrator_test.go | 1 + .../unified/migrations/playlists_test.go | 119 ++++++++++++ pkg/storage/unified/migrations/resources.go | 100 ++++++++++ pkg/storage/unified/migrations/service.go | 45 +++-- 16 files changed, 641 insertions(+), 105 deletions(-) create mode 100644 pkg/registry/apis/dashboard/legacy/query_playlists.sql create mode 100755 pkg/registry/apis/dashboard/legacy/testdata/mysql--query_playlists-list.sql create mode 100755 pkg/registry/apis/dashboard/legacy/testdata/postgres--query_playlists-list.sql create mode 100755 pkg/registry/apis/dashboard/legacy/testdata/sqlite--query_playlists-list.sql create mode 100644 pkg/storage/unified/migrations/playlists_test.go create mode 100644 pkg/storage/unified/migrations/resources.go diff --git a/pkg/cmd/grafana-cli/commands/datamigrations/to_unified_storage.go b/pkg/cmd/grafana-cli/commands/datamigrations/to_unified_storage.go index a884c8f16cf..496adaf060d 100644 --- a/pkg/cmd/grafana-cli/commands/datamigrations/to_unified_storage.go +++ b/pkg/cmd/grafana-cli/commands/datamigrations/to_unified_storage.go @@ -86,22 +86,30 @@ func ToUnifiedStorage(c utils.CommandLine, cfg *setting.Cfg, sqlStore db.DB) err ) if c.Bool("non-interactive") { - migrator := migrations.ProvideUnifiedMigrator(dashboardAccess, grpcClient) - - opts.WithHistory = true // always include history in non-interactive mode - rsp, err := migrator.Migrate(ctx, opts) - if exitErr := handleMigrationError(err, rsp); exitErr != nil { - return exitErr - } - - logger.Info("Migrated legacy resources successfully in", time.Since(start)) - if rsp != nil { - jj, _ := json.MarshalIndent(rsp, "", " ") - logger.Info("Migration summary:", string(jj)) - } - return nil + return runNonInteractiveMigration(ctx, opts, dashboardAccess, grpcClient, start) } + return runInteractiveMigration(ctx, cfg, opts, dashboardAccess, grpcClient, start) +} + +func runNonInteractiveMigration(ctx context.Context, opts legacy.MigrateOptions, dashboardAccess legacy.MigrationDashboardAccessor, grpcClient resource.ResourceClient, start time.Time) error { + migrator := migrations.ProvideUnifiedMigrator(dashboardAccess, grpcClient) + + opts.WithHistory = true // always include history in non-interactive mode + rsp, err := migrator.Migrate(ctx, opts) + if exitErr := handleMigrationError(err, rsp); exitErr != nil { + return exitErr + } + + logger.Info("Migrated legacy resources successfully in", time.Since(start)) + if rsp != nil { + jj, _ := json.MarshalIndent(rsp, "", " ") + logger.Info("Migration summary:", string(jj)) + } + return nil +} + +func runInteractiveMigration(ctx context.Context, cfg *setting.Cfg, opts legacy.MigrateOptions, dashboardAccess legacy.MigrationDashboardAccessor, grpcClient resource.ResourceClient, start time.Time) error { yes, err := promptYesNo(fmt.Sprintf("Count legacy resources for namespace: %s?", opts.Namespace)) if err != nil { return err @@ -143,7 +151,6 @@ func ToUnifiedStorage(c utils.CommandLine, cfg *setting.Cfg, sqlStore db.DB) err } migrator := migrations.ProvideUnifiedMigratorParquet(dashboardAccess, parquetClient) start = time.Now() - last = time.Now() rsp, err := migrator.Migrate(ctx, opts) if err != nil { return err @@ -187,7 +194,6 @@ func ToUnifiedStorage(c utils.CommandLine, cfg *setting.Cfg, sqlStore db.DB) err if yes { migrator := migrations.ProvideUnifiedMigrator(dashboardAccess, grpcClient) start = time.Now() - last = time.Now() rsp, err := migrator.Migrate(ctx, opts) if err != nil { return err diff --git a/pkg/registry/apis/dashboard/legacy/migration_dashboard_accessor_mock.go b/pkg/registry/apis/dashboard/legacy/migration_dashboard_accessor_mock.go index 43f2ce05ad1..dab60c6e29d 100644 --- a/pkg/registry/apis/dashboard/legacy/migration_dashboard_accessor_mock.go +++ b/pkg/registry/apis/dashboard/legacy/migration_dashboard_accessor_mock.go @@ -264,6 +264,67 @@ func (_c *MockMigrationDashboardAccessor_MigrateLibraryPanels_Call) RunAndReturn return _c } +// MigratePlaylists provides a mock function with given fields: ctx, orgId, opts, stream +func (_m *MockMigrationDashboardAccessor) MigratePlaylists(ctx context.Context, orgId int64, opts MigrateOptions, stream resourcepb.BulkStore_BulkProcessClient) (*BlobStoreInfo, error) { + ret := _m.Called(ctx, orgId, opts, stream) + + if len(ret) == 0 { + panic("no return value specified for MigratePlaylists") + } + + var r0 *BlobStoreInfo + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, int64, MigrateOptions, resourcepb.BulkStore_BulkProcessClient) (*BlobStoreInfo, error)); ok { + return rf(ctx, orgId, opts, stream) + } + if rf, ok := ret.Get(0).(func(context.Context, int64, MigrateOptions, resourcepb.BulkStore_BulkProcessClient) *BlobStoreInfo); ok { + r0 = rf(ctx, orgId, opts, stream) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*BlobStoreInfo) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, int64, MigrateOptions, resourcepb.BulkStore_BulkProcessClient) error); ok { + r1 = rf(ctx, orgId, opts, stream) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockMigrationDashboardAccessor_MigratePlaylists_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'MigratePlaylists' +type MockMigrationDashboardAccessor_MigratePlaylists_Call struct { + *mock.Call +} + +// MigratePlaylists is a helper method to define mock.On call +// - ctx context.Context +// - orgId int64 +// - opts MigrateOptions +// - stream resourcepb.BulkStore_BulkProcessClient +func (_e *MockMigrationDashboardAccessor_Expecter) MigratePlaylists(ctx interface{}, orgId interface{}, opts interface{}, stream interface{}) *MockMigrationDashboardAccessor_MigratePlaylists_Call { + return &MockMigrationDashboardAccessor_MigratePlaylists_Call{Call: _e.mock.On("MigratePlaylists", ctx, orgId, opts, stream)} +} + +func (_c *MockMigrationDashboardAccessor_MigratePlaylists_Call) Run(run func(ctx context.Context, orgId int64, opts MigrateOptions, stream resourcepb.BulkStore_BulkProcessClient)) *MockMigrationDashboardAccessor_MigratePlaylists_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(int64), args[2].(MigrateOptions), args[3].(resourcepb.BulkStore_BulkProcessClient)) + }) + return _c +} + +func (_c *MockMigrationDashboardAccessor_MigratePlaylists_Call) Return(_a0 *BlobStoreInfo, _a1 error) *MockMigrationDashboardAccessor_MigratePlaylists_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockMigrationDashboardAccessor_MigratePlaylists_Call) RunAndReturn(run func(context.Context, int64, MigrateOptions, resourcepb.BulkStore_BulkProcessClient) (*BlobStoreInfo, error)) *MockMigrationDashboardAccessor_MigratePlaylists_Call { + _c.Call.Return(run) + return _c +} + // NewMockMigrationDashboardAccessor creates a new instance of MockMigrationDashboardAccessor. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. // The first argument is typically a *testing.T value. func NewMockMigrationDashboardAccessor(t interface { diff --git a/pkg/registry/apis/dashboard/legacy/queries.go b/pkg/registry/apis/dashboard/legacy/queries.go index 56807b9cc5a..28be4f507a0 100644 --- a/pkg/registry/apis/dashboard/legacy/queries.go +++ b/pkg/registry/apis/dashboard/legacy/queries.go @@ -28,6 +28,7 @@ func mustTemplate(filename string) *template.Template { var ( sqlQueryDashboards = mustTemplate("query_dashboards.sql") sqlQueryPanels = mustTemplate("query_panels.sql") + sqlQueryPlaylists = mustTemplate("query_playlists.sql") ) type sqlQuery struct { @@ -83,3 +84,29 @@ func newLibraryQueryReq(sql *legacysql.LegacyDatabaseHelper, query *LibraryPanel UserTable: sql.Table("user"), } } + +type PlaylistQuery struct { + OrgID int64 +} + +type sqlPlaylistQuery struct { + sqltemplate.SQLTemplate + Query *PlaylistQuery + + PlaylistTable string + PlaylistItemTable string +} + +func (r sqlPlaylistQuery) Validate() error { + return nil +} + +func newPlaylistQueryReq(sql *legacysql.LegacyDatabaseHelper, query *PlaylistQuery) sqlPlaylistQuery { + return sqlPlaylistQuery{ + SQLTemplate: sqltemplate.New(sql.DialectForDriver()), + Query: query, + + PlaylistTable: sql.Table("playlist"), + PlaylistItemTable: sql.Table("playlist_item"), + } +} diff --git a/pkg/registry/apis/dashboard/legacy/queries_test.go b/pkg/registry/apis/dashboard/legacy/queries_test.go index 3ee82e899d7..1345bfc2925 100644 --- a/pkg/registry/apis/dashboard/legacy/queries_test.go +++ b/pkg/registry/apis/dashboard/legacy/queries_test.go @@ -29,6 +29,12 @@ func TestDashboardQueries(t *testing.T) { return &v } + getPlaylistQuery := func(q *PlaylistQuery) sqltemplate.SQLTemplate { + v := newPlaylistQueryReq(nodb, q) + v.SQLTemplate = mocks.NewTestingSQLTemplate() + return &v + } + mocks.CheckQuerySnapshots(t, mocks.TemplateTestSetup{ RootDir: "testdata", SQLTemplatesFS: sqlTemplatesFS, @@ -118,6 +124,14 @@ func TestDashboardQueries(t *testing.T) { }), }, }, + sqlQueryPlaylists: { + { + Name: "list", + Data: getPlaylistQuery(&PlaylistQuery{ + OrgID: 1, + }), + }, + }, }, }) } diff --git a/pkg/registry/apis/dashboard/legacy/query_playlists.sql b/pkg/registry/apis/dashboard/legacy/query_playlists.sql new file mode 100644 index 00000000000..5d37c05ad0c --- /dev/null +++ b/pkg/registry/apis/dashboard/legacy/query_playlists.sql @@ -0,0 +1,18 @@ +SELECT + p.id, + p.org_id, + p.uid, + p.name, + p.interval, + p.created_at, + p.updated_at, + pi.type as item_type, + pi.value as item_value +FROM + {{ .Ident .PlaylistTable }} as p + LEFT OUTER JOIN {{ .Ident .PlaylistItemTable }} as pi ON p.id = pi.playlist_id +WHERE + p.org_id = {{ .Arg .Query.OrgID }} +ORDER BY + p.id ASC, + pi.{{ .Ident "order" }} ASC \ No newline at end of file diff --git a/pkg/registry/apis/dashboard/legacy/sql_dashboards.go b/pkg/registry/apis/dashboard/legacy/sql_dashboards.go index 8c465ef7b24..24ec135e785 100644 --- a/pkg/registry/apis/dashboard/legacy/sql_dashboards.go +++ b/pkg/registry/apis/dashboard/legacy/sql_dashboards.go @@ -24,6 +24,7 @@ import ( dashboardV1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1" "github.com/grafana/grafana/apps/dashboard/pkg/migration/schemaversion" folders "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1" + playlistv0 "github.com/grafana/grafana/apps/playlist/pkg/apis/playlist/v0alpha1" "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/apimachinery/utils" @@ -138,7 +139,16 @@ func NewDashboardSQLAccess(sql legacysql.LegacyDatabaseProvider, } } -func (a *dashboardSqlAccess) getRows(ctx context.Context, sql *legacysql.LegacyDatabaseHelper, query *DashboardQuery) (*rowsWrapper, error) { +func (a *dashboardSqlAccess) executeQuery(ctx context.Context, helper *legacysql.LegacyDatabaseHelper, query string, args ...any) (*sql.Rows, error) { + // Use transaction if available in context. + // This allows us to run migrations in a transaction which is specifically required for SQLite. + if tx := resource.TransactionFromContext(ctx); tx != nil { + return tx.QueryContext(ctx, query, args...) + } + return helper.DB.GetSqlxSession().Query(ctx, query, args...) +} + +func (a *dashboardSqlAccess) getRows(ctx context.Context, helper *legacysql.LegacyDatabaseHelper, query *DashboardQuery) (*rowsWrapper, error) { ctx, span := tracer.Start(ctx, "legacy.dashboardSqlAccess.getRows") defer span.End() @@ -150,7 +160,7 @@ func (a *dashboardSqlAccess) getRows(ctx context.Context, sql *legacysql.LegacyD // } } - req := newQueryReq(sql, query) + req := newQueryReq(helper, query) tmpl := sqlQueryDashboards if query.UseHistoryTable() && query.GetTrash { @@ -167,7 +177,7 @@ func (a *dashboardSqlAccess) getRows(ctx context.Context, sql *legacysql.LegacyD // fmt.Printf("DASHBOARD QUERY: %s [%+v] // %+v\n", pretty, req.GetArgs(), query) // } - rows, err := sql.DB.GetSqlxSession().Query(ctx, q, req.GetArgs()...) + rows, err := a.executeQuery(ctx, helper, q, req.GetArgs()...) if err != nil { if rows != nil { _ = rows.Close() @@ -465,6 +475,132 @@ func (a *dashboardSqlAccess) MigrateLibraryPanels(ctx context.Context, orgId int return nil, nil } +// MigratePlaylists handles the playlist migration logic +func (a *dashboardSqlAccess) MigratePlaylists(ctx context.Context, orgId int64, opts MigrateOptions, stream resourcepb.BulkStore_BulkProcessClient) (*BlobStoreInfo, error) { + opts.Progress(-1, "migrating playlists...") + rows, err := a.ListPlaylists(ctx, orgId) + if rows != nil { + defer func() { + _ = rows.Close() + }() + } + if err != nil { + return nil, err + } + + // Group playlist items by playlist ID + type playlistData struct { + id int64 + uid string + name string + interval string + items []playlistv0.PlaylistItem + createdAt int64 + updatedAt int64 + } + + playlists := make(map[int64]*playlistData) + var currentID int64 + var orgID int64 + var uid, name, interval string + var createdAt, updatedAt int64 + var itemType, itemValue sql.NullString + + count := 0 + for rows.Next() { + err = rows.Scan(¤tID, &orgID, &uid, &name, &interval, &createdAt, &updatedAt, &itemType, &itemValue) + if err != nil { + return nil, err + } + + // Get or create playlist entry + pl, exists := playlists[currentID] + if !exists { + pl = &playlistData{ + id: currentID, + uid: uid, + name: name, + interval: interval, + items: []playlistv0.PlaylistItem{}, + createdAt: createdAt, + updatedAt: updatedAt, + } + playlists[currentID] = pl + } + + // Add item if it exists (LEFT JOIN can return NULL for playlists without items) + if itemType.Valid && itemValue.Valid { + pl.items = append(pl.items, playlistv0.PlaylistItem{ + Type: playlistv0.PlaylistItemType(itemType.String), + Value: itemValue.String, + }) + } + } + + if err = rows.Err(); err != nil { + return nil, err + } + + // Convert to K8s objects and send to stream + for _, pl := range playlists { + playlist := &playlistv0.Playlist{ + TypeMeta: metav1.TypeMeta{ + APIVersion: playlistv0.GroupVersion.String(), + Kind: "Playlist", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: pl.uid, + Namespace: opts.Namespace, + CreationTimestamp: metav1.NewTime(time.UnixMilli(pl.createdAt)), + }, + Spec: playlistv0.PlaylistSpec{ + Title: pl.name, + Interval: pl.interval, + Items: pl.items, + }, + } + + // Set updated timestamp if different from created + if pl.updatedAt != pl.createdAt { + meta, err := utils.MetaAccessor(playlist) + if err != nil { + return nil, err + } + updatedTime := time.UnixMilli(pl.updatedAt) + meta.SetUpdatedTimestamp(&updatedTime) + } + + body, err := json.Marshal(playlist) + if err != nil { + return nil, err + } + + req := &resourcepb.BulkRequest{ + Key: &resourcepb.ResourceKey{ + Namespace: opts.Namespace, + Group: "playlist.grafana.app", + Resource: "playlists", + Name: pl.uid, + }, + Value: body, + Action: resourcepb.BulkRequest_ADDED, + } + + opts.Progress(count, fmt.Sprintf("%s (%d)", pl.name, len(req.Value))) + count++ + + err = stream.Send(req) + if err != nil { + if errors.Is(err, io.EOF) { + err = nil + } + return nil, err + } + } + opts.Progress(-2, fmt.Sprintf("finished playlists... (%d)", len(playlists))) + return nil, nil +} + var _ resource.ListIterator = (*rowsWrapper)(nil) type rowsWrapper struct { @@ -903,20 +1039,19 @@ func (a *dashboardSqlAccess) GetLibraryPanels(ctx context.Context, query Library return nil, err } - sqlx, err := a.sql(ctx) + helper, err := a.sql(ctx) if err != nil { return nil, err } - req := newLibraryQueryReq(sqlx, &query) + req := newLibraryQueryReq(helper, &query) rawQuery, err := sqltemplate.Execute(sqlQueryPanels, req) if err != nil { return nil, fmt.Errorf("execute template %q: %w", sqlQueryPanels.Name(), err) } - q := rawQuery res := &dashboardV0.LibraryPanelList{} - rows, err := sqlx.DB.GetSqlxSession().Query(ctx, q, req.GetArgs()...) + rows, err := a.executeQuery(ctx, helper, rawQuery, req.GetArgs()...) defer func() { if rows != nil { _ = rows.Close() @@ -959,7 +1094,7 @@ func (a *dashboardSqlAccess) GetLibraryPanels(ctx context.Context, query Library } } if query.UID == "" { - rv, err := sqlx.GetResourceVersion(ctx, "library_element", "updated") + rv, err := helper.GetResourceVersion(ctx, "library_element", "updated") if err == nil { res.ResourceVersion = strconv.FormatInt(rv*1000, 10) // convert to microseconds } @@ -1038,3 +1173,29 @@ func parseLibraryPanelRow(p panel) (dashboardV0.LibraryPanel, error) { func (b *dashboardSqlAccess) RebuildIndexes(ctx context.Context, req *resourcepb.RebuildIndexesRequest) (*resourcepb.RebuildIndexesResponse, error) { return nil, fmt.Errorf("not implemented") } + +func (a *dashboardSqlAccess) ListPlaylists(ctx context.Context, orgID int64) (*sql.Rows, error) { + ctx, span := tracer.Start(ctx, "legacy.dashboardSqlAccess.ListPlaylists") + defer span.End() + + helper, err := a.sql(ctx) + if err != nil { + return nil, err + } + + req := newPlaylistQueryReq(helper, &PlaylistQuery{ + OrgID: orgID, + }) + + rawQuery, err := sqltemplate.Execute(sqlQueryPlaylists, req) + if err != nil { + return nil, fmt.Errorf("execute template %q: %w", sqlQueryPlaylists.Name(), err) + } + + rows, err := a.executeQuery(ctx, helper, rawQuery, req.GetArgs()...) + if err != nil && rows != nil { + _ = rows.Close() + return nil, err + } + return rows, err +} diff --git a/pkg/registry/apis/dashboard/legacy/testdata/mysql--query_playlists-list.sql b/pkg/registry/apis/dashboard/legacy/testdata/mysql--query_playlists-list.sql new file mode 100755 index 00000000000..d1fc170c8df --- /dev/null +++ b/pkg/registry/apis/dashboard/legacy/testdata/mysql--query_playlists-list.sql @@ -0,0 +1,18 @@ +SELECT + p.id, + p.org_id, + p.uid, + p.name, + p.interval, + p.created_at, + p.updated_at, + pi.type as item_type, + pi.value as item_value +FROM + `grafana`.`playlist` as p + LEFT OUTER JOIN `grafana`.`playlist_item` as pi ON p.id = pi.playlist_id +WHERE + p.org_id = 1 +ORDER BY + p.id ASC, + pi.`order` ASC diff --git a/pkg/registry/apis/dashboard/legacy/testdata/postgres--query_playlists-list.sql b/pkg/registry/apis/dashboard/legacy/testdata/postgres--query_playlists-list.sql new file mode 100755 index 00000000000..4927dcaa049 --- /dev/null +++ b/pkg/registry/apis/dashboard/legacy/testdata/postgres--query_playlists-list.sql @@ -0,0 +1,18 @@ +SELECT + p.id, + p.org_id, + p.uid, + p.name, + p.interval, + p.created_at, + p.updated_at, + pi.type as item_type, + pi.value as item_value +FROM + "grafana"."playlist" as p + LEFT OUTER JOIN "grafana"."playlist_item" as pi ON p.id = pi.playlist_id +WHERE + p.org_id = 1 +ORDER BY + p.id ASC, + pi."order" ASC diff --git a/pkg/registry/apis/dashboard/legacy/testdata/sqlite--query_playlists-list.sql b/pkg/registry/apis/dashboard/legacy/testdata/sqlite--query_playlists-list.sql new file mode 100755 index 00000000000..4927dcaa049 --- /dev/null +++ b/pkg/registry/apis/dashboard/legacy/testdata/sqlite--query_playlists-list.sql @@ -0,0 +1,18 @@ +SELECT + p.id, + p.org_id, + p.uid, + p.name, + p.interval, + p.created_at, + p.updated_at, + pi.type as item_type, + pi.value as item_value +FROM + "grafana"."playlist" as p + LEFT OUTER JOIN "grafana"."playlist_item" as pi ON p.id = pi.playlist_id +WHERE + p.org_id = 1 +ORDER BY + p.id ASC, + pi."order" ASC diff --git a/pkg/registry/apis/dashboard/legacy/types.go b/pkg/registry/apis/dashboard/legacy/types.go index 31d08af16d8..26cd043e8e5 100644 --- a/pkg/registry/apis/dashboard/legacy/types.go +++ b/pkg/registry/apis/dashboard/legacy/types.go @@ -74,4 +74,5 @@ type MigrationDashboardAccessor interface { MigrateDashboards(ctx context.Context, orgId int64, opts MigrateOptions, stream resourcepb.BulkStore_BulkProcessClient) (*BlobStoreInfo, error) MigrateFolders(ctx context.Context, orgId int64, opts MigrateOptions, stream resourcepb.BulkStore_BulkProcessClient) (*BlobStoreInfo, error) MigrateLibraryPanels(ctx context.Context, orgId int64, opts MigrateOptions, stream resourcepb.BulkStore_BulkProcessClient) (*BlobStoreInfo, error) + MigratePlaylists(ctx context.Context, orgId int64, opts MigrateOptions, stream resourcepb.BulkStore_BulkProcessClient) (*BlobStoreInfo, error) } diff --git a/pkg/setting/setting_unified_storage.go b/pkg/setting/setting_unified_storage.go index 9d0f5843ad2..4f69daa64fd 100644 --- a/pkg/setting/setting_unified_storage.go +++ b/pkg/setting/setting_unified_storage.go @@ -8,9 +8,8 @@ import ( "github.com/grafana/grafana/pkg/util/osutil" ) -// nolint:unused -var migratedUnifiedResources = []string{ - //"playlists.playlist.grafana.app", +var MigratedUnifiedResources = []string{ + "playlists.playlist.grafana.app", "folders.folder.grafana.app", "dashboards.dashboard.grafana.app", } @@ -63,7 +62,7 @@ func (cfg *Cfg) setUnifiedStorageConfig() { if !cfg.DisableDataMigrations && cfg.getUnifiedStorageType() == "unified" { // Helper log to find instances running migrations in the future cfg.Logger.Info("Unified migration configs not yet enforced") - //cfg.enforceMigrationToUnifiedConfigs() // TODO: uncomment when ready for release + // cfg.enforceMigrationToUnifiedConfigs() // TODO: uncomment when ready for release } else { // Helper log to find instances disabling migration cfg.Logger.Info("Unified migration configs enforcement disabled", "storage_type", cfg.getUnifiedStorageType(), "disable_data_migrations", cfg.DisableDataMigrations) @@ -116,7 +115,7 @@ func (cfg *Cfg) enforceMigrationToUnifiedConfigs() { section.Key("enable_search").SetValue("true") cfg.EnableSearch = true } - for _, resource := range migratedUnifiedResources { + for _, resource := range MigratedUnifiedResources { cfg.Logger.Info("Enforcing mode 5 for resource in unified storage", "resource", resource) if oldCfg, ok := cfg.UnifiedStorage[resource]; ok { cfg.Logger.Info("Overriding unified storage config for migrated resource", "resource", resource, "old_config", oldCfg) diff --git a/pkg/storage/unified/migrations/migrator.go b/pkg/storage/unified/migrations/migrator.go index d95aa59ace7..d086845d181 100644 --- a/pkg/storage/unified/migrations/migrator.go +++ b/pkg/storage/unified/migrations/migrator.go @@ -10,8 +10,6 @@ import ( authlib "github.com/grafana/authlib/types" - v1beta1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1" - folders "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1" "github.com/grafana/grafana/pkg/storage/unified/resource" "github.com/grafana/grafana/pkg/storage/unified/resourcepb" ) @@ -35,39 +33,26 @@ type streamProvider interface { createStream(ctx context.Context, opts legacy.MigrateOptions) (resourcepb.BulkStore_BulkProcessClient, error) } -// resourceClientStreamProvider creates streams using resource.ResourceClient -type resourceClientStreamProvider struct { - client resource.ResourceClient -} - -func (r *resourceClientStreamProvider) createStream(ctx context.Context, opts legacy.MigrateOptions) (resourcepb.BulkStore_BulkProcessClient, error) { - // Build collection settings for resource client +func buildCollectionSettings(opts legacy.MigrateOptions) resource.BulkSettings { settings := resource.BulkSettings{ RebuildCollection: true, SkipValidation: true, } for _, res := range opts.Resources { - switch fmt.Sprintf("%s/%s", res.Group, res.Resource) { - case "folder.grafana.app/folders": - settings.Collection = append(settings.Collection, &resourcepb.ResourceKey{ - Namespace: opts.Namespace, - Group: folders.GROUP, - Resource: folders.RESOURCE, - }) - case "dashboard.grafana.app/librarypanels": - settings.Collection = append(settings.Collection, &resourcepb.ResourceKey{ - Namespace: opts.Namespace, - Group: v1beta1.GROUP, - Resource: v1beta1.LIBRARY_PANEL_RESOURCE, - }) - case "dashboard.grafana.app/dashboards": - settings.Collection = append(settings.Collection, &resourcepb.ResourceKey{ - Namespace: opts.Namespace, - Group: v1beta1.GROUP, - Resource: v1beta1.DASHBOARD_RESOURCE, - }) + key := buildResourceKey(res.Group, res.Resource, opts.Namespace) + if key != nil { + settings.Collection = append(settings.Collection, key) } } + return settings +} + +type resourceClientStreamProvider struct { + client resource.ResourceClient +} + +func (r *resourceClientStreamProvider) createStream(ctx context.Context, opts legacy.MigrateOptions) (resourcepb.BulkStore_BulkProcessClient, error) { + settings := buildCollectionSettings(opts) ctx = metadata.NewOutgoingContext(ctx, settings.ToMD()) return r.client.BulkProcess(ctx) } @@ -78,33 +63,7 @@ type bulkStoreClientStreamProvider struct { } func (b *bulkStoreClientStreamProvider) createStream(ctx context.Context, opts legacy.MigrateOptions) (resourcepb.BulkStore_BulkProcessClient, error) { - // Build collection settings for resource client - settings := resource.BulkSettings{ - RebuildCollection: true, - SkipValidation: true, - } - for _, res := range opts.Resources { - switch fmt.Sprintf("%s/%s", res.Group, res.Resource) { - case "folder.grafana.app/folders": - settings.Collection = append(settings.Collection, &resourcepb.ResourceKey{ - Namespace: opts.Namespace, - Group: folders.GROUP, - Resource: folders.RESOURCE, - }) - case "dashboard.grafana.app/librarypanels": - settings.Collection = append(settings.Collection, &resourcepb.ResourceKey{ - Namespace: opts.Namespace, - Group: v1beta1.GROUP, - Resource: v1beta1.LIBRARY_PANEL_RESOURCE, - }) - case "dashboard.grafana.app/dashboards": - settings.Collection = append(settings.Collection, &resourcepb.ResourceKey{ - Namespace: opts.Namespace, - Group: v1beta1.GROUP, - Resource: v1beta1.DASHBOARD_RESOURCE, - }) - } - } + settings := buildCollectionSettings(opts) ctx = metadata.NewOutgoingContext(ctx, settings.ToMD()) return b.client.BulkProcess(ctx) } @@ -170,16 +129,11 @@ func (m *unifiedMigration) Migrate(ctx context.Context, opts legacy.MigrateOptio migratorFuncs := []migratorFunc{} for _, res := range opts.Resources { - switch fmt.Sprintf("%s/%s", res.Group, res.Resource) { - case "folder.grafana.app/folders": - migratorFuncs = append(migratorFuncs, m.MigrateFolders) - case "dashboard.grafana.app/librarypanels": - migratorFuncs = append(migratorFuncs, m.MigrateLibraryPanels) - case "dashboard.grafana.app/dashboards": - migratorFuncs = append(migratorFuncs, m.MigrateDashboards) - default: - return nil, fmt.Errorf("unsupported resource: %s", res) + fn := getMigratorFunc(m.MigrationDashboardAccessor, res.Group, res.Resource) + if fn == nil { + return nil, fmt.Errorf("unsupported resource: %s/%s", res.Group, res.Resource) } + migratorFuncs = append(migratorFuncs, fn) } // Execute migrations diff --git a/pkg/storage/unified/migrations/migrator_test.go b/pkg/storage/unified/migrations/migrator_test.go index e75ed26b9e3..92afe698a98 100644 --- a/pkg/storage/unified/migrations/migrator_test.go +++ b/pkg/storage/unified/migrations/migrator_test.go @@ -45,6 +45,7 @@ func TestIntegrationMigrations(t *testing.T) { migrationTestCases := []resourceMigratorTestCase{ newFoldersAndDashboardsTestCase(), + newPlaylistsTestCase(), } runMigrationTestSuite(t, migrationTestCases) diff --git a/pkg/storage/unified/migrations/playlists_test.go b/pkg/storage/unified/migrations/playlists_test.go new file mode 100644 index 00000000000..00f06759865 --- /dev/null +++ b/pkg/storage/unified/migrations/playlists_test.go @@ -0,0 +1,119 @@ +package migrations_test + +import ( + "context" + "testing" + + authlib "github.com/grafana/authlib/types" + "github.com/grafana/grafana/pkg/infra/tracing" + "github.com/grafana/grafana/pkg/services/playlist" + "github.com/grafana/grafana/pkg/services/playlist/playlistimpl" + "github.com/grafana/grafana/pkg/tests/apis" + "github.com/stretchr/testify/require" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// playlistsTestCase tests the "playlists" ResourceMigration +type playlistsTestCase struct { + playlistUIDs []string +} + +// newPlaylistsTestCase creates a test case for the playlists migrator +func newPlaylistsTestCase() resourceMigratorTestCase { + return &playlistsTestCase{ + playlistUIDs: []string{}, + } +} + +func (tc *playlistsTestCase) name() string { + return "playlists" +} + +func (tc *playlistsTestCase) resources() []schema.GroupVersionResource { + return []schema.GroupVersionResource{ + { + Group: "playlist.grafana.app", + Version: "v0alpha1", + Resource: "playlists", + }, + } +} + +func (tc *playlistsTestCase) setup(t *testing.T, helper *apis.K8sTestHelper) { + t.Helper() + + // Get playlist service from the test environment + // The service writes directly to SQL storage, which works in Mode0 + env := helper.GetEnv() + playlistSvc := playlistimpl.ProvideService(env.SQLStore, tracing.InitializeTracerForTest()) + + // Use a non-existent dashboard UID for testing + // This avoids interfering with other test cases + nonExistentDashboardUID := "non-existent-dashboard-uid" + + // Create playlist with dashboard UID items (pointing to non-existent dashboard) + playlist1UID := createTestPlaylist(t, playlistSvc, helper.Org1.OrgID, "Playlist with Dashboard UIDs", "5m", []playlist.PlaylistItem{ + {Type: "dashboard_by_uid", Value: nonExistentDashboardUID, Order: 1}, + }) + tc.playlistUIDs = append(tc.playlistUIDs, playlist1UID) + + // Create playlist with tag items + playlist2UID := createTestPlaylist(t, playlistSvc, helper.Org1.OrgID, "Playlist with Tags", "10m", []playlist.PlaylistItem{ + {Type: "dashboard_by_tag", Value: "test-tag", Order: 1}, + {Type: "dashboard_by_tag", Value: "another-tag", Order: 2}, + }) + tc.playlistUIDs = append(tc.playlistUIDs, playlist2UID) + + // Create playlist with mixed items + playlist3UID := createTestPlaylist(t, playlistSvc, helper.Org1.OrgID, "Playlist with Mixed Items", "15m", []playlist.PlaylistItem{ + {Type: "dashboard_by_uid", Value: nonExistentDashboardUID, Order: 1}, + {Type: "dashboard_by_tag", Value: "mixed-tag", Order: 2}, + }) + tc.playlistUIDs = append(tc.playlistUIDs, playlist3UID) +} + +func (tc *playlistsTestCase) verify(t *testing.T, helper *apis.K8sTestHelper, shouldExist bool) { + t.Helper() + + expectedPlaylistCount := 0 + if shouldExist { + expectedPlaylistCount = len(tc.playlistUIDs) + } + + orgID := helper.Org1.OrgID + namespace := authlib.OrgNamespaceFormatter(orgID) + + // Verify playlists + playlistCli := helper.GetResourceClient(apis.ResourceClientArgs{ + User: helper.Org1.Admin, + Namespace: namespace, + GVR: schema.GroupVersionResource{ + Group: "playlist.grafana.app", + Version: "v0alpha1", + Resource: "playlists", + }, + }) + + verifyResourceCount(t, playlistCli, expectedPlaylistCount) + for _, uid := range tc.playlistUIDs { + verifyResource(t, playlistCli, uid, shouldExist) + } +} + +func createTestPlaylist(t *testing.T, playlistSvc playlist.Service, orgID int64, name, interval string, items []playlist.PlaylistItem) string { + t.Helper() + + cmd := &playlist.CreatePlaylistCommand{ + Name: name, + Interval: interval, + Items: items, + OrgId: orgID, + } + + result, err := playlistSvc.Create(context.Background(), cmd) + require.NoError(t, err) + require.NotNil(t, result) + require.NotEmpty(t, result.UID) + + return result.UID +} diff --git a/pkg/storage/unified/migrations/resources.go b/pkg/storage/unified/migrations/resources.go new file mode 100644 index 00000000000..0a4dcc7766e --- /dev/null +++ b/pkg/storage/unified/migrations/resources.go @@ -0,0 +1,100 @@ +package migrations + +import ( + "fmt" + + v1beta1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1" + folders "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1" + playlists "github.com/grafana/grafana/apps/playlist/pkg/apis/playlist/v0alpha1" + "github.com/grafana/grafana/pkg/registry/apis/dashboard/legacy" + "github.com/grafana/grafana/pkg/setting" + "github.com/grafana/grafana/pkg/storage/unified/resourcepb" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +type ResourceDefinition struct { + GroupResource schema.GroupResource + MigratorFunc string // Name of the method: "MigrateFolders", "MigrateDashboards", etc. +} + +var registeredResources = []ResourceDefinition{ + { + GroupResource: schema.GroupResource{Group: folders.GROUP, Resource: folders.RESOURCE}, + MigratorFunc: "MigrateFolders", + }, + { + GroupResource: schema.GroupResource{Group: v1beta1.GROUP, Resource: v1beta1.LIBRARY_PANEL_RESOURCE}, + MigratorFunc: "MigrateLibraryPanels", + }, + { + GroupResource: schema.GroupResource{Group: v1beta1.GROUP, Resource: v1beta1.DASHBOARD_RESOURCE}, + MigratorFunc: "MigrateDashboards", + }, + { + GroupResource: schema.GroupResource{Group: playlists.APIGroup, Resource: "playlists"}, + MigratorFunc: "MigratePlaylists", + }, +} + +func getResourceDefinition(group, resource string) *ResourceDefinition { + for i := range registeredResources { + r := ®isteredResources[i] + if r.GroupResource.Group == group && r.GroupResource.Resource == resource { + return r + } + } + return nil +} + +func buildResourceKey(group, resource, namespace string) *resourcepb.ResourceKey { + def := getResourceDefinition(group, resource) + if def == nil { + return nil + } + return &resourcepb.ResourceKey{ + Namespace: namespace, + Group: def.GroupResource.Group, + Resource: def.GroupResource.Resource, + } +} + +func getMigratorFunc(accessor legacy.MigrationDashboardAccessor, group, resource string) migratorFunc { + def := getResourceDefinition(group, resource) + if def == nil { + return nil + } + + switch def.MigratorFunc { + case "MigrateFolders": + return accessor.MigrateFolders + case "MigrateLibraryPanels": + return accessor.MigrateLibraryPanels + case "MigrateDashboards": + return accessor.MigrateDashboards + case "MigratePlaylists": + return accessor.MigratePlaylists + default: + return nil + } +} + +func validateRegisteredResources() error { + registeredMap := make(map[string]bool) + for _, gr := range registeredResources { + key := fmt.Sprintf("%s.%s", gr.GroupResource.Resource, gr.GroupResource.Group) + registeredMap[key] = true + } + + var missing []string + for _, expected := range setting.MigratedUnifiedResources { + if !registeredMap[expected] { + missing = append(missing, expected) + } + } + + if len(missing) > 0 { + return fmt.Errorf("resources declared in setting.MigratedUnifiedResources are not registered for migration: %v", missing) + } + + return nil +} diff --git a/pkg/storage/unified/migrations/service.go b/pkg/storage/unified/migrations/service.go index fa1f62ca86a..1fda9b42593 100644 --- a/pkg/storage/unified/migrations/service.go +++ b/pkg/storage/unified/migrations/service.go @@ -85,18 +85,18 @@ func RegisterMigrations( logger.Warn("Failed to register migrator metrics", "error", err) } + if err := validateRegisteredResources(); err != nil { + return err + } + // Register resource migrations registerDashboardAndFolderMigration(mg, migrator, client) + registerPlaylistMigration(mg, migrator, client) // Run all registered migrations (blocking) sec := cfg.Raw.Section("database") - migrationLocking := sec.Key("migration_locking").MustBool(true) - if mg.Dialect.DriverName() == sqlstoremigrator.SQLite { - // disable migration locking for SQLite to avoid "database is locked" errors in the bulk operations - migrationLocking = false - } if err := mg.RunMigrations(ctx, - migrationLocking, + sec.Key("migration_locking").MustBool(true), sec.Key("locking_attempt_timeout_sec").MustInt()); err != nil { return fmt.Errorf("unified storage data migration failed: %w", err) } @@ -106,13 +106,13 @@ func RegisterMigrations( } func registerDashboardAndFolderMigration(mg *sqlstoremigrator.Migrator, migrator UnifiedMigrator, client resource.ResourceClient) { - folders := schema.GroupResource{Group: "folder.grafana.app", Resource: "folders"} - dashboards := schema.GroupResource{Group: "dashboard.grafana.app", Resource: "dashboards"} + foldersDef := getResourceDefinition("folder.grafana.app", "folders") + dashboardsDef := getResourceDefinition("dashboard.grafana.app", "dashboards") driverName := mg.Dialect.DriverName() folderCountValidator := NewCountValidator( client, - folders, + foldersDef.GroupResource, "dashboard", "org_id = ? and is_folder = true", driverName, @@ -120,19 +120,40 @@ func registerDashboardAndFolderMigration(mg *sqlstoremigrator.Migrator, migrator dashboardCountValidator := NewCountValidator( client, - dashboards, + dashboardsDef.GroupResource, "dashboard", "org_id = ? and is_folder = false", driverName, ) - folderTreeValidator := NewFolderTreeValidator(client, folders, driverName) + folderTreeValidator := NewFolderTreeValidator(client, foldersDef.GroupResource, driverName) dashboardsAndFolders := NewResourceMigration( migrator, - []schema.GroupResource{folders, dashboards}, + []schema.GroupResource{foldersDef.GroupResource, dashboardsDef.GroupResource}, "folders-dashboards", []Validator{folderCountValidator, dashboardCountValidator, folderTreeValidator}, ) mg.AddMigration("folders and dashboards migration", dashboardsAndFolders) } + +func registerPlaylistMigration(mg *sqlstoremigrator.Migrator, migrator UnifiedMigrator, client resource.ResourceClient) { + playlistsDef := getResourceDefinition("playlist.grafana.app", "playlists") + driverName := mg.Dialect.DriverName() + + playlistCountValidator := NewCountValidator( + client, + playlistsDef.GroupResource, + "playlist", + "org_id = ?", + driverName, + ) + + playlistsMigration := NewResourceMigration( + migrator, + []schema.GroupResource{playlistsDef.GroupResource}, + "playlists", + []Validator{playlistCountValidator}, + ) + mg.AddMigration("playlists migration", playlistsMigration) +} From 32a58c56ed9e1a9b0959c2fca66157aeb2a03ff6 Mon Sep 17 00:00:00 2001 From: Levente Balogh Date: Thu, 4 Dec 2025 16:22:41 +0100 Subject: [PATCH 033/110] Dashboard Controls: Add UI for displaying under menu (#113517) * feat: add options to render under the controls menu * fix: filter out hidden annotation layers from the dashboard-controls menu * fix: adjust spacing between annotation controls in the dashboard-controls menu * fix: e2e test for variables * feat: move the menu button next to the variables * fix: remove duplicate link controls * fix: show dashboard controls when the dashboard is not saved --- .../new-query-variable.spec.ts | 8 +- eslint-suppressions.json | 15 - .../src/selectors/pages.ts | 6 + .../scene/DashboardControls.tsx | 26 +- .../scene/DashboardControlsMenu.tsx | 137 --------- .../scene/DataLayerControl.tsx | 1 + .../DashboardControlsMenu.tsx | 88 ++++++ .../DashboardControlsMenuButton.test.tsx} | 5 +- .../DashboardControlsMenuButton.tsx | 51 ++++ .../scene/dashboard-controls-menu/utils.tsx | 62 ++++ .../settings/AnnotationsEditView.tsx | 1 + .../AnnotationSettingsEdit.test.tsx | 144 +++++++-- .../annotations/AnnotationSettingsEdit.tsx | 288 ++++++++++++------ .../settings/links/DashboardLinkForm.test.tsx | 118 +++++++ .../settings/links/DashboardLinkForm.tsx | 200 +++++++----- .../variables/VariableEditableElement.tsx | 18 +- .../settings/variables/VariableEditorForm.tsx | 8 +- .../components/VariableDisplaySelect.test.tsx | 84 +++++ .../components/VariableDisplaySelect.tsx | 65 ++++ .../components/VariableHideSelect.tsx | 1 + public/locales/en-US/grafana.json | 36 ++- 21 files changed, 980 insertions(+), 382 deletions(-) delete mode 100644 public/app/features/dashboard-scene/scene/DashboardControlsMenu.tsx create mode 100644 public/app/features/dashboard-scene/scene/dashboard-controls-menu/DashboardControlsMenu.tsx rename public/app/features/dashboard-scene/scene/{DashboardControlsMenu.test.tsx => dashboard-controls-menu/DashboardControlsMenuButton.test.tsx} (97%) create mode 100644 public/app/features/dashboard-scene/scene/dashboard-controls-menu/DashboardControlsMenuButton.tsx create mode 100644 public/app/features/dashboard-scene/scene/dashboard-controls-menu/utils.tsx create mode 100644 public/app/features/dashboard-scene/settings/links/DashboardLinkForm.test.tsx create mode 100644 public/app/features/dashboard-scene/settings/variables/components/VariableDisplaySelect.test.tsx create mode 100644 public/app/features/dashboard-scene/settings/variables/components/VariableDisplaySelect.tsx diff --git a/e2e-playwright/dashboards-suite/new-query-variable.spec.ts b/e2e-playwright/dashboards-suite/new-query-variable.spec.ts index 088f4bd9b12..ed92c79ee36 100644 --- a/e2e-playwright/dashboards-suite/new-query-variable.spec.ts +++ b/e2e-playwright/dashboards-suite/new-query-variable.spec.ts @@ -54,7 +54,13 @@ test.describe( await expect(descriptionInput).toHaveAttribute('placeholder', 'Descriptive text'); await expect(descriptionInput).toHaveValue(''); - await expect(page.locator('label').filter({ hasText: 'Hide' })).toBeVisible(); + // Display + await expect(page.locator('label', { hasText: /^Display$/ })).toBeVisible(); + const displaySelect = dashboardPage.getByGrafanaSelector( + selectors.pages.Dashboard.Settings.Variables.Edit.General.generalDisplaySelect + ); + await expect(displaySelect).toBeVisible(); + await expect(displaySelect).toHaveValue('Above dashboard'); // Check datasource selector const datasourceSelect = dashboardPage.getByGrafanaSelector( diff --git a/eslint-suppressions.json b/eslint-suppressions.json index d358be67002..5c863244c20 100644 --- a/eslint-suppressions.json +++ b/eslint-suppressions.json @@ -1978,16 +1978,6 @@ "count": 2 } }, - "public/app/features/dashboard-scene/settings/annotations/AnnotationSettingsEdit.tsx": { - "no-restricted-syntax": { - "count": 7 - } - }, - "public/app/features/dashboard-scene/settings/links/DashboardLinkForm.tsx": { - "no-restricted-syntax": { - "count": 10 - } - }, "public/app/features/dashboard-scene/settings/variables/VariableEditableElement.tsx": { "react-hooks/rules-of-hooks": { "count": 4 @@ -2008,11 +1998,6 @@ "count": 1 } }, - "public/app/features/dashboard-scene/settings/variables/components/VariableHideSelect.tsx": { - "no-restricted-syntax": { - "count": 1 - } - }, "public/app/features/dashboard-scene/settings/variables/components/VariableSelectField.tsx": { "@typescript-eslint/no-explicit-any": { "count": 1 diff --git a/packages/grafana-e2e-selectors/src/selectors/pages.ts b/packages/grafana-e2e-selectors/src/selectors/pages.ts index 79422ee8db9..47a5573b00d 100644 --- a/packages/grafana-e2e-selectors/src/selectors/pages.ts +++ b/packages/grafana-e2e-selectors/src/selectors/pages.ts @@ -352,6 +352,9 @@ export const versionedPages = { showInLabel: { '11.1.0': 'data-testid show-in-label', }, + annotationControlsDisplay: { + '12.4.0': 'data-testid annotation-controls-display-label', + }, previewInDashboard: { '10.0.0': 'data-testid annotations-preview', }, @@ -445,6 +448,9 @@ export const versionedPages = { generalHideSelectV2: { [MIN_GRAFANA_VERSION]: 'data-testid Variable editor Form Hide select', }, + generalDisplaySelect: { + '12.4.0': 'data-testid Variable editor Display select', + }, selectionOptionsAllowCustomValueSwitch: { [MIN_GRAFANA_VERSION]: 'data-testid Variable editor Form Allow Custom Value switch', }, diff --git a/public/app/features/dashboard-scene/scene/DashboardControls.tsx b/public/app/features/dashboard-scene/scene/DashboardControls.tsx index 653829fcb4f..f21a9ea01aa 100644 --- a/public/app/features/dashboard-scene/scene/DashboardControls.tsx +++ b/public/app/features/dashboard-scene/scene/DashboardControls.tsx @@ -23,11 +23,12 @@ import { playlistSrv } from 'app/features/playlist/PlaylistSrv'; import { PanelEditControls } from '../panel-edit/PanelEditControls'; import { getDashboardSceneFor } from '../utils/utils'; -import { DashboardControlsButton } from './DashboardControlsMenu'; import { DashboardDataLayerControls } from './DashboardDataLayerControls'; import { DashboardLinksControls } from './DashboardLinksControls'; import { DashboardScene } from './DashboardScene'; import { VariableControls } from './VariableControls'; +import { DashboardControlsButton } from './dashboard-controls-menu/DashboardControlsMenuButton'; +import { hasDashboardControls, useHasDashboardControls } from './dashboard-controls-menu/utils'; import { EditDashboardSwitch } from './new-toolbar/actions/EditDashboardSwitch'; import { SaveDashboard } from './new-toolbar/actions/SaveDashboard'; import { ShareDashboardButton } from './new-toolbar/actions/ShareDashboardButton'; @@ -117,19 +118,8 @@ export class DashboardControls extends SceneObjectBase { } } - // Dashboard controls is a separate dropdown menu at the top-right of the controls - public hasDashboardControls(): boolean { - const dashboard = getDashboardSceneFor(this); - const { links } = dashboard.state; - const hasControlMenuVariables = sceneGraph - .getVariables(dashboard) - ?.state.variables.some((v) => v.state.hide === VariableHide.inControlsMenu); - const hasControlMenuLinks = links.some((link) => link.placement === 'inControlsMenu'); - - return hasControlMenuVariables || hasControlMenuLinks; - } - public hasControls(): boolean { + const dashboard = getDashboardSceneFor(this); const hasVariables = sceneGraph .getVariables(this) ?.state.variables.some((v) => v.state.hide !== VariableHide.hideVariable); @@ -138,7 +128,7 @@ export class DashboardControls extends SceneObjectBase { const hideLinks = this.state.hideLinksControls || !hasLinks; const hideVariables = this.state.hideVariableControls || (!hasAnnotations && !hasVariables); const hideTimePicker = this.state.hideTimeControls; - const hideDashboardControls = this.state.hideDashboardControls || !this.hasDashboardControls(); + const hideDashboardControls = this.state.hideDashboardControls || !hasDashboardControls(dashboard); return !(hideVariables && hideLinks && hideTimePicker && hideDashboardControls); } @@ -157,10 +147,10 @@ function DashboardControlsRenderer({ model }: SceneComponentProps{renderHiddenVariables(dashboard)}; } @@ -176,11 +166,6 @@ function DashboardControlsRenderer({ model }: SceneComponentProps )} - {!hideDashboardControls && model.hasDashboardControls() && ( -
- -
- )} {config.featureToggles.dashboardNewLayouts && (
@@ -194,6 +179,7 @@ function DashboardControlsRenderer({ model }: SceneComponentProps )} + {!hideDashboardControls && hasDashboardControls && } {editPanel && } {showDebugger && }
diff --git a/public/app/features/dashboard-scene/scene/DashboardControlsMenu.tsx b/public/app/features/dashboard-scene/scene/DashboardControlsMenu.tsx deleted file mode 100644 index 03ac5f0ea88..00000000000 --- a/public/app/features/dashboard-scene/scene/DashboardControlsMenu.tsx +++ /dev/null @@ -1,137 +0,0 @@ -import { css, cx } from '@emotion/css'; - -import { GrafanaTheme2 } from '@grafana/data'; -import { t } from '@grafana/i18n'; -import { SceneDataLayerProvider, sceneGraph, SceneVariable } from '@grafana/scenes'; -import { DashboardLink, VariableHide } from '@grafana/schema'; -import { Box, Dropdown, Menu, ToolbarButton, useStyles2 } from '@grafana/ui'; - -import { isDashboardDataLayerSetState } from './DashboardDataLayerSet'; -import { DashboardLinkRenderer } from './DashboardLinkRenderer'; -import { DashboardScene } from './DashboardScene'; -import { DataLayerControl } from './DataLayerControl'; -import { VariableValueSelectWrapper } from './VariableControls'; - -export const DASHBOARD_CONTROLS_MENU_ARIA_LABEL = 'Dashboard controls menu'; -export const DASHBOARD_CONTROLS_MENU_TITLE = 'Dashboard controls'; - -export function DashboardControlsButton({ dashboard }: { dashboard: DashboardScene }) { - const { links, uid } = dashboard.useState(); - // Dashboard links are not supported at the moment. - // Reason: nesting components causes issues since the inner dropdown is rendered in a portal, - // so clicking it closes the parent dropdown (the parent sees it as an overlay click, and the event cannot easily be intercepted, - // as it is in different HTML subtree). - const filteredLinks = links.filter((link) => link.placement === 'inControlsMenu' && link.type !== 'dashboards'); - const variables = sceneGraph - .getVariables(dashboard)! - .useState() - .variables.filter((v) => v.state.hide === VariableHide.inControlsMenu); - const dataState = sceneGraph.getData(dashboard).useState(); - const annotationLayers = isDashboardDataLayerSetState(dataState) ? dataState.annotationLayers : []; - const filteredAnnotationLayers = annotationLayers.filter((layer) => layer.state.placement === 'inControlsMenu'); - - if ((variables.length === 0 && filteredLinks.length === 0 && filteredAnnotationLayers.length === 0) || !uid) { - return null; - } - - return ( - - } - > - - - ); -} - -interface DashboardControlsMenuProps { - variables: SceneVariable[]; - links: DashboardLink[]; - annotationLayers: SceneDataLayerProvider[]; - dashboardUID: string; -} - -function DashboardControlsMenu({ variables, links, annotationLayers, dashboardUID }: DashboardControlsMenuProps) { - const styles = useStyles2(getStyles); - - return ( - { - // Normally, clicking the overlay closes the dropdown. - // We stop event propagation here to keep it open while users interact with variable controls. - e.stopPropagation(); - }} - > - {/* Variables */} - {variables.map((variable, index) => ( -
0 })} key={variable.state.key}> - -
- ))} - - {/* Annotation layers */} - {annotationLayers.length > 0 && - annotationLayers.map((layer, index) => ( -
0 || index > 0 })} key={layer.state.key}> - -
- ))} - - {/* Links */} - {links.length > 0 && ( - <> - {(variables.length > 0 || annotationLayers.length > 0) && } - {links.map((link, index) => ( -
- -
- ))} - - )} -
- ); -} - -function MenuDivider() { - const styles = useStyles2(getStyles); - - return ( -
- -
- ); -} - -const getStyles = (theme: GrafanaTheme2) => ({ - divider: css({ - marginTop: theme.spacing(2), - padding: theme.spacing(0, 0.5), - }), - menuItem: css({ - marginTop: theme.spacing(2), - }), -}); diff --git a/public/app/features/dashboard-scene/scene/DataLayerControl.tsx b/public/app/features/dashboard-scene/scene/DataLayerControl.tsx index 6318fa248a6..3fc19bb2a3d 100644 --- a/public/app/features/dashboard-scene/scene/DataLayerControl.tsx +++ b/public/app/features/dashboard-scene/scene/DataLayerControl.tsx @@ -66,6 +66,7 @@ const getStyles = (theme: GrafanaTheme2) => ({ gap: theme.spacing(1), }), controlWrapper: css({ + height: theme.spacing(2), '& > div': { border: 'none', background: 'transparent', diff --git a/public/app/features/dashboard-scene/scene/dashboard-controls-menu/DashboardControlsMenu.tsx b/public/app/features/dashboard-scene/scene/dashboard-controls-menu/DashboardControlsMenu.tsx new file mode 100644 index 00000000000..9d27ee01f0f --- /dev/null +++ b/public/app/features/dashboard-scene/scene/dashboard-controls-menu/DashboardControlsMenu.tsx @@ -0,0 +1,88 @@ +import { css, cx } from '@emotion/css'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { SceneDataLayerProvider, SceneVariable } from '@grafana/scenes'; +import { DashboardLink } from '@grafana/schema'; +import { Box, Menu, useStyles2 } from '@grafana/ui'; + +import { DashboardLinkRenderer } from '../DashboardLinkRenderer'; +import { DataLayerControl } from '../DataLayerControl'; +import { VariableValueSelectWrapper } from '../VariableControls'; + +interface DashboardControlsMenuProps { + variables: SceneVariable[]; + links: DashboardLink[]; + annotations: SceneDataLayerProvider[]; + dashboardUID?: string; +} + +export function DashboardControlsMenu({ variables, links, annotations, dashboardUID }: DashboardControlsMenuProps) { + const styles = useStyles2(getStyles); + + return ( + { + // Normally, clicking the overlay closes the dropdown. + // We stop event propagation here to keep it open while users interact with variable controls. + e.stopPropagation(); + }} + > + {/* Variables */} + {variables.map((variable, index) => ( +
0 })} key={variable.state.key}> + +
+ ))} + + {/* Annotation layers */} + {annotations.length > 0 && + annotations.map((layer, index) => ( +
0 || index > 0 })} key={layer.state.key}> + +
+ ))} + + {/* Links */} + {links.length > 0 && dashboardUID && ( + <> + {(variables.length > 0 || annotations.length > 0) && } + {links.map((link, index) => ( +
+ +
+ ))} + + )} +
+ ); +} + +function MenuDivider() { + const styles = useStyles2(getStyles); + + return ( +
+ +
+ ); +} + +const getStyles = (theme: GrafanaTheme2) => ({ + divider: css({ + marginTop: theme.spacing(2), + padding: theme.spacing(0, 0.5), + }), + menuItem: css({ + marginTop: theme.spacing(2), + }), +}); diff --git a/public/app/features/dashboard-scene/scene/DashboardControlsMenu.test.tsx b/public/app/features/dashboard-scene/scene/dashboard-controls-menu/DashboardControlsMenuButton.test.tsx similarity index 97% rename from public/app/features/dashboard-scene/scene/DashboardControlsMenu.test.tsx rename to public/app/features/dashboard-scene/scene/dashboard-controls-menu/DashboardControlsMenuButton.test.tsx index 101bd69ec97..867513ffc8c 100644 --- a/public/app/features/dashboard-scene/scene/DashboardControlsMenu.test.tsx +++ b/public/app/features/dashboard-scene/scene/dashboard-controls-menu/DashboardControlsMenuButton.test.tsx @@ -4,12 +4,13 @@ import userEvent from '@testing-library/user-event'; import { VariableHide } from '@grafana/data'; import { SceneVariableSet, TextBoxVariable, QueryVariable, CustomVariable, SceneVariable } from '@grafana/scenes'; +import { DashboardScene } from '../DashboardScene'; + import { DASHBOARD_CONTROLS_MENU_ARIA_LABEL, DASHBOARD_CONTROLS_MENU_TITLE, DashboardControlsButton, -} from './DashboardControlsMenu'; -import { DashboardScene } from './DashboardScene'; +} from './DashboardControlsMenuButton'; describe('DashboardControlsMenu', () => { it('should return null and not render anything when there are no variables', () => { diff --git a/public/app/features/dashboard-scene/scene/dashboard-controls-menu/DashboardControlsMenuButton.tsx b/public/app/features/dashboard-scene/scene/dashboard-controls-menu/DashboardControlsMenuButton.tsx new file mode 100644 index 00000000000..898eb471373 --- /dev/null +++ b/public/app/features/dashboard-scene/scene/dashboard-controls-menu/DashboardControlsMenuButton.tsx @@ -0,0 +1,51 @@ +import { css } from '@emotion/css'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { t } from '@grafana/i18n'; +import { Dropdown, ToolbarButton, useStyles2 } from '@grafana/ui'; + +import { DashboardScene } from '../DashboardScene'; + +import { DashboardControlsMenu } from './DashboardControlsMenu'; +import { useDashboardControls } from './utils'; + +export const DASHBOARD_CONTROLS_MENU_ARIA_LABEL = 'Dashboard controls menu'; +export const DASHBOARD_CONTROLS_MENU_TITLE = 'Dashboard controls'; + +export function DashboardControlsButton({ dashboard }: { dashboard: DashboardScene }) { + const styles = useStyles2(getStyles); + const { uid } = dashboard.useState(); + const { variables, links, annotations } = useDashboardControls(dashboard); + const dashboardControlsCount = variables.length + links.length + annotations.length; + const hasDashboardControls = dashboardControlsCount > 0; + + if (!hasDashboardControls) { + return null; + } + + return ( + + } + > + + + {dashboardControlsCount} + + + ); +} + +const getStyles = (theme: GrafanaTheme2) => ({ + dropdownButton: css({ + display: 'inline-flex', + }), +}); diff --git a/public/app/features/dashboard-scene/scene/dashboard-controls-menu/utils.tsx b/public/app/features/dashboard-scene/scene/dashboard-controls-menu/utils.tsx new file mode 100644 index 00000000000..6514ea34ed9 --- /dev/null +++ b/public/app/features/dashboard-scene/scene/dashboard-controls-menu/utils.tsx @@ -0,0 +1,62 @@ +import { SceneDataState, sceneGraph, SceneVariable } from '@grafana/scenes'; +import { DashboardLink, VariableHide } from '@grafana/schema'; + +import { isDashboardDataLayerSetState } from '../DashboardDataLayerSet'; +import { DashboardScene } from '../DashboardScene'; + +export function getDashboardControlsLinks(links: DashboardLink[]) { + // Dashboard links are not supported at the moment. + // Reason: nesting components causes issues since the inner dropdown is rendered in a portal, + // so clicking it closes the parent dropdown (the parent sees it as an overlay click, and the event cannot easily be intercepted, + // as it is in different HTML subtree). + return links.filter((link) => link.placement === 'inControlsMenu' && link.type !== 'dashboards'); +} + +export function getDashboardControlsVariables(variables: SceneVariable[]) { + return variables.filter((v) => v.state.hide === VariableHide.inControlsMenu); +} + +export function getDashboardControlsAnnotations(dataState: SceneDataState) { + return (isDashboardDataLayerSetState(dataState) ? dataState.annotationLayers : []).filter( + (layer) => layer.state.placement === 'inControlsMenu' && !layer.state.isHidden + ); +} + +export function getDashboardControls(dashboard: DashboardScene) { + const variables = getDashboardControlsVariables(sceneGraph.getVariables(dashboard)?.state.variables); + const links = getDashboardControlsLinks(dashboard.state.links); + const annotations = getDashboardControlsAnnotations(sceneGraph.getData(dashboard).state); + + return { + variables, + links, + annotations, + }; +} + +export function useDashboardControls(dashboard: DashboardScene) { + const dashboardState = dashboard.useState(); + const variablesState = sceneGraph.getVariables(dashboard).useState(); + const dataState = sceneGraph.getData(dashboard).useState(); + const links = getDashboardControlsLinks(dashboardState.links); + const variables = getDashboardControlsVariables(variablesState.variables); + const annotations = getDashboardControlsAnnotations(dataState); + + return { + variables, + links, + annotations, + }; +} + +export function useHasDashboardControls(dashboard: DashboardScene) { + const { variables, links, annotations } = useDashboardControls(dashboard); + + return variables.length > 0 || links.length > 0 || annotations.length > 0; +} + +export function hasDashboardControls(dashboard: DashboardScene) { + const { variables, links, annotations } = getDashboardControls(dashboard); + + return variables.length > 0 || links.length > 0 || annotations.length > 0; +} diff --git a/public/app/features/dashboard-scene/settings/AnnotationsEditView.tsx b/public/app/features/dashboard-scene/settings/AnnotationsEditView.tsx index bbc49a8fc30..31520a0d168 100644 --- a/public/app/features/dashboard-scene/settings/AnnotationsEditView.tsx +++ b/public/app/features/dashboard-scene/settings/AnnotationsEditView.tsx @@ -119,6 +119,7 @@ export class AnnotationsEditView extends SceneObjectBase { }; } + // For testing combobox + beforeAll(() => { + const mockGetBoundingClientRect = jest.fn(() => ({ + width: 120, + height: 120, + top: 0, + left: 0, + bottom: 0, + right: 0, + })); + + Object.defineProperty(Element.prototype, 'getBoundingClientRect', { + value: mockGetBoundingClientRect, + }); + }); + afterEach(() => { jest.clearAllMocks(); }); @@ -100,7 +116,9 @@ describe('AnnotationSettingsEdit', () => { const nameInput = getByTestId(selectors.pages.Dashboard.Settings.Annotations.Settings.name); const dataSourceSelect = getByTestId(selectors.components.DataSourcePicker.container); const enableToggle = getByTestId(selectors.pages.Dashboard.Settings.Annotations.NewAnnotation.enable); - const hideToggle = getByTestId(selectors.pages.Dashboard.Settings.Annotations.NewAnnotation.hide); + const annotationControlsDisplaySelect = getByTestId( + selectors.pages.Dashboard.Settings.Annotations.NewAnnotation.annotationControlsDisplay + ); const iconColorToggle = getByTestId(selectors.components.ColorSwatch.name); const panelSelect = getByTestId(selectors.pages.Dashboard.Settings.Annotations.NewAnnotation.showInLabel); const deleteAnno = getByTestId(selectors.pages.Dashboard.Settings.Annotations.NewAnnotation.delete); @@ -109,7 +127,7 @@ describe('AnnotationSettingsEdit', () => { expect(nameInput).toBeInTheDocument(); expect(dataSourceSelect).toBeInTheDocument(); expect(enableToggle).toBeInTheDocument(); - expect(hideToggle).toBeInTheDocument(); + expect(annotationControlsDisplaySelect).toBeInTheDocument(); expect(iconColorToggle).toBeInTheDocument(); expect(panelSelect).toBeInTheDocument(); expect(deleteAnno).toBeInTheDocument(); @@ -147,26 +165,6 @@ describe('AnnotationSettingsEdit', () => { expect(mockOnUpdate).toHaveBeenCalledWith(annoArg, 1); }); - it('should toggle annotation hide on change', async () => { - const { - renderer: { getByTestId }, - user, - anno, - } = await setup(); - - const annoArg = { - ...anno, - hide: !anno.hide, - }; - - const hideToggle = getByTestId(selectors.pages.Dashboard.Settings.Annotations.NewAnnotation.hide); - - await user.click(hideToggle); - - expect(mockOnUpdate).toHaveBeenCalledTimes(1); - expect(mockOnUpdate).toHaveBeenCalledWith(annoArg, 1); - }); - it('should set annotation filter', async () => { const { renderer: { getByTestId }, @@ -207,4 +205,104 @@ describe('AnnotationSettingsEdit', () => { expect(mockGoBackToList).toHaveBeenCalledTimes(1); }); + + it('should render the annotation controls display combobox', async () => { + const { + renderer: { getByTestId }, + } = await setup(); + + const field = getByTestId(selectors.pages.Dashboard.Settings.Annotations.NewAnnotation.annotationControlsDisplay); + const combobox = within(field).getByRole('combobox'); + expect(combobox).toBeInTheDocument(); + expect(combobox).toHaveValue('Above dashboard'); + }); + + it('should set placement to undefined when selecting "Above dashboard" instead of "Controls menu"', async () => { + const annotationQuery: AnnotationQuery = { + name: 'test', + datasource: defaultDatasource, + enable: true, + hide: false, + iconColor: 'blue', + placement: 'inControlsMenu', + }; + + const props = { + annotation: annotationQuery, + onUpdate: mockOnUpdate, + editIndex: 1, + panels: [], + onBackToList: mockGoBackToList, + onDelete: mockOnDelete, + }; + + const { + user, + renderer: { getByTestId, findByText }, + } = { + user: userEvent.setup(), + renderer: await act(async () => render()), + }; + + const field = getByTestId(selectors.pages.Dashboard.Settings.Annotations.NewAnnotation.annotationControlsDisplay); + const combobox = within(field).getByRole('combobox'); + await user.click(combobox); + + const aboveDashboardOption = await findByText('Above dashboard'); + await user.click(aboveDashboardOption); + + expect(mockOnUpdate).toHaveBeenCalledTimes(1); + expect(mockOnUpdate).toHaveBeenCalledWith( + { + ...annotationQuery, + placement: undefined, + }, + 1 + ); + }); + + it('should set `hide: true` and `placement: undefined` when selecting "Hidden"', async () => { + const annotationQuery: AnnotationQuery = { + name: 'test', + datasource: defaultDatasource, + enable: true, + hide: false, + iconColor: 'blue', + placement: 'inControlsMenu', + }; + + const props = { + annotation: annotationQuery, + onUpdate: mockOnUpdate, + editIndex: 1, + panels: [], + onBackToList: mockGoBackToList, + onDelete: mockOnDelete, + }; + + const { + user, + renderer: { getByTestId, findByText }, + } = { + user: userEvent.setup(), + renderer: await act(async () => render()), + }; + + const field = getByTestId(selectors.pages.Dashboard.Settings.Annotations.NewAnnotation.annotationControlsDisplay); + const combobox = within(field).getByRole('combobox'); + await user.click(combobox); + + const hiddenOption = await findByText('Hidden'); + await user.click(hiddenOption); + + expect(mockOnUpdate).toHaveBeenCalledTimes(1); + expect(mockOnUpdate).toHaveBeenCalledWith( + { + ...annotationQuery, + hide: true, + placement: undefined, + }, + 1 + ); + }); }); diff --git a/public/app/features/dashboard-scene/settings/annotations/AnnotationSettingsEdit.tsx b/public/app/features/dashboard-scene/settings/annotations/AnnotationSettingsEdit.tsx index 269d7bc4056..03ed262c210 100644 --- a/public/app/features/dashboard-scene/settings/annotations/AnnotationSettingsEdit.tsx +++ b/public/app/features/dashboard-scene/settings/annotations/AnnotationSettingsEdit.tsx @@ -15,7 +15,20 @@ import { Trans, t } from '@grafana/i18n'; import { config, getDataSourceSrv } from '@grafana/runtime'; import { VizPanel } from '@grafana/scenes'; import { AnnotationPanelFilter } from '@grafana/schema/src/raw/dashboard/x/dashboard_types.gen'; -import { Button, Checkbox, Field, FieldSet, Input, MultiSelect, Select, useStyles2, Stack, Alert } from '@grafana/ui'; +import { + Button, + Checkbox, + Field, + FieldSet, + Input, + MultiSelect, + Select, + useStyles2, + Stack, + Alert, + ComboboxOption, + Combobox, +} from '@grafana/ui'; import { ColorValueEditor } from 'app/core/components/OptionsUI/color'; import StandardAnnotationQueryEditor from 'app/features/annotations/components/StandardAnnotationQueryEditor'; import { DataSourcePicker } from 'app/features/datasources/components/picker/DataSourcePicker'; @@ -31,8 +44,15 @@ type Props = { onDelete: (index: number) => void; }; +const collator = Intl.Collator(); export const newAnnotationName = 'New annotation'; +enum AnnotationControlsDisplay { + Hidden, + AboveDashboard, + InControlsMenu, +} + export const AnnotationSettingsEdit = ({ annotation, editIndex, panels, onUpdate, onBackToList, onDelete }: Props) => { const styles = useStyles2(getStyles); @@ -49,6 +69,63 @@ export const AnnotationSettingsEdit = ({ annotation, editIndex, panels, onUpdate const dsi = getDataSourceSrv().getInstanceSettings(annotation.datasource); + const AnnotationControlsDisplayOptions = useMemo( + () => [ + { + value: AnnotationControlsDisplay.AboveDashboard, + label: t( + 'dashboard-scene.annotation-settings-edit.control-display-options.above-dashboard.label', + 'Above dashboard' + ), + }, + { + value: AnnotationControlsDisplay.InControlsMenu, + label: t( + 'dashboard-scene.annotation-settings-edit.control-display-options.controls-menu.label', + 'Controls menu' + ), + description: t( + 'dashboard-scene.annotation-settings-edit.control-display-options.controls-menu.description', + 'Can be accessed when the controls menu is open' + ), + }, + { + value: AnnotationControlsDisplay.Hidden, + label: t('dashboard-scene.annotation-settings-edit.control-display-options.hidden.label', 'Hidden'), + description: t( + 'dashboard-scene.annotation-settings-edit.control-display-options.hidden.description', + 'Hides the toggle for turning this annotation on or off' + ), + }, + ], + [] + ); + + // The UI is using a single select input for where to display the annotation controls, however under the hood + // it is computed from different fields of the annotation. + const annotationControlsDisplayValue = useMemo(() => { + if (annotation.hide) { + return AnnotationControlsDisplay.Hidden; + } + + if (annotation.placement === 'inControlsMenu') { + return AnnotationControlsDisplay.InControlsMenu; + } + + return AnnotationControlsDisplay.AboveDashboard; + }, [annotation]); + + const onAnnotationControlDisplayChange = (option: ComboboxOption) => { + onUpdate( + { + ...annotation, + placement: option.value === AnnotationControlsDisplay.InControlsMenu ? 'inControlsMenu' : undefined, + hide: option.value === AnnotationControlsDisplay.Hidden ? true : false, + }, + editIndex + ); + }; + const onNameChange = (ev: React.FocusEvent) => { onUpdate( { @@ -143,7 +220,7 @@ export const AnnotationSettingsEdit = ({ annotation, editIndex, panels, onUpdate const sortFn = (a: SelectableValue, b: SelectableValue) => { if (a.label && b.label) { - return a.label.toLowerCase().localeCompare(b.label.toLowerCase()); + return collator.compare(a.label, b.label); } return -1; @@ -169,102 +246,121 @@ export const AnnotationSettingsEdit = ({ annotation, editIndex, panels, onUpdate return (
- - - - - - - {!ds?.meta.annotations && ( - - - The selected data source does not support annotations. Please select a different data source. - - - )} - - - - - - - - - - - - - <> - - {panelFilter !== PanelFilterType.AllPanels && ( - annotation.filter?.ids.includes(panel.value!))} - onChange={onAddFilterPanelID} - isClearable={true} - placeholder={t('dashboard-scene.annotation-settings-edit.placeholder-choose-panels', 'Choose panels')} - width={100} - closeMenuOnSelect={false} - className={styles.select} - data-testid={selectors.components.Annotations.annotationsChoosePanelInput} - /> + + + {/* Data source */} + + + + {!ds?.meta.annotations && ( + + + The selected data source does not support annotations. Please select a different data source. + + + )} + + {/* Enabled */} + - + > + + + + {/* Color */} + + + + + + + {/* Annotation controls display */} + + + + + {/* Show in */} + + <> + - - - + + + {/* Type */} + + - - - - - - + + + {/* Tooltip */} + + + + + {/* Icon */} + + updateValue('target', target)} placeholder="events.eventname" /> -
-
+ + Show Global Annotations? updateIsGlobal(isGlobal)} /> -
- + + ); }; From 8a0fa93aecd8e85c82305b834b726572102ab691 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Fri, 5 Dec 2025 13:55:56 +0100 Subject: [PATCH 062/110] Zanzana: Fix duplicated writes in one request (#114900) * Zanzana: Fix duplicated writes * add tests --- .../authz/zanzana/server/server_mutate.go | 66 ++++++++++++++++++- .../zanzana/server/server_mutate_folder.go | 19 +----- .../zanzana/server/server_mutate_org_role.go | 19 +----- .../server_mutate_resourcepermissions.go | 19 +----- .../server/server_mutate_rolebindings.go | 19 +----- .../zanzana/server/server_mutate_roles.go | 19 +----- .../server/server_mutate_teambindings.go | 19 +----- .../zanzana/server/server_mutate_test.go | 64 ++++++++++++++++++ 8 files changed, 135 insertions(+), 109 deletions(-) diff --git a/pkg/services/authz/zanzana/server/server_mutate.go b/pkg/services/authz/zanzana/server/server_mutate.go index bd339534fe5..15a57404bbc 100644 --- a/pkg/services/authz/zanzana/server/server_mutate.go +++ b/pkg/services/authz/zanzana/server/server_mutate.go @@ -6,8 +6,10 @@ import ( "fmt" "time" - authzextv1 "github.com/grafana/grafana/pkg/services/authz/proto/v1" + openfgav1 "github.com/openfga/api/proto/openfga/v1" "go.opentelemetry.io/otel/codes" + + authzextv1 "github.com/grafana/grafana/pkg/services/authz/proto/v1" ) type OperationGroup string @@ -119,3 +121,65 @@ func groupByOperation(operations []*authzextv1.MutateOperation) (map[OperationGr return grouped, nil } + +func deduplicateTupleKeys(writeTuples []*openfgav1.TupleKey, deleteTuples []*openfgav1.TupleKeyWithoutCondition) ([]*openfgav1.TupleKey, []*openfgav1.TupleKeyWithoutCondition) { + deduplicatedWriteTuples := make([]*openfgav1.TupleKey, 0) + deduplicatedDeleteTuples := make([]*openfgav1.TupleKeyWithoutCondition, 0) + + writeTupleMap := make(map[string]bool) + + for _, writeTuple := range writeTuples { + id := getTupleKeyID(writeTuple) + if !writeTupleMap[id] { + writeTupleMap[id] = true + deduplicatedWriteTuples = append(deduplicatedWriteTuples, writeTuple) + } + } + + // Prioritize writes over deletes. Deletes do not have a condition, so we don't know if write tuple is different from delete one. + for _, deleteTuple := range deleteTuples { + id := getTupleKeyID(deleteTuple) + if !writeTupleMap[id] { + writeTupleMap[id] = true + deduplicatedDeleteTuples = append(deduplicatedDeleteTuples, deleteTuple) + } + } + + return deduplicatedWriteTuples, deduplicatedDeleteTuples +} + +func (s *Server) writeTuples(ctx context.Context, store *storeInfo, writeTuples []*openfgav1.TupleKey, deleteTuples []*openfgav1.TupleKeyWithoutCondition) error { + writeReq := &openfgav1.WriteRequest{ + StoreId: store.ID, + AuthorizationModelId: store.ModelID, + } + + writeTuples, deleteTuples = deduplicateTupleKeys(writeTuples, deleteTuples) + + if len(writeTuples) > 0 { + writeReq.Writes = &openfgav1.WriteRequestWrites{ + TupleKeys: writeTuples, + OnDuplicate: "ignore", + } + } + + if len(deleteTuples) > 0 { + writeReq.Deletes = &openfgav1.WriteRequestDeletes{ + TupleKeys: deleteTuples, + OnMissing: "ignore", + } + } + + _, err := s.openfga.Write(ctx, writeReq) + return err +} + +type TupleKey interface { + GetUser() string + GetRelation() string + GetObject() string +} + +func getTupleKeyID(t TupleKey) string { + return fmt.Sprintf("%s:%s:%s", t.GetUser(), t.GetRelation(), t.GetObject()) +} diff --git a/pkg/services/authz/zanzana/server/server_mutate_folder.go b/pkg/services/authz/zanzana/server/server_mutate_folder.go index 3d92347f404..6d07492b788 100644 --- a/pkg/services/authz/zanzana/server/server_mutate_folder.go +++ b/pkg/services/authz/zanzana/server/server_mutate_folder.go @@ -52,24 +52,7 @@ func (s *Server) mutateFolders(ctx context.Context, store *storeInfo, operations return nil } - writeReq := &openfgav1.WriteRequest{ - StoreId: store.ID, - AuthorizationModelId: store.ModelID, - } - if len(writeTuples) > 0 { - writeReq.Writes = &openfgav1.WriteRequestWrites{ - TupleKeys: writeTuples, - OnDuplicate: "ignore", - } - } - if len(deleteTuples) > 0 { - writeReq.Deletes = &openfgav1.WriteRequestDeletes{ - TupleKeys: deleteTuples, - OnMissing: "ignore", - } - } - - _, err := s.openfga.Write(ctx, writeReq) + err := s.writeTuples(ctx, store, writeTuples, deleteTuples) if err != nil { s.logger.Error("failed to write folder tuples", "error", err) return err diff --git a/pkg/services/authz/zanzana/server/server_mutate_org_role.go b/pkg/services/authz/zanzana/server/server_mutate_org_role.go index bda9decb3d5..843c62859dd 100644 --- a/pkg/services/authz/zanzana/server/server_mutate_org_role.go +++ b/pkg/services/authz/zanzana/server/server_mutate_org_role.go @@ -50,24 +50,7 @@ func (s *Server) mutateOrgRoles(ctx context.Context, store *storeInfo, operation return nil } - writeReq := &openfgav1.WriteRequest{ - StoreId: store.ID, - AuthorizationModelId: store.ModelID, - } - if len(writeTuples) > 0 { - writeReq.Writes = &openfgav1.WriteRequestWrites{ - TupleKeys: writeTuples, - OnDuplicate: "ignore", - } - } - if len(deleteTuples) > 0 { - writeReq.Deletes = &openfgav1.WriteRequestDeletes{ - TupleKeys: deleteTuples, - OnMissing: "ignore", - } - } - - _, err := s.openfga.Write(ctx, writeReq) + err := s.writeTuples(ctx, store, writeTuples, deleteTuples) if err != nil { s.logger.Error("failed to write user org role tuples", "error", err) return err diff --git a/pkg/services/authz/zanzana/server/server_mutate_resourcepermissions.go b/pkg/services/authz/zanzana/server/server_mutate_resourcepermissions.go index fa8b5467235..f85f31900f6 100644 --- a/pkg/services/authz/zanzana/server/server_mutate_resourcepermissions.go +++ b/pkg/services/authz/zanzana/server/server_mutate_resourcepermissions.go @@ -47,24 +47,7 @@ func (s *Server) mutateResourcePermissions(ctx context.Context, store *storeInfo } } - writeReq := &openfgav1.WriteRequest{ - StoreId: store.ID, - AuthorizationModelId: store.ModelID, - } - if len(writeTuples) > 0 { - writeReq.Writes = &openfgav1.WriteRequestWrites{ - TupleKeys: writeTuples, - OnDuplicate: "ignore", - } - } - if len(deleteTuples) > 0 { - writeReq.Deletes = &openfgav1.WriteRequestDeletes{ - TupleKeys: deleteTuples, - OnMissing: "ignore", - } - } - - _, err := s.openfga.Write(ctx, writeReq) + err := s.writeTuples(ctx, store, writeTuples, deleteTuples) if err != nil { s.logger.Error("failed to write resource permission tuples", "error", err) return err diff --git a/pkg/services/authz/zanzana/server/server_mutate_rolebindings.go b/pkg/services/authz/zanzana/server/server_mutate_rolebindings.go index 3b18566bee2..faf23d1f1ed 100644 --- a/pkg/services/authz/zanzana/server/server_mutate_rolebindings.go +++ b/pkg/services/authz/zanzana/server/server_mutate_rolebindings.go @@ -44,24 +44,7 @@ func (s *Server) mutateRoleBindings(ctx context.Context, store *storeInfo, opera } } - writeReq := &openfgav1.WriteRequest{ - StoreId: store.ID, - AuthorizationModelId: store.ModelID, - } - if len(writeTuples) > 0 { - writeReq.Writes = &openfgav1.WriteRequestWrites{ - TupleKeys: writeTuples, - OnDuplicate: "ignore", - } - } - if len(deleteTuples) > 0 { - writeReq.Deletes = &openfgav1.WriteRequestDeletes{ - TupleKeys: deleteTuples, - OnMissing: "ignore", - } - } - - _, err := s.openfga.Write(ctx, writeReq) + err := s.writeTuples(ctx, store, writeTuples, deleteTuples) if err != nil { s.logger.Error("failed to write resource role binding tuples", "error", err) return err diff --git a/pkg/services/authz/zanzana/server/server_mutate_roles.go b/pkg/services/authz/zanzana/server/server_mutate_roles.go index 4c19b1fd288..c0471fdbf17 100644 --- a/pkg/services/authz/zanzana/server/server_mutate_roles.go +++ b/pkg/services/authz/zanzana/server/server_mutate_roles.go @@ -41,24 +41,7 @@ func (s *Server) mutateRoles(ctx context.Context, store *storeInfo, operations [ } } - writeReq := &openfgav1.WriteRequest{ - StoreId: store.ID, - AuthorizationModelId: store.ModelID, - } - if len(writeTuples) > 0 { - writeReq.Writes = &openfgav1.WriteRequestWrites{ - TupleKeys: writeTuples, - OnDuplicate: "ignore", - } - } - if len(deleteTuples) > 0 { - writeReq.Deletes = &openfgav1.WriteRequestDeletes{ - TupleKeys: deleteTuples, - OnMissing: "ignore", - } - } - - _, err := s.openfga.Write(ctx, writeReq) + err := s.writeTuples(ctx, store, writeTuples, deleteTuples) if err != nil { s.logger.Error("failed to write resource role binding tuples", "error", err) return err diff --git a/pkg/services/authz/zanzana/server/server_mutate_teambindings.go b/pkg/services/authz/zanzana/server/server_mutate_teambindings.go index 81e1c9cb437..96690bb96d8 100644 --- a/pkg/services/authz/zanzana/server/server_mutate_teambindings.go +++ b/pkg/services/authz/zanzana/server/server_mutate_teambindings.go @@ -43,24 +43,7 @@ func (s *Server) mutateTeamBindings(ctx context.Context, store *storeInfo, opera } } - writeReq := &openfgav1.WriteRequest{ - StoreId: store.ID, - AuthorizationModelId: store.ModelID, - } - if len(writeTuples) > 0 { - writeReq.Writes = &openfgav1.WriteRequestWrites{ - TupleKeys: writeTuples, - OnDuplicate: "ignore", - } - } - if len(deleteTuples) > 0 { - writeReq.Deletes = &openfgav1.WriteRequestDeletes{ - TupleKeys: deleteTuples, - OnMissing: "ignore", - } - } - - _, err := s.openfga.Write(ctx, writeReq) + err := s.writeTuples(ctx, store, writeTuples, deleteTuples) if err != nil { s.logger.Error("failed to write resource role binding tuples", "error", err) return err diff --git a/pkg/services/authz/zanzana/server/server_mutate_test.go b/pkg/services/authz/zanzana/server/server_mutate_test.go index 70dc1ea2fb8..c1fcfabbe43 100644 --- a/pkg/services/authz/zanzana/server/server_mutate_test.go +++ b/pkg/services/authz/zanzana/server/server_mutate_test.go @@ -5,6 +5,7 @@ import ( openfgav1 "github.com/openfga/api/proto/openfga/v1" "github.com/stretchr/testify/require" + "google.golang.org/protobuf/types/known/structpb" iamv0 "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1" v1 "github.com/grafana/grafana/pkg/services/authz/proto/v1" @@ -133,3 +134,66 @@ func testMutate(t *testing.T, srv *Server) { require.Len(t, res.Tuples, 0) }) } + +func TestDeduplicateTupleKeys(t *testing.T) { + t.Run("should deduplicate write tuples", func(t *testing.T) { + writeTuples := []*openfgav1.TupleKey{ + {User: "user:1", Relation: "get", Object: "object:1"}, + {User: "user:1", Relation: "get", Object: "object:2"}, + } + deleteTuples := []*openfgav1.TupleKeyWithoutCondition{ + {User: "user:1", Relation: "get", Object: "object:1"}, + {User: "user:2", Relation: "get", Object: "object:2"}, + } + + deduplicatedWriteTuples, deduplicatedDeleteTuples := deduplicateTupleKeys(writeTuples, deleteTuples) + require.Len(t, deduplicatedWriteTuples, 2) + require.ElementsMatch(t, deduplicatedWriteTuples, []*openfgav1.TupleKey{ + {User: "user:1", Relation: "get", Object: "object:1"}, + {User: "user:1", Relation: "get", Object: "object:2"}, + }) + + require.Len(t, deduplicatedDeleteTuples, 1) + require.ElementsMatch(t, deduplicatedDeleteTuples, []*openfgav1.TupleKeyWithoutCondition{ + {User: "user:2", Relation: "get", Object: "object:2"}, + }) + }) + + t.Run("should deduplicate write tuples with conditions", func(t *testing.T) { + writeTuples := []*openfgav1.TupleKey{ + {User: "user:1", Relation: "get", Object: "object:1", Condition: &openfgav1.RelationshipCondition{Name: "condition:1", Context: &structpb.Struct{Fields: map[string]*structpb.Value{ + "field:1": structpb.NewStringValue("value:1"), + }}}}, + {User: "user:1", Relation: "get", Object: "object:2"}, + } + deleteTuples := []*openfgav1.TupleKeyWithoutCondition{ + {User: "user:1", Relation: "get", Object: "object:1"}, + } + + deduplicatedWriteTuples, deduplicatedDeleteTuples := deduplicateTupleKeys(writeTuples, deleteTuples) + require.Len(t, deduplicatedWriteTuples, 2) + require.ElementsMatch(t, deduplicatedWriteTuples, []*openfgav1.TupleKey{ + {User: "user:1", Relation: "get", Object: "object:1", Condition: &openfgav1.RelationshipCondition{Name: "condition:1", Context: &structpb.Struct{Fields: map[string]*structpb.Value{ + "field:1": structpb.NewStringValue("value:1"), + }}}}, + {User: "user:1", Relation: "get", Object: "object:2"}, + }) + + require.Len(t, deduplicatedDeleteTuples, 0) + }) + + t.Run("should do nothing for no duplicates", func(t *testing.T) { + writeTuples := []*openfgav1.TupleKey{ + {User: "user:1", Relation: "get", Object: "object:1"}, + } + deleteTuples := []*openfgav1.TupleKeyWithoutCondition{ + {User: "user:2", Relation: "get", Object: "object:2"}, + } + + deduplicatedWriteTuples, deduplicatedDeleteTuples := deduplicateTupleKeys(writeTuples, deleteTuples) + require.Len(t, deduplicatedWriteTuples, 1) + require.ElementsMatch(t, deduplicatedWriteTuples, writeTuples) + require.Len(t, deduplicatedDeleteTuples, 1) + require.ElementsMatch(t, deduplicatedDeleteTuples, deleteTuples) + }) +} From 5ac702a4c16b17ac08f067622ffe98ca4d855f1b Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Fri, 5 Dec 2025 17:21:30 +0300 Subject: [PATCH 063/110] Dashboards: update manifest to avoid useoldmanifestkinds (#114910) --- apps/dashboard/Makefile | 3 +- apps/dashboard/kinds/dashboard.cue | 54 ------------ apps/dashboard/kinds/manifest.cue | 84 +++++++++++++++++-- apps/dashboard/kinds/snapshot.cue | 62 +++++--------- apps/dashboard/pkg/apis/dashboard/utils.go | 2 + .../conversion/v1beta1_to_v2alpha1.go | 4 +- .../conversion/v1beta1_to_v2alpha1_test.go | 4 +- pkg/tsdb/grafanads/grafana.go | 3 +- 8 files changed, 110 insertions(+), 106 deletions(-) diff --git a/apps/dashboard/Makefile b/apps/dashboard/Makefile index fa75513d964..3d5c7060199 100644 --- a/apps/dashboard/Makefile +++ b/apps/dashboard/Makefile @@ -12,8 +12,7 @@ do-generate: install-app-sdk update-app-sdk ## Run Grafana App SDK code generati --grouping=group \ --defencoding=none \ --genoperatorstate=false \ - --noschemasinmanifest \ - --useoldmanifestkinds + --noschemasinmanifest .PHONY: post-generate-cleanup post-generate-cleanup: ## Clean up the generated code diff --git a/apps/dashboard/kinds/dashboard.cue b/apps/dashboard/kinds/dashboard.cue index e8dfea3bf98..5d04cf1e331 100644 --- a/apps/dashboard/kinds/dashboard.cue +++ b/apps/dashboard/kinds/dashboard.cue @@ -1,12 +1,5 @@ package kinds -import ( - v0 "github.com/grafana/grafana/sdkkinds/dashboard/v0alpha1" - v1 "github.com/grafana/grafana/sdkkinds/dashboard/v1beta1" - v2alpha1 "github.com/grafana/grafana/sdkkinds/dashboard/v2alpha1" - v2beta1 "github.com/grafana/grafana/sdkkinds/dashboard/v2beta1" -) - // Status is the shared status of all dashboard versions. DashboardStatus: { // Optional conversion status. @@ -31,50 +24,3 @@ ConversionStatus: { // The original value map[string]any source?: _ } - -dashboard: { - kind: "Dashboard" - pluralName: "Dashboards" - current: "v1beta1" - codegen: { - ts: { - enabled: true - config: { - enumsAsUnionTypes: true - } - } - go: { - enabled: true - config: { - allowMarshalEmptyDisjunctions: true - } - } - } - - versions: { - "v0alpha1": { - schema: { - spec: v0.DashboardSpec - status: DashboardStatus - } - } - "v1beta1": { - schema: { - spec: v1.DashboardSpec - status: DashboardStatus - } - } - "v2alpha1": { - schema: { - spec: v2alpha1.DashboardSpec - status: DashboardStatus - } - } - "v2beta1": { - schema: { - spec: v2beta1.DashboardSpec - status: DashboardStatus - } - } - } -} diff --git a/apps/dashboard/kinds/manifest.cue b/apps/dashboard/kinds/manifest.cue index f1044a39e24..9fb17910664 100644 --- a/apps/dashboard/kinds/manifest.cue +++ b/apps/dashboard/kinds/manifest.cue @@ -1,10 +1,82 @@ package kinds +import ( + v0 "github.com/grafana/grafana/sdkkinds/dashboard/v0alpha1" + v1 "github.com/grafana/grafana/sdkkinds/dashboard/v1beta1" + v2alpha1 "github.com/grafana/grafana/sdkkinds/dashboard/v2alpha1" + v2beta1 "github.com/grafana/grafana/sdkkinds/dashboard/v2beta1" +) + manifest: { - appName: "dashboard" - groupOverride: "dashboard.grafana.app" - kinds: [ - dashboard, - snapshot, - ] + appName: "dashboard" + groupOverride: "dashboard.grafana.app" + preferredVersion: "v1beta1" + + versions: { + "v0alpha1": { + codegen: { + ts: {enabled: false} + go: {enabled: true} + } + kinds: [ + { + kind: "Dashboard" + pluralName: "Dashboards" + schema: { + spec: v0.DashboardSpec + status: DashboardStatus + } + }, + snapshotV0alpha1, // Only exists in v0alpha (for now) + ] + } + "v1beta1": { + codegen: { + ts: {enabled: false} + go: {enabled: true} + } + kinds: [ + { + kind: "Dashboard" + pluralName: "Dashboards" + schema: { + spec: v1.DashboardSpec + status: DashboardStatus + } + } + ] + } + "v2alpha1": { + codegen: { + ts: {enabled: false} + go: {enabled: true} + } + kinds: [ + { + kind: "Dashboard" + pluralName: "Dashboards" + schema: { + spec: v2alpha1.DashboardSpec + status: DashboardStatus + } + } + ] + } + "v2beta1": { + codegen: { + ts: {enabled: false} + go: {enabled: true} + } + kinds: [ + { + kind: "Dashboard" + pluralName: "Dashboards" + schema: { + spec: v2beta1.DashboardSpec + status: DashboardStatus + } + } + ] + } + } } diff --git a/apps/dashboard/kinds/snapshot.cue b/apps/dashboard/kinds/snapshot.cue index c224daf8492..00f445881b5 100644 --- a/apps/dashboard/kinds/snapshot.cue +++ b/apps/dashboard/kinds/snapshot.cue @@ -1,46 +1,30 @@ package kinds -snapshot: { +snapshotV0alpha1: { kind: "Snapshot" pluralName: "Snapshots" - scope: "Namespaced" - current: "v0alpha1" - - codegen: { - ts: { - enabled: true - } - go: { - enabled: true - } - } - - versions: { - "v0alpha1": { - schema: { - spec: { - // Snapshot title - title?: string - - // Optionally auto-remove the snapshot at a future date (Unix timestamp in seconds) - expires?: int64 | *0 - - // When set to true, the snapshot exists in a remote server - external?: bool | *false - - // The external URL where the snapshot can be seen - externalUrl?: string - - // The URL that created the dashboard originally - originalUrl?: string - - // Snapshot creation timestamp - timestamp?: string + schema: { + spec: { + // Snapshot title + title?: string + + // Optionally auto-remove the snapshot at a future date (Unix timestamp in seconds) + expires?: int64 | *0 + + // When set to true, the snapshot exists in a remote server + external?: bool | *false + + // The external URL where the snapshot can be seen + externalUrl?: string + + // The URL that created the dashboard originally + originalUrl?: string + + // Snapshot creation timestamp + timestamp?: string - // The raw dashboard (unstructured for now) - dashboard?: [string]: _ - } - } + // The raw dashboard (unstructured for now) + dashboard?: [string]: _ } } -} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/apis/dashboard/utils.go b/apps/dashboard/pkg/apis/dashboard/utils.go index a5979151a90..f1453d548a0 100644 --- a/apps/dashboard/pkg/apis/dashboard/utils.go +++ b/apps/dashboard/pkg/apis/dashboard/utils.go @@ -6,6 +6,8 @@ import ( "github.com/grafana/grafana/pkg/apimachinery/utils" ) +const GrafanaDatasourceUID = "grafana" + // SetPluginIDMeta sets the repo name to "plugin" and the path to the plugin ID func SetPluginIDMeta(obj *unstructured.Unstructured, pluginID string) { if pluginID == "" { diff --git a/apps/dashboard/pkg/migration/conversion/v1beta1_to_v2alpha1.go b/apps/dashboard/pkg/migration/conversion/v1beta1_to_v2alpha1.go index e6599b1d8eb..231c0ad4131 100644 --- a/apps/dashboard/pkg/migration/conversion/v1beta1_to_v2alpha1.go +++ b/apps/dashboard/pkg/migration/conversion/v1beta1_to_v2alpha1.go @@ -12,11 +12,11 @@ import ( "k8s.io/apiserver/pkg/endpoints/request" "github.com/grafana/authlib/types" + "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard" dashv1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1" dashv2alpha1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1" schemaversion "github.com/grafana/grafana/apps/dashboard/pkg/migration/schemaversion" "github.com/grafana/grafana/pkg/apimachinery/identity" - "github.com/grafana/grafana/pkg/tsdb/grafanads" ) // getDefaultDatasourceType gets the default datasource type using the datasource provider @@ -58,7 +58,7 @@ func getDatasourceTypeByUID(ctx context.Context, uid string, provider schemavers // datasource: { type: "datasource" } with no UID, it should resolve to uid: "grafana". func resolveGrafanaDatasourceUID(dsType, dsUID string) string { if dsType == "datasource" && dsUID == "" { - return grafanads.DatasourceUID + return dashboard.GrafanaDatasourceUID } return dsUID } diff --git a/apps/dashboard/pkg/migration/conversion/v1beta1_to_v2alpha1_test.go b/apps/dashboard/pkg/migration/conversion/v1beta1_to_v2alpha1_test.go index 6bbdf1ca214..3dad9188fe7 100644 --- a/apps/dashboard/pkg/migration/conversion/v1beta1_to_v2alpha1_test.go +++ b/apps/dashboard/pkg/migration/conversion/v1beta1_to_v2alpha1_test.go @@ -7,11 +7,11 @@ import ( "github.com/stretchr/testify/require" "k8s.io/apimachinery/pkg/runtime" + "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard" dashv1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1" dashv2alpha1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1" "github.com/grafana/grafana/apps/dashboard/pkg/migration" migrationtestutil "github.com/grafana/grafana/apps/dashboard/pkg/migration/testutil" - "github.com/grafana/grafana/pkg/tsdb/grafanads" ) // TestV1beta1ToV2alpha1 tests conversion from v1beta1 to v2alpha1 with various datasource scenarios @@ -77,7 +77,7 @@ func TestV1beta1ToV2alpha1(t *testing.T) { // Verify datasource UID is resolved to "grafana" assert.NotNil(t, query.Spec.Datasource.Uid) - assert.Equal(t, grafanads.DatasourceUID, *query.Spec.Datasource.Uid, "type: 'datasource' with no UID should resolve to uid: 'grafana'") + assert.Equal(t, dashboard.GrafanaDatasourceUID, *query.Spec.Datasource.Uid, "type: 'datasource' with no UID should resolve to uid: 'grafana'") // Verify query kind matches datasource type assert.Equal(t, "datasource", query.Spec.Query.Kind) diff --git a/pkg/tsdb/grafanads/grafana.go b/pkg/tsdb/grafanads/grafana.go index 06254b7f0b8..68dc792342c 100644 --- a/pkg/tsdb/grafanads/grafana.go +++ b/pkg/tsdb/grafanads/grafana.go @@ -12,6 +12,7 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/data" + "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard" "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/services/datasources" @@ -31,7 +32,7 @@ const DatasourceID = -1 // DatasourceUID is the fake datasource uid used in requests to identify it as a // Grafana DS command. -const DatasourceUID = "grafana" +const DatasourceUID = dashboard.GrafanaDatasourceUID // Make sure Service implements required interfaces. // This is important to do since otherwise we will only get a From b19e5462545c80705e26a1d0489bae2ccac1b702 Mon Sep 17 00:00:00 2001 From: Santiago Date: Fri, 5 Dec 2025 16:04:42 +0100 Subject: [PATCH 064/110] Remote Alertmanager: Remove X-Remote-Alertmanager header (#114917) Remote Alertmanager: Remove X-Remote-Alertmanager haeder --- pkg/services/ngalert/remote/alertmanager_test.go | 7 ------- .../ngalert/remote/client/mimir_auth_round_tripper.go | 4 +--- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/pkg/services/ngalert/remote/alertmanager_test.go b/pkg/services/ngalert/remote/alertmanager_test.go index 953ef3ef2d6..d6f66756454 100644 --- a/pkg/services/ngalert/remote/alertmanager_test.go +++ b/pkg/services/ngalert/remote/alertmanager_test.go @@ -153,7 +153,6 @@ func TestGetRemoteState(t *testing.T) { getOkHandler := func(state string) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { require.Equal(t, tenantID, r.Header.Get(client.MimirTenantHeader)) - require.Equal(t, "true", r.Header.Get(client.RemoteAlertmanagerHeader)) res := map[string]any{ "status": "success", @@ -268,7 +267,6 @@ func TestIntegrationApplyConfig(t *testing.T) { errorHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { require.Equal(t, tenantID, r.Header.Get(client.MimirTenantHeader)) - require.Equal(t, "true", r.Header.Get(client.RemoteAlertmanagerHeader)) w.Header().Add("content-type", "application/json") w.WriteHeader(http.StatusInternalServerError) require.NoError(t, json.NewEncoder(w).Encode(map[string]string{"status": "error"})) @@ -278,7 +276,6 @@ func TestIntegrationApplyConfig(t *testing.T) { var configSyncs, stateSyncs int okHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { require.Equal(t, tenantID, r.Header.Get(client.MimirTenantHeader)) - require.Equal(t, "true", r.Header.Get(client.RemoteAlertmanagerHeader)) res := map[string]any{"status": "success"} if r.Method == http.MethodPost { @@ -432,7 +429,6 @@ func TestCompareAndSendConfiguration(t *testing.T) { var got string server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { require.Equal(t, tenantID, r.Header.Get(client.MimirTenantHeader)) - require.Equal(t, "true", r.Header.Get(client.RemoteAlertmanagerHeader)) w.Header().Add("content-type", "application/json") b, err := io.ReadAll(r.Body) @@ -639,7 +635,6 @@ func Test_TestReceiversDecryptsSecureSettings(t *testing.T) { var got apimodels.TestReceiversConfigBodyParams server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { require.Equal(t, tenantID, r.Header.Get(client.MimirTenantHeader)) - require.Equal(t, "true", r.Header.Get(client.RemoteAlertmanagerHeader)) w.Header().Add("Content-Type", "application/json") require.NoError(t, json.NewDecoder(r.Body).Decode(&got)) require.NoError(t, r.Body.Close()) @@ -746,7 +741,6 @@ func TestApplyConfigWithExtraConfigs(t *testing.T) { var configSent client.UserGrafanaConfig server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { require.Equal(t, tenantID, r.Header.Get(client.MimirTenantHeader)) - require.Equal(t, "true", r.Header.Get(client.RemoteAlertmanagerHeader)) if r.Method == http.MethodPost && strings.Contains(r.URL.Path, "/config") { require.NoError(t, json.NewDecoder(r.Body).Decode(&configSent)) @@ -828,7 +822,6 @@ func TestCompareAndSendConfigurationWithExtraConfigs(t *testing.T) { var configSent client.UserGrafanaConfig server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { require.Equal(t, tenantID, r.Header.Get(client.MimirTenantHeader)) - require.Equal(t, "true", r.Header.Get(client.RemoteAlertmanagerHeader)) if r.Method == http.MethodPost && strings.Contains(r.URL.Path, "/config") { require.NoError(t, json.NewDecoder(r.Body).Decode(&configSent)) diff --git a/pkg/services/ngalert/remote/client/mimir_auth_round_tripper.go b/pkg/services/ngalert/remote/client/mimir_auth_round_tripper.go index 3a9ff94f4a6..2a7a6314e1d 100644 --- a/pkg/services/ngalert/remote/client/mimir_auth_round_tripper.go +++ b/pkg/services/ngalert/remote/client/mimir_auth_round_tripper.go @@ -5,8 +5,7 @@ import ( ) const ( - MimirTenantHeader = "X-Scope-OrgID" - RemoteAlertmanagerHeader = "X-Remote-Alertmanager" + MimirTenantHeader = "X-Scope-OrgID" ) type MimirAuthRoundTripper struct { @@ -19,7 +18,6 @@ type MimirAuthRoundTripper struct { // It adds an `X-Scope-OrgID` header with the TenantID if only provided with a tenantID or sets HTTP Basic Authentication if both // a tenantID and a password are provided. func (r *MimirAuthRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { - req.Header.Set(RemoteAlertmanagerHeader, "true") if r.TenantID != "" && r.Password == "" { req.Header.Set(MimirTenantHeader, r.TenantID) } From bf042afa9878fcbe91a14da88fb6373f0946c0eb Mon Sep 17 00:00:00 2001 From: Bogdan Matei Date: Fri, 5 Dec 2025 17:12:12 +0200 Subject: [PATCH 065/110] Dashboard: Fix dropping panels in tabs and rows (#114893) --- .../dashboard-scene/scene/layout-rows/RowItemRenderer.tsx | 3 ++- .../dashboard-scene/scene/layout-tabs/TabItemRenderer.tsx | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/public/app/features/dashboard-scene/scene/layout-rows/RowItemRenderer.tsx b/public/app/features/dashboard-scene/scene/layout-rows/RowItemRenderer.tsx index 4c74ee35d99..80c16b2f2a1 100644 --- a/public/app/features/dashboard-scene/scene/layout-rows/RowItemRenderer.tsx +++ b/public/app/features/dashboard-scene/scene/layout-rows/RowItemRenderer.tsx @@ -13,6 +13,7 @@ import { isRepeatCloneOrChildOf } from '../../utils/clone'; import { useDashboardState, useInterpolatedTitle } from '../../utils/utils'; import { DashboardScene } from '../DashboardScene'; import { useSoloPanelContext } from '../SoloPanelContext'; +import { isDashboardLayoutGrid } from '../types/DashboardLayoutGrid'; import { RowItem } from './RowItem'; @@ -83,7 +84,7 @@ export function RowItemRenderer({ model }: SceneComponentProps) { dragProvided.innerRef(ref); model.containerRef.current = ref; }} - data-dashboard-drop-target-key={model.state.key} + data-dashboard-drop-target-key={isDashboardLayoutGrid(layout) ? model.state.key : undefined} className={cx( styles.wrapper, !isCollapsed && styles.wrapperNotCollapsed, diff --git a/public/app/features/dashboard-scene/scene/layout-tabs/TabItemRenderer.tsx b/public/app/features/dashboard-scene/scene/layout-tabs/TabItemRenderer.tsx index 0fa22e9d305..e618fb21c7e 100644 --- a/public/app/features/dashboard-scene/scene/layout-tabs/TabItemRenderer.tsx +++ b/public/app/features/dashboard-scene/scene/layout-tabs/TabItemRenderer.tsx @@ -11,6 +11,7 @@ import { useIsConditionallyHidden } from '../../conditional-rendering/hooks/useI import { isRepeatCloneOrChildOf } from '../../utils/clone'; import { useDashboardState } from '../../utils/utils'; import { useSoloPanelContext } from '../SoloPanelContext'; +import { isDashboardLayoutGrid } from '../types/DashboardLayoutGrid'; import { TabItem } from './TabItem'; @@ -91,7 +92,7 @@ export function TabItemRenderer({ model }: SceneComponentProps) { onSelect?.(evt); }} label={titleInterpolated} - data-dashboard-drop-target-key={model.state.key} + data-dashboard-drop-target-key={isDashboardLayoutGrid(layout) ? model.state.key : undefined} {...titleCollisionProps} /> From 7cd10aa49ed22435b0aef9fc04d471818b7108dc Mon Sep 17 00:00:00 2001 From: Sarah Zinger Date: Fri, 5 Dec 2025 10:14:02 -0500 Subject: [PATCH 066/110] SQL Expressions: Fix alerts with sql expressions that have a cte (#114852) Fix for #114377 - fix alerts with sql expressions that have a cte --- .../components/rule-editor/dag.test.ts | 55 +++++++++++++++++++ .../unified/components/rule-editor/dag.ts | 44 +++++++++++++-- 2 files changed, 95 insertions(+), 4 deletions(-) diff --git a/public/app/features/alerting/unified/components/rule-editor/dag.test.ts b/public/app/features/alerting/unified/components/rule-editor/dag.test.ts index 189bcb6f25e..423f035ddc9 100644 --- a/public/app/features/alerting/unified/components/rule-editor/dag.test.ts +++ b/public/app/features/alerting/unified/components/rule-editor/dag.test.ts @@ -293,6 +293,61 @@ SELECT * FROM table1`) expect(parseRefsFromSqlExpression('SELECT * FROM\ntable1')).toEqual(['table1']); }); }); + + describe('CTE (Common Table Expression) handling', () => { + it('should exclude single CTE name from results', () => { + const query = 'WITH my_cte AS (SELECT * FROM table1) SELECT * FROM my_cte'; + expect(parseRefsFromSqlExpression(query)).toEqual(['table1']); + }); + + it('should exclude multiple CTE names from results', () => { + const query = ` + WITH cte1 AS (SELECT * FROM table1), + cte2 AS (SELECT * FROM table2) + SELECT * FROM cte1 JOIN cte2 ON cte1.id = cte2.id + `; + expect(parseRefsFromSqlExpression(query)).toEqual(['table1', 'table2']); + }); + + it('should handle CTEs with external table references in main query', () => { + const query = ` + WITH summary AS (SELECT id, count FROM table1) + SELECT * FROM summary JOIN table2 ON summary.id = table2.id + `; + expect(parseRefsFromSqlExpression(query)).toEqual(['table1', 'table2']); + }); + + it('should handle CTE names case-insensitively', () => { + const query = 'WITH MyCte AS (SELECT * FROM table1) SELECT * FROM mycte'; + expect(parseRefsFromSqlExpression(query)).toEqual(['table1']); + }); + + it('should handle RECURSIVE CTEs', () => { + const query = ` + WITH RECURSIVE cte AS ( + SELECT * FROM table1 + UNION ALL + SELECT * FROM cte WHERE depth < 10 + ) + SELECT * FROM cte + `; + expect(parseRefsFromSqlExpression(query)).toEqual(['table1']); + }); + + it('should handle queries without CTEs normally', () => { + const query = 'SELECT * FROM table1 JOIN table2 ON table1.id = table2.id'; + expect(parseRefsFromSqlExpression(query)).toEqual(['table1', 'table2']); + }); + + it('should handle CTE that references another CTE', () => { + const query = ` + WITH cte1 AS (SELECT * FROM table1), + cte2 AS (SELECT * FROM cte1) + SELECT * FROM cte2 + `; + expect(parseRefsFromSqlExpression(query)).toEqual(['table1']); + }); + }); }); describe('fingerprints', () => { diff --git a/public/app/features/alerting/unified/components/rule-editor/dag.ts b/public/app/features/alerting/unified/components/rule-editor/dag.ts index 9666186f039..c7078ca7517 100644 --- a/public/app/features/alerting/unified/components/rule-editor/dag.ts +++ b/public/app/features/alerting/unified/components/rule-editor/dag.ts @@ -132,10 +132,15 @@ export function parseRefsFromSqlExpression(input: string): string[] { .replace(/\s+/g, ' ') // Remove any potential multi line comments .replace(/\/\*[\s\S]*?\*\//g, ''); + + // Extract CTE names to exclude them from table references + const cteNames = parseCteNames(query); + const tableMatches = []; // Extract tables after FROM - case insensitive with /i flag - const fromRegex = /from\s+([^;]*?)(?:\s+(?:join|where|group|having|order|limit)|\s*$)/gi; + // Terminate on: SQL keywords, closing paren (for CTEs/subqueries), or end of string + const fromRegex = /from\s+([^;)]*?)(?:\s+(?:join|where|group|having|order|limit|on|select)|\)|$)/gi; for (const match of query.matchAll(fromRegex)) { const fromClause = match[1].trim(); @@ -153,13 +158,44 @@ export function parseRefsFromSqlExpression(input: string): string[] { tableMatches.push(cleanTableName(match[1])); } - return compact(uniq(tableMatches)); + // Filter out CTE names - they're local definitions, not external references + const externalRefs = tableMatches.filter((table) => !cteNames.has(table.toLowerCase())); + + return compact(uniq(externalRefs)); +} + +/** + * Parse CTE (Common Table Expression) names from a SQL query. + * CTEs are defined with: WITH cte_name AS (...), another_cte AS (...) + */ +function parseCteNames(query: string): Set { + const cteNames = new Set(); + + // Match the WITH clause - handles both regular and RECURSIVE CTEs + const withMatch = query.match(/^\s*with\s+(?:recursive\s+)?(.*?)(?:\s+select\s)/i); + + if (!withMatch) { + return cteNames; + } + + const withClause = withMatch[1]; + + // Match CTE names - they appear before "AS" keyword followed by opening paren + // This handles: cte_name AS (, "quoted_name" AS ( + const cteNameRegex = /([a-zA-Z0-9_]+|"[^"]+"|'[^']+')\s+as\s*\(/gi; + + for (const match of withClause.matchAll(cteNameRegex)) { + const cteName = match[1].replace(/['"]/g, '').toLowerCase(); + cteNames.add(cteName); + } + + return cteNames; } // Helper function to clean table names function cleanTableName(tableName: string): string { - // Remove quotes - let name = tableName.replace(/['"]/g, ''); + // Remove quotes and parentheses + let name = tableName.replace(/['"()]/g, ''); // Remove alias if present (both "AS alias" and "alias" forms) if (name.includes(' as ')) { From 0adb2461e9bdd21c56412a2b9d3695fad6e06902 Mon Sep 17 00:00:00 2001 From: "Marc M." <146180665+grafakus@users.noreply.github.com> Date: Fri, 5 Dec 2025 16:48:34 +0100 Subject: [PATCH 067/110] Dashboards: Improve custom variable editor and undo/redo (#114559) --- .../dashboards-edit-custom-variables.spec.ts | 6 +- .../src/selectors/pages.ts | 3 + .../components/VariableValuesPreview.tsx | 2 +- .../CustomVariableEditor/ModalEditor.tsx | 92 +++++++++++++++---- .../editors/CustomVariableEditor/PaneItem.tsx | 2 +- .../CustomVariableEditor/ValuesBuilder.tsx | 52 ----------- .../CustomVariableEditor/ValuesPreview.tsx | 13 --- public/locales/en-US/grafana.json | 4 +- 8 files changed, 85 insertions(+), 89 deletions(-) delete mode 100644 public/app/features/dashboard-scene/settings/variables/editors/CustomVariableEditor/ValuesBuilder.tsx delete mode 100644 public/app/features/dashboard-scene/settings/variables/editors/CustomVariableEditor/ValuesPreview.tsx diff --git a/e2e-playwright/dashboard-new-layouts/dashboards-edit-custom-variables.spec.ts b/e2e-playwright/dashboard-new-layouts/dashboards-edit-custom-variables.spec.ts index 4715dfc7128..e11f2dd099a 100644 --- a/e2e-playwright/dashboard-new-layouts/dashboards-edit-custom-variables.spec.ts +++ b/e2e-playwright/dashboard-new-layouts/dashboards-edit-custom-variables.spec.ts @@ -84,9 +84,9 @@ test.describe( refetchItems(dashboardPage, selectors); }; - const closeModal = async (dashboardPage: DashboardPage, selectors: E2ESelectorGroups) => { + const applyAndcloseModal = async (dashboardPage: DashboardPage, selectors: E2ESelectorGroups) => { await dashboardPage - .getByGrafanaSelector(selectors.pages.Dashboard.Settings.Variables.Edit.CustomVariable.closeButton) + .getByGrafanaSelector(selectors.pages.Dashboard.Settings.Variables.Edit.CustomVariable.applyButton) .click(); }; @@ -149,7 +149,7 @@ test.describe( await removeItem(dashboardPage, selectors, 2); await checkRows(3); await checkPreview(dashboardPage, selectors, ['first value', 'second label', 'fourth value']); - await closeModal(dashboardPage, selectors); + await applyAndcloseModal(dashboardPage, selectors); // assert variable is visible and has the correct values const variableLabel = dashboardPage.getByGrafanaSelector( diff --git a/packages/grafana-e2e-selectors/src/selectors/pages.ts b/packages/grafana-e2e-selectors/src/selectors/pages.ts index 47a5573b00d..1fa640a2563 100644 --- a/packages/grafana-e2e-selectors/src/selectors/pages.ts +++ b/packages/grafana-e2e-selectors/src/selectors/pages.ts @@ -567,6 +567,9 @@ export const versionedPages = { closeButton: { [MIN_GRAFANA_VERSION]: 'data-testid custom-variable-close-button', }, + applyButton: { + [MIN_GRAFANA_VERSION]: 'data-testid custom-variable-apply-button', + }, }, IntervalVariable: { intervalsValueInput: { diff --git a/public/app/features/dashboard-scene/settings/variables/components/VariableValuesPreview.tsx b/public/app/features/dashboard-scene/settings/variables/components/VariableValuesPreview.tsx index 73d57bd811a..ac59419cda7 100644 --- a/public/app/features/dashboard-scene/settings/variables/components/VariableValuesPreview.tsx +++ b/public/app/features/dashboard-scene/settings/variables/components/VariableValuesPreview.tsx @@ -37,7 +37,7 @@ export const VariableValuesPreview = ({ options }: VariableValuesPreviewProps) = {previewOptions.map((o, index) => ( -
{o.label}
+
{o.label || String(o.value)}
))} diff --git a/public/app/features/dashboard-scene/settings/variables/editors/CustomVariableEditor/ModalEditor.tsx b/public/app/features/dashboard-scene/settings/variables/editors/CustomVariableEditor/ModalEditor.tsx index 3e8a8aa57b1..aed926a6809 100644 --- a/public/app/features/dashboard-scene/settings/variables/editors/CustomVariableEditor/ModalEditor.tsx +++ b/public/app/features/dashboard-scene/settings/variables/editors/CustomVariableEditor/ModalEditor.tsx @@ -1,47 +1,103 @@ -import { useCallback, useRef } from 'react'; +import { useRef, useState } from 'react'; +import { lastValueFrom } from 'rxjs'; import { selectors } from '@grafana/e2e-selectors'; import { t, Trans } from '@grafana/i18n'; -import { CustomVariable } from '@grafana/scenes'; +import { CustomVariable, VariableValueOption, VariableValueSingle } from '@grafana/scenes'; import { Button, Modal, Stack } from '@grafana/ui'; -import { VariableStaticOptionsFormRef } from '../../components/VariableStaticOptionsForm'; +import { dashboardEditActions } from '../../../../edit-pane/shared'; +import { VariableStaticOptionsForm, VariableStaticOptionsFormRef } from '../../components/VariableStaticOptionsForm'; import { VariableStaticOptionsFormAddButton } from '../../components/VariableStaticOptionsFormAddButton'; - -import { ValuesBuilder } from './ValuesBuilder'; -import { ValuesPreview } from './ValuesPreview'; +import { VariableValuesPreview } from '../../components/VariableValuesPreview'; interface ModalEditorProps { variable: CustomVariable; - isOpen: boolean; onClose: () => void; } -export function ModalEditor({ variable, isOpen, onClose }: ModalEditorProps) { - const formRef = useRef(null); - - const handleOnAdd = useCallback(() => formRef.current?.addItem(), []); +export function ModalEditor(props: ModalEditorProps) { + const { formRef, onCloseModal, options, onChangeOptions, onAddNewOption, onSaveOptions } = useModalEditor(props); return ( - - + + - }> + }> + ); } + +function useModalEditor({ variable, onClose }: ModalEditorProps) { + const { query } = variable.state; + const [options, setOptions] = useState(() => transformQueryToOptions(variable, query)); + const initialQueryRef = useRef(query); + const formRef = useRef(null); + + return { + formRef, + onCloseModal: onClose, + options, + onChangeOptions: setOptions, + onAddNewOption() { + formRef.current?.addItem(); + }, + onSaveOptions() { + dashboardEditActions.edit({ + source: variable, + description: t('dashboard.edit-pane.variable.custom-options.change-value', 'Change variable value'), + perform: () => { + variable.setState({ query: transformOptionsToQuery(options) }); + lastValueFrom(variable.validateAndUpdate!()); + }, + undo: () => { + variable.setState({ query: initialQueryRef.current }); + lastValueFrom(variable.validateAndUpdate!()); + }, + }); + + onClose(); + }, + }; +} + +const transformQueryToOptions = (variable: ModalEditorProps['variable'], query: string) => + variable.transformCsvStringToOptions(query, false).map(({ label, value }) => ({ + value, + label: value === label ? '' : label, + })); + +const formatOption = (option: VariableValueOption) => { + if (!option.label || option.label === option.value) { + return escapeEntities(option.value); + } + return `${escapeEntities(option.label)} : ${escapeEntities(String(option.value))}`; +}; + +const escapeEntities = (text: VariableValueSingle) => String(text).trim().replaceAll(',', '\\,'); + +const transformOptionsToQuery = (options: VariableValueOption[]) => options.map(formatOption).join(', '); diff --git a/public/app/features/dashboard-scene/settings/variables/editors/CustomVariableEditor/PaneItem.tsx b/public/app/features/dashboard-scene/settings/variables/editors/CustomVariableEditor/PaneItem.tsx index e453fc6b8b8..d1dab1e554f 100644 --- a/public/app/features/dashboard-scene/settings/variables/editors/CustomVariableEditor/PaneItem.tsx +++ b/public/app/features/dashboard-scene/settings/variables/editors/CustomVariableEditor/PaneItem.tsx @@ -31,7 +31,7 @@ export function PaneItem({ variable }: PaneItemProps) { Open variable editor - setIsOpen(false)} /> + {isOpen && setIsOpen(false)} />} ); } diff --git a/public/app/features/dashboard-scene/settings/variables/editors/CustomVariableEditor/ValuesBuilder.tsx b/public/app/features/dashboard-scene/settings/variables/editors/CustomVariableEditor/ValuesBuilder.tsx deleted file mode 100644 index e2eceea5fd3..00000000000 --- a/public/app/features/dashboard-scene/settings/variables/editors/CustomVariableEditor/ValuesBuilder.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import { forwardRef, useCallback } from 'react'; -import { lastValueFrom } from 'rxjs'; - -import { CustomVariable, VariableValueOption, VariableValueSingle } from '@grafana/scenes'; - -import { VariableStaticOptionsForm, VariableStaticOptionsFormRef } from '../../components/VariableStaticOptionsForm'; - -interface ValuesBuilderProps { - variable: CustomVariable; -} - -export const ValuesBuilder = forwardRef(function ( - { variable }: ValuesBuilderProps, - ref -) { - const { query } = variable.useState(); - - const options = variable.transformCsvStringToOptions(query, false).map(({ label, value }) => ({ - value, - label: value === label ? '' : label, - })); - - const escapeEntities = useCallback((text: VariableValueSingle) => String(text).trim().replaceAll(',', '\\,'), []); - - const formatOption = useCallback( - (option: VariableValueOption) => { - if (!option.label || option.label === option.value) { - return escapeEntities(option.value); - } - - return `${escapeEntities(option.label)} : ${escapeEntities(String(option.value))}`; - }, - [escapeEntities] - ); - - const generateQuery = useCallback( - (options: VariableValueOption[]) => options.map(formatOption).join(', '), - [formatOption] - ); - - const handleOptionsChange = useCallback( - async (options: VariableValueOption[]) => { - variable.setState({ query: generateQuery(options) }); - await lastValueFrom(variable.validateAndUpdate!()); - }, - [variable, generateQuery] - ); - - return ; -}); - -ValuesBuilder.displayName = 'ValuesBuilder'; diff --git a/public/app/features/dashboard-scene/settings/variables/editors/CustomVariableEditor/ValuesPreview.tsx b/public/app/features/dashboard-scene/settings/variables/editors/CustomVariableEditor/ValuesPreview.tsx deleted file mode 100644 index 49a3e8dd55b..00000000000 --- a/public/app/features/dashboard-scene/settings/variables/editors/CustomVariableEditor/ValuesPreview.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { CustomVariable } from '@grafana/scenes'; - -import { VariableValuesPreview } from '../../components/VariableValuesPreview'; -import { hasVariableOptions } from '../../utils'; - -export function ValuesPreview({ variable }: { variable: CustomVariable }) { - // Workaround to toggle a component refresh when values change so that the preview is updated - variable.useState(); - - const isHasVariableOptions = hasVariableOptions(variable); - - return isHasVariableOptions ? : null; -} diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index 6ee3b9e50e6..cc056e38526 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -4811,7 +4811,9 @@ }, "variable": { "custom-options": { - "close": "Close", + "apply": "Apply", + "change-value": "Change variable value", + "discard": "Discard", "modal-title": "Custom Variable", "values": "Values separated by comma" }, From 74c7b5a29220301d8809d2fc0caad2efe9f4e853 Mon Sep 17 00:00:00 2001 From: Alexander Akhmetov Date: Fri, 5 Dec 2025 18:02:11 +0100 Subject: [PATCH 068/110] Alerting: Fix creating a new alert rule vesion when only keep_firing_for changes (#114926) Alerting: Create alert rule vesion when keep_firing_for changes --- pkg/services/ngalert/store/models.go | 1 + pkg/services/ngalert/store/models_test.go | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/pkg/services/ngalert/store/models.go b/pkg/services/ngalert/store/models.go index 24167a225dd..25b81053e66 100644 --- a/pkg/services/ngalert/store/models.go +++ b/pkg/services/ngalert/store/models.go @@ -88,6 +88,7 @@ func (a alertRuleVersion) EqualSpec(b alertRuleVersion) bool { a.NoDataState == b.NoDataState && a.ExecErrState == b.ExecErrState && a.For == b.For && + a.KeepFiringFor == b.KeepFiringFor && a.Annotations == b.Annotations && a.Labels == b.Labels && a.IsPaused == b.IsPaused && diff --git a/pkg/services/ngalert/store/models_test.go b/pkg/services/ngalert/store/models_test.go index ddbc34b4036..98703609007 100644 --- a/pkg/services/ngalert/store/models_test.go +++ b/pkg/services/ngalert/store/models_test.go @@ -21,6 +21,7 @@ func TestAlertRuleVersion_EqualSpec(t *testing.T) { NoDataState: "state1", ExecErrState: "state2", For: time.Minute, + KeepFiringFor: 2 * time.Minute, Annotations: `{ "test": "annotation" }`, Labels: `{ "test": "label" }`, IsPaused: true, @@ -119,6 +120,12 @@ func TestAlertRuleVersion_EqualSpec(t *testing.T) { b: func() alertRuleVersion { v := baseVersion; v.For = 2 * time.Minute; return v }(), expect: false, }, + { + name: "different KeepFiringFor durations", + a: baseVersion, + b: func() alertRuleVersion { v := baseVersion; v.KeepFiringFor = 5 * time.Minute; return v }(), + expect: false, + }, { name: "exact match including bools and other types", a: func() alertRuleVersion { From 5b89d3b807d06836a4d7e281bb37cbd1d5dae715 Mon Sep 17 00:00:00 2001 From: Todd Treece <360020+toddtreece@users.noreply.github.com> Date: Fri, 5 Dec 2025 12:56:01 -0500 Subject: [PATCH 069/110] Plugins App: Add access control (#114869) --- pkg/registry/apps/plugins/accesscontrol.go | 127 ++++++++++++++++++ pkg/registry/apps/plugins/register.go | 21 ++- pkg/server/wire_gen.go | 4 +- pkg/services/accesscontrol/permreg/permreg.go | 2 + 4 files changed, 145 insertions(+), 9 deletions(-) create mode 100644 pkg/registry/apps/plugins/accesscontrol.go diff --git a/pkg/registry/apps/plugins/accesscontrol.go b/pkg/registry/apps/plugins/accesscontrol.go new file mode 100644 index 00000000000..d41efa86f97 --- /dev/null +++ b/pkg/registry/apps/plugins/accesscontrol.go @@ -0,0 +1,127 @@ +package plugins + +import ( + "github.com/grafana/grafana/pkg/services/accesscontrol" + "github.com/grafana/grafana/pkg/services/org" +) + +const ( + // Plugins + ActionPluginsPluginsCreate = "plugins.plugins:create" // CREATE. + ActionPluginsPluginsWrite = "plugins.plugins:write" // UPDATE. + ActionPluginsPluginsRead = "plugins.plugins:read" // GET + LIST. + ActionPluginsPluginsDelete = "plugins.plugins:delete" // DELETE. + + // PluginMetas + ActionPluginsPluginsMetaCreate = "plugins.pluginsmeta:create" // CREATE. + ActionPluginsPluginsMetaWrite = "plugins.pluginsmeta:write" // UPDATE. + ActionPluginsPluginsMetaRead = "plugins.pluginsmeta:read" // GET + LIST. + ActionPluginsPluginsMetaDelete = "plugins.pluginsmeta:delete" // DELETE. +) + +var ( + ScopeProviderPluginsPlugins = accesscontrol.NewScopeProvider("plugins.plugins") + ScopeProviderPluginsPluginsMeta = accesscontrol.NewScopeProvider("plugins.pluginsmeta") + + ScopeAllPluginsPlugins = ScopeProviderPluginsPlugins.GetResourceAllScope() + ScopeAllPluginsPluginsMeta = ScopeProviderPluginsPluginsMeta.GetResourceAllScope() +) + +func registerAccessControlRoles(service accesscontrol.Service) error { + // Plugins + pluginsReader := accesscontrol.RoleRegistration{ + Role: accesscontrol.RoleDTO{ + Name: "fixed:plugins.plugins:reader", + DisplayName: "Plugins Reader", + Description: "Read and list plugins.", + Group: "Plugins", + Permissions: []accesscontrol.Permission{ + { + Action: ActionPluginsPluginsRead, + Scope: ScopeAllPluginsPlugins, + }, + }, + }, + Grants: []string{string(org.RoleViewer), string(org.RoleEditor), string(org.RoleAdmin)}, + } + + pluginsWriter := accesscontrol.RoleRegistration{ + Role: accesscontrol.RoleDTO{ + Name: "fixed:plugins.plugins:writer", + DisplayName: "Plugins Writer", + Description: "Create, update and delete plugins.", + Group: "Plugins", + Permissions: []accesscontrol.Permission{ + { + Action: ActionPluginsPluginsCreate, + Scope: ScopeAllPluginsPlugins, + }, + { + Action: ActionPluginsPluginsRead, + Scope: ScopeAllPluginsPlugins, + }, + { + Action: ActionPluginsPluginsWrite, + Scope: ScopeAllPluginsPlugins, + }, + { + Action: ActionPluginsPluginsDelete, + Scope: ScopeAllPluginsPlugins, + }, + }, + }, + Grants: []string{string(org.RoleAdmin)}, + } + + // PluginMetas + pluginsMetaReader := accesscontrol.RoleRegistration{ + Role: accesscontrol.RoleDTO{ + Name: "fixed:plugins.pluginsmeta:reader", + DisplayName: "Plugin Metas Reader", + Description: "Read and list plugin metadata.", + Group: "Plugins", + Permissions: []accesscontrol.Permission{ + { + Action: ActionPluginsPluginsMetaRead, + Scope: ScopeAllPluginsPluginsMeta, + }, + }, + }, + Grants: []string{string(org.RoleViewer), string(org.RoleEditor), string(org.RoleAdmin)}, + } + + pluginsMetaWriter := accesscontrol.RoleRegistration{ + Role: accesscontrol.RoleDTO{ + Name: "fixed:plugins.pluginsmeta:writer", + DisplayName: "Plugin Metas Writer", + Description: "Create, update and delete plugin metadata.", + Group: "Plugins", + Permissions: []accesscontrol.Permission{ + { + Action: ActionPluginsPluginsMetaCreate, + Scope: ScopeAllPluginsPluginsMeta, + }, + { + Action: ActionPluginsPluginsMetaRead, + Scope: ScopeAllPluginsPluginsMeta, + }, + { + Action: ActionPluginsPluginsMetaWrite, + Scope: ScopeAllPluginsPluginsMeta, + }, + { + Action: ActionPluginsPluginsMetaDelete, + Scope: ScopeAllPluginsPluginsMeta, + }, + }, + }, + Grants: []string{string(org.RoleAdmin)}, + } + + return service.DeclareFixedRoles( + pluginsReader, + pluginsWriter, + pluginsMetaReader, + pluginsMetaWriter, + ) +} diff --git a/pkg/registry/apps/plugins/register.go b/pkg/registry/apps/plugins/register.go index 5d452cbe67c..6831d31ef9b 100644 --- a/pkg/registry/apps/plugins/register.go +++ b/pkg/registry/apps/plugins/register.go @@ -1,14 +1,16 @@ package plugins import ( + "fmt" "os" - "k8s.io/apiserver/pkg/authorization/authorizer" - + authlib "github.com/grafana/authlib/types" appsdkapiserver "github.com/grafana/grafana-app-sdk/k8s/apiserver" + "k8s.io/apiserver/pkg/authorization/authorizer" pluginsapp "github.com/grafana/grafana/apps/plugins/pkg/app" "github.com/grafana/grafana/apps/plugins/pkg/app/meta" + "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/apiserver/appinstaller" ) @@ -18,10 +20,14 @@ var ( ) type AppInstaller struct { - appsdkapiserver.AppInstaller + *pluginsapp.PluginAppInstaller } -func ProvideAppInstaller() (*AppInstaller, error) { +func ProvideAppInstaller(accessControlService accesscontrol.Service, accessClient authlib.AccessClient) (*AppInstaller, error) { + if err := registerAccessControlRoles(accessControlService); err != nil { + return nil, fmt.Errorf("registering access control roles: %w", err) + } + grafanaComAPIURL := os.Getenv("GRAFANA_COM_API_URL") if grafanaComAPIURL == "" { grafanaComAPIURL = "https://grafana.com/api/plugins" @@ -36,12 +42,13 @@ func ProvideAppInstaller() (*AppInstaller, error) { return nil, err } + i.WithAccessChecker(accessClient) + return &AppInstaller{ - AppInstaller: i, + PluginAppInstaller: i, }, nil } -// GetAuthorizer returns the authorizer for the plugins app. -func (p *AppInstaller) GetAuthorizer() authorizer.Authorizer { +func (a *AppInstaller) GetAuthorizer() authorizer.Authorizer { return pluginsapp.GetAuthorizer() } diff --git a/pkg/server/wire_gen.go b/pkg/server/wire_gen.go index e920bdbec61..d1a2ecee64f 100644 --- a/pkg/server/wire_gen.go +++ b/pkg/server/wire_gen.go @@ -783,7 +783,7 @@ func Initialize(ctx context.Context, cfg *setting.Cfg, opts Options, apiOpts api if err != nil { return nil, err } - appInstaller, err := plugins.ProvideAppInstaller() + appInstaller, err := plugins.ProvideAppInstaller(acimplService, accessClient) if err != nil { return nil, err } @@ -1436,7 +1436,7 @@ func InitializeForTest(ctx context.Context, t sqlutil.ITestDB, testingT interfac if err != nil { return nil, err } - appInstaller, err := plugins.ProvideAppInstaller() + appInstaller, err := plugins.ProvideAppInstaller(acimplService, accessClient) if err != nil { return nil, err } diff --git a/pkg/services/accesscontrol/permreg/permreg.go b/pkg/services/accesscontrol/permreg/permreg.go index 5d025a1258b..c9f010c2909 100644 --- a/pkg/services/accesscontrol/permreg/permreg.go +++ b/pkg/services/accesscontrol/permreg/permreg.go @@ -84,6 +84,8 @@ func newPermissionRegistry() *permissionRegistry { "annotations": "annotations:type:", "orgs": "orgs:id:", "plugins": "plugins:id:", + "plugins.plugins": "plugins.plugins:uid:", + "plugins.pluginsmeta": "plugins.pluginsmeta:uid:", "provisioners": "provisioners:", "reports": "reports:id:", "permissions": "permissions:type:", From d1cbef9157ef70282e56b83b1a2f91ac418c6caa Mon Sep 17 00:00:00 2001 From: Charandas <542168+charandas@users.noreply.github.com> Date: Fri, 5 Dec 2025 11:53:31 -0800 Subject: [PATCH 070/110] K8s: use runtime config for API Builders (#114601) * Reapply "K8s: read resource configs from API Enablement for API Builders" (#114475) This reverts commit 4130bd9cd300ec7ce0fb492b3a7229968c9167b4. * revert part that broke things * FF service changes are gonna come later --- pkg/services/apiserver/builder/helper.go | 85 +++++++++++++------ pkg/services/apiserver/builder/openapi.go | 19 ++++- .../apiserver/builder/request_handler.go | 10 ++- pkg/services/apiserver/service.go | 8 +- 4 files changed, 90 insertions(+), 32 deletions(-) diff --git a/pkg/services/apiserver/builder/helper.go b/pkg/services/apiserver/builder/helper.go index 603c49f3cb2..a76a01dffba 100644 --- a/pkg/services/apiserver/builder/helper.go +++ b/pkg/services/apiserver/builder/helper.go @@ -22,6 +22,7 @@ import ( k8srequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/registry/generic" genericapiserver "k8s.io/apiserver/pkg/server" + serverstorage "k8s.io/apiserver/pkg/server/storage" "k8s.io/apiserver/pkg/util/openapi" k8sscheme "k8s.io/client-go/kubernetes/scheme" k8stracing "k8s.io/component-base/tracing" @@ -78,7 +79,9 @@ func GetDefaultBuildHandlerChainFunc(builders []APIGroupBuilder, reg prometheus. delegateHandler, c.LoopbackClientConfig, builders, - reg) + reg, + c.MergedResourceConfig, + ) if err != nil { panic(fmt.Sprintf("could not build the request handler for specified API builders: %s", err.Error())) } @@ -105,6 +108,8 @@ func GetDefaultBuildHandlerChainFunc(builders []APIGroupBuilder, reg prometheus. } } +// SetupConfig sets up the server config for the API server +// specify isAggregator=true, if the chain is being constructed for kube-aggregator func SetupConfig( scheme *runtime.Scheme, serverConfig *genericapiserver.RecommendedConfig, @@ -114,6 +119,7 @@ func SetupConfig( gvs []schema.GroupVersion, additionalOpenAPIDefGetters []common.GetOpenAPIDefinitions, reg prometheus.Registerer, + apiResourceConfig *serverstorage.ResourceConfig, ) error { serverConfig.AdmissionControl = NewAdmissionFromBuilders(builders) defsGetter := GetOpenAPIDefinitions(builders, additionalOpenAPIDefGetters...) @@ -126,7 +132,7 @@ func SetupConfig( openapinamer.NewDefinitionNamer(scheme, k8sscheme.Scheme)) // Add the custom routes to service discovery - serverConfig.OpenAPIV3Config.PostProcessSpec = getOpenAPIPostProcessor(buildVersion, builders, gvs) + serverConfig.OpenAPIV3Config.PostProcessSpec = getOpenAPIPostProcessor(buildVersion, builders, gvs, apiResourceConfig) serverConfig.OpenAPIV3Config.GetOperationIDAndTagsFromRoute = func(r common.Route) (string, []string, error) { meta := r.Metadata() kind := "" @@ -287,6 +293,7 @@ func InstallAPIs( features featuremgmt.FeatureToggles, dualWriterMetrics *grafanarest.DualWriterMetrics, builderMetrics *BuilderMetrics, + apiResourceConfig *serverstorage.ResourceConfig, ) error { // dual writing is only enabled when the storage type is not legacy. // this is needed to support setting a default RESTOptionsGetter for new APIs that don't @@ -401,34 +408,9 @@ func InstallAPIs( for group, buildersForGroup := range buildersGroupMap { g := genericapiserver.NewDefaultAPIGroupInfo(group, scheme, metav1.ParameterCodec, codecs) for _, b := range buildersForGroup { - if err := b.UpdateAPIGroupInfo(&g, APIGroupOptions{ - Scheme: scheme, - OptsGetter: optsGetter, - DualWriteBuilder: dualWrite, - MetricsRegister: reg, - StorageOptsRegister: optsregister, - StorageOpts: storageOpts, - }); err != nil { + if err := installAPIGroupsForBuilder(&g, group, b, apiResourceConfig, scheme, optsGetter, dualWrite, reg, optsregister, storageOpts, features); err != nil { return err } - if len(g.PrioritizedVersions) < 1 { - continue - } - - // if grafanaAPIServerWithExperimentalAPIs is not enabled, remove v0alpha1 resources unless explicitly allowed - //nolint:staticcheck // not yet migrated to OpenFeature - if !features.IsEnabledGlobally(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs) { - if resources, ok := g.VersionedResourcesStorageMap["v0alpha1"]; ok { - for name := range resources { - if !allowRegisteringResourceByInfo(b.AllowedV0Alpha1Resources(), name) { - delete(resources, name) - } - } - if len(resources) == 0 { - delete(g.VersionedResourcesStorageMap, "v0alpha1") - } - } - } } // skip installing the group if there are no resources left after filtering @@ -445,6 +427,53 @@ func InstallAPIs( return nil } +func installAPIGroupsForBuilder(g *genericapiserver.APIGroupInfo, group string, b APIGroupBuilder, apiResourceConfig *serverstorage.ResourceConfig, scheme *runtime.Scheme, + optsGetter generic.RESTOptionsGetter, dualWrite grafanarest.DualWriteBuilder, reg prometheus.Registerer, optsregister apistore.StorageOptionsRegister, + storageOpts *options.StorageOptions, features featuremgmt.FeatureToggles) error { + if err := b.UpdateAPIGroupInfo(g, APIGroupOptions{ + Scheme: scheme, + OptsGetter: optsGetter, + DualWriteBuilder: dualWrite, + MetricsRegister: reg, + StorageOptsRegister: optsregister, + StorageOpts: storageOpts, + }); err != nil { + return err + } + if len(g.PrioritizedVersions) < 1 { + return nil + } + + // filter out api groups that are disabled in APIEnablementOptions + for version := range g.VersionedResourcesStorageMap { + gvr := schema.GroupVersionResource{ + Group: group, + Version: version, + } + if apiResourceConfig != nil && !apiResourceConfig.ResourceEnabled(gvr) { + klog.InfoS("Skipping storage for disabled resource", "gvr", gvr.String()) + delete(g.VersionedResourcesStorageMap, version) + } + } + + // if grafanaAPIServerWithExperimentalAPIs is not enabled, remove v0alpha1 resources unless explicitly allowed + //nolint:staticcheck // not yet migrated to OpenFeature + if !features.IsEnabledGlobally(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs) { + if resources, ok := g.VersionedResourcesStorageMap["v0alpha1"]; ok { + for name := range resources { + if !allowRegisteringResourceByInfo(b.AllowedV0Alpha1Resources(), name) { + delete(resources, name) + } + } + if len(resources) == 0 { + delete(g.VersionedResourcesStorageMap, "v0alpha1") + } + } + } + + return nil +} + // AddPostStartHooks adds post start hooks to a generic API server config func AddPostStartHooks( config *genericapiserver.RecommendedConfig, diff --git a/pkg/services/apiserver/builder/openapi.go b/pkg/services/apiserver/builder/openapi.go index 9cb7cc17f7c..6d3b33f4baf 100644 --- a/pkg/services/apiserver/builder/openapi.go +++ b/pkg/services/apiserver/builder/openapi.go @@ -9,6 +9,8 @@ import ( apiequality "k8s.io/apimachinery/pkg/api/equality" "k8s.io/apimachinery/pkg/runtime/schema" + serverstorage "k8s.io/apiserver/pkg/server/storage" + "k8s.io/klog/v2" openapi "k8s.io/kube-openapi/pkg/common" "k8s.io/kube-openapi/pkg/spec3" spec "k8s.io/kube-openapi/pkg/validation/spec" @@ -76,6 +78,7 @@ func addBuilderRoutes( targetGroupVersion schema.GroupVersion, openAPISpec *spec3.OpenAPI, apiGroupBuilders []APIGroupBuilder, + apiResourceConfig *serverstorage.ResourceConfig, ) (*spec3.OpenAPI, error) { for _, apiGroupBuilder := range apiGroupBuilders { // Optionally include raw http handlers for all builders @@ -107,12 +110,24 @@ func addBuilderRoutes( } } } + + // filter out api groups that are disabled in APIEnablementOptions + for path := range openAPISpec.Paths.Paths { + if strings.HasPrefix(path, "/apis/"+targetGroupVersion.String()+"/") { + gv := targetGroupVersion.WithResource("") + if apiResourceConfig != nil && !apiResourceConfig.ResourceEnabled(gv) { + klog.InfoS("removing openapi routes for disabled resource", "gv", gv.String()) + delete(openAPISpec.Paths.Paths, path) + } + } + } + return openAPISpec, nil } // Modify the OpenAPI spec to include the additional routes. // nolint:gocyclo -func getOpenAPIPostProcessor(version string, builders []APIGroupBuilder, gvs []schema.GroupVersion) func(*spec3.OpenAPI) (*spec3.OpenAPI, error) { +func getOpenAPIPostProcessor(version string, builders []APIGroupBuilder, gvs []schema.GroupVersion, apiResourceConfig *serverstorage.ResourceConfig) func(*spec3.OpenAPI) (*spec3.OpenAPI, error) { return func(s *spec3.OpenAPI) (*spec3.OpenAPI, error) { if s.Paths == nil { return s, nil @@ -227,7 +242,7 @@ func getOpenAPIPostProcessor(version string, builders []APIGroupBuilder, gvs []s } } } - return addBuilderRoutes(gv, ©, builders) + return addBuilderRoutes(gv, ©, builders, apiResourceConfig) } } return s, nil diff --git a/pkg/services/apiserver/builder/request_handler.go b/pkg/services/apiserver/builder/request_handler.go index 80fbf6d0eaa..50761f1d42c 100644 --- a/pkg/services/apiserver/builder/request_handler.go +++ b/pkg/services/apiserver/builder/request_handler.go @@ -6,7 +6,9 @@ import ( "github.com/gorilla/mux" "github.com/prometheus/client_golang/prometheus" + serverstorage "k8s.io/apiserver/pkg/server/storage" restclient "k8s.io/client-go/rest" + klog "k8s.io/klog/v2" "k8s.io/kube-openapi/pkg/spec3" ) @@ -14,7 +16,7 @@ type requestHandler struct { router *mux.Router } -func GetCustomRoutesHandler(delegateHandler http.Handler, restConfig *restclient.Config, builders []APIGroupBuilder, metricsRegistry prometheus.Registerer) (http.Handler, error) { +func GetCustomRoutesHandler(delegateHandler http.Handler, restConfig *restclient.Config, builders []APIGroupBuilder, metricsRegistry prometheus.Registerer, apiResourceConfig *serverstorage.ResourceConfig) (http.Handler, error) { useful := false // only true if any routes exist anywhere router := mux.NewRouter() @@ -27,6 +29,12 @@ func GetCustomRoutesHandler(delegateHandler http.Handler, restConfig *restclient } for _, gv := range GetGroupVersions(builder) { + // filter out api groups that are disabled in APIEnablementOptions + gvr := gv.WithResource("") + if apiResourceConfig != nil && !apiResourceConfig.ResourceEnabled(gvr) { + klog.InfoS("Skipping custom route handler for disabled group version", "gv", gv.String()) + continue + } routes := provider.GetAPIRoutes(gv) if routes == nil { continue diff --git a/pkg/services/apiserver/service.go b/pkg/services/apiserver/service.go index 28da416536c..605016c112d 100644 --- a/pkg/services/apiserver/service.go +++ b/pkg/services/apiserver/service.go @@ -316,7 +316,11 @@ func (s *service) start(ctx context.Context) error { s.cfg.BuildBranch, ) - if err := o.APIEnablementOptions.ApplyTo(&serverConfig.Config, appinstaller.NewAPIResourceConfig(s.appInstallers), s.scheme); err != nil { + apiResourceConfig := appinstaller.NewAPIResourceConfig(s.appInstallers) + // add the builder group versions to the api resource config + apiResourceConfig.EnableVersions(groupVersions...) + + if err := o.APIEnablementOptions.ApplyTo(&serverConfig.Config, apiResourceConfig, s.scheme); err != nil { return err } @@ -359,6 +363,7 @@ func (s *service) start(ctx context.Context) error { groupVersions, defGetters, s.metrics, + apiResourceConfig, ) if err != nil { return err @@ -400,6 +405,7 @@ func (s *service) start(ctx context.Context) error { s.features, s.dualWriterMetrics, s.builderMetrics, + apiResourceConfig, ) if err != nil { return err From 0f9d0317dc2a2e2b362e48e25acbd992633029bd Mon Sep 17 00:00:00 2001 From: "grafana-pr-automation[bot]" <140550294+grafana-pr-automation[bot]@users.noreply.github.com> Date: Sat, 6 Dec 2025 00:40:23 +0000 Subject: [PATCH 071/110] I18n: Download translations from Crowdin (#114938) New Crowdin translations by GitHub Action Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- public/locales/cs-CZ/grafana.json | 7 +++++-- public/locales/de-DE/grafana.json | 7 +++++-- public/locales/es-ES/grafana.json | 7 +++++-- public/locales/fr-FR/grafana.json | 7 +++++-- public/locales/hu-HU/grafana.json | 7 +++++-- public/locales/id-ID/grafana.json | 7 +++++-- public/locales/it-IT/grafana.json | 7 +++++-- public/locales/ja-JP/grafana.json | 7 +++++-- public/locales/ko-KR/grafana.json | 7 +++++-- public/locales/nl-NL/grafana.json | 7 +++++-- public/locales/pl-PL/grafana.json | 7 +++++-- public/locales/pt-BR/grafana.json | 7 +++++-- public/locales/pt-PT/grafana.json | 7 +++++-- public/locales/ru-RU/grafana.json | 7 +++++-- public/locales/sv-SE/grafana.json | 7 +++++-- public/locales/tr-TR/grafana.json | 7 +++++-- public/locales/zh-Hans/grafana.json | 7 +++++-- public/locales/zh-Hant/grafana.json | 7 +++++-- 18 files changed, 90 insertions(+), 36 deletions(-) diff --git a/public/locales/cs-CZ/grafana.json b/public/locales/cs-CZ/grafana.json index 99f381a244e..e90a588053c 100644 --- a/public/locales/cs-CZ/grafana.json +++ b/public/locales/cs-CZ/grafana.json @@ -4851,7 +4851,9 @@ }, "variable": { "custom-options": { - "close": "", + "apply": "", + "change-value": "", + "discard": "", "modal-title": "", "values": "Hodnoty oddělené čárkou" }, @@ -12502,7 +12504,8 @@ "bar-glow": "", "center-glow": "", "rounded-bars": "", - "spotlight": "" + "spotlight": "", + "spotlight-tooltip": "" }, "gradient": "", "gradient-auto": "", diff --git a/public/locales/de-DE/grafana.json b/public/locales/de-DE/grafana.json index f84c77dd97f..e0a149baab7 100644 --- a/public/locales/de-DE/grafana.json +++ b/public/locales/de-DE/grafana.json @@ -4811,7 +4811,9 @@ }, "variable": { "custom-options": { - "close": "", + "apply": "", + "change-value": "", + "discard": "", "modal-title": "", "values": "Werte werden durch Komma getrennt" }, @@ -12396,7 +12398,8 @@ "bar-glow": "", "center-glow": "", "rounded-bars": "", - "spotlight": "" + "spotlight": "", + "spotlight-tooltip": "" }, "gradient": "", "gradient-auto": "", diff --git a/public/locales/es-ES/grafana.json b/public/locales/es-ES/grafana.json index 70b7097bcc7..ff65f840041 100644 --- a/public/locales/es-ES/grafana.json +++ b/public/locales/es-ES/grafana.json @@ -4811,7 +4811,9 @@ }, "variable": { "custom-options": { - "close": "", + "apply": "", + "change-value": "", + "discard": "", "modal-title": "", "values": "Valores separados por coma" }, @@ -12396,7 +12398,8 @@ "bar-glow": "", "center-glow": "", "rounded-bars": "", - "spotlight": "" + "spotlight": "", + "spotlight-tooltip": "" }, "gradient": "", "gradient-auto": "", diff --git a/public/locales/fr-FR/grafana.json b/public/locales/fr-FR/grafana.json index 098050412e8..01bbb43fff9 100644 --- a/public/locales/fr-FR/grafana.json +++ b/public/locales/fr-FR/grafana.json @@ -4811,7 +4811,9 @@ }, "variable": { "custom-options": { - "close": "", + "apply": "", + "change-value": "", + "discard": "", "modal-title": "", "values": "Valeurs séparées par une virgule" }, @@ -12396,7 +12398,8 @@ "bar-glow": "", "center-glow": "", "rounded-bars": "", - "spotlight": "" + "spotlight": "", + "spotlight-tooltip": "" }, "gradient": "", "gradient-auto": "", diff --git a/public/locales/hu-HU/grafana.json b/public/locales/hu-HU/grafana.json index 28130cb2ec2..146b2a78d04 100644 --- a/public/locales/hu-HU/grafana.json +++ b/public/locales/hu-HU/grafana.json @@ -4811,7 +4811,9 @@ }, "variable": { "custom-options": { - "close": "", + "apply": "", + "change-value": "", + "discard": "", "modal-title": "", "values": "Értékek vesszővel elválasztva" }, @@ -12396,7 +12398,8 @@ "bar-glow": "", "center-glow": "", "rounded-bars": "", - "spotlight": "" + "spotlight": "", + "spotlight-tooltip": "" }, "gradient": "", "gradient-auto": "", diff --git a/public/locales/id-ID/grafana.json b/public/locales/id-ID/grafana.json index 72598112d52..7b8e043e947 100644 --- a/public/locales/id-ID/grafana.json +++ b/public/locales/id-ID/grafana.json @@ -4791,7 +4791,9 @@ }, "variable": { "custom-options": { - "close": "", + "apply": "", + "change-value": "", + "discard": "", "modal-title": "", "values": "Nilai dipisahkan dengan koma" }, @@ -12343,7 +12345,8 @@ "bar-glow": "", "center-glow": "", "rounded-bars": "", - "spotlight": "" + "spotlight": "", + "spotlight-tooltip": "" }, "gradient": "", "gradient-auto": "", diff --git a/public/locales/it-IT/grafana.json b/public/locales/it-IT/grafana.json index 5b8c69dff0b..68364a78d6f 100644 --- a/public/locales/it-IT/grafana.json +++ b/public/locales/it-IT/grafana.json @@ -4811,7 +4811,9 @@ }, "variable": { "custom-options": { - "close": "", + "apply": "", + "change-value": "", + "discard": "", "modal-title": "", "values": "Valori separati da virgola" }, @@ -12396,7 +12398,8 @@ "bar-glow": "", "center-glow": "", "rounded-bars": "", - "spotlight": "" + "spotlight": "", + "spotlight-tooltip": "" }, "gradient": "", "gradient-auto": "", diff --git a/public/locales/ja-JP/grafana.json b/public/locales/ja-JP/grafana.json index 14c46a83f7f..c023581e3cf 100644 --- a/public/locales/ja-JP/grafana.json +++ b/public/locales/ja-JP/grafana.json @@ -4791,7 +4791,9 @@ }, "variable": { "custom-options": { - "close": "", + "apply": "", + "change-value": "", + "discard": "", "modal-title": "", "values": "カンマで区切った値" }, @@ -12343,7 +12345,8 @@ "bar-glow": "", "center-glow": "", "rounded-bars": "", - "spotlight": "" + "spotlight": "", + "spotlight-tooltip": "" }, "gradient": "", "gradient-auto": "", diff --git a/public/locales/ko-KR/grafana.json b/public/locales/ko-KR/grafana.json index df4cad85150..e9cb03b8350 100644 --- a/public/locales/ko-KR/grafana.json +++ b/public/locales/ko-KR/grafana.json @@ -4791,7 +4791,9 @@ }, "variable": { "custom-options": { - "close": "", + "apply": "", + "change-value": "", + "discard": "", "modal-title": "", "values": "쉼표로 구분된 값" }, @@ -12343,7 +12345,8 @@ "bar-glow": "", "center-glow": "", "rounded-bars": "", - "spotlight": "" + "spotlight": "", + "spotlight-tooltip": "" }, "gradient": "", "gradient-auto": "", diff --git a/public/locales/nl-NL/grafana.json b/public/locales/nl-NL/grafana.json index 126ad3f53f9..7872baf058a 100644 --- a/public/locales/nl-NL/grafana.json +++ b/public/locales/nl-NL/grafana.json @@ -4811,7 +4811,9 @@ }, "variable": { "custom-options": { - "close": "", + "apply": "", + "change-value": "", + "discard": "", "modal-title": "", "values": "Waarden gescheiden door komma" }, @@ -12396,7 +12398,8 @@ "bar-glow": "", "center-glow": "", "rounded-bars": "", - "spotlight": "" + "spotlight": "", + "spotlight-tooltip": "" }, "gradient": "", "gradient-auto": "", diff --git a/public/locales/pl-PL/grafana.json b/public/locales/pl-PL/grafana.json index 0f441a67b12..acd3b23e77f 100644 --- a/public/locales/pl-PL/grafana.json +++ b/public/locales/pl-PL/grafana.json @@ -4851,7 +4851,9 @@ }, "variable": { "custom-options": { - "close": "", + "apply": "", + "change-value": "", + "discard": "", "modal-title": "", "values": "Wartości rozdzielone przecinkami" }, @@ -12502,7 +12504,8 @@ "bar-glow": "", "center-glow": "", "rounded-bars": "", - "spotlight": "" + "spotlight": "", + "spotlight-tooltip": "" }, "gradient": "", "gradient-auto": "", diff --git a/public/locales/pt-BR/grafana.json b/public/locales/pt-BR/grafana.json index a96c531b03c..e091960845a 100644 --- a/public/locales/pt-BR/grafana.json +++ b/public/locales/pt-BR/grafana.json @@ -4811,7 +4811,9 @@ }, "variable": { "custom-options": { - "close": "", + "apply": "", + "change-value": "", + "discard": "", "modal-title": "", "values": "Valores separados por vírgula" }, @@ -12396,7 +12398,8 @@ "bar-glow": "", "center-glow": "", "rounded-bars": "", - "spotlight": "" + "spotlight": "", + "spotlight-tooltip": "" }, "gradient": "", "gradient-auto": "", diff --git a/public/locales/pt-PT/grafana.json b/public/locales/pt-PT/grafana.json index dc7e3c11c34..3a69ffa3194 100644 --- a/public/locales/pt-PT/grafana.json +++ b/public/locales/pt-PT/grafana.json @@ -4811,7 +4811,9 @@ }, "variable": { "custom-options": { - "close": "", + "apply": "", + "change-value": "", + "discard": "", "modal-title": "", "values": "Valores separados por vírgulas" }, @@ -12396,7 +12398,8 @@ "bar-glow": "", "center-glow": "", "rounded-bars": "", - "spotlight": "" + "spotlight": "", + "spotlight-tooltip": "" }, "gradient": "", "gradient-auto": "", diff --git a/public/locales/ru-RU/grafana.json b/public/locales/ru-RU/grafana.json index cb1af71f6ed..a85c8b00dd5 100644 --- a/public/locales/ru-RU/grafana.json +++ b/public/locales/ru-RU/grafana.json @@ -4851,7 +4851,9 @@ }, "variable": { "custom-options": { - "close": "", + "apply": "", + "change-value": "", + "discard": "", "modal-title": "", "values": "Значения, разделенные запятыми" }, @@ -12502,7 +12504,8 @@ "bar-glow": "", "center-glow": "", "rounded-bars": "", - "spotlight": "" + "spotlight": "", + "spotlight-tooltip": "" }, "gradient": "", "gradient-auto": "", diff --git a/public/locales/sv-SE/grafana.json b/public/locales/sv-SE/grafana.json index 883cac40ddf..733bfdd230b 100644 --- a/public/locales/sv-SE/grafana.json +++ b/public/locales/sv-SE/grafana.json @@ -4811,7 +4811,9 @@ }, "variable": { "custom-options": { - "close": "", + "apply": "", + "change-value": "", + "discard": "", "modal-title": "", "values": "Värden åtskilda med kommatecken" }, @@ -12396,7 +12398,8 @@ "bar-glow": "", "center-glow": "", "rounded-bars": "", - "spotlight": "" + "spotlight": "", + "spotlight-tooltip": "" }, "gradient": "", "gradient-auto": "", diff --git a/public/locales/tr-TR/grafana.json b/public/locales/tr-TR/grafana.json index 2037475ec8a..4b26d75fcb5 100644 --- a/public/locales/tr-TR/grafana.json +++ b/public/locales/tr-TR/grafana.json @@ -4811,7 +4811,9 @@ }, "variable": { "custom-options": { - "close": "", + "apply": "", + "change-value": "", + "discard": "", "modal-title": "", "values": "Virgülle ayrılmış değerler" }, @@ -12396,7 +12398,8 @@ "bar-glow": "", "center-glow": "", "rounded-bars": "", - "spotlight": "" + "spotlight": "", + "spotlight-tooltip": "" }, "gradient": "", "gradient-auto": "", diff --git a/public/locales/zh-Hans/grafana.json b/public/locales/zh-Hans/grafana.json index 8d34dcd8286..45cbea59c45 100644 --- a/public/locales/zh-Hans/grafana.json +++ b/public/locales/zh-Hans/grafana.json @@ -4791,7 +4791,9 @@ }, "variable": { "custom-options": { - "close": "", + "apply": "", + "change-value": "", + "discard": "", "modal-title": "", "values": "以逗号分隔的值" }, @@ -12343,7 +12345,8 @@ "bar-glow": "", "center-glow": "", "rounded-bars": "", - "spotlight": "" + "spotlight": "", + "spotlight-tooltip": "" }, "gradient": "", "gradient-auto": "", diff --git a/public/locales/zh-Hant/grafana.json b/public/locales/zh-Hant/grafana.json index 97699c7bdd8..5548f9f3d56 100644 --- a/public/locales/zh-Hant/grafana.json +++ b/public/locales/zh-Hant/grafana.json @@ -4791,7 +4791,9 @@ }, "variable": { "custom-options": { - "close": "", + "apply": "", + "change-value": "", + "discard": "", "modal-title": "", "values": "以逗號分隔的值" }, @@ -12343,7 +12345,8 @@ "bar-glow": "", "center-glow": "", "rounded-bars": "", - "spotlight": "" + "spotlight": "", + "spotlight-tooltip": "" }, "gradient": "", "gradient-auto": "", From e9ba45ca4fd7f28d34cd2590762b75c6a7afd7c6 Mon Sep 17 00:00:00 2001 From: Steve Simpson Date: Sat, 6 Dec 2025 08:34:18 +0100 Subject: [PATCH 072/110] Update grafana-app-sdk to v0.48.5 (#114810) Co-authored-by: Owen Smallwood Co-authored-by: Ryan McKinley --- apps/advisor/go.mod | 2 +- apps/advisor/go.sum | 4 +- apps/alerting/alertenrichment/go.mod | 2 +- apps/alerting/alertenrichment/go.sum | 4 +- apps/alerting/historian/go.mod | 2 +- apps/alerting/historian/go.sum | 4 +- apps/alerting/notifications/go.mod | 2 +- apps/alerting/notifications/go.sum | 4 +- .../v0alpha1/receiver_object_gen.go | 6 + .../v0alpha1/receiver_schema_gen.go | 2 +- .../v0alpha1/routingtree_object_gen.go | 6 + .../v0alpha1/routingtree_schema_gen.go | 2 +- .../v0alpha1/templategroup_object_gen.go | 6 + .../v0alpha1/templategroup_schema_gen.go | 2 +- .../v0alpha1/timeinterval_object_gen.go | 6 + .../v0alpha1/timeinterval_schema_gen.go | 2 +- apps/alerting/rules/go.mod | 2 +- apps/alerting/rules/go.sum | 4 +- apps/annotation/go.mod | 2 +- apps/annotation/go.sum | 4 +- apps/collections/go.mod | 2 +- apps/collections/go.sum | 4 +- apps/correlations/go.mod | 2 +- apps/correlations/go.sum | 4 +- apps/dashboard/go.mod | 2 +- apps/dashboard/go.sum | 4 +- .../v0alpha1/dashboard_object_gen.go | 7 ++ .../v0alpha1/dashboard_schema_gen.go | 2 +- .../dashboard/v0alpha1/snapshot_object_gen.go | 6 + .../dashboard/v0alpha1/snapshot_schema_gen.go | 2 +- .../dashboard/v1beta1/dashboard_object_gen.go | 7 ++ .../dashboard/v1beta1/dashboard_schema_gen.go | 2 +- .../v2alpha1/dashboard_object_gen.go | 7 ++ .../v2alpha1/dashboard_schema_gen.go | 2 +- .../dashboard/v2beta1/dashboard_object_gen.go | 7 ++ .../dashboard/v2beta1/dashboard_schema_gen.go | 2 +- apps/example/go.mod | 2 +- apps/example/go.sum | 4 +- apps/folder/go.mod | 27 +++- apps/folder/go.sum | 69 ++++++++++- .../apis/folder/v1beta1/folder_object_gen.go | 6 + .../apis/folder/v1beta1/folder_schema_gen.go | 2 +- .../pkg/apis/manifestdata/folder_manifest.go | 116 ++++++++++++++++++ apps/iam/go.mod | 2 +- apps/iam/go.sum | 4 +- apps/investigations/go.mod | 2 +- apps/investigations/go.sum | 4 +- apps/logsdrilldown/go.mod | 2 +- apps/logsdrilldown/go.sum | 4 +- apps/playlist/go.mod | 2 +- apps/playlist/go.sum | 4 +- apps/plugins/go.mod | 17 +-- apps/plugins/go.sum | 24 ++-- apps/preferences/go.mod | 2 +- apps/preferences/go.sum | 4 +- apps/provisioning/go.mod | 2 +- apps/provisioning/go.sum | 4 +- apps/sdk.mk | 2 +- apps/secret/go.mod | 2 +- apps/secret/go.sum | 4 +- apps/shorturl/go.mod | 2 +- apps/shorturl/go.sum | 4 +- go.mod | 2 +- go.sum | 4 +- go.work.sum | 3 +- 65 files changed, 365 insertions(+), 91 deletions(-) create mode 100644 apps/folder/pkg/apis/manifestdata/folder_manifest.go diff --git a/apps/advisor/go.mod b/apps/advisor/go.mod index 2318df205ce..941d8b9bc0f 100644 --- a/apps/advisor/go.mod +++ b/apps/advisor/go.mod @@ -8,7 +8,7 @@ require ( github.com/google/go-github/v70 v70.0.0 github.com/grafana/authlib/types v0.0.0-20251119142549-be091cf2f4d4 github.com/grafana/grafana v0.0.0-00010101000000-000000000000 - github.com/grafana/grafana-app-sdk v0.48.4 + github.com/grafana/grafana-app-sdk v0.48.5 github.com/grafana/grafana-app-sdk/logging v0.48.3 github.com/grafana/grafana-plugin-sdk-go v0.284.0 github.com/grafana/grafana/pkg/apimachinery v0.0.0 diff --git a/apps/advisor/go.sum b/apps/advisor/go.sum index 76208d30349..4695785bbd1 100644 --- a/apps/advisor/go.sum +++ b/apps/advisor/go.sum @@ -618,8 +618,8 @@ github.com/grafana/dataplane/sdata v0.0.9 h1:AGL1LZnCUG4MnQtnWpBPbQ8ZpptaZs14w6k github.com/grafana/dataplane/sdata v0.0.9/go.mod h1:Jvs5ddpGmn6vcxT7tCTWAZ1mgi4sbcdFt9utQx5uMAU= github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4 h1:jSojuc7njleS3UOz223WDlXOinmuLAIPI0z2vtq8EgI= github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4/go.mod h1:VahT+GtfQIM+o8ht2StR6J9g+Ef+C2Vokh5uuSmOD/4= -github.com/grafana/grafana-app-sdk v0.48.4 h1:t9r+Y6E7D832ZxQ2c1n0lp6cvsYKhhrAodVYzE1y0s0= -github.com/grafana/grafana-app-sdk v0.48.4/go.mod h1:HJsMOSBmt/D/Ihs1SvagOwmXKi0coBMVHlfvdd+qe9Y= +github.com/grafana/grafana-app-sdk v0.48.5 h1:MS8l9fTZz+VbTfgApn09jw27GxhQ6fNOWGhC4ydvZmM= +github.com/grafana/grafana-app-sdk v0.48.5/go.mod h1:HJsMOSBmt/D/Ihs1SvagOwmXKi0coBMVHlfvdd+qe9Y= github.com/grafana/grafana-app-sdk/logging v0.48.3 h1:72NUpGNiJXCNQz/on++YSsl38xuVYYBKv5kKQaOClX4= github.com/grafana/grafana-app-sdk/logging v0.48.3/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA= github.com/grafana/grafana-aws-sdk v1.3.0 h1:/bfJzP93rCel1GbWoRSq0oUo424MZXt8jAp2BK9w8tM= diff --git a/apps/alerting/alertenrichment/go.mod b/apps/alerting/alertenrichment/go.mod index 95466bad6bb..bb020e52bff 100644 --- a/apps/alerting/alertenrichment/go.mod +++ b/apps/alerting/alertenrichment/go.mod @@ -3,7 +3,7 @@ module github.com/grafana/grafana/apps/alerting/alertenrichment go 1.25.5 require ( - github.com/grafana/grafana-app-sdk v0.48.4 + github.com/grafana/grafana-app-sdk v0.48.5 github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250901080157-a0280d701b28 k8s.io/apimachinery v0.34.2 k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 diff --git a/apps/alerting/alertenrichment/go.sum b/apps/alerting/alertenrichment/go.sum index 5dff965c88b..ef36568611c 100644 --- a/apps/alerting/alertenrichment/go.sum +++ b/apps/alerting/alertenrichment/go.sum @@ -23,8 +23,8 @@ github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7O github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/grafana/grafana-app-sdk v0.48.4 h1:t9r+Y6E7D832ZxQ2c1n0lp6cvsYKhhrAodVYzE1y0s0= -github.com/grafana/grafana-app-sdk v0.48.4/go.mod h1:HJsMOSBmt/D/Ihs1SvagOwmXKi0coBMVHlfvdd+qe9Y= +github.com/grafana/grafana-app-sdk v0.48.5 h1:MS8l9fTZz+VbTfgApn09jw27GxhQ6fNOWGhC4ydvZmM= +github.com/grafana/grafana-app-sdk v0.48.5/go.mod h1:HJsMOSBmt/D/Ihs1SvagOwmXKi0coBMVHlfvdd+qe9Y= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250901080157-a0280d701b28 h1:PgMfX4OPENz/iXmtDDIW9+poZY4UD0hhmXm7flVclDo= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250901080157-a0280d701b28/go.mod h1:av5N0Naq+8VV9MLF7zAkihy/mVq5UbS2EvRSJukDHlY= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= diff --git a/apps/alerting/historian/go.mod b/apps/alerting/historian/go.mod index 5963ac8139c..21ad42c90af 100644 --- a/apps/alerting/historian/go.mod +++ b/apps/alerting/historian/go.mod @@ -6,7 +6,7 @@ require ( github.com/go-kit/log v0.2.1 github.com/grafana/alerting v0.0.0-20251204145817-de8c2bbf9eba github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4 - github.com/grafana/grafana-app-sdk v0.48.4 + github.com/grafana/grafana-app-sdk v0.48.5 github.com/grafana/grafana-app-sdk/logging v0.48.3 github.com/prometheus/client_golang v1.23.2 github.com/spf13/pflag v1.0.10 diff --git a/apps/alerting/historian/go.sum b/apps/alerting/historian/go.sum index 9e44308979b..b4d2d1dc2e2 100644 --- a/apps/alerting/historian/go.sum +++ b/apps/alerting/historian/go.sum @@ -216,14 +216,14 @@ github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grafana/grafana-app-sdk v0.48.5 h1:MS8l9fTZz+VbTfgApn09jw27GxhQ6fNOWGhC4ydvZmM= +github.com/grafana/grafana-app-sdk v0.48.5/go.mod h1:HJsMOSBmt/D/Ihs1SvagOwmXKi0coBMVHlfvdd+qe9Y= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/grafana/alerting v0.0.0-20251204145817-de8c2bbf9eba h1:psKWNETD5nGxmFAlqnWsXoRyUwSa2GHNEMSEDKGKfQ4= github.com/grafana/alerting v0.0.0-20251204145817-de8c2bbf9eba/go.mod h1:l7v67cgP7x72ajB9UPZlumdrHqNztpKoqQ52cU8T3LU= github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4 h1:jSojuc7njleS3UOz223WDlXOinmuLAIPI0z2vtq8EgI= github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4/go.mod h1:VahT+GtfQIM+o8ht2StR6J9g+Ef+C2Vokh5uuSmOD/4= -github.com/grafana/grafana-app-sdk v0.48.4 h1:t9r+Y6E7D832ZxQ2c1n0lp6cvsYKhhrAodVYzE1y0s0= -github.com/grafana/grafana-app-sdk v0.48.4/go.mod h1:HJsMOSBmt/D/Ihs1SvagOwmXKi0coBMVHlfvdd+qe9Y= github.com/grafana/grafana-app-sdk/logging v0.48.3 h1:72NUpGNiJXCNQz/on++YSsl38xuVYYBKv5kKQaOClX4= github.com/grafana/grafana-app-sdk/logging v0.48.3/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA= github.com/grafana/loki/pkg/push v0.0.0-20250823105456-332df2b20000 h1:/5LKSYgLmAhwA4m6iGUD4w1YkydEWWjazn9qxCFT8W0= diff --git a/apps/alerting/notifications/go.mod b/apps/alerting/notifications/go.mod index 80dd39f9ca7..f72c4c9775b 100644 --- a/apps/alerting/notifications/go.mod +++ b/apps/alerting/notifications/go.mod @@ -3,7 +3,7 @@ module github.com/grafana/grafana/apps/alerting/notifications go 1.25.5 require ( - github.com/grafana/grafana-app-sdk v0.48.4 + github.com/grafana/grafana-app-sdk v0.48.5 github.com/grafana/grafana-app-sdk/logging v0.48.3 k8s.io/apimachinery v0.34.2 k8s.io/apiserver v0.34.2 diff --git a/apps/alerting/notifications/go.sum b/apps/alerting/notifications/go.sum index 6ab34248ef8..57b099ab2f1 100644 --- a/apps/alerting/notifications/go.sum +++ b/apps/alerting/notifications/go.sum @@ -71,8 +71,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= -github.com/grafana/grafana-app-sdk v0.48.4 h1:t9r+Y6E7D832ZxQ2c1n0lp6cvsYKhhrAodVYzE1y0s0= -github.com/grafana/grafana-app-sdk v0.48.4/go.mod h1:HJsMOSBmt/D/Ihs1SvagOwmXKi0coBMVHlfvdd+qe9Y= +github.com/grafana/grafana-app-sdk v0.48.5 h1:MS8l9fTZz+VbTfgApn09jw27GxhQ6fNOWGhC4ydvZmM= +github.com/grafana/grafana-app-sdk v0.48.5/go.mod h1:HJsMOSBmt/D/Ihs1SvagOwmXKi0coBMVHlfvdd+qe9Y= github.com/grafana/grafana-app-sdk/logging v0.48.3 h1:72NUpGNiJXCNQz/on++YSsl38xuVYYBKv5kKQaOClX4= github.com/grafana/grafana-app-sdk/logging v0.48.3/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA= github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0 h1:QGLs/O40yoNK9vmy4rhUGBVyMf1lISBGtXRpsu/Qu/o= diff --git a/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1/receiver_object_gen.go b/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1/receiver_object_gen.go index 00bb1a34745..de0a0d5320f 100644 --- a/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1/receiver_object_gen.go +++ b/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1/receiver_object_gen.go @@ -23,6 +23,12 @@ type Receiver struct { Spec ReceiverSpec `json:"spec" yaml:"spec"` } +func NewReceiver() *Receiver { + return &Receiver{ + Spec: *NewReceiverSpec(), + } +} + func (o *Receiver) GetSpec() any { return o.Spec } diff --git a/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1/receiver_schema_gen.go b/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1/receiver_schema_gen.go index ea4e3b8e363..27047b00601 100644 --- a/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1/receiver_schema_gen.go +++ b/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1/receiver_schema_gen.go @@ -12,7 +12,7 @@ import ( // schema is unexported to prevent accidental overwrites var ( - schemaReceiver = resource.NewSimpleSchema("notifications.alerting.grafana.app", "v0alpha1", &Receiver{}, &ReceiverList{}, resource.WithKind("Receiver"), + schemaReceiver = resource.NewSimpleSchema("notifications.alerting.grafana.app", "v0alpha1", NewReceiver(), &ReceiverList{}, resource.WithKind("Receiver"), resource.WithPlural("receivers"), resource.WithScope(resource.NamespacedScope), resource.WithSelectableFields([]resource.SelectableField{{ FieldSelector: "spec.title", FieldValueFunc: func(o resource.Object) (string, error) { diff --git a/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1/routingtree_object_gen.go b/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1/routingtree_object_gen.go index e59f0dada5c..354e009d77f 100644 --- a/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1/routingtree_object_gen.go +++ b/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1/routingtree_object_gen.go @@ -23,6 +23,12 @@ type RoutingTree struct { Spec RoutingTreeSpec `json:"spec" yaml:"spec"` } +func NewRoutingTree() *RoutingTree { + return &RoutingTree{ + Spec: *NewRoutingTreeSpec(), + } +} + func (o *RoutingTree) GetSpec() any { return o.Spec } diff --git a/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1/routingtree_schema_gen.go b/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1/routingtree_schema_gen.go index 6838c9cdebd..2a1812a2846 100644 --- a/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1/routingtree_schema_gen.go +++ b/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1/routingtree_schema_gen.go @@ -10,7 +10,7 @@ import ( // schema is unexported to prevent accidental overwrites var ( - schemaRoutingTree = resource.NewSimpleSchema("notifications.alerting.grafana.app", "v0alpha1", &RoutingTree{}, &RoutingTreeList{}, resource.WithKind("RoutingTree"), + schemaRoutingTree = resource.NewSimpleSchema("notifications.alerting.grafana.app", "v0alpha1", NewRoutingTree(), &RoutingTreeList{}, resource.WithKind("RoutingTree"), resource.WithPlural("routingtrees"), resource.WithScope(resource.NamespacedScope)) kindRoutingTree = resource.Kind{ Schema: schemaRoutingTree, diff --git a/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1/templategroup_object_gen.go b/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1/templategroup_object_gen.go index 0866bcb258c..d755a887a3f 100644 --- a/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1/templategroup_object_gen.go +++ b/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1/templategroup_object_gen.go @@ -23,6 +23,12 @@ type TemplateGroup struct { Spec TemplateGroupSpec `json:"spec" yaml:"spec"` } +func NewTemplateGroup() *TemplateGroup { + return &TemplateGroup{ + Spec: *NewTemplateGroupSpec(), + } +} + func (o *TemplateGroup) GetSpec() any { return o.Spec } diff --git a/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1/templategroup_schema_gen.go b/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1/templategroup_schema_gen.go index 0be8cb1c6de..ba92e2c4c4c 100644 --- a/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1/templategroup_schema_gen.go +++ b/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1/templategroup_schema_gen.go @@ -10,7 +10,7 @@ import ( // schema is unexported to prevent accidental overwrites var ( - schemaTemplateGroup = resource.NewSimpleSchema("notifications.alerting.grafana.app", "v0alpha1", &TemplateGroup{}, &TemplateGroupList{}, resource.WithKind("TemplateGroup"), + schemaTemplateGroup = resource.NewSimpleSchema("notifications.alerting.grafana.app", "v0alpha1", NewTemplateGroup(), &TemplateGroupList{}, resource.WithKind("TemplateGroup"), resource.WithPlural("templategroups"), resource.WithScope(resource.NamespacedScope)) kindTemplateGroup = resource.Kind{ Schema: schemaTemplateGroup, diff --git a/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1/timeinterval_object_gen.go b/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1/timeinterval_object_gen.go index 0ef813ee40c..e87b49dc958 100644 --- a/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1/timeinterval_object_gen.go +++ b/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1/timeinterval_object_gen.go @@ -23,6 +23,12 @@ type TimeInterval struct { Spec TimeIntervalSpec `json:"spec" yaml:"spec"` } +func NewTimeInterval() *TimeInterval { + return &TimeInterval{ + Spec: *NewTimeIntervalSpec(), + } +} + func (o *TimeInterval) GetSpec() any { return o.Spec } diff --git a/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1/timeinterval_schema_gen.go b/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1/timeinterval_schema_gen.go index 715bfbc0fe7..d342cb90637 100644 --- a/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1/timeinterval_schema_gen.go +++ b/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1/timeinterval_schema_gen.go @@ -10,7 +10,7 @@ import ( // schema is unexported to prevent accidental overwrites var ( - schemaTimeInterval = resource.NewSimpleSchema("notifications.alerting.grafana.app", "v0alpha1", &TimeInterval{}, &TimeIntervalList{}, resource.WithKind("TimeInterval"), + schemaTimeInterval = resource.NewSimpleSchema("notifications.alerting.grafana.app", "v0alpha1", NewTimeInterval(), &TimeIntervalList{}, resource.WithKind("TimeInterval"), resource.WithPlural("timeintervals"), resource.WithScope(resource.NamespacedScope)) kindTimeInterval = resource.Kind{ Schema: schemaTimeInterval, diff --git a/apps/alerting/rules/go.mod b/apps/alerting/rules/go.mod index 7286ccf5376..63da00536a6 100644 --- a/apps/alerting/rules/go.mod +++ b/apps/alerting/rules/go.mod @@ -3,7 +3,7 @@ module github.com/grafana/grafana/apps/alerting/rules go 1.25.5 require ( - github.com/grafana/grafana-app-sdk v0.48.4 + github.com/grafana/grafana-app-sdk v0.48.5 github.com/grafana/grafana-app-sdk/logging v0.48.3 github.com/prometheus/common v0.67.3 k8s.io/apimachinery v0.34.2 diff --git a/apps/alerting/rules/go.sum b/apps/alerting/rules/go.sum index c447da4e325..3df8e8b5ebe 100644 --- a/apps/alerting/rules/go.sum +++ b/apps/alerting/rules/go.sum @@ -48,8 +48,8 @@ github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grafana/grafana-app-sdk v0.48.4 h1:t9r+Y6E7D832ZxQ2c1n0lp6cvsYKhhrAodVYzE1y0s0= -github.com/grafana/grafana-app-sdk v0.48.4/go.mod h1:HJsMOSBmt/D/Ihs1SvagOwmXKi0coBMVHlfvdd+qe9Y= +github.com/grafana/grafana-app-sdk v0.48.5 h1:MS8l9fTZz+VbTfgApn09jw27GxhQ6fNOWGhC4ydvZmM= +github.com/grafana/grafana-app-sdk v0.48.5/go.mod h1:HJsMOSBmt/D/Ihs1SvagOwmXKi0coBMVHlfvdd+qe9Y= github.com/grafana/grafana-app-sdk/logging v0.48.3 h1:72NUpGNiJXCNQz/on++YSsl38xuVYYBKv5kKQaOClX4= github.com/grafana/grafana-app-sdk/logging v0.48.3/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg= diff --git a/apps/annotation/go.mod b/apps/annotation/go.mod index a042d852413..946c42fe38f 100644 --- a/apps/annotation/go.mod +++ b/apps/annotation/go.mod @@ -3,7 +3,7 @@ module github.com/grafana/grafana/apps/annotation go 1.25.5 require ( - github.com/grafana/grafana-app-sdk v0.48.4 + github.com/grafana/grafana-app-sdk v0.48.5 github.com/grafana/grafana-app-sdk/logging v0.48.3 k8s.io/apimachinery v0.34.2 k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 diff --git a/apps/annotation/go.sum b/apps/annotation/go.sum index c447da4e325..3df8e8b5ebe 100644 --- a/apps/annotation/go.sum +++ b/apps/annotation/go.sum @@ -48,8 +48,8 @@ github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grafana/grafana-app-sdk v0.48.4 h1:t9r+Y6E7D832ZxQ2c1n0lp6cvsYKhhrAodVYzE1y0s0= -github.com/grafana/grafana-app-sdk v0.48.4/go.mod h1:HJsMOSBmt/D/Ihs1SvagOwmXKi0coBMVHlfvdd+qe9Y= +github.com/grafana/grafana-app-sdk v0.48.5 h1:MS8l9fTZz+VbTfgApn09jw27GxhQ6fNOWGhC4ydvZmM= +github.com/grafana/grafana-app-sdk v0.48.5/go.mod h1:HJsMOSBmt/D/Ihs1SvagOwmXKi0coBMVHlfvdd+qe9Y= github.com/grafana/grafana-app-sdk/logging v0.48.3 h1:72NUpGNiJXCNQz/on++YSsl38xuVYYBKv5kKQaOClX4= github.com/grafana/grafana-app-sdk/logging v0.48.3/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg= diff --git a/apps/collections/go.mod b/apps/collections/go.mod index 81872ad4505..00575d7e6d4 100644 --- a/apps/collections/go.mod +++ b/apps/collections/go.mod @@ -3,7 +3,7 @@ module github.com/grafana/grafana/apps/collections go 1.25.5 require ( - github.com/grafana/grafana-app-sdk v0.48.4 + github.com/grafana/grafana-app-sdk v0.48.5 github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250804150913-990f1c69ecc2 github.com/stretchr/testify v1.11.1 k8s.io/apimachinery v0.34.2 diff --git a/apps/collections/go.sum b/apps/collections/go.sum index 22bf6a8cbcd..75a19848d73 100644 --- a/apps/collections/go.sum +++ b/apps/collections/go.sum @@ -33,8 +33,8 @@ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grafana/grafana-app-sdk v0.48.4 h1:t9r+Y6E7D832ZxQ2c1n0lp6cvsYKhhrAodVYzE1y0s0= -github.com/grafana/grafana-app-sdk v0.48.4/go.mod h1:HJsMOSBmt/D/Ihs1SvagOwmXKi0coBMVHlfvdd+qe9Y= +github.com/grafana/grafana-app-sdk v0.48.5 h1:MS8l9fTZz+VbTfgApn09jw27GxhQ6fNOWGhC4ydvZmM= +github.com/grafana/grafana-app-sdk v0.48.5/go.mod h1:HJsMOSBmt/D/Ihs1SvagOwmXKi0coBMVHlfvdd+qe9Y= github.com/grafana/grafana-app-sdk/logging v0.48.3 h1:72NUpGNiJXCNQz/on++YSsl38xuVYYBKv5kKQaOClX4= github.com/grafana/grafana-app-sdk/logging v0.48.3/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250804150913-990f1c69ecc2 h1:X0cnaFdR+iz+sDSuoZmkryFSjOirchHe2MdKSRwBWgM= diff --git a/apps/correlations/go.mod b/apps/correlations/go.mod index e1c9242aa14..29bc91e70a6 100644 --- a/apps/correlations/go.mod +++ b/apps/correlations/go.mod @@ -3,7 +3,7 @@ module github.com/grafana/grafana/apps/correlations go 1.25.5 require ( - github.com/grafana/grafana-app-sdk v0.48.4 + github.com/grafana/grafana-app-sdk v0.48.5 github.com/grafana/grafana-app-sdk/logging v0.48.3 k8s.io/apimachinery v0.34.2 k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 diff --git a/apps/correlations/go.sum b/apps/correlations/go.sum index c447da4e325..3df8e8b5ebe 100644 --- a/apps/correlations/go.sum +++ b/apps/correlations/go.sum @@ -48,8 +48,8 @@ github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grafana/grafana-app-sdk v0.48.4 h1:t9r+Y6E7D832ZxQ2c1n0lp6cvsYKhhrAodVYzE1y0s0= -github.com/grafana/grafana-app-sdk v0.48.4/go.mod h1:HJsMOSBmt/D/Ihs1SvagOwmXKi0coBMVHlfvdd+qe9Y= +github.com/grafana/grafana-app-sdk v0.48.5 h1:MS8l9fTZz+VbTfgApn09jw27GxhQ6fNOWGhC4ydvZmM= +github.com/grafana/grafana-app-sdk v0.48.5/go.mod h1:HJsMOSBmt/D/Ihs1SvagOwmXKi0coBMVHlfvdd+qe9Y= github.com/grafana/grafana-app-sdk/logging v0.48.3 h1:72NUpGNiJXCNQz/on++YSsl38xuVYYBKv5kKQaOClX4= github.com/grafana/grafana-app-sdk/logging v0.48.3/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg= diff --git a/apps/dashboard/go.mod b/apps/dashboard/go.mod index 4baf37a3657..0a19120fe80 100644 --- a/apps/dashboard/go.mod +++ b/apps/dashboard/go.mod @@ -5,7 +5,7 @@ go 1.25.5 require ( cuelang.org/go v0.11.1 github.com/grafana/authlib/types v0.0.0-20251119142549-be091cf2f4d4 - github.com/grafana/grafana-app-sdk v0.48.4 + github.com/grafana/grafana-app-sdk v0.48.5 github.com/grafana/grafana-app-sdk/logging v0.48.3 github.com/grafana/grafana-plugin-sdk-go v0.284.0 github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250514132646-acbc7b54ed9e diff --git a/apps/dashboard/go.sum b/apps/dashboard/go.sum index 9c8cde10af6..0faeaaf78ba 100644 --- a/apps/dashboard/go.sum +++ b/apps/dashboard/go.sum @@ -85,8 +85,8 @@ github.com/grafana/authlib/types v0.0.0-20251119142549-be091cf2f4d4 h1:Muoy+FMGr github.com/grafana/authlib/types v0.0.0-20251119142549-be091cf2f4d4/go.mod h1:qeWYbnWzaYGl88JlL9+DsP1GT2Cudm58rLtx13fKZdw= github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4 h1:jSojuc7njleS3UOz223WDlXOinmuLAIPI0z2vtq8EgI= github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4/go.mod h1:VahT+GtfQIM+o8ht2StR6J9g+Ef+C2Vokh5uuSmOD/4= -github.com/grafana/grafana-app-sdk v0.48.4 h1:t9r+Y6E7D832ZxQ2c1n0lp6cvsYKhhrAodVYzE1y0s0= -github.com/grafana/grafana-app-sdk v0.48.4/go.mod h1:HJsMOSBmt/D/Ihs1SvagOwmXKi0coBMVHlfvdd+qe9Y= +github.com/grafana/grafana-app-sdk v0.48.5 h1:MS8l9fTZz+VbTfgApn09jw27GxhQ6fNOWGhC4ydvZmM= +github.com/grafana/grafana-app-sdk v0.48.5/go.mod h1:HJsMOSBmt/D/Ihs1SvagOwmXKi0coBMVHlfvdd+qe9Y= github.com/grafana/grafana-app-sdk/logging v0.48.3 h1:72NUpGNiJXCNQz/on++YSsl38xuVYYBKv5kKQaOClX4= github.com/grafana/grafana-app-sdk/logging v0.48.3/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA= github.com/grafana/grafana-plugin-sdk-go v0.284.0 h1:1bK7eWsnPBLUWDcWJWe218Ik5ad0a5JpEL4mH9ry7Ws= diff --git a/apps/dashboard/pkg/apis/dashboard/v0alpha1/dashboard_object_gen.go b/apps/dashboard/pkg/apis/dashboard/v0alpha1/dashboard_object_gen.go index a267e0c8df8..ac8a0a61685 100644 --- a/apps/dashboard/pkg/apis/dashboard/v0alpha1/dashboard_object_gen.go +++ b/apps/dashboard/pkg/apis/dashboard/v0alpha1/dashboard_object_gen.go @@ -25,6 +25,13 @@ type Dashboard struct { Status DashboardStatus `json:"status" yaml:"status"` } +func NewDashboard() *Dashboard { + return &Dashboard{ + Spec: *NewDashboardSpec(), + Status: *NewDashboardStatus(), + } +} + func (o *Dashboard) GetSpec() any { return o.Spec } diff --git a/apps/dashboard/pkg/apis/dashboard/v0alpha1/dashboard_schema_gen.go b/apps/dashboard/pkg/apis/dashboard/v0alpha1/dashboard_schema_gen.go index 5b2da44ec05..1ec0884202f 100644 --- a/apps/dashboard/pkg/apis/dashboard/v0alpha1/dashboard_schema_gen.go +++ b/apps/dashboard/pkg/apis/dashboard/v0alpha1/dashboard_schema_gen.go @@ -10,7 +10,7 @@ import ( // schema is unexported to prevent accidental overwrites var ( - schemaDashboard = resource.NewSimpleSchema("dashboard.grafana.app", "v0alpha1", &Dashboard{}, &DashboardList{}, resource.WithKind("Dashboard"), + schemaDashboard = resource.NewSimpleSchema("dashboard.grafana.app", "v0alpha1", NewDashboard(), &DashboardList{}, resource.WithKind("Dashboard"), resource.WithPlural("dashboards"), resource.WithScope(resource.NamespacedScope)) kindDashboard = resource.Kind{ Schema: schemaDashboard, diff --git a/apps/dashboard/pkg/apis/dashboard/v0alpha1/snapshot_object_gen.go b/apps/dashboard/pkg/apis/dashboard/v0alpha1/snapshot_object_gen.go index d917cebc0bf..64924eac264 100644 --- a/apps/dashboard/pkg/apis/dashboard/v0alpha1/snapshot_object_gen.go +++ b/apps/dashboard/pkg/apis/dashboard/v0alpha1/snapshot_object_gen.go @@ -23,6 +23,12 @@ type Snapshot struct { Spec SnapshotSpec `json:"spec" yaml:"spec"` } +func NewSnapshot() *Snapshot { + return &Snapshot{ + Spec: *NewSnapshotSpec(), + } +} + func (o *Snapshot) GetSpec() any { return o.Spec } diff --git a/apps/dashboard/pkg/apis/dashboard/v0alpha1/snapshot_schema_gen.go b/apps/dashboard/pkg/apis/dashboard/v0alpha1/snapshot_schema_gen.go index b6086c5fd1f..596c5bb2890 100644 --- a/apps/dashboard/pkg/apis/dashboard/v0alpha1/snapshot_schema_gen.go +++ b/apps/dashboard/pkg/apis/dashboard/v0alpha1/snapshot_schema_gen.go @@ -10,7 +10,7 @@ import ( // schema is unexported to prevent accidental overwrites var ( - schemaSnapshot = resource.NewSimpleSchema("dashboard.grafana.app", "v0alpha1", &Snapshot{}, &SnapshotList{}, resource.WithKind("Snapshot"), + schemaSnapshot = resource.NewSimpleSchema("dashboard.grafana.app", "v0alpha1", NewSnapshot(), &SnapshotList{}, resource.WithKind("Snapshot"), resource.WithPlural("snapshots"), resource.WithScope(resource.NamespacedScope)) kindSnapshot = resource.Kind{ Schema: schemaSnapshot, diff --git a/apps/dashboard/pkg/apis/dashboard/v1beta1/dashboard_object_gen.go b/apps/dashboard/pkg/apis/dashboard/v1beta1/dashboard_object_gen.go index be021b5f003..35bb8900ab0 100644 --- a/apps/dashboard/pkg/apis/dashboard/v1beta1/dashboard_object_gen.go +++ b/apps/dashboard/pkg/apis/dashboard/v1beta1/dashboard_object_gen.go @@ -25,6 +25,13 @@ type Dashboard struct { Status DashboardStatus `json:"status" yaml:"status"` } +func NewDashboard() *Dashboard { + return &Dashboard{ + Spec: *NewDashboardSpec(), + Status: *NewDashboardStatus(), + } +} + func (o *Dashboard) GetSpec() any { return o.Spec } diff --git a/apps/dashboard/pkg/apis/dashboard/v1beta1/dashboard_schema_gen.go b/apps/dashboard/pkg/apis/dashboard/v1beta1/dashboard_schema_gen.go index e944e0afc33..006312837e0 100644 --- a/apps/dashboard/pkg/apis/dashboard/v1beta1/dashboard_schema_gen.go +++ b/apps/dashboard/pkg/apis/dashboard/v1beta1/dashboard_schema_gen.go @@ -10,7 +10,7 @@ import ( // schema is unexported to prevent accidental overwrites var ( - schemaDashboard = resource.NewSimpleSchema("dashboard.grafana.app", "v1beta1", &Dashboard{}, &DashboardList{}, resource.WithKind("Dashboard"), + schemaDashboard = resource.NewSimpleSchema("dashboard.grafana.app", "v1beta1", NewDashboard(), &DashboardList{}, resource.WithKind("Dashboard"), resource.WithPlural("dashboards"), resource.WithScope(resource.NamespacedScope)) kindDashboard = resource.Kind{ Schema: schemaDashboard, diff --git a/apps/dashboard/pkg/apis/dashboard/v2alpha1/dashboard_object_gen.go b/apps/dashboard/pkg/apis/dashboard/v2alpha1/dashboard_object_gen.go index 99cf7df0da9..6a06594656e 100644 --- a/apps/dashboard/pkg/apis/dashboard/v2alpha1/dashboard_object_gen.go +++ b/apps/dashboard/pkg/apis/dashboard/v2alpha1/dashboard_object_gen.go @@ -25,6 +25,13 @@ type Dashboard struct { Status DashboardStatus `json:"status" yaml:"status"` } +func NewDashboard() *Dashboard { + return &Dashboard{ + Spec: *NewDashboardSpec(), + Status: *NewDashboardStatus(), + } +} + func (o *Dashboard) GetSpec() any { return o.Spec } diff --git a/apps/dashboard/pkg/apis/dashboard/v2alpha1/dashboard_schema_gen.go b/apps/dashboard/pkg/apis/dashboard/v2alpha1/dashboard_schema_gen.go index 136698cf70f..1a5f27d0fb5 100644 --- a/apps/dashboard/pkg/apis/dashboard/v2alpha1/dashboard_schema_gen.go +++ b/apps/dashboard/pkg/apis/dashboard/v2alpha1/dashboard_schema_gen.go @@ -10,7 +10,7 @@ import ( // schema is unexported to prevent accidental overwrites var ( - schemaDashboard = resource.NewSimpleSchema("dashboard.grafana.app", "v2alpha1", &Dashboard{}, &DashboardList{}, resource.WithKind("Dashboard"), + schemaDashboard = resource.NewSimpleSchema("dashboard.grafana.app", "v2alpha1", NewDashboard(), &DashboardList{}, resource.WithKind("Dashboard"), resource.WithPlural("dashboards"), resource.WithScope(resource.NamespacedScope)) kindDashboard = resource.Kind{ Schema: schemaDashboard, diff --git a/apps/dashboard/pkg/apis/dashboard/v2beta1/dashboard_object_gen.go b/apps/dashboard/pkg/apis/dashboard/v2beta1/dashboard_object_gen.go index 5076b7d9b0c..bb64a321a1d 100644 --- a/apps/dashboard/pkg/apis/dashboard/v2beta1/dashboard_object_gen.go +++ b/apps/dashboard/pkg/apis/dashboard/v2beta1/dashboard_object_gen.go @@ -25,6 +25,13 @@ type Dashboard struct { Status DashboardStatus `json:"status" yaml:"status"` } +func NewDashboard() *Dashboard { + return &Dashboard{ + Spec: *NewDashboardSpec(), + Status: *NewDashboardStatus(), + } +} + func (o *Dashboard) GetSpec() any { return o.Spec } diff --git a/apps/dashboard/pkg/apis/dashboard/v2beta1/dashboard_schema_gen.go b/apps/dashboard/pkg/apis/dashboard/v2beta1/dashboard_schema_gen.go index 30c2237ca31..35d87fd07f7 100644 --- a/apps/dashboard/pkg/apis/dashboard/v2beta1/dashboard_schema_gen.go +++ b/apps/dashboard/pkg/apis/dashboard/v2beta1/dashboard_schema_gen.go @@ -10,7 +10,7 @@ import ( // schema is unexported to prevent accidental overwrites var ( - schemaDashboard = resource.NewSimpleSchema("dashboard.grafana.app", "v2beta1", &Dashboard{}, &DashboardList{}, resource.WithKind("Dashboard"), + schemaDashboard = resource.NewSimpleSchema("dashboard.grafana.app", "v2beta1", NewDashboard(), &DashboardList{}, resource.WithKind("Dashboard"), resource.WithPlural("dashboards"), resource.WithScope(resource.NamespacedScope)) kindDashboard = resource.Kind{ Schema: schemaDashboard, diff --git a/apps/example/go.mod b/apps/example/go.mod index deb8763474c..d63d23be302 100644 --- a/apps/example/go.mod +++ b/apps/example/go.mod @@ -3,7 +3,7 @@ module github.com/grafana/grafana/apps/example go 1.25.5 require ( - github.com/grafana/grafana-app-sdk v0.48.4 + github.com/grafana/grafana-app-sdk v0.48.5 github.com/grafana/grafana-app-sdk/logging v0.48.3 github.com/grafana/grafana/pkg/apimachinery v0.0.0-20251017153501-8512b219c5fe k8s.io/apimachinery v0.34.2 diff --git a/apps/example/go.sum b/apps/example/go.sum index 4a43a44d46c..70c83a9fe37 100644 --- a/apps/example/go.sum +++ b/apps/example/go.sum @@ -56,8 +56,8 @@ github.com/grafana/authlib/types v0.0.0-20251119142549-be091cf2f4d4 h1:Muoy+FMGr github.com/grafana/authlib/types v0.0.0-20251119142549-be091cf2f4d4/go.mod h1:qeWYbnWzaYGl88JlL9+DsP1GT2Cudm58rLtx13fKZdw= github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4 h1:jSojuc7njleS3UOz223WDlXOinmuLAIPI0z2vtq8EgI= github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4/go.mod h1:VahT+GtfQIM+o8ht2StR6J9g+Ef+C2Vokh5uuSmOD/4= -github.com/grafana/grafana-app-sdk v0.48.4 h1:t9r+Y6E7D832ZxQ2c1n0lp6cvsYKhhrAodVYzE1y0s0= -github.com/grafana/grafana-app-sdk v0.48.4/go.mod h1:HJsMOSBmt/D/Ihs1SvagOwmXKi0coBMVHlfvdd+qe9Y= +github.com/grafana/grafana-app-sdk v0.48.5 h1:MS8l9fTZz+VbTfgApn09jw27GxhQ6fNOWGhC4ydvZmM= +github.com/grafana/grafana-app-sdk v0.48.5/go.mod h1:HJsMOSBmt/D/Ihs1SvagOwmXKi0coBMVHlfvdd+qe9Y= github.com/grafana/grafana-app-sdk/logging v0.48.3 h1:72NUpGNiJXCNQz/on++YSsl38xuVYYBKv5kKQaOClX4= github.com/grafana/grafana-app-sdk/logging v0.48.3/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20251017153501-8512b219c5fe h1:pPoFj2bQKDBg5EyEdOU+Jn+0hQN+M775Qihk73RbdSs= diff --git a/apps/folder/go.mod b/apps/folder/go.mod index c40b11f7add..476c6949c73 100644 --- a/apps/folder/go.mod +++ b/apps/folder/go.mod @@ -3,42 +3,67 @@ module github.com/grafana/grafana/apps/folder go 1.25.5 require ( - github.com/grafana/grafana-app-sdk v0.48.4 + github.com/grafana/grafana-app-sdk v0.48.5 github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250514132646-acbc7b54ed9e k8s.io/apimachinery v0.34.2 k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 ) require ( + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/emicklei/go-restful/v3 v3.13.0 // indirect github.com/fxamacker/cbor/v2 v2.9.0 // indirect + github.com/getkin/kin-openapi v0.133.0 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-openapi/jsonpointer v0.22.1 // indirect github.com/go-openapi/jsonreference v0.21.2 // indirect github.com/go-openapi/swag v0.23.1 // indirect github.com/go-openapi/swag/jsonname v0.25.1 // indirect + github.com/go-test/deep v1.1.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/google/gnostic-models v0.7.0 // indirect + github.com/grafana/grafana-app-sdk/logging v0.48.3 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/mailru/easyjson v0.9.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect + github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect + github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect + github.com/perimeterx/marshmallow v1.1.5 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/prometheus/client_golang v1.23.2 // indirect + github.com/prometheus/client_model v0.6.2 // indirect + github.com/prometheus/common v0.67.3 // indirect + github.com/prometheus/procfs v0.16.1 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/stretchr/testify v1.11.1 // indirect + github.com/woodsbury/decimal128 v1.3.0 // indirect github.com/x448/float16 v0.8.4 // indirect + go.opentelemetry.io/otel v1.38.0 // indirect + go.opentelemetry.io/otel/trace v1.38.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/net v0.47.0 // indirect + golang.org/x/oauth2 v0.33.0 // indirect + golang.org/x/sys v0.38.0 // indirect + golang.org/x/term v0.37.0 // indirect golang.org/x/text v0.31.0 // indirect + golang.org/x/time v0.14.0 // indirect google.golang.org/protobuf v1.36.10 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/client-go v0.34.2 // indirect k8s.io/klog/v2 v2.130.1 // indirect k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect sigs.k8s.io/randfill v1.0.0 // indirect sigs.k8s.io/structured-merge-diff/v6 v6.3.1 // indirect + sigs.k8s.io/yaml v1.6.0 // indirect ) diff --git a/apps/folder/go.sum b/apps/folder/go.sum index 286d9142c77..d8454185267 100644 --- a/apps/folder/go.sum +++ b/apps/folder/go.sum @@ -1,3 +1,7 @@ +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -6,6 +10,8 @@ github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bF github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= +github.com/getkin/kin-openapi v0.133.0 h1:pJdmNohVIJ97r4AUFtEXRXwESr8b0bD721u/Tz6k8PQ= +github.com/getkin/kin-openapi v0.133.0/go.mod h1:boAciF6cXk5FhPqe/NQeBTeenbjqU4LhWBf09ILVvWE= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-openapi/jsonpointer v0.22.1 h1:sHYI1He3b9NqJ4wXLoJDKmUmHkWy/L7rtEo92JUxBNk= @@ -16,6 +22,8 @@ github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZ github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0= github.com/go-openapi/swag/jsonname v0.25.1 h1:Sgx+qbwa4ej6AomWC6pEfXrA6uP2RkaNjA9BR8a1RJU= github.com/go-openapi/swag/jsonname v0.25.1/go.mod h1:71Tekow6UOLBD3wS7XhdT98g5J5GR13NOTQ9/6Q11Zo= +github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U= +github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= @@ -23,20 +31,33 @@ github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7O github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/grafana/grafana-app-sdk v0.48.4 h1:t9r+Y6E7D832ZxQ2c1n0lp6cvsYKhhrAodVYzE1y0s0= -github.com/grafana/grafana-app-sdk v0.48.4/go.mod h1:HJsMOSBmt/D/Ihs1SvagOwmXKi0coBMVHlfvdd+qe9Y= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grafana/grafana-app-sdk v0.48.5 h1:MS8l9fTZz+VbTfgApn09jw27GxhQ6fNOWGhC4ydvZmM= +github.com/grafana/grafana-app-sdk v0.48.5/go.mod h1:HJsMOSBmt/D/Ihs1SvagOwmXKi0coBMVHlfvdd+qe9Y= +github.com/grafana/grafana-app-sdk/logging v0.48.3 h1:72NUpGNiJXCNQz/on++YSsl38xuVYYBKv5kKQaOClX4= +github.com/grafana/grafana-app-sdk/logging v0.48.3/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250514132646-acbc7b54ed9e h1:BTKk7LHuG1kmAkucwTA7DuMbKpKvJTKrGdBmUNO4dfQ= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250514132646-acbc7b54ed9e/go.mod h1:IA4SOwun8QyST9c5UNs/fN37XL6boXXDvRYFcFwbipg= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -45,9 +66,29 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY= +github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw= +github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletIKwUIt4x3t8n2SxavmoclizMb8c= +github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o= +github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= +github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= +github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.67.3 h1:shd26MlnwTw5jksTDhC7rTQIteBxy+ZZDr3t7F2xN2Q= +github.com/prometheus/common v0.67.3/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= +github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= +github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= @@ -58,10 +99,20 @@ github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= +github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/woodsbury/decimal128 v1.3.0 h1:8pffMNWIlC0O5vbyHWFZAt5yWvWcrHA+3ovIIjVWss0= +github.com/woodsbury/decimal128 v1.3.0/go.mod h1:C5UTmyTjW3JftjUFzOVhC20BEQa2a4ZKOB5I6Zjb+ds= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= +go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= +go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= +go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= @@ -77,16 +128,24 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= +golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo= +golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= +golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= +golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= +golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= @@ -100,12 +159,18 @@ google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= +gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +k8s.io/api v0.34.2 h1:fsSUNZhV+bnL6Aqrp6O7lMTy6o5x2C4XLjnh//8SLYY= +k8s.io/api v0.34.2/go.mod h1:MMBPaWlED2a8w4RSeanD76f7opUoypY8TFYkSM+3XHw= k8s.io/apimachinery v0.34.2 h1:zQ12Uk3eMHPxrsbUJgNF8bTauTVR2WgqJsTmwTE/NW4= k8s.io/apimachinery v0.34.2/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/client-go v0.34.2 h1:Co6XiknN+uUZqiddlfAjT68184/37PS4QAzYvQvDR8M= +k8s.io/client-go v0.34.2/go.mod h1:2VYDl1XXJsdcAxw7BenFslRQX28Dxz91U9MWKjX97fE= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= diff --git a/apps/folder/pkg/apis/folder/v1beta1/folder_object_gen.go b/apps/folder/pkg/apis/folder/v1beta1/folder_object_gen.go index 226af606f6f..6fabb8f8958 100644 --- a/apps/folder/pkg/apis/folder/v1beta1/folder_object_gen.go +++ b/apps/folder/pkg/apis/folder/v1beta1/folder_object_gen.go @@ -23,6 +23,12 @@ type Folder struct { Spec FolderSpec `json:"spec" yaml:"spec"` } +func NewFolder() *Folder { + return &Folder{ + Spec: *NewFolderSpec(), + } +} + func (o *Folder) GetSpec() any { return o.Spec } diff --git a/apps/folder/pkg/apis/folder/v1beta1/folder_schema_gen.go b/apps/folder/pkg/apis/folder/v1beta1/folder_schema_gen.go index f0d4fffe6b0..e626e4773ee 100644 --- a/apps/folder/pkg/apis/folder/v1beta1/folder_schema_gen.go +++ b/apps/folder/pkg/apis/folder/v1beta1/folder_schema_gen.go @@ -10,7 +10,7 @@ import ( // schema is unexported to prevent accidental overwrites var ( - schemaFolder = resource.NewSimpleSchema("folder.grafana.app", "v1beta1", &Folder{}, &FolderList{}, resource.WithKind("Folder"), + schemaFolder = resource.NewSimpleSchema("folder.grafana.app", "v1beta1", NewFolder(), &FolderList{}, resource.WithKind("Folder"), resource.WithPlural("folders"), resource.WithScope(resource.NamespacedScope)) kindFolder = resource.Kind{ Schema: schemaFolder, diff --git a/apps/folder/pkg/apis/manifestdata/folder_manifest.go b/apps/folder/pkg/apis/manifestdata/folder_manifest.go new file mode 100644 index 00000000000..7c053e52c38 --- /dev/null +++ b/apps/folder/pkg/apis/manifestdata/folder_manifest.go @@ -0,0 +1,116 @@ +// +// This file is generated by grafana-app-sdk +// DO NOT EDIT +// + +package manifestdata + +import ( + "fmt" + "strings" + + "github.com/grafana/grafana-app-sdk/app" + "github.com/grafana/grafana-app-sdk/resource" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/kube-openapi/pkg/spec3" + "k8s.io/kube-openapi/pkg/validation/spec" + + v1beta1 "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1" +) + +var appManifestData = app.ManifestData{ + AppName: "folder", + Group: "folder.grafana.app", + PreferredVersion: "v1beta1", + Versions: []app.ManifestVersion{ + { + Name: "v1beta1", + Served: true, + Kinds: []app.ManifestVersionKind{ + { + Kind: "Folder", + Plural: "Folders", + Scope: "Namespaced", + Conversion: false, + }, + }, + Routes: app.ManifestVersionRoutes{ + Namespaced: map[string]spec3.PathProps{}, + Cluster: map[string]spec3.PathProps{}, + Schemas: map[string]spec.Schema{}, + }, + }, + }, +} + +func LocalManifest() app.Manifest { + return app.NewEmbeddedManifest(appManifestData) +} + +func RemoteManifest() app.Manifest { + return app.NewAPIServerManifest("folder") +} + +var kindVersionToGoType = map[string]resource.Kind{ + "Folder/v1beta1": v1beta1.FolderKind(), +} + +// ManifestGoTypeAssociator returns the associated resource.Kind instance for a given Kind and Version, if one exists. +// If there is no association for the provided Kind and Version, exists will return false. +func ManifestGoTypeAssociator(kind, version string) (goType resource.Kind, exists bool) { + goType, exists = kindVersionToGoType[fmt.Sprintf("%s/%s", kind, version)] + return goType, exists +} + +var customRouteToGoResponseType = map[string]any{} + +// ManifestCustomRouteResponsesAssociator returns the associated response go type for a given kind, version, custom route path, and method, if one exists. +// kind may be empty for custom routes which are not kind subroutes. Leading slashes are removed from subroute paths. +// If there is no association for the provided kind, version, custom route path, and method, exists will return false. +// Resource routes (those without a kind) should prefix their route with "/" if the route is namespaced (otherwise the route is assumed to be cluster-scope) +func ManifestCustomRouteResponsesAssociator(kind, version, path, verb string) (goType any, exists bool) { + if len(path) > 0 && path[0] == '/' { + path = path[1:] + } + goType, exists = customRouteToGoResponseType[fmt.Sprintf("%s|%s|%s|%s", version, kind, path, strings.ToUpper(verb))] + return goType, exists +} + +var customRouteToGoParamsType = map[string]runtime.Object{} + +func ManifestCustomRouteQueryAssociator(kind, version, path, verb string) (goType runtime.Object, exists bool) { + if len(path) > 0 && path[0] == '/' { + path = path[1:] + } + goType, exists = customRouteToGoParamsType[fmt.Sprintf("%s|%s|%s|%s", version, kind, path, strings.ToUpper(verb))] + return goType, exists +} + +var customRouteToGoRequestBodyType = map[string]any{} + +func ManifestCustomRouteRequestBodyAssociator(kind, version, path, verb string) (goType any, exists bool) { + if len(path) > 0 && path[0] == '/' { + path = path[1:] + } + goType, exists = customRouteToGoRequestBodyType[fmt.Sprintf("%s|%s|%s|%s", version, kind, path, strings.ToUpper(verb))] + return goType, exists +} + +type GoTypeAssociator struct{} + +func NewGoTypeAssociator() *GoTypeAssociator { + return &GoTypeAssociator{} +} + +func (g *GoTypeAssociator) KindToGoType(kind, version string) (goType resource.Kind, exists bool) { + return ManifestGoTypeAssociator(kind, version) +} +func (g *GoTypeAssociator) CustomRouteReturnGoType(kind, version, path, verb string) (goType any, exists bool) { + return ManifestCustomRouteResponsesAssociator(kind, version, path, verb) +} +func (g *GoTypeAssociator) CustomRouteQueryGoType(kind, version, path, verb string) (goType runtime.Object, exists bool) { + return ManifestCustomRouteQueryAssociator(kind, version, path, verb) +} +func (g *GoTypeAssociator) CustomRouteRequestBodyGoType(kind, version, path, verb string) (goType any, exists bool) { + return ManifestCustomRouteRequestBodyAssociator(kind, version, path, verb) +} diff --git a/apps/iam/go.mod b/apps/iam/go.mod index bfe1fa1c50d..474779e0efe 100644 --- a/apps/iam/go.mod +++ b/apps/iam/go.mod @@ -52,7 +52,7 @@ replace github.com/prometheus/alertmanager => github.com/grafana/prometheus-aler require ( github.com/grafana/grafana v0.0.0-00010101000000-000000000000 - github.com/grafana/grafana-app-sdk v0.48.4 + github.com/grafana/grafana-app-sdk v0.48.5 github.com/grafana/grafana-app-sdk/logging v0.48.3 github.com/grafana/grafana/apps/folder v0.0.0 github.com/grafana/grafana/pkg/apimachinery v0.0.0 diff --git a/apps/iam/go.sum b/apps/iam/go.sum index 4c25bd92d43..7f2fd8f462f 100644 --- a/apps/iam/go.sum +++ b/apps/iam/go.sum @@ -835,8 +835,8 @@ github.com/grafana/gofpdf v0.0.0-20250307124105-3b9c5d35577f h1:5xkjl5Y/j2QefJKO github.com/grafana/gofpdf v0.0.0-20250307124105-3b9c5d35577f/go.mod h1:+O5QxOwwgP10jedZHapzXY+IPKTnzHBtIs5UUb9G+kI= github.com/grafana/gomemcache v0.0.0-20250828162811-a96f6acee2fe h1:q+QaVANzNZxvTovycpQvDTfsNZ2rHh4XIIaccMnrIR4= github.com/grafana/gomemcache v0.0.0-20250828162811-a96f6acee2fe/go.mod h1:j/s0jkda4UXTemDs7Pgw/vMT06alWc42CHisvYac0qw= -github.com/grafana/grafana-app-sdk v0.48.4 h1:t9r+Y6E7D832ZxQ2c1n0lp6cvsYKhhrAodVYzE1y0s0= -github.com/grafana/grafana-app-sdk v0.48.4/go.mod h1:HJsMOSBmt/D/Ihs1SvagOwmXKi0coBMVHlfvdd+qe9Y= +github.com/grafana/grafana-app-sdk v0.48.5 h1:MS8l9fTZz+VbTfgApn09jw27GxhQ6fNOWGhC4ydvZmM= +github.com/grafana/grafana-app-sdk v0.48.5/go.mod h1:HJsMOSBmt/D/Ihs1SvagOwmXKi0coBMVHlfvdd+qe9Y= github.com/grafana/grafana-app-sdk/logging v0.48.3 h1:72NUpGNiJXCNQz/on++YSsl38xuVYYBKv5kKQaOClX4= github.com/grafana/grafana-app-sdk/logging v0.48.3/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA= github.com/grafana/grafana-aws-sdk v1.3.0 h1:/bfJzP93rCel1GbWoRSq0oUo424MZXt8jAp2BK9w8tM= diff --git a/apps/investigations/go.mod b/apps/investigations/go.mod index 0faf2c40efb..3844fec2689 100644 --- a/apps/investigations/go.mod +++ b/apps/investigations/go.mod @@ -3,7 +3,7 @@ module github.com/grafana/grafana/apps/investigations go 1.25.5 require ( - github.com/grafana/grafana-app-sdk v0.48.4 + github.com/grafana/grafana-app-sdk v0.48.5 k8s.io/apimachinery v0.34.2 k8s.io/klog/v2 v2.130.1 k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 diff --git a/apps/investigations/go.sum b/apps/investigations/go.sum index c447da4e325..3df8e8b5ebe 100644 --- a/apps/investigations/go.sum +++ b/apps/investigations/go.sum @@ -48,8 +48,8 @@ github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grafana/grafana-app-sdk v0.48.4 h1:t9r+Y6E7D832ZxQ2c1n0lp6cvsYKhhrAodVYzE1y0s0= -github.com/grafana/grafana-app-sdk v0.48.4/go.mod h1:HJsMOSBmt/D/Ihs1SvagOwmXKi0coBMVHlfvdd+qe9Y= +github.com/grafana/grafana-app-sdk v0.48.5 h1:MS8l9fTZz+VbTfgApn09jw27GxhQ6fNOWGhC4ydvZmM= +github.com/grafana/grafana-app-sdk v0.48.5/go.mod h1:HJsMOSBmt/D/Ihs1SvagOwmXKi0coBMVHlfvdd+qe9Y= github.com/grafana/grafana-app-sdk/logging v0.48.3 h1:72NUpGNiJXCNQz/on++YSsl38xuVYYBKv5kKQaOClX4= github.com/grafana/grafana-app-sdk/logging v0.48.3/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg= diff --git a/apps/logsdrilldown/go.mod b/apps/logsdrilldown/go.mod index b70fb8a42f5..2a35278d19a 100644 --- a/apps/logsdrilldown/go.mod +++ b/apps/logsdrilldown/go.mod @@ -3,7 +3,7 @@ module github.com/grafana/grafana/apps/logsdrilldown go 1.25.5 require ( - github.com/grafana/grafana-app-sdk v0.48.4 + github.com/grafana/grafana-app-sdk v0.48.5 github.com/grafana/grafana-app-sdk/logging v0.48.3 k8s.io/apimachinery v0.34.2 k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 diff --git a/apps/logsdrilldown/go.sum b/apps/logsdrilldown/go.sum index c447da4e325..3df8e8b5ebe 100644 --- a/apps/logsdrilldown/go.sum +++ b/apps/logsdrilldown/go.sum @@ -48,8 +48,8 @@ github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grafana/grafana-app-sdk v0.48.4 h1:t9r+Y6E7D832ZxQ2c1n0lp6cvsYKhhrAodVYzE1y0s0= -github.com/grafana/grafana-app-sdk v0.48.4/go.mod h1:HJsMOSBmt/D/Ihs1SvagOwmXKi0coBMVHlfvdd+qe9Y= +github.com/grafana/grafana-app-sdk v0.48.5 h1:MS8l9fTZz+VbTfgApn09jw27GxhQ6fNOWGhC4ydvZmM= +github.com/grafana/grafana-app-sdk v0.48.5/go.mod h1:HJsMOSBmt/D/Ihs1SvagOwmXKi0coBMVHlfvdd+qe9Y= github.com/grafana/grafana-app-sdk/logging v0.48.3 h1:72NUpGNiJXCNQz/on++YSsl38xuVYYBKv5kKQaOClX4= github.com/grafana/grafana-app-sdk/logging v0.48.3/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg= diff --git a/apps/playlist/go.mod b/apps/playlist/go.mod index 4bdbe8c003c..ae38e3d0cd3 100644 --- a/apps/playlist/go.mod +++ b/apps/playlist/go.mod @@ -3,7 +3,7 @@ module github.com/grafana/grafana/apps/playlist go 1.25.5 require ( - github.com/grafana/grafana-app-sdk v0.48.4 + github.com/grafana/grafana-app-sdk v0.48.5 k8s.io/apimachinery v0.34.2 k8s.io/client-go v0.34.2 k8s.io/klog/v2 v2.130.1 diff --git a/apps/playlist/go.sum b/apps/playlist/go.sum index c447da4e325..3df8e8b5ebe 100644 --- a/apps/playlist/go.sum +++ b/apps/playlist/go.sum @@ -48,8 +48,8 @@ github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grafana/grafana-app-sdk v0.48.4 h1:t9r+Y6E7D832ZxQ2c1n0lp6cvsYKhhrAodVYzE1y0s0= -github.com/grafana/grafana-app-sdk v0.48.4/go.mod h1:HJsMOSBmt/D/Ihs1SvagOwmXKi0coBMVHlfvdd+qe9Y= +github.com/grafana/grafana-app-sdk v0.48.5 h1:MS8l9fTZz+VbTfgApn09jw27GxhQ6fNOWGhC4ydvZmM= +github.com/grafana/grafana-app-sdk v0.48.5/go.mod h1:HJsMOSBmt/D/Ihs1SvagOwmXKi0coBMVHlfvdd+qe9Y= github.com/grafana/grafana-app-sdk/logging v0.48.3 h1:72NUpGNiJXCNQz/on++YSsl38xuVYYBKv5kKQaOClX4= github.com/grafana/grafana-app-sdk/logging v0.48.3/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg= diff --git a/apps/plugins/go.mod b/apps/plugins/go.mod index c55674cc950..669c7c46844 100644 --- a/apps/plugins/go.mod +++ b/apps/plugins/go.mod @@ -10,8 +10,9 @@ replace github.com/grafana/grafana/pkg/apiserver => ../../pkg/apiserver require ( github.com/emicklei/go-restful/v3 v3.13.0 + github.com/grafana/authlib/types v0.0.0-20251119142549-be091cf2f4d4 github.com/grafana/grafana v0.0.0-00010101000000-000000000000 - github.com/grafana/grafana-app-sdk v0.48.4 + github.com/grafana/grafana-app-sdk v0.48.5 github.com/grafana/grafana-app-sdk/logging v0.48.3 github.com/grafana/grafana/pkg/apimachinery v0.0.0 github.com/stretchr/testify v1.11.1 @@ -59,7 +60,7 @@ require ( github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.22.1 // indirect github.com/go-openapi/jsonreference v0.21.2 // indirect - github.com/go-openapi/swag v0.23.0 // indirect + github.com/go-openapi/swag v0.23.1 // indirect github.com/go-openapi/swag/jsonname v0.25.1 // indirect github.com/go-stack/stack v1.8.1 // indirect github.com/go-test/deep v1.1.1 // indirect @@ -75,9 +76,8 @@ require ( github.com/google/gnostic-models v0.7.0 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/grafana/alerting v0.0.0-20251119204204-77fa75125181 // indirect + github.com/grafana/alerting v0.0.0-20251204145817-de8c2bbf9eba // indirect github.com/grafana/authlib v0.0.0-20250930082137-a40e2c2b094f // indirect - github.com/grafana/authlib/types v0.0.0-20251119142549-be091cf2f4d4 // indirect github.com/grafana/dataplane/sdata v0.0.9 // indirect github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4 // indirect github.com/grafana/grafana-aws-sdk v1.3.0 // indirect @@ -142,7 +142,7 @@ require ( github.com/pierrec/lz4/v4 v4.1.22 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/prometheus/alertmanager v0.28.0 // indirect + github.com/prometheus/alertmanager v0.28.2 // indirect github.com/prometheus/client_golang v1.23.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.67.3 // indirect @@ -194,8 +194,8 @@ require ( golang.org/x/tools v0.39.0 // indirect golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba // indirect google.golang.org/grpc v1.77.0 // indirect google.golang.org/protobuf v1.36.10 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect @@ -215,3 +215,6 @@ require ( sigs.k8s.io/structured-merge-diff/v6 v6.3.1 // indirect sigs.k8s.io/yaml v1.6.0 // indirect ) + +// Use our fork of the upstream Alertmanager. +replace github.com/prometheus/alertmanager => github.com/grafana/prometheus-alertmanager v0.25.1-0.20250911094103-5456b6e45604 diff --git a/apps/plugins/go.sum b/apps/plugins/go.sum index d582270e43b..1d7387b28b3 100644 --- a/apps/plugins/go.sum +++ b/apps/plugins/go.sum @@ -110,8 +110,8 @@ github.com/go-openapi/jsonpointer v0.22.1 h1:sHYI1He3b9NqJ4wXLoJDKmUmHkWy/L7rtEo github.com/go-openapi/jsonpointer v0.22.1/go.mod h1:pQT9OsLkfz1yWoMgYFy4x3U5GY5nUlsOn1qSBH5MkCM= github.com/go-openapi/jsonreference v0.21.2 h1:Wxjda4M/BBQllegefXrY/9aq1fxBA8sI5M/lFU6tSWU= github.com/go-openapi/jsonreference v0.21.2/go.mod h1:pp3PEjIsJ9CZDGCNOyXIQxsNuroxm8FAJ/+quA0yKzQ= -github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= -github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU= +github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0= github.com/go-openapi/swag/jsonname v0.25.1 h1:Sgx+qbwa4ej6AomWC6pEfXrA6uP2RkaNjA9BR8a1RJU= github.com/go-openapi/swag/jsonname v0.25.1/go.mod h1:71Tekow6UOLBD3wS7XhdT98g5J5GR13NOTQ9/6Q11Zo= github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo= @@ -174,8 +174,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= -github.com/grafana/alerting v0.0.0-20251119204204-77fa75125181 h1:nbxKRtrbuhvOYmI2RhOYauHRJCtpR+vTNIgg1lFUCws= -github.com/grafana/alerting v0.0.0-20251119204204-77fa75125181/go.mod h1:VtPNIFlEOJPPEc13Ax6ZTbNV3M/sAzLID72YjgzOPVA= +github.com/grafana/alerting v0.0.0-20251204145817-de8c2bbf9eba h1:psKWNETD5nGxmFAlqnWsXoRyUwSa2GHNEMSEDKGKfQ4= +github.com/grafana/alerting v0.0.0-20251204145817-de8c2bbf9eba/go.mod h1:l7v67cgP7x72ajB9UPZlumdrHqNztpKoqQ52cU8T3LU= github.com/grafana/authlib v0.0.0-20250930082137-a40e2c2b094f h1:Cbm6OKkOcJ+7CSZsGsEJzktC/SIa5bxVeYKQLuYK86o= github.com/grafana/authlib v0.0.0-20250930082137-a40e2c2b094f/go.mod h1:axY0cdOg3q0TZHwpHnIz5x16xZ8ZBxJHShsSHHXcHQg= github.com/grafana/authlib/types v0.0.0-20251119142549-be091cf2f4d4 h1:Muoy+FMGrHj3GdFbvsMzUT7eusgii9PKf9L1ZaXDDbY= @@ -184,8 +184,8 @@ github.com/grafana/dataplane/sdata v0.0.9 h1:AGL1LZnCUG4MnQtnWpBPbQ8ZpptaZs14w6k github.com/grafana/dataplane/sdata v0.0.9/go.mod h1:Jvs5ddpGmn6vcxT7tCTWAZ1mgi4sbcdFt9utQx5uMAU= github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4 h1:jSojuc7njleS3UOz223WDlXOinmuLAIPI0z2vtq8EgI= github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4/go.mod h1:VahT+GtfQIM+o8ht2StR6J9g+Ef+C2Vokh5uuSmOD/4= -github.com/grafana/grafana-app-sdk v0.48.4 h1:t9r+Y6E7D832ZxQ2c1n0lp6cvsYKhhrAodVYzE1y0s0= -github.com/grafana/grafana-app-sdk v0.48.4/go.mod h1:HJsMOSBmt/D/Ihs1SvagOwmXKi0coBMVHlfvdd+qe9Y= +github.com/grafana/grafana-app-sdk v0.48.5 h1:MS8l9fTZz+VbTfgApn09jw27GxhQ6fNOWGhC4ydvZmM= +github.com/grafana/grafana-app-sdk v0.48.5/go.mod h1:HJsMOSBmt/D/Ihs1SvagOwmXKi0coBMVHlfvdd+qe9Y= github.com/grafana/grafana-app-sdk/logging v0.48.3 h1:72NUpGNiJXCNQz/on++YSsl38xuVYYBKv5kKQaOClX4= github.com/grafana/grafana-app-sdk/logging v0.48.3/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA= github.com/grafana/grafana-aws-sdk v1.3.0 h1:/bfJzP93rCel1GbWoRSq0oUo424MZXt8jAp2BK9w8tM= @@ -196,6 +196,8 @@ github.com/grafana/grafana-plugin-sdk-go v0.284.0 h1:1bK7eWsnPBLUWDcWJWe218Ik5ad github.com/grafana/grafana-plugin-sdk-go v0.284.0/go.mod h1:lHPniaSxq3SL5MxDIPy04TYB1jnTp/ivkYO+xn5Rz3E= github.com/grafana/otel-profiling-go v0.5.1 h1:stVPKAFZSa7eGiqbYuG25VcqYksR6iWvF3YH66t4qL8= github.com/grafana/otel-profiling-go v0.5.1/go.mod h1:ftN/t5A/4gQI19/8MoWurBEtC6gFw8Dns1sJZ9W4Tls= +github.com/grafana/prometheus-alertmanager v0.25.1-0.20250911094103-5456b6e45604 h1:aXfUhVN/Ewfpbko2CCtL65cIiGgwStOo4lWH2b6gw2U= +github.com/grafana/prometheus-alertmanager v0.25.1-0.20250911094103-5456b6e45604/go.mod h1:O/QP1BCm0HHIzbKvgMzqb5sSyH88rzkFk84F4TfJjBU= github.com/grafana/pyroscope-go/godeltaprof v0.1.9 h1:c1Us8i6eSmkW+Ez05d3co8kasnuOY813tbMN8i/a3Og= github.com/grafana/pyroscope-go/godeltaprof v0.1.9/go.mod h1:2+l7K7twW49Ct4wFluZD3tZ6e0SjanjcUUBPVD/UuGU= github.com/grafana/sqlds/v4 v4.2.7 h1:sFQhsS7DBakNMdxa++yOfJ9BVvkZwFJ0B95o57K0/XA= @@ -368,8 +370,6 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/alertmanager v0.28.0 h1:sLN+6HhZet8hrbmGHLAHWsTXgZSVCvq9Ix3U3wvivqc= -github.com/prometheus/alertmanager v0.28.0/go.mod h1:/okSnb2LlodbMlRoOWQEKtqI/coOo2NKZDm2Hu9QHLQ= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= @@ -611,10 +611,10 @@ gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8 h1:mepRgnBZa07I4TRuomDE4sTIYieg/osKmzIf4USdWS4= -google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8/go.mod h1:fDMmzKV90WSg1NbozdqrE64fkuTv6mlq2zxo9ad+3yo= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 h1:M1rk8KBnUsBDg1oPGHNCxG4vc1f49epmTO7xscSajMk= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba h1:B14OtaXuMaCQsl2deSvNkyPKIzq3BjfxQp8d00QyWx4= +google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:G5IanEx8/PgI9w6CFcYQf7jMtHQhZruvfM1i3qOqk5U= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba h1:UKgtfRM7Yh93Sya0Fo8ZzhDP4qBckrrxEr2oF5UIVb8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/grpc v1.18.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= diff --git a/apps/preferences/go.mod b/apps/preferences/go.mod index 9a5c8088c0f..661002a3b59 100644 --- a/apps/preferences/go.mod +++ b/apps/preferences/go.mod @@ -3,7 +3,7 @@ module github.com/grafana/grafana/apps/preferences go 1.25.5 require ( - github.com/grafana/grafana-app-sdk v0.48.4 + github.com/grafana/grafana-app-sdk v0.48.5 github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250804150913-990f1c69ecc2 k8s.io/apimachinery v0.34.2 k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 diff --git a/apps/preferences/go.sum b/apps/preferences/go.sum index 22bf6a8cbcd..75a19848d73 100644 --- a/apps/preferences/go.sum +++ b/apps/preferences/go.sum @@ -33,8 +33,8 @@ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grafana/grafana-app-sdk v0.48.4 h1:t9r+Y6E7D832ZxQ2c1n0lp6cvsYKhhrAodVYzE1y0s0= -github.com/grafana/grafana-app-sdk v0.48.4/go.mod h1:HJsMOSBmt/D/Ihs1SvagOwmXKi0coBMVHlfvdd+qe9Y= +github.com/grafana/grafana-app-sdk v0.48.5 h1:MS8l9fTZz+VbTfgApn09jw27GxhQ6fNOWGhC4ydvZmM= +github.com/grafana/grafana-app-sdk v0.48.5/go.mod h1:HJsMOSBmt/D/Ihs1SvagOwmXKi0coBMVHlfvdd+qe9Y= github.com/grafana/grafana-app-sdk/logging v0.48.3 h1:72NUpGNiJXCNQz/on++YSsl38xuVYYBKv5kKQaOClX4= github.com/grafana/grafana-app-sdk/logging v0.48.3/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250804150913-990f1c69ecc2 h1:X0cnaFdR+iz+sDSuoZmkryFSjOirchHe2MdKSRwBWgM= diff --git a/apps/provisioning/go.mod b/apps/provisioning/go.mod index e84ab0d35da..3cccd7dd8be 100644 --- a/apps/provisioning/go.mod +++ b/apps/provisioning/go.mod @@ -44,7 +44,7 @@ require ( github.com/gorilla/mux v1.8.1 // indirect github.com/grafana/authlib/types v0.0.0-20251119142549-be091cf2f4d4 // indirect github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4 // indirect - github.com/grafana/grafana-app-sdk v0.48.4 // indirect + github.com/grafana/grafana-app-sdk v0.48.5 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.18.0 // indirect diff --git a/apps/provisioning/go.sum b/apps/provisioning/go.sum index 791bf68e797..0e1ba180fad 100644 --- a/apps/provisioning/go.sum +++ b/apps/provisioning/go.sum @@ -62,8 +62,8 @@ github.com/grafana/authlib/types v0.0.0-20251119142549-be091cf2f4d4 h1:Muoy+FMGr github.com/grafana/authlib/types v0.0.0-20251119142549-be091cf2f4d4/go.mod h1:qeWYbnWzaYGl88JlL9+DsP1GT2Cudm58rLtx13fKZdw= github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4 h1:jSojuc7njleS3UOz223WDlXOinmuLAIPI0z2vtq8EgI= github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4/go.mod h1:VahT+GtfQIM+o8ht2StR6J9g+Ef+C2Vokh5uuSmOD/4= -github.com/grafana/grafana-app-sdk v0.48.4 h1:t9r+Y6E7D832ZxQ2c1n0lp6cvsYKhhrAodVYzE1y0s0= -github.com/grafana/grafana-app-sdk v0.48.4/go.mod h1:HJsMOSBmt/D/Ihs1SvagOwmXKi0coBMVHlfvdd+qe9Y= +github.com/grafana/grafana-app-sdk v0.48.5 h1:MS8l9fTZz+VbTfgApn09jw27GxhQ6fNOWGhC4ydvZmM= +github.com/grafana/grafana-app-sdk v0.48.5/go.mod h1:HJsMOSBmt/D/Ihs1SvagOwmXKi0coBMVHlfvdd+qe9Y= github.com/grafana/grafana-app-sdk/logging v0.48.3 h1:72NUpGNiJXCNQz/on++YSsl38xuVYYBKv5kKQaOClX4= github.com/grafana/grafana-app-sdk/logging v0.48.3/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA= github.com/grafana/grafana/apps/secret v0.0.0-20250902093454-b56b7add012f h1:f+Z5Xpfp1WNYjUe23ginerWsHWUsRgOWrr3WGu3SlWs= diff --git a/apps/sdk.mk b/apps/sdk.mk index 62d89ed8ed3..68cf4c1c9c9 100644 --- a/apps/sdk.mk +++ b/apps/sdk.mk @@ -1,4 +1,4 @@ -APP_SDK_VERSION = v0.48.4 +APP_SDK_VERSION = v0.48.5 APP_SDK_DIR = $(shell go env GOPATH)/bin/app-sdk-$(APP_SDK_VERSION) APP_SDK_BIN = $(APP_SDK_DIR)/grafana-app-sdk diff --git a/apps/secret/go.mod b/apps/secret/go.mod index da71ef8e666..a1ed48be6e7 100644 --- a/apps/secret/go.mod +++ b/apps/secret/go.mod @@ -3,7 +3,7 @@ module github.com/grafana/grafana/apps/secret go 1.25.5 require ( - github.com/grafana/grafana-app-sdk v0.48.4 + github.com/grafana/grafana-app-sdk v0.48.5 github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250710134100-1f3dc0533caf github.com/stretchr/testify v1.11.1 go.yaml.in/yaml/v3 v3.0.4 diff --git a/apps/secret/go.sum b/apps/secret/go.sum index 950843f4a7d..166e281f5af 100644 --- a/apps/secret/go.sum +++ b/apps/secret/go.sum @@ -37,8 +37,8 @@ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grafana/grafana-app-sdk v0.48.4 h1:t9r+Y6E7D832ZxQ2c1n0lp6cvsYKhhrAodVYzE1y0s0= -github.com/grafana/grafana-app-sdk v0.48.4/go.mod h1:HJsMOSBmt/D/Ihs1SvagOwmXKi0coBMVHlfvdd+qe9Y= +github.com/grafana/grafana-app-sdk v0.48.5 h1:MS8l9fTZz+VbTfgApn09jw27GxhQ6fNOWGhC4ydvZmM= +github.com/grafana/grafana-app-sdk v0.48.5/go.mod h1:HJsMOSBmt/D/Ihs1SvagOwmXKi0coBMVHlfvdd+qe9Y= github.com/grafana/grafana-app-sdk/logging v0.48.3 h1:72NUpGNiJXCNQz/on++YSsl38xuVYYBKv5kKQaOClX4= github.com/grafana/grafana-app-sdk/logging v0.48.3/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250710134100-1f3dc0533caf h1:BBGDHffvVNLoYQlXEpbXcxE0vbpq7pm/8OWF5I+UDZg= diff --git a/apps/shorturl/go.mod b/apps/shorturl/go.mod index 128609888ba..4a414de5b4e 100644 --- a/apps/shorturl/go.mod +++ b/apps/shorturl/go.mod @@ -3,7 +3,7 @@ module github.com/grafana/grafana/apps/shorturl go 1.25.5 require ( - github.com/grafana/grafana-app-sdk v0.48.4 + github.com/grafana/grafana-app-sdk v0.48.5 github.com/grafana/grafana-app-sdk/logging v0.48.3 github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250915132226-585b53bc7dba k8s.io/apimachinery v0.34.2 diff --git a/apps/shorturl/go.sum b/apps/shorturl/go.sum index c7f57094324..58ecc31fd82 100644 --- a/apps/shorturl/go.sum +++ b/apps/shorturl/go.sum @@ -56,8 +56,8 @@ github.com/grafana/authlib/types v0.0.0-20251119142549-be091cf2f4d4 h1:Muoy+FMGr github.com/grafana/authlib/types v0.0.0-20251119142549-be091cf2f4d4/go.mod h1:qeWYbnWzaYGl88JlL9+DsP1GT2Cudm58rLtx13fKZdw= github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4 h1:jSojuc7njleS3UOz223WDlXOinmuLAIPI0z2vtq8EgI= github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4/go.mod h1:VahT+GtfQIM+o8ht2StR6J9g+Ef+C2Vokh5uuSmOD/4= -github.com/grafana/grafana-app-sdk v0.48.4 h1:t9r+Y6E7D832ZxQ2c1n0lp6cvsYKhhrAodVYzE1y0s0= -github.com/grafana/grafana-app-sdk v0.48.4/go.mod h1:HJsMOSBmt/D/Ihs1SvagOwmXKi0coBMVHlfvdd+qe9Y= +github.com/grafana/grafana-app-sdk v0.48.5 h1:MS8l9fTZz+VbTfgApn09jw27GxhQ6fNOWGhC4ydvZmM= +github.com/grafana/grafana-app-sdk v0.48.5/go.mod h1:HJsMOSBmt/D/Ihs1SvagOwmXKi0coBMVHlfvdd+qe9Y= github.com/grafana/grafana-app-sdk/logging v0.48.3 h1:72NUpGNiJXCNQz/on++YSsl38xuVYYBKv5kKQaOClX4= github.com/grafana/grafana-app-sdk/logging v0.48.3/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250915132226-585b53bc7dba h1:Qam8QzVRsyZN39zgZ9Vj6e8PEfswvv2McnqCZ/v5NcI= diff --git a/go.mod b/go.mod index 7ef730e391d..e589e5a18a5 100644 --- a/go.mod +++ b/go.mod @@ -97,7 +97,7 @@ require ( github.com/grafana/gofpdf v0.0.0-20250307124105-3b9c5d35577f // @grafana/sharing-squad github.com/grafana/gomemcache v0.0.0-20250318131618-74242eea118d // @grafana/grafana-operator-experience-squad github.com/grafana/grafana-api-golang-client v0.27.0 // @grafana/alerting-backend - github.com/grafana/grafana-app-sdk v0.48.4 // @grafana/grafana-app-platform-squad + github.com/grafana/grafana-app-sdk v0.48.5 // @grafana/grafana-app-platform-squad github.com/grafana/grafana-app-sdk/logging v0.48.3 // @grafana/grafana-app-platform-squad github.com/grafana/grafana-aws-sdk v1.3.0 // @grafana/aws-datasources github.com/grafana/grafana-azure-sdk-go/v2 v2.3.1 // @grafana/partner-datasources diff --git a/go.sum b/go.sum index 7723cfa19a9..91104bfeaf4 100644 --- a/go.sum +++ b/go.sum @@ -1635,8 +1635,8 @@ github.com/grafana/gomemcache v0.0.0-20250318131618-74242eea118d h1:oXRJlb9UjVsl github.com/grafana/gomemcache v0.0.0-20250318131618-74242eea118d/go.mod h1:j/s0jkda4UXTemDs7Pgw/vMT06alWc42CHisvYac0qw= github.com/grafana/grafana-api-golang-client v0.27.0 h1:zIwMXcbCB4n588i3O2N6HfNcQogCNTd/vPkEXTr7zX8= github.com/grafana/grafana-api-golang-client v0.27.0/go.mod h1:uNLZEmgKtTjHBtCQMwNn3qsx2mpMb8zU+7T4Xv3NR9Y= -github.com/grafana/grafana-app-sdk v0.48.4 h1:t9r+Y6E7D832ZxQ2c1n0lp6cvsYKhhrAodVYzE1y0s0= -github.com/grafana/grafana-app-sdk v0.48.4/go.mod h1:HJsMOSBmt/D/Ihs1SvagOwmXKi0coBMVHlfvdd+qe9Y= +github.com/grafana/grafana-app-sdk v0.48.5 h1:MS8l9fTZz+VbTfgApn09jw27GxhQ6fNOWGhC4ydvZmM= +github.com/grafana/grafana-app-sdk v0.48.5/go.mod h1:HJsMOSBmt/D/Ihs1SvagOwmXKi0coBMVHlfvdd+qe9Y= github.com/grafana/grafana-app-sdk/logging v0.48.3 h1:72NUpGNiJXCNQz/on++YSsl38xuVYYBKv5kKQaOClX4= github.com/grafana/grafana-app-sdk/logging v0.48.3/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA= github.com/grafana/grafana-aws-sdk v1.3.0 h1:/bfJzP93rCel1GbWoRSq0oUo424MZXt8jAp2BK9w8tM= diff --git a/go.work.sum b/go.work.sum index 36bd9f3510d..3017d8eb878 100644 --- a/go.work.sum +++ b/go.work.sum @@ -775,7 +775,6 @@ github.com/go-openapi/loads v0.22.0/go.mod h1:yLsaTCS92mnSAZX5WWoxszLj0u+Ojl+Zs5 github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk= github.com/go-openapi/strfmt v0.23.0/go.mod h1:NrtIpfKtWIygRkKVsxh7XQMDQW5HKQl6S5ik2elW+K4= github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= -github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU= github.com/go-openapi/validate v0.24.0/go.mod h1:iyeX1sEufmv3nPbBdX3ieNviWnOZaJ1+zquzJEf2BAQ= github.com/go-pdf/fpdf v0.6.0 h1:MlgtGIfsdMEEQJr2le6b/HNr1ZlQwxyWr77r2aj2U/8= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= @@ -2084,6 +2083,7 @@ google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c/go. google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5/go.mod h1:j3QtIyytwqGr1JUDtYXwtMXWPKsEa5LtzIFN1Wn5WvE= google.golang.org/genproto/googleapis/api v0.0.0-20250908214217-97024824d090/go.mod h1:U8EXRNSd8sUYyDfs/It7KVWodQr+Hf9xtxyxWudSwEw= google.golang.org/genproto/googleapis/api v0.0.0-20250929231259-57b25ae835d4/go.mod h1:NnuHhy+bxcg30o7FnVAZbXsPHUDQ9qKWAQKCD7VxFtk= +google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8/go.mod h1:fDMmzKV90WSg1NbozdqrE64fkuTv6mlq2zxo9ad+3yo= google.golang.org/genproto/googleapis/bytestream v0.0.0-20250603155806-513f23925822 h1:zWFRixYR5QlotL+Uv3YfsPRENIrQFXiGs+iwqel6fOQ= google.golang.org/genproto/googleapis/bytestream v0.0.0-20250603155806-513f23925822/go.mod h1:h6yxum/C2qRb4txaZRLDHK8RyS0H/o2oEDeKY4onY/Y= google.golang.org/genproto/googleapis/rpc v0.0.0-20231002182017-d307bd883b97/go.mod h1:v7nGkzlmW8P3n/bKmWBn2WpBjpOEx8Q6gMueudAmKfY= @@ -2114,6 +2114,7 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20250908214217-97024824d090/go. google.golang.org/genproto/googleapis/rpc v0.0.0-20250929231259-57b25ae835d4/go.mod h1:HSkG/KdJWusxU1F6CNrwNDjBMgisKxGnc5dAZfT0mjQ= google.golang.org/genproto/googleapis/rpc v0.0.0-20251002232023-7c0ddcbb5797/go.mod h1:HSkG/KdJWusxU1F6CNrwNDjBMgisKxGnc5dAZfT0mjQ= google.golang.org/genproto/googleapis/rpc v0.0.0-20251014184007-4626949a642f/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= From 8e11851bb085fac6ce2438d4b7dd5d340100ce48 Mon Sep 17 00:00:00 2001 From: Austin Pond Date: Sat, 6 Dec 2025 03:01:28 -0500 Subject: [PATCH 073/110] =?UTF-8?q?Dashboards:=20Use=20the=20OpenAPI=20gen?= =?UTF-8?q?erated=20by=20app-sdk=20in=20the=20manifest=20to=20=E2=80=A6=20?= =?UTF-8?q?(#114858)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/dashboard/Makefile | 3 +- apps/dashboard/pkg/apis/dashboard_manifest.go | 24 + pkg/registry/apis/dashboard/register.go | 45 + .../dashboard.grafana.app-v2alpha1.json | 2061 ++++---- .../dashboard.grafana.app-v2beta1.json | 4591 +++++++++++++++++ pkg/tests/apis/openapi_test.go | 3 + 6 files changed, 5552 insertions(+), 1175 deletions(-) create mode 100644 pkg/tests/apis/openapi_snapshots/dashboard.grafana.app-v2beta1.json diff --git a/apps/dashboard/Makefile b/apps/dashboard/Makefile index 3d5c7060199..7ff9b946dc9 100644 --- a/apps/dashboard/Makefile +++ b/apps/dashboard/Makefile @@ -11,8 +11,7 @@ do-generate: install-app-sdk update-app-sdk ## Run Grafana App SDK code generati --tsgenpath=../../packages/grafana-schema/src/schema \ --grouping=group \ --defencoding=none \ - --genoperatorstate=false \ - --noschemasinmanifest + --genoperatorstate=false .PHONY: post-generate-cleanup post-generate-cleanup: ## Clean up the generated code diff --git a/apps/dashboard/pkg/apis/dashboard_manifest.go b/apps/dashboard/pkg/apis/dashboard_manifest.go index 974062efcec..c4e35bd8f40 100644 --- a/apps/dashboard/pkg/apis/dashboard_manifest.go +++ b/apps/dashboard/pkg/apis/dashboard_manifest.go @@ -6,6 +6,7 @@ package apis import ( + "encoding/json" "fmt" "strings" @@ -21,6 +22,24 @@ import ( v2beta1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1" ) +var ( + rawSchemaDashboardv0alpha1 = []byte(`{"ConversionStatus":{"additionalProperties":false,"description":"ConversionStatus is the status of the conversion of the dashboard.","properties":{"error":{"description":"The error message from the conversion.\nEmpty if the conversion has not failed.","type":"string"},"failed":{"description":"Whether from another version has failed.\nIf true, means that the dashboard is not valid,\nand the caller should instead fetch the stored version.","type":"boolean"},"source":{"additionalProperties":{},"description":"The original value map[string]any","type":"object"},"storedVersion":{"description":"The version which was stored when the dashboard was created / updated.\nFetching this version should always succeed.","type":"string"}},"required":["failed"],"type":"object"},"Dashboard":{"properties":{"spec":{"$ref":"#/components/schemas/spec"},"status":{"$ref":"#/components/schemas/status"}},"required":["spec"]},"spec":{"additionalProperties":{"additionalProperties":{},"type":"object"},"type":"object"},"status":{"additionalProperties":false,"properties":{"conversion":{"$ref":"#/components/schemas/ConversionStatus","description":"Optional conversion status."}},"type":"object"}}`) + versionSchemaDashboardv0alpha1 app.VersionSchema + _ = json.Unmarshal(rawSchemaDashboardv0alpha1, &versionSchemaDashboardv0alpha1) + rawSchemaSnapshotv0alpha1 = []byte(`{"Snapshot":{"properties":{"spec":{"$ref":"#/components/schemas/spec"}},"required":["spec"]},"spec":{"additionalProperties":false,"properties":{"dashboard":{"additionalProperties":{"additionalProperties":{},"type":"object"},"description":"The raw dashboard (unstructured for now)","type":"object"},"expires":{"default":0,"description":"Optionally auto-remove the snapshot at a future date (Unix timestamp in seconds)","type":"integer"},"external":{"default":false,"description":"When set to true, the snapshot exists in a remote server","type":"boolean"},"externalUrl":{"description":"The external URL where the snapshot can be seen","type":"string"},"originalUrl":{"description":"The URL that created the dashboard originally","type":"string"},"timestamp":{"description":"Snapshot creation timestamp","type":"string"},"title":{"description":"Snapshot title","type":"string"}},"type":"object"}}`) + versionSchemaSnapshotv0alpha1 app.VersionSchema + _ = json.Unmarshal(rawSchemaSnapshotv0alpha1, &versionSchemaSnapshotv0alpha1) + rawSchemaDashboardv1beta1 = []byte(`{"ConversionStatus":{"additionalProperties":false,"description":"ConversionStatus is the status of the conversion of the dashboard.","properties":{"error":{"description":"The error message from the conversion.\nEmpty if the conversion has not failed.","type":"string"},"failed":{"description":"Whether from another version has failed.\nIf true, means that the dashboard is not valid,\nand the caller should instead fetch the stored version.","type":"boolean"},"source":{"additionalProperties":{},"description":"The original value map[string]any","type":"object"},"storedVersion":{"description":"The version which was stored when the dashboard was created / updated.\nFetching this version should always succeed.","type":"string"}},"required":["failed"],"type":"object"},"Dashboard":{"properties":{"spec":{"$ref":"#/components/schemas/spec"},"status":{"$ref":"#/components/schemas/status"}},"required":["spec"]},"spec":{"additionalProperties":{"additionalProperties":{},"type":"object"},"type":"object"},"status":{"additionalProperties":false,"properties":{"conversion":{"$ref":"#/components/schemas/ConversionStatus","description":"Optional conversion status."}},"type":"object"}}`) + versionSchemaDashboardv1beta1 app.VersionSchema + _ = json.Unmarshal(rawSchemaDashboardv1beta1, &versionSchemaDashboardv1beta1) + rawSchemaDashboardv2alpha1 = []byte(`{"Action":{"additionalProperties":false,"properties":{"confirmation":{"type":"string"},"fetch":{"$ref":"#/components/schemas/FetchOptions"},"infinity":{"$ref":"#/components/schemas/InfinityOptions"},"oneClick":{"type":"boolean"},"style":{"additionalProperties":false,"properties":{"backgroundColor":{"type":"string"}},"type":"object"},"title":{"type":"string"},"type":{"$ref":"#/components/schemas/ActionType"},"variables":{"items":{"$ref":"#/components/schemas/ActionVariable"},"type":"array"}},"required":["type","title"],"type":"object"},"ActionType":{"enum":["fetch","infinity"],"type":"string"},"ActionVariable":{"additionalProperties":false,"properties":{"key":{"type":"string"},"name":{"type":"string"},"type":{"$ref":"#/components/schemas/ActionVariableType"}},"required":["key","name","type"],"type":"object"},"ActionVariableType":{"const":"string","description":"Action variable type","type":"string"},"AdHocFilterWithLabels":{"additionalProperties":false,"description":"Define the AdHocFilterWithLabels type","properties":{"condition":{"description":"@deprecated","type":"string"},"forceEdit":{"type":"boolean"},"key":{"type":"string"},"keyLabel":{"type":"string"},"operator":{"type":"string"},"origin":{"$ref":"#/components/schemas/FilterOrigin"},"value":{"type":"string"},"valueLabels":{"items":{"type":"string"},"type":"array"},"values":{"items":{"type":"string"},"type":"array"}},"required":["key","operator","value"],"type":"object"},"AdhocVariableKind":{"additionalProperties":false,"description":"Adhoc variable kind","properties":{"kind":{"const":"AdhocVariable","type":"string"},"spec":{"$ref":"#/components/schemas/AdhocVariableSpec"}},"required":["kind","spec"],"type":"object"},"AdhocVariableSpec":{"additionalProperties":false,"description":"Adhoc variable specification","properties":{"allowCustomValue":{"default":true,"type":"boolean"},"baseFilters":{"items":{"$ref":"#/components/schemas/AdHocFilterWithLabels"},"type":"array"},"datasource":{"$ref":"#/components/schemas/DataSourceRef"},"defaultKeys":{"items":{"$ref":"#/components/schemas/MetricFindValue"},"type":"array"},"description":{"type":"string"},"filters":{"items":{"$ref":"#/components/schemas/AdHocFilterWithLabels"},"type":"array"},"hide":{"$ref":"#/components/schemas/VariableHide","default":"dontHide"},"label":{"type":"string"},"name":{"default":"","type":"string"},"skipUrlSync":{"default":false,"type":"boolean"}},"required":["name","baseFilters","filters","defaultKeys","hide","skipUrlSync","allowCustomValue"],"type":"object"},"AnnotationEventFieldMapping":{"additionalProperties":false,"description":"Annotation event field mapping. Defines how to map a data frame field to an annotation event field.","properties":{"regex":{"description":"Regular expression to apply to the field value","type":"string"},"source":{"default":"field","description":"Source type for the field value","type":"string"},"value":{"description":"Constant value to use when source is \"text\"","type":"string"}},"type":"object"},"AnnotationPanelFilter":{"additionalProperties":false,"properties":{"exclude":{"default":false,"description":"Should the specified panels be included or excluded","type":"boolean"},"ids":{"description":"Panel IDs that should be included or excluded","items":{"type":"integer"},"type":"array"}},"required":["ids"],"type":"object"},"AnnotationQueryKind":{"additionalProperties":false,"properties":{"kind":{"const":"AnnotationQuery","type":"string"},"spec":{"$ref":"#/components/schemas/AnnotationQuerySpec"}},"required":["kind","spec"],"type":"object"},"AnnotationQuerySpec":{"additionalProperties":false,"properties":{"builtIn":{"default":false,"type":"boolean"},"datasource":{"$ref":"#/components/schemas/DataSourceRef"},"enable":{"type":"boolean"},"filter":{"$ref":"#/components/schemas/AnnotationPanelFilter"},"hide":{"type":"boolean"},"iconColor":{"type":"string"},"legacyOptions":{"additionalProperties":{"additionalProperties":{},"type":"object"},"description":"Catch-all field for datasource-specific properties","type":"object"},"mappings":{"additionalProperties":{"$ref":"#/components/schemas/AnnotationEventFieldMapping"},"description":"Mappings define how to convert data frame fields to annotation event fields.","type":"object"},"name":{"type":"string"},"query":{"$ref":"#/components/schemas/DataQueryKind"}},"required":["enable","hide","iconColor","name"],"type":"object"},"AutoGridLayoutItemKind":{"additionalProperties":false,"properties":{"kind":{"const":"AutoGridLayoutItem","type":"string"},"spec":{"$ref":"#/components/schemas/AutoGridLayoutItemSpec"}},"required":["kind","spec"],"type":"object"},"AutoGridLayoutItemSpec":{"additionalProperties":false,"properties":{"conditionalRendering":{"$ref":"#/components/schemas/ConditionalRenderingGroupKind"},"element":{"$ref":"#/components/schemas/ElementReference"},"repeat":{"$ref":"#/components/schemas/AutoGridRepeatOptions"}},"required":["element"],"type":"object"},"AutoGridLayoutKind":{"additionalProperties":false,"properties":{"kind":{"const":"AutoGridLayout","type":"string"},"spec":{"$ref":"#/components/schemas/AutoGridLayoutSpec"}},"required":["kind","spec"],"type":"object"},"AutoGridLayoutSpec":{"additionalProperties":false,"properties":{"columnWidth":{"type":"number"},"columnWidthMode":{"default":"standard","enum":["narrow","standard","wide","custom"],"type":"string"},"fillScreen":{"default":false,"type":"boolean"},"items":{"items":{"$ref":"#/components/schemas/AutoGridLayoutItemKind"},"type":"array"},"maxColumnCount":{"default":3,"type":"number"},"rowHeight":{"type":"number"},"rowHeightMode":{"default":"standard","enum":["short","standard","tall","custom"],"type":"string"}},"required":["columnWidthMode","rowHeightMode","items"],"type":"object"},"AutoGridRepeatOptions":{"additionalProperties":false,"properties":{"mode":{"$ref":"#/components/schemas/RepeatMode"},"value":{"type":"string"}},"required":["mode","value"],"type":"object"},"ConditionalRenderingDataKind":{"additionalProperties":false,"properties":{"kind":{"const":"ConditionalRenderingData","type":"string"},"spec":{"$ref":"#/components/schemas/ConditionalRenderingDataSpec"}},"required":["kind","spec"],"type":"object"},"ConditionalRenderingDataSpec":{"additionalProperties":false,"properties":{"value":{"type":"boolean"}},"required":["value"],"type":"object"},"ConditionalRenderingGroupKind":{"additionalProperties":false,"properties":{"kind":{"const":"ConditionalRenderingGroup","type":"string"},"spec":{"$ref":"#/components/schemas/ConditionalRenderingGroupSpec"}},"required":["kind","spec"],"type":"object"},"ConditionalRenderingGroupSpec":{"additionalProperties":false,"properties":{"condition":{"enum":["and","or"],"type":"string"},"items":{"items":{"oneOf":[{"$ref":"#/components/schemas/ConditionalRenderingVariableKind"},{"$ref":"#/components/schemas/ConditionalRenderingDataKind"},{"$ref":"#/components/schemas/ConditionalRenderingTimeRangeSizeKind"}]},"type":"array"},"visibility":{"enum":["show","hide"],"type":"string"}},"required":["visibility","condition","items"],"type":"object"},"ConditionalRenderingTimeRangeSizeKind":{"additionalProperties":false,"properties":{"kind":{"const":"ConditionalRenderingTimeRangeSize","type":"string"},"spec":{"$ref":"#/components/schemas/ConditionalRenderingTimeRangeSizeSpec"}},"required":["kind","spec"],"type":"object"},"ConditionalRenderingTimeRangeSizeSpec":{"additionalProperties":false,"properties":{"value":{"type":"string"}},"required":["value"],"type":"object"},"ConditionalRenderingVariableKind":{"additionalProperties":false,"properties":{"kind":{"const":"ConditionalRenderingVariable","type":"string"},"spec":{"$ref":"#/components/schemas/ConditionalRenderingVariableSpec"}},"required":["kind","spec"],"type":"object"},"ConditionalRenderingVariableSpec":{"additionalProperties":false,"properties":{"operator":{"enum":["equals","notEquals","matches","notMatches"],"type":"string"},"value":{"type":"string"},"variable":{"type":"string"}},"required":["variable","operator","value"],"type":"object"},"ConstantVariableKind":{"additionalProperties":false,"description":"Constant variable kind","properties":{"kind":{"const":"ConstantVariable","type":"string"},"spec":{"$ref":"#/components/schemas/ConstantVariableSpec"}},"required":["kind","spec"],"type":"object"},"ConstantVariableSpec":{"additionalProperties":false,"description":"Constant variable specification","properties":{"current":{"$ref":"#/components/schemas/VariableOption","default":{"text":"","value":""}},"description":{"type":"string"},"hide":{"$ref":"#/components/schemas/VariableHide","default":"dontHide"},"label":{"type":"string"},"name":{"default":"","type":"string"},"query":{"default":"","type":"string"},"skipUrlSync":{"default":false,"type":"boolean"}},"required":["name","query","current","hide","skipUrlSync"],"type":"object"},"ConversionStatus":{"additionalProperties":false,"description":"ConversionStatus is the status of the conversion of the dashboard.","properties":{"error":{"description":"The error message from the conversion.\nEmpty if the conversion has not failed.","type":"string"},"failed":{"description":"Whether from another version has failed.\nIf true, means that the dashboard is not valid,\nand the caller should instead fetch the stored version.","type":"boolean"},"source":{"additionalProperties":{},"description":"The original value map[string]any","type":"object"},"storedVersion":{"description":"The version which was stored when the dashboard was created / updated.\nFetching this version should always succeed.","type":"string"}},"required":["failed"],"type":"object"},"CustomVariableKind":{"additionalProperties":false,"description":"Custom variable kind","properties":{"kind":{"const":"CustomVariable","type":"string"},"spec":{"$ref":"#/components/schemas/CustomVariableSpec"}},"required":["kind","spec"],"type":"object"},"CustomVariableSpec":{"additionalProperties":false,"description":"Custom variable specification","properties":{"allValue":{"type":"string"},"allowCustomValue":{"default":true,"type":"boolean"},"current":{"$ref":"#/components/schemas/VariableOption"},"description":{"type":"string"},"hide":{"$ref":"#/components/schemas/VariableHide","default":"dontHide"},"includeAll":{"default":false,"type":"boolean"},"label":{"type":"string"},"multi":{"default":false,"type":"boolean"},"name":{"default":"","type":"string"},"options":{"items":{"$ref":"#/components/schemas/VariableOption"},"type":"array"},"query":{"default":"","type":"string"},"skipUrlSync":{"default":false,"type":"boolean"}},"required":["name","query","current","options","multi","includeAll","hide","skipUrlSync","allowCustomValue"],"type":"object"},"Dashboard":{"properties":{"spec":{"$ref":"#/components/schemas/spec"},"status":{"$ref":"#/components/schemas/status"}},"required":["spec"]},"DashboardCursorSync":{"description":"\"Off\" for no shared crosshair or tooltip (default).\n\"Crosshair\" for shared crosshair.\n\"Tooltip\" for shared crosshair AND shared tooltip.","enum":["Crosshair","Tooltip","Off"],"type":"string"},"DashboardLink":{"additionalProperties":false,"description":"Links with references to other dashboards or external resources","properties":{"asDropdown":{"default":false,"description":"If true, all dashboards links will be displayed in a dropdown. If false, all dashboards links will be displayed side by side. Only valid if the type is dashboards","type":"boolean"},"icon":{"description":"Icon name to be displayed with the link","type":"string"},"includeVars":{"default":false,"description":"If true, includes current template variables values in the link as query params","type":"boolean"},"keepTime":{"default":false,"description":"If true, includes current time range in the link as query params","type":"boolean"},"placement":{"$ref":"#/components/schemas/DashboardLinkPlacement","description":"Placement can be used to display the link somewhere else on the dashboard other than above the visualisations."},"tags":{"description":"List of tags to limit the linked dashboards. If empty, all dashboards will be displayed. Only valid if the type is dashboards","items":{"type":"string"},"type":"array"},"targetBlank":{"default":false,"description":"If true, the link will be opened in a new tab","type":"boolean"},"title":{"description":"Title to display with the link","type":"string"},"tooltip":{"description":"Tooltip to display when the user hovers their mouse over it","type":"string"},"type":{"$ref":"#/components/schemas/DashboardLinkType","description":"Link type. Accepted values are dashboards (to refer to another dashboard) and link (to refer to an external resource)\nFIXME: The type is generated as ` + "`" + `type: DashboardLinkType | dashboardLinkType.Link;` + "`" + ` but it should be ` + "`" + `type: DashboardLinkType` + "`" + `"},"url":{"description":"Link URL. Only required/valid if the type is link","type":"string"}},"required":["title","type","icon","tooltip","tags","asDropdown","targetBlank","includeVars","keepTime"],"type":"object"},"DashboardLinkPlacement":{"const":"inControlsMenu","description":"Dashboard Link placement. Defines where the link should be displayed.\n- \"inControlsMenu\" renders the link in bottom part of the dashboard controls dropdown menu","type":"string"},"DashboardLinkType":{"description":"Dashboard Link type. Accepted values are dashboards (to refer to another dashboard) and link (to refer to an external resource)","enum":["link","dashboards"],"type":"string"},"DataLink":{"additionalProperties":false,"properties":{"targetBlank":{"type":"boolean"},"title":{"type":"string"},"url":{"type":"string"}},"required":["title","url"],"type":"object"},"DataQueryKind":{"additionalProperties":false,"properties":{"kind":{"description":"The kind of a DataQueryKind is the datasource type","type":"string"},"spec":{"additionalProperties":{"additionalProperties":{},"type":"object"},"type":"object"}},"required":["kind","spec"],"type":"object"},"DataSourceRef":{"additionalProperties":false,"properties":{"type":{"description":"The plugin type-id","type":"string"},"uid":{"description":"Specific datasource instance","type":"string"}},"type":"object"},"DataTopic":{"description":"A topic is attached to DataFrame metadata in query results.\nThis specifies where the data should be used.","enum":["series","annotations","alertStates"],"type":"string"},"DataTransformerConfig":{"additionalProperties":false,"description":"Transformations allow to manipulate data returned by a query before the system applies a visualization.\nUsing transformations you can: rename fields, join time series data, perform mathematical operations across queries,\nuse the output of one transformation as the input to another transformation, etc.","properties":{"disabled":{"description":"Disabled transformations are skipped","type":"boolean"},"filter":{"$ref":"#/components/schemas/MatcherConfig","description":"Optional frame matcher. When missing it will be applied to all results"},"id":{"description":"Unique identifier of transformer","type":"string"},"options":{"additionalProperties":{},"description":"Options to be passed to the transformer\nValid options depend on the transformer id","type":"object"},"topic":{"$ref":"#/components/schemas/DataTopic","description":"Where to pull DataFrames from as input to transformation"}},"required":["id","options"],"type":"object"},"DatasourceVariableKind":{"additionalProperties":false,"description":"Datasource variable kind","properties":{"kind":{"const":"DatasourceVariable","type":"string"},"spec":{"$ref":"#/components/schemas/DatasourceVariableSpec"}},"required":["kind","spec"],"type":"object"},"DatasourceVariableSpec":{"additionalProperties":false,"description":"Datasource variable specification","properties":{"allValue":{"type":"string"},"allowCustomValue":{"default":true,"type":"boolean"},"current":{"$ref":"#/components/schemas/VariableOption","default":{"text":"","value":""}},"description":{"type":"string"},"hide":{"$ref":"#/components/schemas/VariableHide","default":"dontHide"},"includeAll":{"default":false,"type":"boolean"},"label":{"type":"string"},"multi":{"default":false,"type":"boolean"},"name":{"default":"","type":"string"},"options":{"items":{"$ref":"#/components/schemas/VariableOption"},"type":"array"},"pluginId":{"default":"","type":"string"},"refresh":{"$ref":"#/components/schemas/VariableRefresh","default":"never"},"regex":{"default":"","type":"string"},"skipUrlSync":{"default":false,"type":"boolean"}},"required":["name","pluginId","refresh","regex","current","options","multi","includeAll","hide","skipUrlSync","allowCustomValue"],"type":"object"},"DynamicConfigValue":{"additionalProperties":false,"properties":{"id":{"default":"","type":"string"},"value":{"additionalProperties":{},"type":"object"}},"required":["id"],"type":"object"},"Element":{"description":"Supported dashboard elements\n|* more element types in the future","oneOf":[{"$ref":"#/components/schemas/PanelKind"},{"$ref":"#/components/schemas/LibraryPanelKind"}]},"ElementReference":{"additionalProperties":false,"properties":{"kind":{"const":"ElementReference","type":"string"},"name":{"type":"string"}},"required":["kind","name"],"type":"object"},"FetchOptions":{"additionalProperties":false,"properties":{"body":{"type":"string"},"headers":{"items":{"items":{"type":"string"},"type":"array"},"type":"array"},"method":{"$ref":"#/components/schemas/HttpRequestMethod"},"queryParams":{"description":"These are 2D arrays of strings, each representing a key-value pair\nWe are defining them this way because we can't generate a go struct that\nthat would have exactly two strings in each sub-array","items":{"items":{"type":"string"},"type":"array"},"type":"array"},"url":{"type":"string"}},"required":["method","url"],"type":"object"},"FieldColor":{"additionalProperties":false,"description":"Map a field to a color.","properties":{"fixedColor":{"description":"The fixed color value for fixed or shades color modes.","type":"string"},"mode":{"$ref":"#/components/schemas/FieldColorModeId","description":"The main color scheme mode."},"seriesBy":{"$ref":"#/components/schemas/FieldColorSeriesByMode","description":"Some visualizations need to know how to assign a series color from by value color schemes."}},"required":["mode"],"type":"object"},"FieldColorModeId":{"description":"Color mode for a field. You can specify a single color, or select a continuous (gradient) color schemes, based on a value.\nContinuous color interpolates a color using the percentage of a value relative to min and max.\nAccepted values are:\n` + "`" + `thresholds` + "`" + `: From thresholds. Informs Grafana to take the color from the matching threshold\n` + "`" + `palette-classic` + "`" + `: Classic palette. Grafana will assign color by looking up a color in a palette by series index. Useful for Graphs and pie charts and other categorical data visualizations\n` + "`" + `palette-classic-by-name` + "`" + `: Classic palette (by name). Grafana will assign color by looking up a color in a palette by series name. Useful for Graphs and pie charts and other categorical data visualizations\n` + "`" + `continuous-viridis` + "`" + `: Continuous Viridis palette mode\n` + "`" + `continuous-magma` + "`" + `: Continuous Magma palette mode\n` + "`" + `continuous-plasma` + "`" + `: Continuous Plasma palette mode\n` + "`" + `continuous-inferno` + "`" + `: Continuous Inferno palette mode\n` + "`" + `continuous-cividis` + "`" + `: Continuous Cividis palette mode\n` + "`" + `continuous-GrYlRd` + "`" + `: Continuous Green-Yellow-Red palette mode\n` + "`" + `continuous-RdYlGr` + "`" + `: Continuous Red-Yellow-Green palette mode\n` + "`" + `continuous-BlYlRd` + "`" + `: Continuous Blue-Yellow-Red palette mode\n` + "`" + `continuous-YlRd` + "`" + `: Continuous Yellow-Red palette mode\n` + "`" + `continuous-BlPu` + "`" + `: Continuous Blue-Purple palette mode\n` + "`" + `continuous-YlBl` + "`" + `: Continuous Yellow-Blue palette mode\n` + "`" + `continuous-blues` + "`" + `: Continuous Blue palette mode\n` + "`" + `continuous-reds` + "`" + `: Continuous Red palette mode\n` + "`" + `continuous-greens` + "`" + `: Continuous Green palette mode\n` + "`" + `continuous-purples` + "`" + `: Continuous Purple palette mode\n` + "`" + `shades` + "`" + `: Shades of a single color. Specify a single color, useful in an override rule.\n` + "`" + `fixed` + "`" + `: Fixed color mode. Specify a single color, useful in an override rule.","enum":["thresholds","palette-classic","palette-classic-by-name","continuous-viridis","continuous-magma","continuous-plasma","continuous-inferno","continuous-cividis","continuous-GrYlRd","continuous-RdYlGr","continuous-BlYlRd","continuous-YlRd","continuous-BlPu","continuous-YlBl","continuous-blues","continuous-reds","continuous-greens","continuous-purples","fixed","shades"],"type":"string"},"FieldColorSeriesByMode":{"description":"Defines how to assign a series color from \"by value\" color schemes. For example for an aggregated data points like a timeseries, the color can be assigned by the min, max or last value.","enum":["min","max","last"],"type":"string"},"FieldConfig":{"additionalProperties":false,"description":"The data model used in Grafana, namely the data frame, is a columnar-oriented table structure that unifies both time series and table query results.\nEach column within this structure is called a field. A field can represent a single time series or table column.\nField options allow you to change how the data is displayed in your visualizations.","properties":{"actions":{"description":"Define interactive HTTP requests that can be triggered from data visualizations.","items":{"$ref":"#/components/schemas/Action"},"type":"array"},"color":{"$ref":"#/components/schemas/FieldColor","description":"Panel color configuration"},"custom":{"additionalProperties":{"additionalProperties":{},"type":"object"},"description":"custom is specified by the FieldConfig field\nin panel plugin schemas.","type":"object"},"decimals":{"description":"Specify the number of decimals Grafana includes in the rendered value.\nIf you leave this field blank, Grafana automatically truncates the number of decimals based on the value.\nFor example 1.1234 will display as 1.12 and 100.456 will display as 100.\nTo display all decimals, set the unit to ` + "`" + `String` + "`" + `.","type":"number"},"description":{"description":"Human readable field metadata","type":"string"},"displayName":{"description":"The display value for this field. This supports template variables blank is auto","type":"string"},"displayNameFromDS":{"description":"This can be used by data sources that return and explicit naming structure for values and labels\nWhen this property is configured, this value is used rather than the default naming strategy.","type":"string"},"filterable":{"description":"True if data source field supports ad-hoc filters","type":"boolean"},"links":{"description":"The behavior when clicking on a result","items":{"additionalProperties":{},"type":"object"},"type":"array"},"mappings":{"description":"Convert input values into a display string","items":{"$ref":"#/components/schemas/ValueMapping"},"type":"array"},"max":{"description":"The maximum value used in percentage threshold calculations. Leave blank for auto calculation based on all series and fields.","type":"number"},"min":{"description":"The minimum value used in percentage threshold calculations. Leave blank for auto calculation based on all series and fields.","type":"number"},"noValue":{"description":"Alternative to empty string","type":"string"},"path":{"description":"An explicit path to the field in the datasource. When the frame meta includes a path,\nThis will default to ` + "`" + `${frame.meta.path}/${field.name}\n\nWhen defined, this value can be used as an identifier within the datasource scope, and\nmay be used to update the results","type":"string"},"thresholds":{"$ref":"#/components/schemas/ThresholdsConfig","description":"Map numeric values to states"},"unit":{"description":"Unit a field should use. The unit you select is applied to all fields except time.\nYou can use the units ID availables in Grafana or a custom unit.\nAvailable units in Grafana: https://github.com/grafana/grafana/blob/main/packages/grafana-data/src/valueFormats/categories.ts\nAs custom unit, you can use the following formats:\n` + "`" + `suffix:\u003csuffix\u003e` + "`" + ` for custom unit that should go after value.\n` + "`" + `prefix:\u003cprefix\u003e` + "`" + ` for custom unit that should go before value.\n` + "`" + `time:\u003cformat\u003e` + "`" + ` For custom date time formats type for example ` + "`" + `time:YYYY-MM-DD` + "`" + `.\n` + "`" + `si:\u003cbase scale\u003e\u003cunit characters\u003e` + "`" + ` for custom SI units. For example: ` + "`" + `si: mF` + "`" + `. This one is a bit more advanced as you can specify both a unit and the source data scale. So if your source data is represented as milli (thousands of) something prefix the unit with that SI scale character.\n` + "`" + `count:\u003cunit\u003e` + "`" + ` for a custom count unit.\n` + "`" + `currency:\u003cunit\u003e` + "`" + ` for custom a currency unit.","type":"string"},"writeable":{"description":"True if data source can write a value to the path. Auth/authz are supported separately","type":"boolean"}},"type":"object"},"FieldConfigSource":{"additionalProperties":false,"description":"The data model used in Grafana, namely the data frame, is a columnar-oriented table structure that unifies both time series and table query results.\nEach column within this structure is called a field. A field can represent a single time series or table column.\nField options allow you to change how the data is displayed in your visualizations.","properties":{"defaults":{"$ref":"#/components/schemas/FieldConfig","description":"Defaults are the options applied to all fields."},"overrides":{"description":"Overrides are the options applied to specific fields overriding the defaults.","items":{"additionalProperties":false,"properties":{"__systemRef":{"description":"Describes config override rules created when interacting with Grafana.","type":"string"},"matcher":{"$ref":"#/components/schemas/MatcherConfig"},"properties":{"items":{"$ref":"#/components/schemas/DynamicConfigValue"},"type":"array"}},"required":["matcher","properties"],"type":"object"},"type":"array"}},"required":["defaults","overrides"],"type":"object"},"FilterOrigin":{"const":"dashboard","description":"Determine the origin of the adhoc variable filter","type":"string"},"GridLayoutItemKind":{"additionalProperties":false,"properties":{"kind":{"const":"GridLayoutItem","type":"string"},"spec":{"$ref":"#/components/schemas/GridLayoutItemSpec"}},"required":["kind","spec"],"type":"object"},"GridLayoutItemSpec":{"additionalProperties":false,"properties":{"element":{"$ref":"#/components/schemas/ElementReference","description":"reference to a PanelKind from dashboard.spec.elements Expressed as JSON Schema reference"},"height":{"type":"integer"},"repeat":{"$ref":"#/components/schemas/RepeatOptions"},"width":{"type":"integer"},"x":{"type":"integer"},"y":{"type":"integer"}},"required":["x","y","width","height","element"],"type":"object"},"GridLayoutKind":{"additionalProperties":false,"properties":{"kind":{"const":"GridLayout","type":"string"},"spec":{"$ref":"#/components/schemas/GridLayoutSpec"}},"required":["kind","spec"],"type":"object"},"GridLayoutSpec":{"additionalProperties":false,"properties":{"items":{"items":{"$ref":"#/components/schemas/GridLayoutItemKind"},"type":"array"}},"required":["items"],"type":"object"},"GroupByVariableKind":{"additionalProperties":false,"description":"Group variable kind","properties":{"kind":{"const":"GroupByVariable","type":"string"},"spec":{"$ref":"#/components/schemas/GroupByVariableSpec"}},"required":["kind","spec"],"type":"object"},"GroupByVariableSpec":{"additionalProperties":false,"description":"GroupBy variable specification","properties":{"current":{"$ref":"#/components/schemas/VariableOption","default":{"text":"","value":""}},"datasource":{"$ref":"#/components/schemas/DataSourceRef"},"defaultValue":{"$ref":"#/components/schemas/VariableOption"},"description":{"type":"string"},"hide":{"$ref":"#/components/schemas/VariableHide","default":"dontHide"},"label":{"type":"string"},"multi":{"default":false,"type":"boolean"},"name":{"default":"","type":"string"},"options":{"items":{"$ref":"#/components/schemas/VariableOption"},"type":"array"},"skipUrlSync":{"default":false,"type":"boolean"}},"required":["name","current","options","multi","hide","skipUrlSync"],"type":"object"},"HttpRequestMethod":{"enum":["GET","PUT","POST","DELETE","PATCH"],"type":"string"},"InfinityOptions":{"additionalProperties":false,"properties":{"body":{"type":"string"},"datasourceUid":{"type":"string"},"headers":{"items":{"items":{"type":"string"},"type":"array"},"type":"array"},"method":{"$ref":"#/components/schemas/HttpRequestMethod"},"queryParams":{"description":"These are 2D arrays of strings, each representing a key-value pair\nWe are defining them this way because we can't generate a go struct that\nthat would have exactly two strings in each sub-array","items":{"items":{"type":"string"},"type":"array"},"type":"array"},"url":{"type":"string"}},"required":["method","url","datasourceUid"],"type":"object"},"IntervalVariableKind":{"additionalProperties":false,"description":"Interval variable kind","properties":{"kind":{"const":"IntervalVariable","type":"string"},"spec":{"$ref":"#/components/schemas/IntervalVariableSpec"}},"required":["kind","spec"],"type":"object"},"IntervalVariableSpec":{"additionalProperties":false,"description":"Interval variable specification","properties":{"auto":{"default":false,"type":"boolean"},"auto_count":{"default":0,"type":"integer"},"auto_min":{"default":"","type":"string"},"current":{"$ref":"#/components/schemas/VariableOption","default":{"text":"","value":""}},"description":{"type":"string"},"hide":{"$ref":"#/components/schemas/VariableHide","default":"dontHide"},"label":{"type":"string"},"name":{"default":"","type":"string"},"options":{"items":{"$ref":"#/components/schemas/VariableOption"},"type":"array"},"query":{"default":"","type":"string"},"refresh":{"$ref":"#/components/schemas/VariableRefresh","default":"never"},"skipUrlSync":{"default":false,"type":"boolean"}},"required":["name","query","current","options","auto","auto_min","auto_count","refresh","hide","skipUrlSync"],"type":"object"},"LibraryPanelKind":{"additionalProperties":false,"properties":{"kind":{"const":"LibraryPanel","type":"string"},"spec":{"$ref":"#/components/schemas/LibraryPanelKindSpec"}},"required":["kind","spec"],"type":"object"},"LibraryPanelKindSpec":{"additionalProperties":false,"properties":{"id":{"description":"Panel ID for the library panel in the dashboard","type":"number"},"libraryPanel":{"$ref":"#/components/schemas/LibraryPanelRef"},"title":{"description":"Title for the library panel in the dashboard","type":"string"}},"required":["id","title","libraryPanel"],"type":"object"},"LibraryPanelRef":{"additionalProperties":false,"description":"A library panel is a reusable panel that you can use in any dashboard.\nWhen you make a change to a library panel, that change propagates to all instances of where the panel is used.\nLibrary panels streamline reuse of panels across multiple dashboards.","properties":{"name":{"description":"Library panel name","type":"string"},"uid":{"description":"Library panel uid","type":"string"}},"required":["name","uid"],"type":"object"},"MappingType":{"description":"Supported value mapping types\n` + "`" + `value` + "`" + `: Maps text values to a color or different display text and color. For example, you can configure a value mapping so that all instances of the value 10 appear as Perfection! rather than the number.\n` + "`" + `range` + "`" + `: Maps numerical ranges to a display text and color. For example, if a value is within a certain range, you can configure a range value mapping to display Low or High rather than the number.\n` + "`" + `regex` + "`" + `: Maps regular expressions to replacement text and a color. For example, if a value is www.example.com, you can configure a regex value mapping so that Grafana displays www and truncates the domain.\n` + "`" + `special` + "`" + `: Maps special values like Null, NaN (not a number), and boolean values like true and false to a display text and color. See SpecialValueMatch to see the list of special values. For example, you can configure a special value mapping so that null values appear as N/A.","enum":["value","range","regex","special"],"type":"string"},"MatcherConfig":{"additionalProperties":false,"description":"Matcher is a predicate configuration. Based on the config a set of field(s) or values is filtered in order to apply override / transformation.\nIt comes with in id ( to resolve implementation from registry) and a configuration that’s specific to a particular matcher type.","properties":{"id":{"default":"","description":"The matcher id. This is used to find the matcher implementation from registry.","type":"string"},"options":{"additionalProperties":{},"description":"The matcher options. This is specific to the matcher implementation.","type":"object"}},"required":["id"],"type":"object"},"MetricFindValue":{"additionalProperties":false,"description":"Define the MetricFindValue type","properties":{"expandable":{"type":"boolean"},"group":{"type":"string"},"text":{"type":"string"},"value":{"oneOf":[{"type":"string"},{"type":"number"}]}},"required":["text"],"type":"object"},"PanelKind":{"additionalProperties":false,"properties":{"kind":{"const":"Panel","type":"string"},"spec":{"$ref":"#/components/schemas/PanelSpec"}},"required":["kind","spec"],"type":"object"},"PanelQueryKind":{"additionalProperties":false,"properties":{"kind":{"const":"PanelQuery","type":"string"},"spec":{"$ref":"#/components/schemas/PanelQuerySpec"}},"required":["kind","spec"],"type":"object"},"PanelQuerySpec":{"additionalProperties":false,"properties":{"datasource":{"$ref":"#/components/schemas/DataSourceRef"},"hidden":{"type":"boolean"},"query":{"$ref":"#/components/schemas/DataQueryKind"},"refId":{"type":"string"}},"required":["query","refId","hidden"],"type":"object"},"PanelSpec":{"additionalProperties":false,"properties":{"data":{"$ref":"#/components/schemas/QueryGroupKind"},"description":{"type":"string"},"id":{"type":"number"},"links":{"items":{"$ref":"#/components/schemas/DataLink"},"type":"array"},"title":{"type":"string"},"transparent":{"type":"boolean"},"vizConfig":{"$ref":"#/components/schemas/VizConfigKind"}},"required":["id","title","description","links","data","vizConfig"],"type":"object"},"QueryGroupKind":{"additionalProperties":false,"properties":{"kind":{"const":"QueryGroup","type":"string"},"spec":{"$ref":"#/components/schemas/QueryGroupSpec"}},"required":["kind","spec"],"type":"object"},"QueryGroupSpec":{"additionalProperties":false,"properties":{"queries":{"items":{"$ref":"#/components/schemas/PanelQueryKind"},"type":"array"},"queryOptions":{"$ref":"#/components/schemas/QueryOptionsSpec"},"transformations":{"items":{"$ref":"#/components/schemas/TransformationKind"},"type":"array"}},"required":["queries","transformations","queryOptions"],"type":"object"},"QueryOptionsSpec":{"additionalProperties":false,"properties":{"cacheTimeout":{"type":"string"},"hideTimeOverride":{"type":"boolean"},"interval":{"type":"string"},"maxDataPoints":{"type":"integer"},"queryCachingTTL":{"type":"integer"},"timeFrom":{"type":"string"},"timeShift":{"type":"string"}},"type":"object"},"QueryVariableKind":{"additionalProperties":false,"description":"Query variable kind","properties":{"kind":{"const":"QueryVariable","type":"string"},"spec":{"$ref":"#/components/schemas/QueryVariableSpec"}},"required":["kind","spec"],"type":"object"},"QueryVariableSpec":{"additionalProperties":false,"description":"Query variable specification","properties":{"allValue":{"type":"string"},"allowCustomValue":{"default":true,"type":"boolean"},"current":{"$ref":"#/components/schemas/VariableOption","default":{"text":"","value":""}},"datasource":{"$ref":"#/components/schemas/DataSourceRef"},"definition":{"type":"string"},"description":{"type":"string"},"hide":{"$ref":"#/components/schemas/VariableHide","default":"dontHide"},"includeAll":{"default":false,"type":"boolean"},"label":{"type":"string"},"multi":{"default":false,"type":"boolean"},"name":{"default":"","type":"string"},"options":{"items":{"$ref":"#/components/schemas/VariableOption"},"type":"array"},"placeholder":{"type":"string"},"query":{"$ref":"#/components/schemas/DataQueryKind"},"refresh":{"$ref":"#/components/schemas/VariableRefresh","default":"never"},"regex":{"default":"","type":"string"},"skipUrlSync":{"default":false,"type":"boolean"},"sort":{"$ref":"#/components/schemas/VariableSort"},"staticOptions":{"items":{"$ref":"#/components/schemas/VariableOption"},"type":"array"},"staticOptionsOrder":{"enum":["before","after","sorted"],"type":"string"}},"required":["name","current","hide","refresh","skipUrlSync","query","regex","sort","options","multi","includeAll","allowCustomValue"],"type":"object"},"RangeMap":{"additionalProperties":false,"description":"Maps numerical ranges to a display text and color.\nFor example, if a value is within a certain range, you can configure a range value mapping to display Low or High rather than the number.","properties":{"options":{"additionalProperties":false,"description":"Range to match against and the result to apply when the value is within the range","properties":{"from":{"description":"Min value of the range. It can be null which means -Infinity","type":"number"},"result":{"$ref":"#/components/schemas/ValueMappingResult","description":"Config to apply when the value is within the range"},"to":{"description":"Max value of the range. It can be null which means +Infinity","type":"number"}},"required":["from","to","result"],"type":"object"},"type":{"$ref":"#/components/schemas/MappingType"}},"required":["type","options"],"type":"object"},"RegexMap":{"additionalProperties":false,"description":"Maps regular expressions to replacement text and a color.\nFor example, if a value is www.example.com, you can configure a regex value mapping so that Grafana displays www and truncates the domain.","properties":{"options":{"additionalProperties":false,"description":"Regular expression to match against and the result to apply when the value matches the regex","properties":{"pattern":{"description":"Regular expression to match against","type":"string"},"result":{"$ref":"#/components/schemas/ValueMappingResult","description":"Config to apply when the value matches the regex"}},"required":["pattern","result"],"type":"object"},"type":{"$ref":"#/components/schemas/MappingType"}},"required":["type","options"],"type":"object"},"RepeatMode":{"const":"variable","description":"other repeat modes will be added in the future: label, frame","type":"string"},"RepeatOptions":{"additionalProperties":false,"properties":{"direction":{"enum":["h","v"],"type":"string"},"maxPerRow":{"type":"integer"},"mode":{"$ref":"#/components/schemas/RepeatMode"},"value":{"type":"string"}},"required":["mode","value"],"type":"object"},"RowRepeatOptions":{"additionalProperties":false,"properties":{"mode":{"$ref":"#/components/schemas/RepeatMode"},"value":{"type":"string"}},"required":["mode","value"],"type":"object"},"RowsLayoutKind":{"additionalProperties":false,"properties":{"kind":{"const":"RowsLayout","type":"string"},"spec":{"$ref":"#/components/schemas/RowsLayoutSpec"}},"required":["kind","spec"],"type":"object"},"RowsLayoutRowKind":{"additionalProperties":false,"properties":{"kind":{"const":"RowsLayoutRow","type":"string"},"spec":{"$ref":"#/components/schemas/RowsLayoutRowSpec"}},"required":["kind","spec"],"type":"object"},"RowsLayoutRowSpec":{"additionalProperties":false,"properties":{"collapse":{"type":"boolean"},"conditionalRendering":{"$ref":"#/components/schemas/ConditionalRenderingGroupKind"},"fillScreen":{"type":"boolean"},"hideHeader":{"type":"boolean"},"layout":{"oneOf":[{"$ref":"#/components/schemas/GridLayoutKind"},{"$ref":"#/components/schemas/AutoGridLayoutKind"},{"$ref":"#/components/schemas/TabsLayoutKind"},{"$ref":"#/components/schemas/RowsLayoutKind"}]},"repeat":{"$ref":"#/components/schemas/RowRepeatOptions"},"title":{"type":"string"}},"required":["layout"],"type":"object"},"RowsLayoutSpec":{"additionalProperties":false,"properties":{"rows":{"items":{"$ref":"#/components/schemas/RowsLayoutRowKind"},"type":"array"}},"required":["rows"],"type":"object"},"SpecialValueMap":{"additionalProperties":false,"description":"Maps special values like Null, NaN (not a number), and boolean values like true and false to a display text and color.\nSee SpecialValueMatch to see the list of special values.\nFor example, you can configure a special value mapping so that null values appear as N/A.","properties":{"options":{"additionalProperties":false,"properties":{"match":{"$ref":"#/components/schemas/SpecialValueMatch","description":"Special value to match against"},"result":{"$ref":"#/components/schemas/ValueMappingResult","description":"Config to apply when the value matches the special value"}},"required":["match","result"],"type":"object"},"type":{"$ref":"#/components/schemas/MappingType"}},"required":["type","options"],"type":"object"},"SpecialValueMatch":{"description":"Special value types supported by the ` + "`" + `SpecialValueMap` + "`" + `","enum":["true","false","null","nan","null+nan","empty"],"type":"string"},"SwitchVariableKind":{"additionalProperties":false,"properties":{"kind":{"const":"SwitchVariable","type":"string"},"spec":{"$ref":"#/components/schemas/SwitchVariableSpec"}},"required":["kind","spec"],"type":"object"},"SwitchVariableSpec":{"additionalProperties":false,"description":"Switch variable specification","properties":{"current":{"default":"false","type":"string"},"description":{"type":"string"},"disabledValue":{"default":"false","type":"string"},"enabledValue":{"default":"true","type":"string"},"hide":{"$ref":"#/components/schemas/VariableHide","default":"dontHide"},"label":{"type":"string"},"name":{"default":"","type":"string"},"skipUrlSync":{"default":false,"type":"boolean"}},"required":["name","current","enabledValue","disabledValue","hide","skipUrlSync"],"type":"object"},"TabRepeatOptions":{"additionalProperties":false,"properties":{"mode":{"$ref":"#/components/schemas/RepeatMode"},"value":{"type":"string"}},"required":["mode","value"],"type":"object"},"TabsLayoutKind":{"additionalProperties":false,"properties":{"kind":{"const":"TabsLayout","type":"string"},"spec":{"$ref":"#/components/schemas/TabsLayoutSpec"}},"required":["kind","spec"],"type":"object"},"TabsLayoutSpec":{"additionalProperties":false,"properties":{"tabs":{"items":{"$ref":"#/components/schemas/TabsLayoutTabKind"},"type":"array"}},"required":["tabs"],"type":"object"},"TabsLayoutTabKind":{"additionalProperties":false,"properties":{"kind":{"const":"TabsLayoutTab","type":"string"},"spec":{"$ref":"#/components/schemas/TabsLayoutTabSpec"}},"required":["kind","spec"],"type":"object"},"TabsLayoutTabSpec":{"additionalProperties":false,"properties":{"conditionalRendering":{"$ref":"#/components/schemas/ConditionalRenderingGroupKind"},"layout":{"oneOf":[{"$ref":"#/components/schemas/GridLayoutKind"},{"$ref":"#/components/schemas/RowsLayoutKind"},{"$ref":"#/components/schemas/AutoGridLayoutKind"},{"$ref":"#/components/schemas/TabsLayoutKind"}]},"repeat":{"$ref":"#/components/schemas/TabRepeatOptions"},"title":{"type":"string"}},"required":["layout"],"type":"object"},"TextVariableKind":{"additionalProperties":false,"description":"Text variable kind","properties":{"kind":{"const":"TextVariable","type":"string"},"spec":{"$ref":"#/components/schemas/TextVariableSpec"}},"required":["kind","spec"],"type":"object"},"TextVariableSpec":{"additionalProperties":false,"description":"Text variable specification","properties":{"current":{"$ref":"#/components/schemas/VariableOption","default":{"text":"","value":""}},"description":{"type":"string"},"hide":{"$ref":"#/components/schemas/VariableHide","default":"dontHide"},"label":{"type":"string"},"name":{"default":"","type":"string"},"query":{"default":"","type":"string"},"skipUrlSync":{"default":false,"type":"boolean"}},"required":["name","current","query","hide","skipUrlSync"],"type":"object"},"Threshold":{"additionalProperties":false,"properties":{"color":{"type":"string"},"value":{"description":"Value null means -Infinity","type":"number"}},"required":["value","color"],"type":"object"},"ThresholdsConfig":{"additionalProperties":false,"properties":{"mode":{"$ref":"#/components/schemas/ThresholdsMode"},"steps":{"items":{"$ref":"#/components/schemas/Threshold"},"type":"array"}},"required":["mode","steps"],"type":"object"},"ThresholdsMode":{"enum":["absolute","percentage"],"type":"string"},"TimeRangeOption":{"additionalProperties":false,"properties":{"display":{"default":"Last 6 hours","type":"string"},"from":{"default":"now-6h","type":"string"},"to":{"default":"now","type":"string"}},"required":["display","from","to"],"type":"object"},"TimeSettingsSpec":{"additionalProperties":false,"description":"Time configuration\nIt defines the default time config for the time picker, the refresh picker for the specific dashboard.","properties":{"autoRefresh":{"default":"","description":"Refresh rate of dashboard. Represented via interval string, e.g. \"5s\", \"1m\", \"1h\", \"1d\".\nv1: refresh","type":"string"},"autoRefreshIntervals":{"default":["5s","10s","30s","1m","5m","15m","30m","1h","2h","1d"],"description":"Interval options available in the refresh picker dropdown.\nv1: timepicker.refresh_intervals","items":{"type":"string"},"type":"array"},"fiscalYearStartMonth":{"default":0,"description":"The month that the fiscal year starts on. 0 = January, 11 = December","type":"integer"},"from":{"default":"now-6h","description":"Start time range for dashboard.\nAccepted values are relative time strings like \"now-6h\" or absolute time strings like \"2020-07-10T08:00:00.000Z\".","type":"string"},"hideTimepicker":{"default":false,"description":"Whether timepicker is visible or not.\nv1: timepicker.hidden","type":"boolean"},"nowDelay":{"description":"Override the now time by entering a time delay. Use this option to accommodate known delays in data aggregation to avoid null values.\nv1: timepicker.nowDelay","type":"string"},"quickRanges":{"description":"Selectable options available in the time picker dropdown. Has no effect on provisioned dashboard.\nv1: timepicker.quick_ranges , not exposed in the UI","items":{"$ref":"#/components/schemas/TimeRangeOption"},"type":"array"},"timezone":{"default":"browser","description":"Timezone of dashboard. Accepted values are IANA TZDB zone ID or \"browser\" or \"utc\".","type":"string"},"to":{"default":"now","description":"End time range for dashboard.\nAccepted values are relative time strings like \"now-6h\" or absolute time strings like \"2020-07-10T08:00:00.000Z\".","type":"string"},"weekStart":{"description":"Day when the week starts. Expressed by the name of the day in lowercase, e.g. \"monday\".","enum":["saturday","monday","sunday"],"type":"string"}},"required":["from","to","autoRefresh","autoRefreshIntervals","hideTimepicker","fiscalYearStartMonth"],"type":"object"},"TransformationKind":{"additionalProperties":false,"properties":{"kind":{"description":"The kind of a TransformationKind is the transformation ID","type":"string"},"spec":{"$ref":"#/components/schemas/DataTransformerConfig"}},"required":["kind","spec"],"type":"object"},"ValueMap":{"additionalProperties":false,"description":"Maps text values to a color or different display text and color.\nFor example, you can configure a value mapping so that all instances of the value 10 appear as Perfection! rather than the number.","properties":{"options":{"additionalProperties":{"$ref":"#/components/schemas/ValueMappingResult"},"description":"Map with \u003cvalue_to_match\u003e: ValueMappingResult. For example: { \"10\": { text: \"Perfection!\", color: \"green\" } }","type":"object"},"type":{"$ref":"#/components/schemas/MappingType"}},"required":["type","options"],"type":"object"},"ValueMapping":{"oneOf":[{"$ref":"#/components/schemas/ValueMap"},{"$ref":"#/components/schemas/RangeMap"},{"$ref":"#/components/schemas/RegexMap"},{"$ref":"#/components/schemas/SpecialValueMap"}]},"ValueMappingResult":{"additionalProperties":false,"description":"Result used as replacement with text and color when the value matches","properties":{"color":{"description":"Text to use when the value matches","type":"string"},"icon":{"description":"Icon to display when the value matches. Only specific visualizations.","type":"string"},"index":{"description":"Position in the mapping array. Only used internally.","type":"integer"},"text":{"description":"Text to display when the value matches","type":"string"}},"type":"object"},"VariableHide":{"description":"Determine if the variable shows on dashboard\nAccepted values are ` + "`" + `dontHide` + "`" + ` (show label and value), ` + "`" + `hideLabel` + "`" + ` (show value only), ` + "`" + `hideVariable` + "`" + ` (show nothing).","enum":["dontHide","hideLabel","hideVariable"],"type":"string"},"VariableKind":{"oneOf":[{"$ref":"#/components/schemas/QueryVariableKind"},{"$ref":"#/components/schemas/TextVariableKind"},{"$ref":"#/components/schemas/ConstantVariableKind"},{"$ref":"#/components/schemas/DatasourceVariableKind"},{"$ref":"#/components/schemas/IntervalVariableKind"},{"$ref":"#/components/schemas/CustomVariableKind"},{"$ref":"#/components/schemas/GroupByVariableKind"},{"$ref":"#/components/schemas/AdhocVariableKind"},{"$ref":"#/components/schemas/SwitchVariableKind"}]},"VariableOption":{"additionalProperties":false,"description":"Variable option specification","properties":{"selected":{"description":"Whether the option is selected or not","type":"boolean"},"text":{"description":"Text to be displayed for the option","oneOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}]},"value":{"description":"Value of the option","oneOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}]}},"required":["text","value"],"type":"object"},"VariableRefresh":{"description":"Options to config when to refresh a variable\n` + "`" + `never` + "`" + `: Never refresh the variable\n` + "`" + `onDashboardLoad` + "`" + `: Queries the data source every time the dashboard loads.\n` + "`" + `onTimeRangeChanged` + "`" + `: Queries the data source when the dashboard time range changes.","enum":["never","onDashboardLoad","onTimeRangeChanged"],"type":"string"},"VariableSort":{"description":"Sort variable options\nAccepted values are:\n` + "`" + `disabled` + "`" + `: No sorting\n` + "`" + `alphabeticalAsc` + "`" + `: Alphabetical ASC\n` + "`" + `alphabeticalDesc` + "`" + `: Alphabetical DESC\n` + "`" + `numericalAsc` + "`" + `: Numerical ASC\n` + "`" + `numericalDesc` + "`" + `: Numerical DESC\n` + "`" + `alphabeticalCaseInsensitiveAsc` + "`" + `: Alphabetical Case Insensitive ASC\n` + "`" + `alphabeticalCaseInsensitiveDesc` + "`" + `: Alphabetical Case Insensitive DESC\n` + "`" + `naturalAsc` + "`" + `: Natural ASC\n` + "`" + `naturalDesc` + "`" + `: Natural DESC\nVariableSort enum with default value","enum":["disabled","alphabeticalAsc","alphabeticalDesc","numericalAsc","numericalDesc","alphabeticalCaseInsensitiveAsc","alphabeticalCaseInsensitiveDesc","naturalAsc","naturalDesc"],"type":"string"},"VizConfigKind":{"additionalProperties":false,"properties":{"kind":{"description":"The kind of a VizConfigKind is the plugin ID","type":"string"},"spec":{"$ref":"#/components/schemas/VizConfigSpec"}},"required":["kind","spec"],"type":"object"},"VizConfigSpec":{"additionalProperties":false,"description":"--- Kinds ---","properties":{"fieldConfig":{"$ref":"#/components/schemas/FieldConfigSource"},"options":{"additionalProperties":{"additionalProperties":{},"type":"object"},"type":"object"},"pluginVersion":{"type":"string"}},"required":["pluginVersion","options","fieldConfig"],"type":"object"},"spec":{"additionalProperties":false,"properties":{"annotations":{"items":{"$ref":"#/components/schemas/AnnotationQueryKind"},"type":"array"},"cursorSync":{"$ref":"#/components/schemas/DashboardCursorSync","default":"Off","description":"Configuration of dashboard cursor sync behavior.\n\"Off\" for no shared crosshair or tooltip (default).\n\"Crosshair\" for shared crosshair.\n\"Tooltip\" for shared crosshair AND shared tooltip."},"description":{"description":"Description of dashboard.","type":"string"},"editable":{"default":true,"description":"Whether a dashboard is editable or not.","type":"boolean"},"elements":{"additionalProperties":{"$ref":"#/components/schemas/Element"},"type":"object"},"layout":{"oneOf":[{"$ref":"#/components/schemas/GridLayoutKind"},{"$ref":"#/components/schemas/RowsLayoutKind"},{"$ref":"#/components/schemas/AutoGridLayoutKind"},{"$ref":"#/components/schemas/TabsLayoutKind"}]},"links":{"description":"Links with references to other dashboards or external websites.","items":{"$ref":"#/components/schemas/DashboardLink"},"type":"array"},"liveNow":{"description":"When set to true, the dashboard will redraw panels at an interval matching the pixel width.\nThis will keep data \"moving left\" regardless of the query refresh rate. This setting helps\navoid dashboards presenting stale live data.","type":"boolean"},"preload":{"default":false,"description":"When set to true, the dashboard will load all panels in the dashboard when it's loaded.","type":"boolean"},"revision":{"description":"Plugins only. The version of the dashboard installed together with the plugin.\nThis is used to determine if the dashboard should be updated when the plugin is updated.","type":"integer"},"tags":{"description":"Tags associated with dashboard.","items":{"type":"string"},"type":"array"},"timeSettings":{"$ref":"#/components/schemas/TimeSettingsSpec"},"title":{"description":"Title of dashboard.","type":"string"},"variables":{"description":"Configured template variables.","items":{"$ref":"#/components/schemas/VariableKind"},"type":"array"}},"required":["annotations","cursorSync","elements","layout","links","preload","tags","timeSettings","title","variables"],"type":"object"},"status":{"additionalProperties":false,"properties":{"conversion":{"$ref":"#/components/schemas/ConversionStatus","description":"Optional conversion status."}},"type":"object"}}`) + versionSchemaDashboardv2alpha1 app.VersionSchema + _ = json.Unmarshal(rawSchemaDashboardv2alpha1, &versionSchemaDashboardv2alpha1) + rawSchemaDashboardv2beta1 = []byte(`{"Action":{"additionalProperties":false,"properties":{"confirmation":{"type":"string"},"fetch":{"$ref":"#/components/schemas/FetchOptions"},"infinity":{"$ref":"#/components/schemas/InfinityOptions"},"oneClick":{"type":"boolean"},"style":{"additionalProperties":false,"properties":{"backgroundColor":{"type":"string"}},"type":"object"},"title":{"type":"string"},"type":{"$ref":"#/components/schemas/ActionType"},"variables":{"items":{"$ref":"#/components/schemas/ActionVariable"},"type":"array"}},"required":["type","title"],"type":"object"},"ActionType":{"enum":["fetch","infinity"],"type":"string"},"ActionVariable":{"additionalProperties":false,"properties":{"key":{"type":"string"},"name":{"type":"string"},"type":{"$ref":"#/components/schemas/ActionVariableType"}},"required":["key","name","type"],"type":"object"},"ActionVariableType":{"const":"string","description":"Action variable type","type":"string"},"AdHocFilterWithLabels":{"additionalProperties":false,"description":"Define the AdHocFilterWithLabels type","properties":{"condition":{"description":"@deprecated","type":"string"},"forceEdit":{"type":"boolean"},"key":{"type":"string"},"keyLabel":{"type":"string"},"operator":{"type":"string"},"origin":{"$ref":"#/components/schemas/FilterOrigin"},"value":{"type":"string"},"valueLabels":{"items":{"type":"string"},"type":"array"},"values":{"items":{"type":"string"},"type":"array"}},"required":["key","operator","value"],"type":"object"},"AdhocVariableKind":{"additionalProperties":false,"description":"Adhoc variable kind","properties":{"datasource":{"additionalProperties":false,"properties":{"name":{"type":"string"}},"type":"object"},"group":{"type":"string"},"kind":{"const":"AdhocVariable","type":"string"},"spec":{"$ref":"#/components/schemas/AdhocVariableSpec"}},"required":["kind","group","spec"],"type":"object"},"AdhocVariableSpec":{"additionalProperties":false,"description":"Adhoc variable specification","properties":{"allowCustomValue":{"default":true,"type":"boolean"},"baseFilters":{"items":{"$ref":"#/components/schemas/AdHocFilterWithLabels"},"type":"array"},"defaultKeys":{"items":{"$ref":"#/components/schemas/MetricFindValue"},"type":"array"},"description":{"type":"string"},"filters":{"items":{"$ref":"#/components/schemas/AdHocFilterWithLabels"},"type":"array"},"hide":{"$ref":"#/components/schemas/VariableHide","default":"dontHide"},"label":{"type":"string"},"name":{"default":"","type":"string"},"skipUrlSync":{"default":false,"type":"boolean"}},"required":["name","baseFilters","filters","defaultKeys","hide","skipUrlSync","allowCustomValue"],"type":"object"},"AnnotationEventFieldMapping":{"additionalProperties":false,"description":"Annotation event field mapping. Defines how to map a data frame field to an annotation event field.","properties":{"regex":{"description":"Regular expression to apply to the field value","type":"string"},"source":{"default":"field","description":"Source type for the field value","type":"string"},"value":{"description":"Constant value to use when source is \"text\"","type":"string"}},"type":"object"},"AnnotationPanelFilter":{"additionalProperties":false,"properties":{"exclude":{"default":false,"description":"Should the specified panels be included or excluded","type":"boolean"},"ids":{"description":"Panel IDs that should be included or excluded","items":{"type":"integer"},"type":"array"}},"required":["ids"],"type":"object"},"AnnotationQueryKind":{"additionalProperties":false,"properties":{"kind":{"const":"AnnotationQuery","type":"string"},"spec":{"$ref":"#/components/schemas/AnnotationQuerySpec"}},"required":["kind","spec"],"type":"object"},"AnnotationQueryPlacement":{"const":"inControlsMenu","description":"Annotation Query placement. Defines where the annotation query should be displayed.\n- \"inControlsMenu\" renders the annotation query in the dashboard controls dropdown menu","type":"string"},"AnnotationQuerySpec":{"additionalProperties":false,"properties":{"builtIn":{"default":false,"type":"boolean"},"enable":{"type":"boolean"},"filter":{"$ref":"#/components/schemas/AnnotationPanelFilter"},"hide":{"type":"boolean"},"iconColor":{"type":"string"},"legacyOptions":{"additionalProperties":{"additionalProperties":{},"type":"object"},"description":"Catch-all field for datasource-specific properties. Should not be available in as code tooling.","type":"object"},"mappings":{"additionalProperties":{"$ref":"#/components/schemas/AnnotationEventFieldMapping"},"description":"Mappings define how to convert data frame fields to annotation event fields.","type":"object"},"name":{"type":"string"},"placement":{"$ref":"#/components/schemas/AnnotationQueryPlacement","description":"Placement can be used to display the annotation query somewhere else on the dashboard other than the default location."},"query":{"$ref":"#/components/schemas/DataQueryKind"}},"required":["query","enable","hide","iconColor","name"],"type":"object"},"AutoGridLayoutItemKind":{"additionalProperties":false,"properties":{"kind":{"const":"AutoGridLayoutItem","type":"string"},"spec":{"$ref":"#/components/schemas/AutoGridLayoutItemSpec"}},"required":["kind","spec"],"type":"object"},"AutoGridLayoutItemSpec":{"additionalProperties":false,"properties":{"conditionalRendering":{"$ref":"#/components/schemas/ConditionalRenderingGroupKind"},"element":{"$ref":"#/components/schemas/ElementReference"},"repeat":{"$ref":"#/components/schemas/AutoGridRepeatOptions"}},"required":["element"],"type":"object"},"AutoGridLayoutKind":{"additionalProperties":false,"properties":{"kind":{"const":"AutoGridLayout","type":"string"},"spec":{"$ref":"#/components/schemas/AutoGridLayoutSpec"}},"required":["kind","spec"],"type":"object"},"AutoGridLayoutSpec":{"additionalProperties":false,"properties":{"columnWidth":{"type":"number"},"columnWidthMode":{"default":"standard","enum":["narrow","standard","wide","custom"],"type":"string"},"fillScreen":{"default":false,"type":"boolean"},"items":{"items":{"$ref":"#/components/schemas/AutoGridLayoutItemKind"},"type":"array"},"maxColumnCount":{"default":3,"type":"number"},"rowHeight":{"type":"number"},"rowHeightMode":{"default":"standard","enum":["short","standard","tall","custom"],"type":"string"}},"required":["columnWidthMode","rowHeightMode","items"],"type":"object"},"AutoGridRepeatOptions":{"additionalProperties":false,"properties":{"mode":{"$ref":"#/components/schemas/RepeatMode"},"value":{"type":"string"}},"required":["mode","value"],"type":"object"},"ConditionalRenderingDataKind":{"additionalProperties":false,"properties":{"kind":{"const":"ConditionalRenderingData","type":"string"},"spec":{"$ref":"#/components/schemas/ConditionalRenderingDataSpec"}},"required":["kind","spec"],"type":"object"},"ConditionalRenderingDataSpec":{"additionalProperties":false,"properties":{"value":{"type":"boolean"}},"required":["value"],"type":"object"},"ConditionalRenderingGroupKind":{"additionalProperties":false,"properties":{"kind":{"const":"ConditionalRenderingGroup","type":"string"},"spec":{"$ref":"#/components/schemas/ConditionalRenderingGroupSpec"}},"required":["kind","spec"],"type":"object"},"ConditionalRenderingGroupSpec":{"additionalProperties":false,"properties":{"condition":{"enum":["and","or"],"type":"string"},"items":{"items":{"oneOf":[{"$ref":"#/components/schemas/ConditionalRenderingVariableKind"},{"$ref":"#/components/schemas/ConditionalRenderingDataKind"},{"$ref":"#/components/schemas/ConditionalRenderingTimeRangeSizeKind"}]},"type":"array"},"visibility":{"enum":["show","hide"],"type":"string"}},"required":["visibility","condition","items"],"type":"object"},"ConditionalRenderingTimeRangeSizeKind":{"additionalProperties":false,"properties":{"kind":{"const":"ConditionalRenderingTimeRangeSize","type":"string"},"spec":{"$ref":"#/components/schemas/ConditionalRenderingTimeRangeSizeSpec"}},"required":["kind","spec"],"type":"object"},"ConditionalRenderingTimeRangeSizeSpec":{"additionalProperties":false,"properties":{"value":{"type":"string"}},"required":["value"],"type":"object"},"ConditionalRenderingVariableKind":{"additionalProperties":false,"properties":{"kind":{"const":"ConditionalRenderingVariable","type":"string"},"spec":{"$ref":"#/components/schemas/ConditionalRenderingVariableSpec"}},"required":["kind","spec"],"type":"object"},"ConditionalRenderingVariableSpec":{"additionalProperties":false,"properties":{"operator":{"enum":["equals","notEquals","matches","notMatches"],"type":"string"},"value":{"type":"string"},"variable":{"type":"string"}},"required":["variable","operator","value"],"type":"object"},"ConstantVariableKind":{"additionalProperties":false,"description":"Constant variable kind","properties":{"kind":{"const":"ConstantVariable","type":"string"},"spec":{"$ref":"#/components/schemas/ConstantVariableSpec"}},"required":["kind","spec"],"type":"object"},"ConstantVariableSpec":{"additionalProperties":false,"description":"Constant variable specification","properties":{"current":{"$ref":"#/components/schemas/VariableOption","default":{"text":"","value":""}},"description":{"type":"string"},"hide":{"$ref":"#/components/schemas/VariableHide","default":"dontHide"},"label":{"type":"string"},"name":{"default":"","type":"string"},"query":{"default":"","type":"string"},"skipUrlSync":{"default":false,"type":"boolean"}},"required":["name","query","current","hide","skipUrlSync"],"type":"object"},"ConversionStatus":{"additionalProperties":false,"description":"ConversionStatus is the status of the conversion of the dashboard.","properties":{"error":{"description":"The error message from the conversion.\nEmpty if the conversion has not failed.","type":"string"},"failed":{"description":"Whether from another version has failed.\nIf true, means that the dashboard is not valid,\nand the caller should instead fetch the stored version.","type":"boolean"},"source":{"additionalProperties":{},"description":"The original value map[string]any","type":"object"},"storedVersion":{"description":"The version which was stored when the dashboard was created / updated.\nFetching this version should always succeed.","type":"string"}},"required":["failed"],"type":"object"},"CustomVariableKind":{"additionalProperties":false,"description":"Custom variable kind","properties":{"kind":{"const":"CustomVariable","type":"string"},"spec":{"$ref":"#/components/schemas/CustomVariableSpec"}},"required":["kind","spec"],"type":"object"},"CustomVariableSpec":{"additionalProperties":false,"description":"Custom variable specification","properties":{"allValue":{"type":"string"},"allowCustomValue":{"default":true,"type":"boolean"},"current":{"$ref":"#/components/schemas/VariableOption"},"description":{"type":"string"},"hide":{"$ref":"#/components/schemas/VariableHide","default":"dontHide"},"includeAll":{"default":false,"type":"boolean"},"label":{"type":"string"},"multi":{"default":false,"type":"boolean"},"name":{"default":"","type":"string"},"options":{"items":{"$ref":"#/components/schemas/VariableOption"},"type":"array"},"query":{"default":"","type":"string"},"skipUrlSync":{"default":false,"type":"boolean"}},"required":["name","query","current","options","multi","includeAll","hide","skipUrlSync","allowCustomValue"],"type":"object"},"Dashboard":{"properties":{"spec":{"$ref":"#/components/schemas/spec"},"status":{"$ref":"#/components/schemas/status"}},"required":["spec"]},"DashboardCursorSync":{"description":"\"Off\" for no shared crosshair or tooltip (default).\n\"Crosshair\" for shared crosshair.\n\"Tooltip\" for shared crosshair AND shared tooltip.","enum":["Crosshair","Tooltip","Off"],"type":"string"},"DashboardLink":{"additionalProperties":false,"description":"Links with references to other dashboards or external resources","properties":{"asDropdown":{"default":false,"description":"If true, all dashboards links will be displayed in a dropdown. If false, all dashboards links will be displayed side by side. Only valid if the type is dashboards","type":"boolean"},"icon":{"description":"Icon name to be displayed with the link","type":"string"},"includeVars":{"default":false,"description":"If true, includes current template variables values in the link as query params","type":"boolean"},"keepTime":{"default":false,"description":"If true, includes current time range in the link as query params","type":"boolean"},"placement":{"$ref":"#/components/schemas/DashboardLinkPlacement","description":"Placement can be used to display the link somewhere else on the dashboard other than above the visualisations."},"tags":{"description":"List of tags to limit the linked dashboards. If empty, all dashboards will be displayed. Only valid if the type is dashboards","items":{"type":"string"},"type":"array"},"targetBlank":{"default":false,"description":"If true, the link will be opened in a new tab","type":"boolean"},"title":{"description":"Title to display with the link","type":"string"},"tooltip":{"description":"Tooltip to display when the user hovers their mouse over it","type":"string"},"type":{"$ref":"#/components/schemas/DashboardLinkType","description":"Link type. Accepted values are dashboards (to refer to another dashboard) and link (to refer to an external resource)\nFIXME: The type is generated as ` + "`" + `type: DashboardLinkType | dashboardLinkType.Link;` + "`" + ` but it should be ` + "`" + `type: DashboardLinkType` + "`" + `"},"url":{"description":"Link URL. Only required/valid if the type is link","type":"string"}},"required":["title","type","icon","tooltip","tags","asDropdown","targetBlank","includeVars","keepTime"],"type":"object"},"DashboardLinkPlacement":{"const":"inControlsMenu","description":"Dashboard Link placement. Defines where the link should be displayed.\n- \"inControlsMenu\" renders the link in bottom part of the dashboard controls dropdown menu","type":"string"},"DashboardLinkType":{"description":"Dashboard Link type. Accepted values are dashboards (to refer to another dashboard) and link (to refer to an external resource)","enum":["link","dashboards"],"type":"string"},"DataLink":{"additionalProperties":false,"properties":{"targetBlank":{"type":"boolean"},"title":{"type":"string"},"url":{"type":"string"}},"required":["title","url"],"type":"object"},"DataQueryKind":{"additionalProperties":false,"properties":{"datasource":{"additionalProperties":false,"description":"New type for datasource reference\nNot creating a new type until we figure out how to handle DS refs for group by, adhoc, and every place that uses DataSourceRef in TS.","properties":{"name":{"type":"string"}},"type":"object"},"group":{"type":"string"},"kind":{"const":"DataQuery","type":"string"},"spec":{"additionalProperties":{"additionalProperties":{},"type":"object"},"type":"object"},"version":{"default":"v0","type":"string"}},"required":["kind","group","version","spec"],"type":"object"},"DataTopic":{"description":"A topic is attached to DataFrame metadata in query results.\nThis specifies where the data should be used.","enum":["series","annotations","alertStates"],"type":"string"},"DataTransformerConfig":{"additionalProperties":false,"description":"Transformations allow to manipulate data returned by a query before the system applies a visualization.\nUsing transformations you can: rename fields, join time series data, perform mathematical operations across queries,\nuse the output of one transformation as the input to another transformation, etc.","properties":{"disabled":{"description":"Disabled transformations are skipped","type":"boolean"},"filter":{"$ref":"#/components/schemas/MatcherConfig","description":"Optional frame matcher. When missing it will be applied to all results"},"id":{"description":"Unique identifier of transformer","type":"string"},"options":{"additionalProperties":{},"description":"Options to be passed to the transformer\nValid options depend on the transformer id","type":"object"},"topic":{"$ref":"#/components/schemas/DataTopic","description":"Where to pull DataFrames from as input to transformation"}},"required":["id","options"],"type":"object"},"DatasourceVariableKind":{"additionalProperties":false,"description":"Datasource variable kind","properties":{"kind":{"const":"DatasourceVariable","type":"string"},"spec":{"$ref":"#/components/schemas/DatasourceVariableSpec"}},"required":["kind","spec"],"type":"object"},"DatasourceVariableSpec":{"additionalProperties":false,"description":"Datasource variable specification","properties":{"allValue":{"type":"string"},"allowCustomValue":{"default":true,"type":"boolean"},"current":{"$ref":"#/components/schemas/VariableOption","default":{"text":"","value":""}},"description":{"type":"string"},"hide":{"$ref":"#/components/schemas/VariableHide","default":"dontHide"},"includeAll":{"default":false,"type":"boolean"},"label":{"type":"string"},"multi":{"default":false,"type":"boolean"},"name":{"default":"","type":"string"},"options":{"items":{"$ref":"#/components/schemas/VariableOption"},"type":"array"},"pluginId":{"default":"","type":"string"},"refresh":{"$ref":"#/components/schemas/VariableRefresh","default":"never"},"regex":{"default":"","type":"string"},"skipUrlSync":{"default":false,"type":"boolean"}},"required":["name","pluginId","refresh","regex","current","options","multi","includeAll","hide","skipUrlSync","allowCustomValue"],"type":"object"},"DynamicConfigValue":{"additionalProperties":false,"properties":{"id":{"default":"","type":"string"},"value":{"additionalProperties":{},"type":"object"}},"required":["id"],"type":"object"},"Element":{"description":"Supported dashboard elements\n|* more element types in the future","oneOf":[{"$ref":"#/components/schemas/PanelKind"},{"$ref":"#/components/schemas/LibraryPanelKind"}]},"ElementReference":{"additionalProperties":false,"properties":{"kind":{"const":"ElementReference","type":"string"},"name":{"type":"string"}},"required":["kind","name"],"type":"object"},"FetchOptions":{"additionalProperties":false,"properties":{"body":{"type":"string"},"headers":{"items":{"items":{"type":"string"},"type":"array"},"type":"array"},"method":{"$ref":"#/components/schemas/HttpRequestMethod"},"queryParams":{"description":"These are 2D arrays of strings, each representing a key-value pair\nWe are defining them this way because we can't generate a go struct that\nthat would have exactly two strings in each sub-array","items":{"items":{"type":"string"},"type":"array"},"type":"array"},"url":{"type":"string"}},"required":["method","url"],"type":"object"},"FieldColor":{"additionalProperties":false,"description":"Map a field to a color.","properties":{"fixedColor":{"description":"The fixed color value for fixed or shades color modes.","type":"string"},"mode":{"$ref":"#/components/schemas/FieldColorModeId","description":"The main color scheme mode."},"seriesBy":{"$ref":"#/components/schemas/FieldColorSeriesByMode","description":"Some visualizations need to know how to assign a series color from by value color schemes."}},"required":["mode"],"type":"object"},"FieldColorModeId":{"description":"Color mode for a field. You can specify a single color, or select a continuous (gradient) color schemes, based on a value.\nContinuous color interpolates a color using the percentage of a value relative to min and max.\nAccepted values are:\n` + "`" + `thresholds` + "`" + `: From thresholds. Informs Grafana to take the color from the matching threshold\n` + "`" + `palette-classic` + "`" + `: Classic palette. Grafana will assign color by looking up a color in a palette by series index. Useful for Graphs and pie charts and other categorical data visualizations\n` + "`" + `palette-classic-by-name` + "`" + `: Classic palette (by name). Grafana will assign color by looking up a color in a palette by series name. Useful for Graphs and pie charts and other categorical data visualizations\n` + "`" + `continuous-viridis` + "`" + `: Continuous Viridis palette mode\n` + "`" + `continuous-magma` + "`" + `: Continuous Magma palette mode\n` + "`" + `continuous-plasma` + "`" + `: Continuous Plasma palette mode\n` + "`" + `continuous-inferno` + "`" + `: Continuous Inferno palette mode\n` + "`" + `continuous-cividis` + "`" + `: Continuous Cividis palette mode\n` + "`" + `continuous-GrYlRd` + "`" + `: Continuous Green-Yellow-Red palette mode\n` + "`" + `continuous-RdYlGr` + "`" + `: Continuous Red-Yellow-Green palette mode\n` + "`" + `continuous-BlYlRd` + "`" + `: Continuous Blue-Yellow-Red palette mode\n` + "`" + `continuous-YlRd` + "`" + `: Continuous Yellow-Red palette mode\n` + "`" + `continuous-BlPu` + "`" + `: Continuous Blue-Purple palette mode\n` + "`" + `continuous-YlBl` + "`" + `: Continuous Yellow-Blue palette mode\n` + "`" + `continuous-blues` + "`" + `: Continuous Blue palette mode\n` + "`" + `continuous-reds` + "`" + `: Continuous Red palette mode\n` + "`" + `continuous-greens` + "`" + `: Continuous Green palette mode\n` + "`" + `continuous-purples` + "`" + `: Continuous Purple palette mode\n` + "`" + `shades` + "`" + `: Shades of a single color. Specify a single color, useful in an override rule.\n` + "`" + `fixed` + "`" + `: Fixed color mode. Specify a single color, useful in an override rule.","enum":["thresholds","palette-classic","palette-classic-by-name","continuous-viridis","continuous-magma","continuous-plasma","continuous-inferno","continuous-cividis","continuous-GrYlRd","continuous-RdYlGr","continuous-BlYlRd","continuous-YlRd","continuous-BlPu","continuous-YlBl","continuous-blues","continuous-reds","continuous-greens","continuous-purples","fixed","shades"],"type":"string"},"FieldColorSeriesByMode":{"description":"Defines how to assign a series color from \"by value\" color schemes. For example for an aggregated data points like a timeseries, the color can be assigned by the min, max or last value.","enum":["min","max","last"],"type":"string"},"FieldConfig":{"additionalProperties":false,"description":"The data model used in Grafana, namely the data frame, is a columnar-oriented table structure that unifies both time series and table query results.\nEach column within this structure is called a field. A field can represent a single time series or table column.\nField options allow you to change how the data is displayed in your visualizations.","properties":{"actions":{"description":"Define interactive HTTP requests that can be triggered from data visualizations.","items":{"$ref":"#/components/schemas/Action"},"type":"array"},"color":{"$ref":"#/components/schemas/FieldColor","description":"Panel color configuration"},"custom":{"additionalProperties":{"additionalProperties":{},"type":"object"},"description":"custom is specified by the FieldConfig field\nin panel plugin schemas.","type":"object"},"decimals":{"description":"Specify the number of decimals Grafana includes in the rendered value.\nIf you leave this field blank, Grafana automatically truncates the number of decimals based on the value.\nFor example 1.1234 will display as 1.12 and 100.456 will display as 100.\nTo display all decimals, set the unit to ` + "`" + `String` + "`" + `.","type":"number"},"description":{"description":"Human readable field metadata","type":"string"},"displayName":{"description":"The display value for this field. This supports template variables blank is auto","type":"string"},"displayNameFromDS":{"description":"This can be used by data sources that return and explicit naming structure for values and labels\nWhen this property is configured, this value is used rather than the default naming strategy.","type":"string"},"filterable":{"description":"True if data source field supports ad-hoc filters","type":"boolean"},"links":{"description":"The behavior when clicking on a result","items":{"additionalProperties":{},"type":"object"},"type":"array"},"mappings":{"description":"Convert input values into a display string","items":{"$ref":"#/components/schemas/ValueMapping"},"type":"array"},"max":{"description":"The maximum value used in percentage threshold calculations. Leave blank for auto calculation based on all series and fields.","type":"number"},"min":{"description":"The minimum value used in percentage threshold calculations. Leave blank for auto calculation based on all series and fields.","type":"number"},"noValue":{"description":"Alternative to empty string","type":"string"},"path":{"description":"An explicit path to the field in the datasource. When the frame meta includes a path,\nThis will default to ` + "`" + `${frame.meta.path}/${field.name}\n\nWhen defined, this value can be used as an identifier within the datasource scope, and\nmay be used to update the results","type":"string"},"thresholds":{"$ref":"#/components/schemas/ThresholdsConfig","description":"Map numeric values to states"},"unit":{"description":"Unit a field should use. The unit you select is applied to all fields except time.\nYou can use the units ID availables in Grafana or a custom unit.\nAvailable units in Grafana: https://github.com/grafana/grafana/blob/main/packages/grafana-data/src/valueFormats/categories.ts\nAs custom unit, you can use the following formats:\n` + "`" + `suffix:\u003csuffix\u003e` + "`" + ` for custom unit that should go after value.\n` + "`" + `prefix:\u003cprefix\u003e` + "`" + ` for custom unit that should go before value.\n` + "`" + `time:\u003cformat\u003e` + "`" + ` For custom date time formats type for example ` + "`" + `time:YYYY-MM-DD` + "`" + `.\n` + "`" + `si:\u003cbase scale\u003e\u003cunit characters\u003e` + "`" + ` for custom SI units. For example: ` + "`" + `si: mF` + "`" + `. This one is a bit more advanced as you can specify both a unit and the source data scale. So if your source data is represented as milli (thousands of) something prefix the unit with that SI scale character.\n` + "`" + `count:\u003cunit\u003e` + "`" + ` for a custom count unit.\n` + "`" + `currency:\u003cunit\u003e` + "`" + ` for custom a currency unit.","type":"string"},"writeable":{"description":"True if data source can write a value to the path. Auth/authz are supported separately","type":"boolean"}},"type":"object"},"FieldConfigSource":{"additionalProperties":false,"description":"The data model used in Grafana, namely the data frame, is a columnar-oriented table structure that unifies both time series and table query results.\nEach column within this structure is called a field. A field can represent a single time series or table column.\nField options allow you to change how the data is displayed in your visualizations.","properties":{"defaults":{"$ref":"#/components/schemas/FieldConfig","description":"Defaults are the options applied to all fields."},"overrides":{"description":"Overrides are the options applied to specific fields overriding the defaults.","items":{"additionalProperties":false,"properties":{"__systemRef":{"description":"Describes config override rules created when interacting with Grafana.","type":"string"},"matcher":{"$ref":"#/components/schemas/MatcherConfig"},"properties":{"items":{"$ref":"#/components/schemas/DynamicConfigValue"},"type":"array"}},"required":["matcher","properties"],"type":"object"},"type":"array"}},"required":["defaults","overrides"],"type":"object"},"FilterOrigin":{"const":"dashboard","description":"Determine the origin of the adhoc variable filter","type":"string"},"GridLayoutItemKind":{"additionalProperties":false,"properties":{"kind":{"const":"GridLayoutItem","type":"string"},"spec":{"$ref":"#/components/schemas/GridLayoutItemSpec"}},"required":["kind","spec"],"type":"object"},"GridLayoutItemSpec":{"additionalProperties":false,"properties":{"element":{"$ref":"#/components/schemas/ElementReference","description":"reference to a PanelKind from dashboard.spec.elements Expressed as JSON Schema reference"},"height":{"type":"integer"},"repeat":{"$ref":"#/components/schemas/RepeatOptions"},"width":{"type":"integer"},"x":{"type":"integer"},"y":{"type":"integer"}},"required":["x","y","width","height","element"],"type":"object"},"GridLayoutKind":{"additionalProperties":false,"properties":{"kind":{"const":"GridLayout","type":"string"},"spec":{"$ref":"#/components/schemas/GridLayoutSpec"}},"required":["kind","spec"],"type":"object"},"GridLayoutSpec":{"additionalProperties":false,"properties":{"items":{"items":{"$ref":"#/components/schemas/GridLayoutItemKind"},"type":"array"}},"required":["items"],"type":"object"},"GroupByVariableKind":{"additionalProperties":false,"description":"Group variable kind","properties":{"datasource":{"additionalProperties":false,"properties":{"name":{"type":"string"}},"type":"object"},"group":{"type":"string"},"kind":{"const":"GroupByVariable","type":"string"},"spec":{"$ref":"#/components/schemas/GroupByVariableSpec"}},"required":["kind","group","spec"],"type":"object"},"GroupByVariableSpec":{"additionalProperties":false,"description":"GroupBy variable specification","properties":{"current":{"$ref":"#/components/schemas/VariableOption","default":{"text":"","value":""}},"defaultValue":{"$ref":"#/components/schemas/VariableOption"},"description":{"type":"string"},"hide":{"$ref":"#/components/schemas/VariableHide","default":"dontHide"},"label":{"type":"string"},"multi":{"default":false,"type":"boolean"},"name":{"default":"","type":"string"},"options":{"items":{"$ref":"#/components/schemas/VariableOption"},"type":"array"},"skipUrlSync":{"default":false,"type":"boolean"}},"required":["name","current","options","multi","hide","skipUrlSync"],"type":"object"},"HttpRequestMethod":{"enum":["GET","PUT","POST","DELETE","PATCH"],"type":"string"},"InfinityOptions":{"additionalProperties":false,"properties":{"body":{"type":"string"},"datasourceUid":{"type":"string"},"headers":{"items":{"items":{"type":"string"},"type":"array"},"type":"array"},"method":{"$ref":"#/components/schemas/HttpRequestMethod"},"queryParams":{"description":"These are 2D arrays of strings, each representing a key-value pair\nWe are defining them this way because we can't generate a go struct that\nthat would have exactly two strings in each sub-array","items":{"items":{"type":"string"},"type":"array"},"type":"array"},"url":{"type":"string"}},"required":["method","url","datasourceUid"],"type":"object"},"IntervalVariableKind":{"additionalProperties":false,"description":"Interval variable kind","properties":{"kind":{"const":"IntervalVariable","type":"string"},"spec":{"$ref":"#/components/schemas/IntervalVariableSpec"}},"required":["kind","spec"],"type":"object"},"IntervalVariableSpec":{"additionalProperties":false,"description":"Interval variable specification","properties":{"auto":{"default":false,"type":"boolean"},"auto_count":{"default":0,"type":"integer"},"auto_min":{"default":"","type":"string"},"current":{"$ref":"#/components/schemas/VariableOption","default":{"text":"","value":""}},"description":{"type":"string"},"hide":{"$ref":"#/components/schemas/VariableHide","default":"dontHide"},"label":{"type":"string"},"name":{"default":"","type":"string"},"options":{"items":{"$ref":"#/components/schemas/VariableOption"},"type":"array"},"query":{"default":"","type":"string"},"refresh":{"const":"onTimeRangeChanged","type":"string"},"skipUrlSync":{"default":false,"type":"boolean"}},"required":["name","query","current","options","auto","auto_min","auto_count","refresh","hide","skipUrlSync"],"type":"object"},"LibraryPanelKind":{"additionalProperties":false,"properties":{"kind":{"const":"LibraryPanel","type":"string"},"spec":{"$ref":"#/components/schemas/LibraryPanelKindSpec"}},"required":["kind","spec"],"type":"object"},"LibraryPanelKindSpec":{"additionalProperties":false,"properties":{"id":{"description":"Panel ID for the library panel in the dashboard","type":"number"},"libraryPanel":{"$ref":"#/components/schemas/LibraryPanelRef"},"title":{"description":"Title for the library panel in the dashboard","type":"string"}},"required":["id","title","libraryPanel"],"type":"object"},"LibraryPanelRef":{"additionalProperties":false,"description":"A library panel is a reusable panel that you can use in any dashboard.\nWhen you make a change to a library panel, that change propagates to all instances of where the panel is used.\nLibrary panels streamline reuse of panels across multiple dashboards.","properties":{"name":{"description":"Library panel name","type":"string"},"uid":{"description":"Library panel uid","type":"string"}},"required":["name","uid"],"type":"object"},"MappingType":{"description":"Supported value mapping types\n` + "`" + `value` + "`" + `: Maps text values to a color or different display text and color. For example, you can configure a value mapping so that all instances of the value 10 appear as Perfection! rather than the number.\n` + "`" + `range` + "`" + `: Maps numerical ranges to a display text and color. For example, if a value is within a certain range, you can configure a range value mapping to display Low or High rather than the number.\n` + "`" + `regex` + "`" + `: Maps regular expressions to replacement text and a color. For example, if a value is www.example.com, you can configure a regex value mapping so that Grafana displays www and truncates the domain.\n` + "`" + `special` + "`" + `: Maps special values like Null, NaN (not a number), and boolean values like true and false to a display text and color. See SpecialValueMatch to see the list of special values. For example, you can configure a special value mapping so that null values appear as N/A.","enum":["value","range","regex","special"],"type":"string"},"MatcherConfig":{"additionalProperties":false,"description":"Matcher is a predicate configuration. Based on the config a set of field(s) or values is filtered in order to apply override / transformation.\nIt comes with in id ( to resolve implementation from registry) and a configuration that’s specific to a particular matcher type.","properties":{"id":{"default":"","description":"The matcher id. This is used to find the matcher implementation from registry.","type":"string"},"options":{"additionalProperties":{},"description":"The matcher options. This is specific to the matcher implementation.","type":"object"}},"required":["id"],"type":"object"},"MetricFindValue":{"additionalProperties":false,"description":"Define the MetricFindValue type","properties":{"expandable":{"type":"boolean"},"group":{"type":"string"},"text":{"type":"string"},"value":{"oneOf":[{"type":"string"},{"type":"number"}]}},"required":["text"],"type":"object"},"PanelKind":{"additionalProperties":false,"properties":{"kind":{"const":"Panel","type":"string"},"spec":{"$ref":"#/components/schemas/PanelSpec"}},"required":["kind","spec"],"type":"object"},"PanelQueryKind":{"additionalProperties":false,"properties":{"kind":{"const":"PanelQuery","type":"string"},"spec":{"$ref":"#/components/schemas/PanelQuerySpec"}},"required":["kind","spec"],"type":"object"},"PanelQuerySpec":{"additionalProperties":false,"properties":{"hidden":{"type":"boolean"},"query":{"$ref":"#/components/schemas/DataQueryKind"},"refId":{"default":"A","type":"string"}},"required":["query","refId","hidden"],"type":"object"},"PanelSpec":{"additionalProperties":false,"properties":{"data":{"$ref":"#/components/schemas/QueryGroupKind"},"description":{"type":"string"},"id":{"type":"number"},"links":{"items":{"$ref":"#/components/schemas/DataLink"},"type":"array"},"title":{"type":"string"},"transparent":{"type":"boolean"},"vizConfig":{"$ref":"#/components/schemas/VizConfigKind"}},"required":["id","title","description","links","data","vizConfig"],"type":"object"},"QueryGroupKind":{"additionalProperties":false,"properties":{"kind":{"const":"QueryGroup","type":"string"},"spec":{"$ref":"#/components/schemas/QueryGroupSpec"}},"required":["kind","spec"],"type":"object"},"QueryGroupSpec":{"additionalProperties":false,"properties":{"queries":{"items":{"$ref":"#/components/schemas/PanelQueryKind"},"type":"array"},"queryOptions":{"$ref":"#/components/schemas/QueryOptionsSpec"},"transformations":{"items":{"$ref":"#/components/schemas/TransformationKind"},"type":"array"}},"required":["queries","transformations","queryOptions"],"type":"object"},"QueryOptionsSpec":{"additionalProperties":false,"properties":{"cacheTimeout":{"type":"string"},"hideTimeOverride":{"type":"boolean"},"interval":{"type":"string"},"maxDataPoints":{"type":"integer"},"queryCachingTTL":{"type":"integer"},"timeCompare":{"type":"string"},"timeFrom":{"type":"string"},"timeShift":{"type":"string"}},"type":"object"},"QueryVariableKind":{"additionalProperties":false,"description":"Query variable kind","properties":{"kind":{"const":"QueryVariable","type":"string"},"spec":{"$ref":"#/components/schemas/QueryVariableSpec"}},"required":["kind","spec"],"type":"object"},"QueryVariableSpec":{"additionalProperties":false,"description":"Query variable specification","properties":{"allValue":{"type":"string"},"allowCustomValue":{"default":true,"type":"boolean"},"current":{"$ref":"#/components/schemas/VariableOption","default":{"text":"","value":""}},"definition":{"type":"string"},"description":{"type":"string"},"hide":{"$ref":"#/components/schemas/VariableHide","default":"dontHide"},"includeAll":{"default":false,"type":"boolean"},"label":{"type":"string"},"multi":{"default":false,"type":"boolean"},"name":{"default":"","type":"string"},"options":{"items":{"$ref":"#/components/schemas/VariableOption"},"type":"array"},"placeholder":{"type":"string"},"query":{"$ref":"#/components/schemas/DataQueryKind"},"refresh":{"$ref":"#/components/schemas/VariableRefresh","default":"never"},"regex":{"default":"","type":"string"},"skipUrlSync":{"default":false,"type":"boolean"},"sort":{"$ref":"#/components/schemas/VariableSort"},"staticOptions":{"items":{"$ref":"#/components/schemas/VariableOption"},"type":"array"},"staticOptionsOrder":{"enum":["before","after","sorted"],"type":"string"}},"required":["name","current","hide","refresh","skipUrlSync","query","regex","sort","options","multi","includeAll","allowCustomValue"],"type":"object"},"RangeMap":{"additionalProperties":false,"description":"Maps numerical ranges to a display text and color.\nFor example, if a value is within a certain range, you can configure a range value mapping to display Low or High rather than the number.","properties":{"options":{"additionalProperties":false,"description":"Range to match against and the result to apply when the value is within the range","properties":{"from":{"description":"Min value of the range. It can be null which means -Infinity","type":"number"},"result":{"$ref":"#/components/schemas/ValueMappingResult","description":"Config to apply when the value is within the range"},"to":{"description":"Max value of the range. It can be null which means +Infinity","type":"number"}},"required":["from","to","result"],"type":"object"},"type":{"$ref":"#/components/schemas/MappingType"}},"required":["type","options"],"type":"object"},"RegexMap":{"additionalProperties":false,"description":"Maps regular expressions to replacement text and a color.\nFor example, if a value is www.example.com, you can configure a regex value mapping so that Grafana displays www and truncates the domain.","properties":{"options":{"additionalProperties":false,"description":"Regular expression to match against and the result to apply when the value matches the regex","properties":{"pattern":{"description":"Regular expression to match against","type":"string"},"result":{"$ref":"#/components/schemas/ValueMappingResult","description":"Config to apply when the value matches the regex"}},"required":["pattern","result"],"type":"object"},"type":{"$ref":"#/components/schemas/MappingType"}},"required":["type","options"],"type":"object"},"RepeatMode":{"const":"variable","description":"other repeat modes will be added in the future: label, frame","type":"string"},"RepeatOptions":{"additionalProperties":false,"properties":{"direction":{"enum":["h","v"],"type":"string"},"maxPerRow":{"type":"integer"},"mode":{"$ref":"#/components/schemas/RepeatMode"},"value":{"type":"string"}},"required":["mode","value"],"type":"object"},"RowRepeatOptions":{"additionalProperties":false,"properties":{"mode":{"$ref":"#/components/schemas/RepeatMode"},"value":{"type":"string"}},"required":["mode","value"],"type":"object"},"RowsLayoutKind":{"additionalProperties":false,"properties":{"kind":{"const":"RowsLayout","type":"string"},"spec":{"$ref":"#/components/schemas/RowsLayoutSpec"}},"required":["kind","spec"],"type":"object"},"RowsLayoutRowKind":{"additionalProperties":false,"properties":{"kind":{"const":"RowsLayoutRow","type":"string"},"spec":{"$ref":"#/components/schemas/RowsLayoutRowSpec"}},"required":["kind","spec"],"type":"object"},"RowsLayoutRowSpec":{"additionalProperties":false,"properties":{"collapse":{"type":"boolean"},"conditionalRendering":{"$ref":"#/components/schemas/ConditionalRenderingGroupKind"},"fillScreen":{"type":"boolean"},"hideHeader":{"type":"boolean"},"layout":{"oneOf":[{"$ref":"#/components/schemas/GridLayoutKind"},{"$ref":"#/components/schemas/AutoGridLayoutKind"},{"$ref":"#/components/schemas/TabsLayoutKind"},{"$ref":"#/components/schemas/RowsLayoutKind"}]},"repeat":{"$ref":"#/components/schemas/RowRepeatOptions"},"title":{"type":"string"}},"required":["layout"],"type":"object"},"RowsLayoutSpec":{"additionalProperties":false,"properties":{"rows":{"items":{"$ref":"#/components/schemas/RowsLayoutRowKind"},"type":"array"}},"required":["rows"],"type":"object"},"SpecialValueMap":{"additionalProperties":false,"description":"Maps special values like Null, NaN (not a number), and boolean values like true and false to a display text and color.\nSee SpecialValueMatch to see the list of special values.\nFor example, you can configure a special value mapping so that null values appear as N/A.","properties":{"options":{"additionalProperties":false,"properties":{"match":{"$ref":"#/components/schemas/SpecialValueMatch","description":"Special value to match against"},"result":{"$ref":"#/components/schemas/ValueMappingResult","description":"Config to apply when the value matches the special value"}},"required":["match","result"],"type":"object"},"type":{"$ref":"#/components/schemas/MappingType"}},"required":["type","options"],"type":"object"},"SpecialValueMatch":{"description":"Special value types supported by the ` + "`" + `SpecialValueMap` + "`" + `","enum":["true","false","null","nan","null+nan","empty"],"type":"string"},"SwitchVariableKind":{"additionalProperties":false,"properties":{"kind":{"const":"SwitchVariable","type":"string"},"spec":{"$ref":"#/components/schemas/SwitchVariableSpec"}},"required":["kind","spec"],"type":"object"},"SwitchVariableSpec":{"additionalProperties":false,"properties":{"current":{"default":"false","type":"string"},"description":{"type":"string"},"disabledValue":{"default":"false","type":"string"},"enabledValue":{"default":"true","type":"string"},"hide":{"$ref":"#/components/schemas/VariableHide","default":"dontHide"},"label":{"type":"string"},"name":{"default":"","type":"string"},"skipUrlSync":{"default":false,"type":"boolean"}},"required":["name","current","enabledValue","disabledValue","hide","skipUrlSync"],"type":"object"},"TabRepeatOptions":{"additionalProperties":false,"properties":{"mode":{"$ref":"#/components/schemas/RepeatMode"},"value":{"type":"string"}},"required":["mode","value"],"type":"object"},"TabsLayoutKind":{"additionalProperties":false,"properties":{"kind":{"const":"TabsLayout","type":"string"},"spec":{"$ref":"#/components/schemas/TabsLayoutSpec"}},"required":["kind","spec"],"type":"object"},"TabsLayoutSpec":{"additionalProperties":false,"properties":{"tabs":{"items":{"$ref":"#/components/schemas/TabsLayoutTabKind"},"type":"array"}},"required":["tabs"],"type":"object"},"TabsLayoutTabKind":{"additionalProperties":false,"properties":{"kind":{"const":"TabsLayoutTab","type":"string"},"spec":{"$ref":"#/components/schemas/TabsLayoutTabSpec"}},"required":["kind","spec"],"type":"object"},"TabsLayoutTabSpec":{"additionalProperties":false,"properties":{"conditionalRendering":{"$ref":"#/components/schemas/ConditionalRenderingGroupKind"},"layout":{"oneOf":[{"$ref":"#/components/schemas/GridLayoutKind"},{"$ref":"#/components/schemas/RowsLayoutKind"},{"$ref":"#/components/schemas/AutoGridLayoutKind"},{"$ref":"#/components/schemas/TabsLayoutKind"}]},"repeat":{"$ref":"#/components/schemas/TabRepeatOptions"},"title":{"type":"string"}},"required":["layout"],"type":"object"},"TextVariableKind":{"additionalProperties":false,"description":"Text variable kind","properties":{"kind":{"const":"TextVariable","type":"string"},"spec":{"$ref":"#/components/schemas/TextVariableSpec"}},"required":["kind","spec"],"type":"object"},"TextVariableSpec":{"additionalProperties":false,"description":"Text variable specification","properties":{"current":{"$ref":"#/components/schemas/VariableOption","default":{"text":"","value":""}},"description":{"type":"string"},"hide":{"$ref":"#/components/schemas/VariableHide","default":"dontHide"},"label":{"type":"string"},"name":{"default":"","type":"string"},"query":{"default":"","type":"string"},"skipUrlSync":{"default":false,"type":"boolean"}},"required":["name","current","query","hide","skipUrlSync"],"type":"object"},"Threshold":{"additionalProperties":false,"properties":{"color":{"type":"string"},"value":{"description":"Value null means -Infinity","type":"number"}},"required":["value","color"],"type":"object"},"ThresholdsConfig":{"additionalProperties":false,"properties":{"mode":{"$ref":"#/components/schemas/ThresholdsMode"},"steps":{"items":{"$ref":"#/components/schemas/Threshold"},"type":"array"}},"required":["mode","steps"],"type":"object"},"ThresholdsMode":{"enum":["absolute","percentage"],"type":"string"},"TimeRangeOption":{"additionalProperties":false,"properties":{"display":{"default":"Last 6 hours","type":"string"},"from":{"default":"now-6h","type":"string"},"to":{"default":"now","type":"string"}},"required":["display","from","to"],"type":"object"},"TimeSettingsSpec":{"additionalProperties":false,"description":"Time configuration\nIt defines the default time config for the time picker, the refresh picker for the specific dashboard.","properties":{"autoRefresh":{"default":"","description":"Refresh rate of dashboard. Represented via interval string, e.g. \"5s\", \"1m\", \"1h\", \"1d\".\nv1: refresh","type":"string"},"autoRefreshIntervals":{"default":["5s","10s","30s","1m","5m","15m","30m","1h","2h","1d"],"description":"Interval options available in the refresh picker dropdown.\nv1: timepicker.refresh_intervals","items":{"type":"string"},"type":"array"},"fiscalYearStartMonth":{"default":0,"description":"The month that the fiscal year starts on. 0 = January, 11 = December","type":"integer"},"from":{"default":"now-6h","description":"Start time range for dashboard.\nAccepted values are relative time strings like \"now-6h\" or absolute time strings like \"2020-07-10T08:00:00.000Z\".","type":"string"},"hideTimepicker":{"default":false,"description":"Whether timepicker is visible or not.\nv1: timepicker.hidden","type":"boolean"},"nowDelay":{"description":"Override the now time by entering a time delay. Use this option to accommodate known delays in data aggregation to avoid null values.\nv1: timepicker.nowDelay","type":"string"},"quickRanges":{"description":"Selectable options available in the time picker dropdown. Has no effect on provisioned dashboard.\nv1: timepicker.quick_ranges , not exposed in the UI","items":{"$ref":"#/components/schemas/TimeRangeOption"},"type":"array"},"timezone":{"default":"browser","description":"Timezone of dashboard. Accepted values are IANA TZDB zone ID or \"browser\" or \"utc\".","type":"string"},"to":{"default":"now","description":"End time range for dashboard.\nAccepted values are relative time strings like \"now-6h\" or absolute time strings like \"2020-07-10T08:00:00.000Z\".","type":"string"},"weekStart":{"description":"Day when the week starts. Expressed by the name of the day in lowercase, e.g. \"monday\".","enum":["saturday","monday","sunday"],"type":"string"}},"required":["from","to","autoRefresh","autoRefreshIntervals","hideTimepicker","fiscalYearStartMonth"],"type":"object"},"TransformationKind":{"additionalProperties":false,"properties":{"kind":{"description":"The kind of a TransformationKind is the transformation ID","type":"string"},"spec":{"$ref":"#/components/schemas/DataTransformerConfig"}},"required":["kind","spec"],"type":"object"},"ValueMap":{"additionalProperties":false,"description":"Maps text values to a color or different display text and color.\nFor example, you can configure a value mapping so that all instances of the value 10 appear as Perfection! rather than the number.","properties":{"options":{"additionalProperties":{"$ref":"#/components/schemas/ValueMappingResult"},"description":"Map with \u003cvalue_to_match\u003e: ValueMappingResult. For example: { \"10\": { text: \"Perfection!\", color: \"green\" } }","type":"object"},"type":{"$ref":"#/components/schemas/MappingType"}},"required":["type","options"],"type":"object"},"ValueMapping":{"oneOf":[{"$ref":"#/components/schemas/ValueMap"},{"$ref":"#/components/schemas/RangeMap"},{"$ref":"#/components/schemas/RegexMap"},{"$ref":"#/components/schemas/SpecialValueMap"}]},"ValueMappingResult":{"additionalProperties":false,"description":"Result used as replacement with text and color when the value matches","properties":{"color":{"description":"Text to use when the value matches","type":"string"},"icon":{"description":"Icon to display when the value matches. Only specific visualizations.","type":"string"},"index":{"description":"Position in the mapping array. Only used internally.","type":"integer"},"text":{"description":"Text to display when the value matches","type":"string"}},"type":"object"},"VariableHide":{"description":"Determine if the variable shows on dashboard\nAccepted values are ` + "`" + `dontHide` + "`" + ` (show label and value), ` + "`" + `hideLabel` + "`" + ` (show value only), ` + "`" + `hideVariable` + "`" + ` (show nothing), ` + "`" + `inControlsMenu` + "`" + ` (show in a drop-down menu).","enum":["dontHide","hideLabel","hideVariable","inControlsMenu"],"type":"string"},"VariableKind":{"oneOf":[{"$ref":"#/components/schemas/QueryVariableKind"},{"$ref":"#/components/schemas/TextVariableKind"},{"$ref":"#/components/schemas/ConstantVariableKind"},{"$ref":"#/components/schemas/DatasourceVariableKind"},{"$ref":"#/components/schemas/IntervalVariableKind"},{"$ref":"#/components/schemas/CustomVariableKind"},{"$ref":"#/components/schemas/GroupByVariableKind"},{"$ref":"#/components/schemas/AdhocVariableKind"},{"$ref":"#/components/schemas/SwitchVariableKind"}]},"VariableOption":{"additionalProperties":false,"description":"Variable option specification","properties":{"selected":{"description":"Whether the option is selected or not","type":"boolean"},"text":{"description":"Text to be displayed for the option","oneOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}]},"value":{"description":"Value of the option","oneOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}]}},"required":["text","value"],"type":"object"},"VariableRefresh":{"description":"Options to config when to refresh a variable\n` + "`" + `never` + "`" + `: Never refresh the variable\n` + "`" + `onDashboardLoad` + "`" + `: Queries the data source every time the dashboard loads.\n` + "`" + `onTimeRangeChanged` + "`" + `: Queries the data source when the dashboard time range changes.","enum":["never","onDashboardLoad","onTimeRangeChanged"],"type":"string"},"VariableSort":{"description":"Sort variable options\nAccepted values are:\n` + "`" + `disabled` + "`" + `: No sorting\n` + "`" + `alphabeticalAsc` + "`" + `: Alphabetical ASC\n` + "`" + `alphabeticalDesc` + "`" + `: Alphabetical DESC\n` + "`" + `numericalAsc` + "`" + `: Numerical ASC\n` + "`" + `numericalDesc` + "`" + `: Numerical DESC\n` + "`" + `alphabeticalCaseInsensitiveAsc` + "`" + `: Alphabetical Case Insensitive ASC\n` + "`" + `alphabeticalCaseInsensitiveDesc` + "`" + `: Alphabetical Case Insensitive DESC\n` + "`" + `naturalAsc` + "`" + `: Natural ASC\n` + "`" + `naturalDesc` + "`" + `: Natural DESC\nVariableSort enum with default value","enum":["disabled","alphabeticalAsc","alphabeticalDesc","numericalAsc","numericalDesc","alphabeticalCaseInsensitiveAsc","alphabeticalCaseInsensitiveDesc","naturalAsc","naturalDesc"],"type":"string"},"VizConfigKind":{"additionalProperties":false,"properties":{"group":{"description":"The group is the plugin ID","type":"string"},"kind":{"const":"VizConfig","type":"string"},"spec":{"$ref":"#/components/schemas/VizConfigSpec"},"version":{"type":"string"}},"required":["kind","group","version","spec"],"type":"object"},"VizConfigSpec":{"additionalProperties":false,"description":"--- Kinds ---","properties":{"fieldConfig":{"$ref":"#/components/schemas/FieldConfigSource"},"options":{"additionalProperties":{"additionalProperties":{},"type":"object"},"type":"object"}},"required":["options","fieldConfig"],"type":"object"},"spec":{"additionalProperties":false,"properties":{"annotations":{"items":{"$ref":"#/components/schemas/AnnotationQueryKind"},"type":"array"},"cursorSync":{"$ref":"#/components/schemas/DashboardCursorSync","default":"Off","description":"Configuration of dashboard cursor sync behavior.\n\"Off\" for no shared crosshair or tooltip (default).\n\"Crosshair\" for shared crosshair.\n\"Tooltip\" for shared crosshair AND shared tooltip."},"description":{"description":"Description of dashboard.","type":"string"},"editable":{"default":true,"description":"Whether a dashboard is editable or not.","type":"boolean"},"elements":{"additionalProperties":{"$ref":"#/components/schemas/Element"},"type":"object"},"layout":{"oneOf":[{"$ref":"#/components/schemas/GridLayoutKind"},{"$ref":"#/components/schemas/RowsLayoutKind"},{"$ref":"#/components/schemas/AutoGridLayoutKind"},{"$ref":"#/components/schemas/TabsLayoutKind"}]},"links":{"description":"Links with references to other dashboards or external websites.","items":{"$ref":"#/components/schemas/DashboardLink"},"type":"array"},"liveNow":{"description":"When set to true, the dashboard will redraw panels at an interval matching the pixel width.\nThis will keep data \"moving left\" regardless of the query refresh rate. This setting helps\navoid dashboards presenting stale live data.","type":"boolean"},"preload":{"default":false,"description":"When set to true, the dashboard will load all panels in the dashboard when it's loaded.","type":"boolean"},"revision":{"description":"Plugins only. The version of the dashboard installed together with the plugin.\nThis is used to determine if the dashboard should be updated when the plugin is updated.","type":"integer"},"tags":{"description":"Tags associated with dashboard.","items":{"type":"string"},"type":"array"},"timeSettings":{"$ref":"#/components/schemas/TimeSettingsSpec"},"title":{"description":"Title of dashboard.","type":"string"},"variables":{"description":"Configured template variables.","items":{"$ref":"#/components/schemas/VariableKind"},"type":"array"}},"required":["annotations","cursorSync","elements","layout","links","preload","tags","timeSettings","title","variables"],"type":"object"},"status":{"additionalProperties":false,"properties":{"conversion":{"$ref":"#/components/schemas/ConversionStatus","description":"Optional conversion status."}},"type":"object"}}`) + versionSchemaDashboardv2beta1 app.VersionSchema + _ = json.Unmarshal(rawSchemaDashboardv2beta1, &versionSchemaDashboardv2beta1) +) + var appManifestData = app.ManifestData{ AppName: "dashboard", Group: "dashboard.grafana.app", @@ -35,6 +54,7 @@ var appManifestData = app.ManifestData{ Plural: "Dashboards", Scope: "Namespaced", Conversion: false, + Schema: &versionSchemaDashboardv0alpha1, }, { @@ -42,6 +62,7 @@ var appManifestData = app.ManifestData{ Plural: "Snapshots", Scope: "Namespaced", Conversion: false, + Schema: &versionSchemaSnapshotv0alpha1, }, }, Routes: app.ManifestVersionRoutes{ @@ -60,6 +81,7 @@ var appManifestData = app.ManifestData{ Plural: "Dashboards", Scope: "Namespaced", Conversion: false, + Schema: &versionSchemaDashboardv1beta1, }, }, Routes: app.ManifestVersionRoutes{ @@ -78,6 +100,7 @@ var appManifestData = app.ManifestData{ Plural: "Dashboards", Scope: "Namespaced", Conversion: false, + Schema: &versionSchemaDashboardv2alpha1, }, }, Routes: app.ManifestVersionRoutes{ @@ -96,6 +119,7 @@ var appManifestData = app.ManifestData{ Plural: "Dashboards", Scope: "Namespaced", Conversion: false, + Schema: &versionSchemaDashboardv2beta1, }, }, Routes: app.ManifestVersionRoutes{ diff --git a/pkg/registry/apis/dashboard/register.go b/pkg/registry/apis/dashboard/register.go index 16873ec2101..e9925f0a3b7 100644 --- a/pkg/registry/apis/dashboard/register.go +++ b/pkg/registry/apis/dashboard/register.go @@ -24,6 +24,7 @@ import ( authlib "github.com/grafana/authlib/types" "github.com/grafana/grafana-app-sdk/logging" + manifestdata "github.com/grafana/grafana/apps/dashboard/pkg/apis" internal "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard" dashv0 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v0alpha1" dashv1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1" @@ -843,6 +844,50 @@ func (b *DashboardsAPIBuilder) GetOpenAPIDefinitions() common.GetOpenAPIDefiniti maps.Copy(defs, dashv1.GetOpenAPIDefinitions(ref)) maps.Copy(defs, dashv2alpha1.GetOpenAPIDefinitions(ref)) maps.Copy(defs, dashv2beta1.GetOpenAPIDefinitions(ref)) + md := manifestdata.LocalManifest().ManifestData + // Overwrite the OpenAPI generated from kubernetes (sourced from the go types) with the OpenAPI generated by grafana-app-sdk + // from the manifest CUE, as it correctly handles the CUE disjunctions in the dashboard spec. + // We don't touch any types which were not specified in the manifest CUE (such as custom route types). + for _, version := range md.Versions { + // We don't need to correct the v0 or v1 openAPI as the spec type is just `any` + if len(version.Name) > 1 && (version.Name[1] == '0' || version.Name[1] == '1') { + continue + } + for _, kind := range version.Kinds { + pkgPrefix := fmt.Sprintf("github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/%s", version.Name) + oapi, err := kind.Schema.AsKubeOpenAPI(schema.GroupVersionKind{ + Group: md.Group, + Version: version.Name, + Kind: kind.Kind, + }, ref, pkgPrefix) + if err != nil { + logging.DefaultLogger.Error("unable to generate openAPI for kind %s: %w", kind.Kind, err) + continue + } + maps.Copy(defs, oapi) + } + } + + // Fix legacyOptions schema for v2alpha1 and v2beta1 to allow any value type + // The generated schema incorrectly restricts values to objects, but map[string]interface{} can hold any type + // This fix must be applied here so structured-merge-diff uses the correct schema + // For some reason this issue occurs with both the kubernetes-generated openAPI sourced from go, _and_ the OpenAPI from the AppManifest + // TODO: @IfSentient this should really be addressed in the app-sdk's generation, or work out what about this particular CUE value is broken + for _, defKey := range []string{ + "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardAnnotationQuerySpec", + "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1.DashboardAnnotationQuerySpec", + } { + if def, ok := defs[defKey]; ok { + if legacyOptions, ok := def.Schema.Properties["legacyOptions"]; ok { + // Fix: Use additionalProperties: true to allow any value type (string, number, boolean, array, object, etc.) + // instead of restricting to objects only. This must match map[string]interface{} semantics. + legacyOptions.AdditionalProperties = &spec.SchemaOrBool{Allows: true} + def.Schema.Properties["legacyOptions"] = legacyOptions + defs[defKey] = def + } + } + } + return defs } } diff --git a/pkg/tests/apis/openapi_snapshots/dashboard.grafana.app-v2alpha1.json b/pkg/tests/apis/openapi_snapshots/dashboard.grafana.app-v2alpha1.json index 567636ff7fc..2cad6213d04 100644 --- a/pkg/tests/apis/openapi_snapshots/dashboard.grafana.app-v2alpha1.json +++ b/pkg/tests/apis/openapi_snapshots/dashboard.grafana.app-v2alpha1.json @@ -968,9 +968,10 @@ "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.Dashboard": { "type": "object", "required": [ + "kind", + "apiVersion", "metadata", - "spec", - "status" + "spec" ], "properties": { "apiVersion": { @@ -990,21 +991,10 @@ ] }, "spec": { - "description": "Spec is the spec of the Dashboard", - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardSpec" - } - ] + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardSpec" }, "status": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardStatus" - } - ] + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardStatus" } }, "x-kubernetes-group-version-kind": [ @@ -1085,28 +1075,35 @@ "type": "boolean" }, "style": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardV2alpha1ActionStyle" + "type": "object", + "properties": { + "backgroundColor": { + "type": "string" + } + }, + "additionalProperties": false }, "title": { - "type": "string", - "default": "" + "type": "string" }, "type": { - "type": "string", - "default": "" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardActionType" }, "variables": { "type": "array", "items": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardActionVariable" - } - ] + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardActionVariable" } } - } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardActionType": { + "type": "string", + "enum": [ + "fetch", + "infinity" + ] }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardActionVariable": { "type": "object", @@ -1117,18 +1114,20 @@ ], "properties": { "key": { - "type": "string", - "default": "" + "type": "string" }, "name": { - "type": "string", - "default": "" + "type": "string" }, "type": { - "type": "string", - "default": "" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardActionVariableType" } - } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardActionVariableType": { + "description": "Action variable type", + "type": "string" }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardAdHocFilterWithLabels": { "description": "Define the AdHocFilterWithLabels type", @@ -1147,38 +1146,34 @@ "type": "boolean" }, "key": { - "type": "string", - "default": "" + "type": "string" }, "keyLabel": { "type": "string" }, "operator": { - "type": "string", - "default": "" - }, - "origin": { "type": "string" }, + "origin": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardFilterOrigin" + }, "value": { - "type": "string", - "default": "" + "type": "string" }, "valueLabels": { "type": "array", "items": { - "type": "string", - "default": "" + "type": "string" } }, "values": { "type": "array", "items": { - "type": "string", - "default": "" + "type": "string" } } - } + }, + "additionalProperties": false }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardAdhocVariableKind": { "description": "Adhoc variable kind", @@ -1189,18 +1184,13 @@ ], "properties": { "kind": { - "type": "string", - "default": "" + "type": "string" }, "spec": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardAdhocVariableSpec" - } - ] + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardAdhocVariableSpec" } - } + }, + "additionalProperties": false }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardAdhocVariableSpec": { "description": "Adhoc variable specification", @@ -1217,17 +1207,12 @@ "properties": { "allowCustomValue": { "type": "boolean", - "default": false + "default": true }, "baseFilters": { "type": "array", "items": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardAdHocFilterWithLabels" - } - ] + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardAdHocFilterWithLabels" } }, "datasource": { @@ -1236,12 +1221,7 @@ "defaultKeys": { "type": "array", "items": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardMetricFindValue" - } - ] + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardMetricFindValue" } }, "description": { @@ -1250,17 +1230,11 @@ "filters": { "type": "array", "items": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardAdHocFilterWithLabels" - } - ] + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardAdHocFilterWithLabels" } }, "hide": { - "type": "string", - "default": "" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardVariableHide" }, "label": { "type": "string" @@ -1273,7 +1247,8 @@ "type": "boolean", "default": false } - } + }, + "additionalProperties": false }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardAnnotationEventFieldMapping": { "description": "Annotation event field mapping. Defines how to map a data frame field to an annotation event field.", @@ -1285,13 +1260,15 @@ }, "source": { "description": "Source type for the field value", - "type": "string" + "type": "string", + "default": "field" }, "value": { "description": "Constant value to use when source is \"text\"", "type": "string" } - } + }, + "additionalProperties": false }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardAnnotationPanelFilter": { "type": "object", @@ -1301,18 +1278,18 @@ "properties": { "exclude": { "description": "Should the specified panels be included or excluded", - "type": "boolean" + "type": "boolean", + "default": false }, "ids": { "description": "Panel IDs that should be included or excluded", "type": "array", "items": { - "type": "integer", - "format": "int64", - "default": 0 + "type": "integer" } } - } + }, + "additionalProperties": false }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardAnnotationQueryKind": { "type": "object", @@ -1322,18 +1299,13 @@ ], "properties": { "kind": { - "type": "string", - "default": "" + "type": "string" }, "spec": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardAnnotationQuerySpec" - } - ] + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardAnnotationQuerySpec" } - } + }, + "additionalProperties": false }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardAnnotationQuerySpec": { "type": "object", @@ -1345,53 +1317,44 @@ ], "properties": { "builtIn": { - "type": "boolean" + "type": "boolean", + "default": false }, "datasource": { "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardDataSourceRef" }, "enable": { - "type": "boolean", - "default": false + "type": "boolean" }, "filter": { "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardAnnotationPanelFilter" }, "hide": { - "type": "boolean", - "default": false + "type": "boolean" }, "iconColor": { - "type": "string", - "default": "" + "type": "string" }, "legacyOptions": { "description": "Catch-all field for datasource-specific properties", "type": "object", - "additionalProperties": { - "type": "object" - } + "additionalProperties": true }, "mappings": { "description": "Mappings define how to convert data frame fields to annotation event fields.", "type": "object", "additionalProperties": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardAnnotationEventFieldMapping" - } - ] + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardAnnotationEventFieldMapping" } }, "name": { - "type": "string", - "default": "" + "type": "string" }, "query": { "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardDataQueryKind" } - } + }, + "additionalProperties": false }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardAutoGridLayoutItemKind": { "type": "object", @@ -1401,18 +1364,13 @@ ], "properties": { "kind": { - "type": "string", - "default": "" + "type": "string" }, "spec": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardAutoGridLayoutItemSpec" - } - ] + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardAutoGridLayoutItemSpec" } - } + }, + "additionalProperties": false }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardAutoGridLayoutItemSpec": { "type": "object", @@ -1424,17 +1382,13 @@ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardConditionalRenderingGroupKind" }, "element": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardElementReference" - } - ] + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardElementReference" }, "repeat": { "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardAutoGridRepeatOptions" } - } + }, + "additionalProperties": false }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardAutoGridLayoutKind": { "type": "object", @@ -1444,18 +1398,13 @@ ], "properties": { "kind": { - "type": "string", - "default": "" + "type": "string" }, "spec": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardAutoGridLayoutSpec" - } - ] + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardAutoGridLayoutSpec" } - } + }, + "additionalProperties": false }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardAutoGridLayoutSpec": { "type": "object", @@ -1466,40 +1415,47 @@ ], "properties": { "columnWidth": { - "type": "number", - "format": "double" + "type": "number" }, "columnWidthMode": { "type": "string", - "default": "" + "default": "standard", + "enum": [ + "narrow", + "standard", + "wide", + "custom" + ] }, "fillScreen": { - "type": "boolean" + "type": "boolean", + "default": false }, "items": { "type": "array", "items": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardAutoGridLayoutItemKind" - } - ] + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardAutoGridLayoutItemKind" } }, "maxColumnCount": { "type": "number", - "format": "double" + "default": 3 }, "rowHeight": { - "type": "number", - "format": "double" + "type": "number" }, "rowHeightMode": { "type": "string", - "default": "" + "default": "standard", + "enum": [ + "short", + "standard", + "tall", + "custom" + ] } - } + }, + "additionalProperties": false }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardAutoGridRepeatOptions": { "type": "object", @@ -1509,14 +1465,13 @@ ], "properties": { "mode": { - "type": "string", - "default": "" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardRepeatMode" }, "value": { - "type": "string", - "default": "" + "type": "string" } - } + }, + "additionalProperties": false }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardConditionalRenderingDataKind": { "type": "object", @@ -1526,18 +1481,13 @@ ], "properties": { "kind": { - "type": "string", - "default": "" + "type": "string" }, "spec": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardConditionalRenderingDataSpec" - } - ] + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardConditionalRenderingDataSpec" } - } + }, + "additionalProperties": false }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardConditionalRenderingDataSpec": { "type": "object", @@ -1546,10 +1496,10 @@ ], "properties": { "value": { - "type": "boolean", - "default": false + "type": "boolean" } - } + }, + "additionalProperties": false }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardConditionalRenderingGroupKind": { "type": "object", @@ -1559,18 +1509,13 @@ ], "properties": { "kind": { - "type": "string", - "default": "" + "type": "string" }, "spec": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardConditionalRenderingGroupSpec" - } - ] + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardConditionalRenderingGroupSpec" } - } + }, + "additionalProperties": false }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardConditionalRenderingGroupSpec": { "type": "object", @@ -1582,19 +1527,36 @@ "properties": { "condition": { "type": "string", - "default": "" + "enum": [ + "and", + "or" + ] }, "items": { "type": "array", "items": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardConditionalRenderingVariableKindOrConditionalRenderingDataKindOrConditionalRenderingTimeRangeSizeKind" + "oneOf": [ + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardConditionalRenderingVariableKind" + }, + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardConditionalRenderingDataKind" + }, + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardConditionalRenderingTimeRangeSizeKind" + } + ] } }, "visibility": { "type": "string", - "default": "" + "enum": [ + "show", + "hide" + ] } - } + }, + "additionalProperties": false }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardConditionalRenderingTimeRangeSizeKind": { "type": "object", @@ -1604,18 +1566,13 @@ ], "properties": { "kind": { - "type": "string", - "default": "" + "type": "string" }, "spec": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardConditionalRenderingTimeRangeSizeSpec" - } - ] + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardConditionalRenderingTimeRangeSizeSpec" } - } + }, + "additionalProperties": false }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardConditionalRenderingTimeRangeSizeSpec": { "type": "object", @@ -1624,10 +1581,10 @@ ], "properties": { "value": { - "type": "string", - "default": "" + "type": "string" } - } + }, + "additionalProperties": false }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardConditionalRenderingVariableKind": { "type": "object", @@ -1637,32 +1594,13 @@ ], "properties": { "kind": { - "type": "string", - "default": "" + "type": "string" }, "spec": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardConditionalRenderingVariableSpec" - } - ] + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardConditionalRenderingVariableSpec" } - } - }, - "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardConditionalRenderingVariableKindOrConditionalRenderingDataKindOrConditionalRenderingTimeRangeSizeKind": { - "type": "object", - "properties": { - "ConditionalRenderingDataKind": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardConditionalRenderingDataKind" - }, - "ConditionalRenderingTimeRangeSizeKind": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardConditionalRenderingTimeRangeSizeKind" - }, - "ConditionalRenderingVariableKind": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardConditionalRenderingVariableKind" - } - } + }, + "additionalProperties": false }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardConditionalRenderingVariableSpec": { "type": "object", @@ -1674,17 +1612,21 @@ "properties": { "operator": { "type": "string", - "default": "" + "enum": [ + "equals", + "notEquals", + "matches", + "notMatches" + ] }, "value": { - "type": "string", - "default": "" + "type": "string" }, "variable": { - "type": "string", - "default": "" + "type": "string" } - } + }, + "additionalProperties": false }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardConstantVariableKind": { "description": "Constant variable kind", @@ -1695,18 +1637,13 @@ ], "properties": { "kind": { - "type": "string", - "default": "" + "type": "string" }, "spec": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardConstantVariableSpec" - } - ] + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardConstantVariableSpec" } - } + }, + "additionalProperties": false }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardConstantVariableSpec": { "description": "Constant variable specification", @@ -1720,19 +1657,13 @@ ], "properties": { "current": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardVariableOption" - } - ] + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardVariableOption" }, "description": { "type": "string" }, "hide": { - "type": "string", - "default": "" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardVariableHide" }, "label": { "type": "string" @@ -1749,7 +1680,8 @@ "type": "boolean", "default": false } - } + }, + "additionalProperties": false }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardConversionStatus": { "description": "ConversionStatus is the status of the conversion of the dashboard.", @@ -1759,23 +1691,24 @@ ], "properties": { "error": { - "description": "The error message from the conversion. Empty if the conversion has not failed.", + "description": "The error message from the conversion.\nEmpty if the conversion has not failed.", "type": "string" }, "failed": { - "description": "Whether from another version has failed. If true, means that the dashboard is not valid, and the caller should instead fetch the stored version.", - "type": "boolean", - "default": false + "description": "Whether from another version has failed.\nIf true, means that the dashboard is not valid,\nand the caller should instead fetch the stored version.", + "type": "boolean" }, "source": { "description": "The original value map[string]any", - "type": "object" + "type": "object", + "additionalProperties": {} }, "storedVersion": { - "description": "The version which was stored when the dashboard was created / updated. Fetching this version should always succeed.", + "description": "The version which was stored when the dashboard was created / updated.\nFetching this version should always succeed.", "type": "string" } - } + }, + "additionalProperties": false }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardCustomVariableKind": { "description": "Custom variable kind", @@ -1786,18 +1719,13 @@ ], "properties": { "kind": { - "type": "string", - "default": "" + "type": "string" }, "spec": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardCustomVariableSpec" - } - ] + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardCustomVariableSpec" } - } + }, + "additionalProperties": false }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardCustomVariableSpec": { "description": "Custom variable specification", @@ -1819,22 +1747,16 @@ }, "allowCustomValue": { "type": "boolean", - "default": false + "default": true }, "current": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardVariableOption" - } - ] + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardVariableOption" }, "description": { "type": "string" }, "hide": { - "type": "string", - "default": "" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardVariableHide" }, "includeAll": { "type": "boolean", @@ -1854,12 +1776,7 @@ "options": { "type": "array", "items": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardVariableOption" - } - ] + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardVariableOption" } }, "query": { @@ -1870,7 +1787,17 @@ "type": "boolean", "default": false } - } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardDashboardCursorSync": { + "description": "\"Off\" for no shared crosshair or tooltip (default).\n\"Crosshair\" for shared crosshair.\n\"Tooltip\" for shared crosshair AND shared tooltip.", + "type": "string", + "enum": [ + "Crosshair", + "Tooltip", + "Off" + ] }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardDashboardLink": { "description": "Links with references to other dashboards or external resources", @@ -1894,8 +1821,7 @@ }, "icon": { "description": "Icon name to be displayed with the link", - "type": "string", - "default": "" + "type": "string" }, "includeVars": { "description": "If true, includes current template variables values in the link as query params", @@ -1908,15 +1834,13 @@ "default": false }, "placement": { - "description": "Placement can be used to display the link somewhere else on the dashboard other than above the visualisations.", - "type": "string" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardDashboardLinkPlacement" }, "tags": { "description": "List of tags to limit the linked dashboards. If empty, all dashboards will be displayed. Only valid if the type is dashboards", "type": "array", "items": { - "type": "string", - "default": "" + "type": "string" } }, "targetBlank": { @@ -1926,24 +1850,33 @@ }, "title": { "description": "Title to display with the link", - "type": "string", - "default": "" + "type": "string" }, "tooltip": { "description": "Tooltip to display when the user hovers their mouse over it", - "type": "string", - "default": "" + "type": "string" }, "type": { - "description": "Link type. Accepted values are dashboards (to refer to another dashboard) and link (to refer to an external resource) FIXME: The type is generated as `type: DashboardLinkType | dashboardLinkType.Link;` but it should be `type: DashboardLinkType`", - "type": "string", - "default": "" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardDashboardLinkType" }, "url": { "description": "Link URL. Only required/valid if the type is link", "type": "string" } - } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardDashboardLinkPlacement": { + "description": "Dashboard Link placement. Defines where the link should be displayed.\n- \"inControlsMenu\" renders the link in bottom part of the dashboard controls dropdown menu", + "type": "string" + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardDashboardLinkType": { + "description": "Dashboard Link type. Accepted values are dashboards (to refer to another dashboard) and link (to refer to an external resource)", + "type": "string", + "enum": [ + "link", + "dashboards" + ] }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardDataLink": { "type": "object", @@ -1956,14 +1889,13 @@ "type": "boolean" }, "title": { - "type": "string", - "default": "" + "type": "string" }, "url": { - "type": "string", - "default": "" + "type": "string" } - } + }, + "additionalProperties": false }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardDataQueryKind": { "type": "object", @@ -1974,16 +1906,17 @@ "properties": { "kind": { "description": "The kind of a DataQueryKind is the datasource type", - "type": "string", - "default": "" + "type": "string" }, "spec": { "type": "object", "additionalProperties": { - "type": "object" + "type": "object", + "additionalProperties": {} } } - } + }, + "additionalProperties": false }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardDataSourceRef": { "type": "object", @@ -1996,10 +1929,20 @@ "description": "Specific datasource instance", "type": "string" } - } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardDataTopic": { + "description": "A topic is attached to DataFrame metadata in query results.\nThis specifies where the data should be used.", + "type": "string", + "enum": [ + "series", + "annotations", + "alertStates" + ] }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardDataTransformerConfig": { - "description": "Transformations allow to manipulate data returned by a query before the system applies a visualization. Using transformations you can: rename fields, join time series data, perform mathematical operations across queries, use the output of one transformation as the input to another transformation, etc.", + "description": "Transformations allow to manipulate data returned by a query before the system applies a visualization.\nUsing transformations you can: rename fields, join time series data, perform mathematical operations across queries,\nuse the output of one transformation as the input to another transformation, etc.", "type": "object", "required": [ "id", @@ -2011,27 +1954,22 @@ "type": "boolean" }, "filter": { - "description": "Optional frame matcher. When missing it will be applied to all results", - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardMatcherConfig" - } - ] + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardMatcherConfig" }, "id": { "description": "Unique identifier of transformer", - "type": "string", - "default": "" + "type": "string" }, "options": { - "description": "Options to be passed to the transformer Valid options depend on the transformer id", - "type": "object" + "description": "Options to be passed to the transformer\nValid options depend on the transformer id", + "type": "object", + "additionalProperties": {} }, "topic": { - "description": "Where to pull DataFrames from as input to transformation", - "type": "string" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardDataTopic" } - } + }, + "additionalProperties": false }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardDatasourceVariableKind": { "description": "Datasource variable kind", @@ -2042,18 +1980,13 @@ ], "properties": { "kind": { - "type": "string", - "default": "" + "type": "string" }, "spec": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardDatasourceVariableSpec" - } - ] + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardDatasourceVariableSpec" } - } + }, + "additionalProperties": false }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardDatasourceVariableSpec": { "description": "Datasource variable specification", @@ -2077,22 +2010,16 @@ }, "allowCustomValue": { "type": "boolean", - "default": false + "default": true }, "current": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardVariableOption" - } - ] + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardVariableOption" }, "description": { "type": "string" }, "hide": { - "type": "string", - "default": "" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardVariableHide" }, "includeAll": { "type": "boolean", @@ -2112,12 +2039,7 @@ "options": { "type": "array", "items": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardVariableOption" - } - ] + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardVariableOption" } }, "pluginId": { @@ -2125,8 +2047,7 @@ "default": "" }, "refresh": { - "type": "string", - "default": "" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardVariableRefresh" }, "regex": { "type": "string", @@ -2136,7 +2057,8 @@ "type": "boolean", "default": false } - } + }, + "additionalProperties": false }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardDynamicConfigValue": { "type": "object", @@ -2149,9 +2071,22 @@ "default": "" }, "value": { - "type": "object" + "type": "object", + "additionalProperties": {} } - } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardElement": { + "description": "Supported dashboard elements\n|* more element types in the future", + "oneOf": [ + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardPanelKind" + }, + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardLibraryPanelKind" + } + ] }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardElementReference": { "type": "object", @@ -2161,14 +2096,13 @@ ], "properties": { "kind": { - "type": "string", - "default": "" + "type": "string" }, "name": { - "type": "string", - "default": "" + "type": "string" } - } + }, + "additionalProperties": false }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardFetchOptions": { "type": "object", @@ -2185,31 +2119,28 @@ "items": { "type": "array", "items": { - "type": "string", - "default": "" + "type": "string" } } }, "method": { - "type": "string", - "default": "" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardHttpRequestMethod" }, "queryParams": { - "description": "These are 2D arrays of strings, each representing a key-value pair We are defining them this way because we can't generate a go struct that that would have exactly two strings in each sub-array", + "description": "These are 2D arrays of strings, each representing a key-value pair\nWe are defining them this way because we can't generate a go struct that\nthat would have exactly two strings in each sub-array", "type": "array", "items": { "type": "array", "items": { - "type": "string", - "default": "" + "type": "string" } } }, "url": { - "type": "string", - "default": "" + "type": "string" } - } + }, + "additionalProperties": false }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardFieldColor": { "description": "Map a field to a color.", @@ -2223,51 +2154,74 @@ "type": "string" }, "mode": { - "description": "The main color scheme mode.", - "type": "string", - "default": "" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardFieldColorModeId" }, "seriesBy": { - "description": "Some visualizations need to know how to assign a series color from by value color schemes.", - "type": "string" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardFieldColorSeriesByMode" } - } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardFieldColorModeId": { + "description": "Color mode for a field. You can specify a single color, or select a continuous (gradient) color schemes, based on a value.\nContinuous color interpolates a color using the percentage of a value relative to min and max.\nAccepted values are:\n`thresholds`: From thresholds. Informs Grafana to take the color from the matching threshold\n`palette-classic`: Classic palette. Grafana will assign color by looking up a color in a palette by series index. Useful for Graphs and pie charts and other categorical data visualizations\n`palette-classic-by-name`: Classic palette (by name). Grafana will assign color by looking up a color in a palette by series name. Useful for Graphs and pie charts and other categorical data visualizations\n`continuous-viridis`: Continuous Viridis palette mode\n`continuous-magma`: Continuous Magma palette mode\n`continuous-plasma`: Continuous Plasma palette mode\n`continuous-inferno`: Continuous Inferno palette mode\n`continuous-cividis`: Continuous Cividis palette mode\n`continuous-GrYlRd`: Continuous Green-Yellow-Red palette mode\n`continuous-RdYlGr`: Continuous Red-Yellow-Green palette mode\n`continuous-BlYlRd`: Continuous Blue-Yellow-Red palette mode\n`continuous-YlRd`: Continuous Yellow-Red palette mode\n`continuous-BlPu`: Continuous Blue-Purple palette mode\n`continuous-YlBl`: Continuous Yellow-Blue palette mode\n`continuous-blues`: Continuous Blue palette mode\n`continuous-reds`: Continuous Red palette mode\n`continuous-greens`: Continuous Green palette mode\n`continuous-purples`: Continuous Purple palette mode\n`shades`: Shades of a single color. Specify a single color, useful in an override rule.\n`fixed`: Fixed color mode. Specify a single color, useful in an override rule.", + "type": "string", + "enum": [ + "thresholds", + "palette-classic", + "palette-classic-by-name", + "continuous-viridis", + "continuous-magma", + "continuous-plasma", + "continuous-inferno", + "continuous-cividis", + "continuous-GrYlRd", + "continuous-RdYlGr", + "continuous-BlYlRd", + "continuous-YlRd", + "continuous-BlPu", + "continuous-YlBl", + "continuous-blues", + "continuous-reds", + "continuous-greens", + "continuous-purples", + "fixed", + "shades" + ] + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardFieldColorSeriesByMode": { + "description": "Defines how to assign a series color from \"by value\" color schemes. For example for an aggregated data points like a timeseries, the color can be assigned by the min, max or last value.", + "type": "string", + "enum": [ + "min", + "max", + "last" + ] }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardFieldConfig": { - "description": "The data model used in Grafana, namely the data frame, is a columnar-oriented table structure that unifies both time series and table query results. Each column within this structure is called a field. A field can represent a single time series or table column. Field options allow you to change how the data is displayed in your visualizations.", + "description": "The data model used in Grafana, namely the data frame, is a columnar-oriented table structure that unifies both time series and table query results.\nEach column within this structure is called a field. A field can represent a single time series or table column.\nField options allow you to change how the data is displayed in your visualizations.", "type": "object", "properties": { "actions": { "description": "Define interactive HTTP requests that can be triggered from data visualizations.", "type": "array", "items": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardAction" - } - ] + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardAction" } }, "color": { - "description": "Panel color configuration", - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardFieldColor" - } - ] + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardFieldColor" }, "custom": { - "description": "custom is specified by the FieldConfig field in panel plugin schemas.", + "description": "custom is specified by the FieldConfig field\nin panel plugin schemas.", "type": "object", "additionalProperties": { - "type": "object" + "type": "object", + "additionalProperties": {} } }, "decimals": { - "description": "Specify the number of decimals Grafana includes in the rendered value. If you leave this field blank, Grafana automatically truncates the number of decimals based on the value. For example 1.1234 will display as 1.12 and 100.456 will display as 100. To display all decimals, set the unit to `String`.", - "type": "number", - "format": "double" + "description": "Specify the number of decimals Grafana includes in the rendered value.\nIf you leave this field blank, Grafana automatically truncates the number of decimals based on the value.\nFor example 1.1234 will display as 1.12 and 100.456 will display as 100.\nTo display all decimals, set the unit to `String`.", + "type": "number" }, "description": { "description": "Human readable field metadata", @@ -2278,7 +2232,7 @@ "type": "string" }, "displayNameFromDS": { - "description": "This can be used by data sources that return and explicit naming structure for values and labels When this property is configured, this value is used rather than the default naming strategy.", + "description": "This can be used by data sources that return and explicit naming structure for values and labels\nWhen this property is configured, this value is used rather than the default naming strategy.", "type": "string" }, "filterable": { @@ -2289,54 +2243,49 @@ "description": "The behavior when clicking on a result", "type": "array", "items": { - "type": "object" + "type": "object", + "additionalProperties": {} } }, "mappings": { "description": "Convert input values into a display string", "type": "array", "items": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardValueMapOrRangeMapOrRegexMapOrSpecialValueMap" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardValueMapping" } }, "max": { "description": "The maximum value used in percentage threshold calculations. Leave blank for auto calculation based on all series and fields.", - "type": "number", - "format": "double" + "type": "number" }, "min": { "description": "The minimum value used in percentage threshold calculations. Leave blank for auto calculation based on all series and fields.", - "type": "number", - "format": "double" + "type": "number" }, "noValue": { "description": "Alternative to empty string", "type": "string" }, "path": { - "description": "An explicit path to the field in the datasource. When the frame meta includes a path, This will default to `${frame.meta.path}/${field.name}\n\nWhen defined, this value can be used as an identifier within the datasource scope, and may be used to update the results", + "description": "An explicit path to the field in the datasource. When the frame meta includes a path,\nThis will default to `${frame.meta.path}/${field.name}\n\nWhen defined, this value can be used as an identifier within the datasource scope, and\nmay be used to update the results", "type": "string" }, "thresholds": { - "description": "Map numeric values to states", - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardThresholdsConfig" - } - ] + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardThresholdsConfig" }, "unit": { - "description": "Unit a field should use. The unit you select is applied to all fields except time. You can use the units ID availables in Grafana or a custom unit. Available units in Grafana: https://github.com/grafana/grafana/blob/main/packages/grafana-data/src/valueFormats/categories.ts As custom unit, you can use the following formats: `suffix:\u003csuffix\u003e` for custom unit that should go after value. `prefix:\u003cprefix\u003e` for custom unit that should go before value. `time:\u003cformat\u003e` For custom date time formats type for example `time:YYYY-MM-DD`. `si:\u003cbase scale\u003e\u003cunit characters\u003e` for custom SI units. For example: `si: mF`. This one is a bit more advanced as you can specify both a unit and the source data scale. So if your source data is represented as milli (thousands of) something prefix the unit with that SI scale character. `count:\u003cunit\u003e` for a custom count unit. `currency:\u003cunit\u003e` for custom a currency unit.", + "description": "Unit a field should use. The unit you select is applied to all fields except time.\nYou can use the units ID availables in Grafana or a custom unit.\nAvailable units in Grafana: https://github.com/grafana/grafana/blob/main/packages/grafana-data/src/valueFormats/categories.ts\nAs custom unit, you can use the following formats:\n`suffix:` for custom unit that should go after value.\n`prefix:` for custom unit that should go before value.\n`time:` For custom date time formats type for example `time:YYYY-MM-DD`.\n`si:` for custom SI units. For example: `si: mF`. This one is a bit more advanced as you can specify both a unit and the source data scale. So if your source data is represented as milli (thousands of) something prefix the unit with that SI scale character.\n`count:` for a custom count unit.\n`currency:` for custom a currency unit.", "type": "string" }, "writeable": { "description": "True if data source can write a value to the path. Auth/authz are supported separately", "type": "boolean" } - } + }, + "additionalProperties": false }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardFieldConfigSource": { - "description": "The data model used in Grafana, namely the data frame, is a columnar-oriented table structure that unifies both time series and table query results. Each column within this structure is called a field. A field can represent a single time series or table column. Field options allow you to change how the data is displayed in your visualizations.", + "description": "The data model used in Grafana, namely the data frame, is a columnar-oriented table structure that unifies both time series and table query results.\nEach column within this structure is called a field. A field can represent a single time series or table column.\nField options allow you to change how the data is displayed in your visualizations.", "type": "object", "required": [ "defaults", @@ -2344,27 +2293,41 @@ ], "properties": { "defaults": { - "description": "Defaults are the options applied to all fields.", - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardFieldConfig" - } - ] + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardFieldConfig" }, "overrides": { "description": "Overrides are the options applied to specific fields overriding the defaults.", "type": "array", "items": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardV2alpha1FieldConfigSourceOverrides" + "type": "object", + "required": [ + "matcher", + "properties" + ], + "properties": { + "__systemRef": { + "description": "Describes config override rules created when interacting with Grafana.", + "type": "string" + }, + "matcher": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardMatcherConfig" + }, + "properties": { + "type": "array", + "items": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardDynamicConfigValue" + } } - ] + }, + "additionalProperties": false } } - } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardFilterOrigin": { + "description": "Determine the origin of the adhoc variable filter", + "type": "string" }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardGridLayoutItemKind": { "type": "object", @@ -2374,18 +2337,13 @@ ], "properties": { "kind": { - "type": "string", - "default": "" + "type": "string" }, "spec": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardGridLayoutItemSpec" - } - ] + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardGridLayoutItemSpec" } - } + }, + "additionalProperties": false }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardGridLayoutItemSpec": { "type": "object", @@ -2398,38 +2356,25 @@ ], "properties": { "element": { - "description": "reference to a PanelKind from dashboard.spec.elements Expressed as JSON Schema reference", - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardElementReference" - } - ] + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardElementReference" }, "height": { - "type": "integer", - "format": "int64", - "default": 0 + "type": "integer" }, "repeat": { "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardRepeatOptions" }, "width": { - "type": "integer", - "format": "int64", - "default": 0 + "type": "integer" }, "x": { - "type": "integer", - "format": "int64", - "default": 0 + "type": "integer" }, "y": { - "type": "integer", - "format": "int64", - "default": 0 + "type": "integer" } - } + }, + "additionalProperties": false }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardGridLayoutKind": { "type": "object", @@ -2439,52 +2384,13 @@ ], "properties": { "kind": { - "type": "string", - "default": "" + "type": "string" }, "spec": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardGridLayoutSpec" - } - ] + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardGridLayoutSpec" } - } - }, - "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardGridLayoutKindOrAutoGridLayoutKindOrTabsLayoutKindOrRowsLayoutKind": { - "type": "object", - "properties": { - "AutoGridLayoutKind": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardAutoGridLayoutKind" - }, - "GridLayoutKind": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardGridLayoutKind" - }, - "RowsLayoutKind": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardRowsLayoutKind" - }, - "TabsLayoutKind": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardTabsLayoutKind" - } - } - }, - "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardGridLayoutKindOrRowsLayoutKindOrAutoGridLayoutKindOrTabsLayoutKind": { - "type": "object", - "properties": { - "AutoGridLayoutKind": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardAutoGridLayoutKind" - }, - "GridLayoutKind": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardGridLayoutKind" - }, - "RowsLayoutKind": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardRowsLayoutKind" - }, - "TabsLayoutKind": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardTabsLayoutKind" - } - } + }, + "additionalProperties": false }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardGridLayoutSpec": { "type": "object", @@ -2495,15 +2401,11 @@ "items": { "type": "array", "items": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardGridLayoutItemKind" - } - ] + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardGridLayoutItemKind" } } - } + }, + "additionalProperties": false }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardGroupByVariableKind": { "description": "Group variable kind", @@ -2514,18 +2416,13 @@ ], "properties": { "kind": { - "type": "string", - "default": "" + "type": "string" }, "spec": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardGroupByVariableSpec" - } - ] + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardGroupByVariableSpec" } - } + }, + "additionalProperties": false }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardGroupByVariableSpec": { "description": "GroupBy variable specification", @@ -2540,12 +2437,7 @@ ], "properties": { "current": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardVariableOption" - } - ] + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardVariableOption" }, "datasource": { "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardDataSourceRef" @@ -2557,8 +2449,7 @@ "type": "string" }, "hide": { - "type": "string", - "default": "" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardVariableHide" }, "label": { "type": "string" @@ -2574,19 +2465,25 @@ "options": { "type": "array", "items": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardVariableOption" - } - ] + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardVariableOption" } }, "skipUrlSync": { "type": "boolean", "default": false } - } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardHttpRequestMethod": { + "type": "string", + "enum": [ + "GET", + "PUT", + "POST", + "DELETE", + "PATCH" + ] }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardInfinityOptions": { "type": "object", @@ -2600,39 +2497,35 @@ "type": "string" }, "datasourceUid": { - "type": "string", - "default": "" + "type": "string" }, "headers": { "type": "array", "items": { "type": "array", "items": { - "type": "string", - "default": "" + "type": "string" } } }, "method": { - "type": "string", - "default": "" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardHttpRequestMethod" }, "queryParams": { - "description": "These are 2D arrays of strings, each representing a key-value pair We are defining them this way because we can't generate a go struct that that would have exactly two strings in each sub-array", + "description": "These are 2D arrays of strings, each representing a key-value pair\nWe are defining them this way because we can't generate a go struct that\nthat would have exactly two strings in each sub-array", "type": "array", "items": { "type": "array", "items": { - "type": "string", - "default": "" + "type": "string" } } }, "url": { - "type": "string", - "default": "" + "type": "string" } - } + }, + "additionalProperties": false }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardIntervalVariableKind": { "description": "Interval variable kind", @@ -2643,18 +2536,13 @@ ], "properties": { "kind": { - "type": "string", - "default": "" + "type": "string" }, "spec": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardIntervalVariableSpec" - } - ] + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardIntervalVariableSpec" } - } + }, + "additionalProperties": false }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardIntervalVariableSpec": { "description": "Interval variable specification", @@ -2678,7 +2566,6 @@ }, "auto_count": { "type": "integer", - "format": "int64", "default": 0 }, "auto_min": { @@ -2686,19 +2573,13 @@ "default": "" }, "current": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardVariableOption" - } - ] + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardVariableOption" }, "description": { "type": "string" }, "hide": { - "type": "string", - "default": "" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardVariableHide" }, "label": { "type": "string" @@ -2710,12 +2591,7 @@ "options": { "type": "array", "items": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardVariableOption" - } - ] + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardVariableOption" } }, "query": { @@ -2723,14 +2599,14 @@ "default": "" }, "refresh": { - "type": "string", - "default": "" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardVariableRefresh" }, "skipUrlSync": { "type": "boolean", "default": false } - } + }, + "additionalProperties": false }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardLibraryPanelKind": { "type": "object", @@ -2740,18 +2616,13 @@ ], "properties": { "kind": { - "type": "string", - "default": "" + "type": "string" }, "spec": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardLibraryPanelKindSpec" - } - ] + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardLibraryPanelKindSpec" } - } + }, + "additionalProperties": false }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardLibraryPanelKindSpec": { "type": "object", @@ -2763,27 +2634,20 @@ "properties": { "id": { "description": "Panel ID for the library panel in the dashboard", - "type": "number", - "format": "double", - "default": 0 + "type": "number" }, "libraryPanel": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardLibraryPanelRef" - } - ] + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardLibraryPanelRef" }, "title": { "description": "Title for the library panel in the dashboard", - "type": "string", - "default": "" + "type": "string" } - } + }, + "additionalProperties": false }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardLibraryPanelRef": { - "description": "A library panel is a reusable panel that you can use in any dashboard. When you make a change to a library panel, that change propagates to all instances of where the panel is used. Library panels streamline reuse of panels across multiple dashboards.", + "description": "A library panel is a reusable panel that you can use in any dashboard.\nWhen you make a change to a library panel, that change propagates to all instances of where the panel is used.\nLibrary panels streamline reuse of panels across multiple dashboards.", "type": "object", "required": [ "name", @@ -2792,15 +2656,14 @@ "properties": { "name": { "description": "Library panel name", - "type": "string", - "default": "" + "type": "string" }, "uid": { "description": "Library panel uid", - "type": "string", - "default": "" + "type": "string" } - } + }, + "additionalProperties": false }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardList": { "type": "object", @@ -2845,8 +2708,18 @@ } ] }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardMappingType": { + "description": "Supported value mapping types\n`value`: Maps text values to a color or different display text and color. For example, you can configure a value mapping so that all instances of the value 10 appear as Perfection! rather than the number.\n`range`: Maps numerical ranges to a display text and color. For example, if a value is within a certain range, you can configure a range value mapping to display Low or High rather than the number.\n`regex`: Maps regular expressions to replacement text and a color. For example, if a value is www.example.com, you can configure a regex value mapping so that Grafana displays www and truncates the domain.\n`special`: Maps special values like Null, NaN (not a number), and boolean values like true and false to a display text and color. See SpecialValueMatch to see the list of special values. For example, you can configure a special value mapping so that null values appear as N/A.", + "type": "string", + "enum": [ + "value", + "range", + "regex", + "special" + ] + }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardMatcherConfig": { - "description": "Matcher is a predicate configuration. Based on the config a set of field(s) or values is filtered in order to apply override / transformation. It comes with in id ( to resolve implementation from registry) and a configuration that’s specific to a particular matcher type.", + "description": "Matcher is a predicate configuration. Based on the config a set of field(s) or values is filtered in order to apply override / transformation.\nIt comes with in id ( to resolve implementation from registry) and a configuration that’s specific to a particular matcher type.", "type": "object", "required": [ "id" @@ -2859,9 +2732,11 @@ }, "options": { "description": "The matcher options. This is specific to the matcher implementation.", - "type": "object" + "type": "object", + "additionalProperties": {} } - } + }, + "additionalProperties": false }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardMetricFindValue": { "description": "Define the MetricFindValue type", @@ -2877,13 +2752,20 @@ "type": "string" }, "text": { - "type": "string", - "default": "" + "type": "string" }, "value": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardStringOrFloat64" + "oneOf": [ + { + "type": "string" + }, + { + "type": "number" + } + ] } - } + }, + "additionalProperties": false }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardPanelKind": { "type": "object", @@ -2893,29 +2775,13 @@ ], "properties": { "kind": { - "type": "string", - "default": "" + "type": "string" }, "spec": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardPanelSpec" - } - ] + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardPanelSpec" } - } - }, - "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardPanelKindOrLibraryPanelKind": { - "type": "object", - "properties": { - "LibraryPanelKind": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardLibraryPanelKind" - }, - "PanelKind": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardPanelKind" - } - } + }, + "additionalProperties": false }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardPanelQueryKind": { "type": "object", @@ -2925,18 +2791,13 @@ ], "properties": { "kind": { - "type": "string", - "default": "" + "type": "string" }, "spec": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardPanelQuerySpec" - } - ] + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardPanelQuerySpec" } - } + }, + "additionalProperties": false }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardPanelQuerySpec": { "type": "object", @@ -2950,22 +2811,16 @@ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardDataSourceRef" }, "hidden": { - "type": "boolean", - "default": false + "type": "boolean" }, "query": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardDataQueryKind" - } - ] + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardDataQueryKind" }, "refId": { - "type": "string", - "default": "" + "type": "string" } - } + }, + "additionalProperties": false }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardPanelSpec": { "type": "object", @@ -2979,49 +2834,31 @@ ], "properties": { "data": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardQueryGroupKind" - } - ] + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardQueryGroupKind" }, "description": { - "type": "string", - "default": "" + "type": "string" }, "id": { - "type": "number", - "format": "double", - "default": 0 + "type": "number" }, "links": { "type": "array", "items": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardDataLink" - } - ] + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardDataLink" } }, "title": { - "type": "string", - "default": "" + "type": "string" }, "transparent": { "type": "boolean" }, "vizConfig": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardVizConfigKind" - } - ] + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardVizConfigKind" } - } + }, + "additionalProperties": false }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardQueryGroupKind": { "type": "object", @@ -3031,18 +2868,13 @@ ], "properties": { "kind": { - "type": "string", - "default": "" + "type": "string" }, "spec": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardQueryGroupSpec" - } - ] + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardQueryGroupSpec" } - } + }, + "additionalProperties": false }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardQueryGroupSpec": { "type": "object", @@ -3055,34 +2887,20 @@ "queries": { "type": "array", "items": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardPanelQueryKind" - } - ] + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardPanelQueryKind" } }, "queryOptions": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardQueryOptionsSpec" - } - ] + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardQueryOptionsSpec" }, "transformations": { "type": "array", "items": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardTransformationKind" - } - ] + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardTransformationKind" } } - } + }, + "additionalProperties": false }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardQueryOptionsSpec": { "type": "object", @@ -3097,12 +2915,10 @@ "type": "string" }, "maxDataPoints": { - "type": "integer", - "format": "int64" + "type": "integer" }, "queryCachingTTL": { - "type": "integer", - "format": "int64" + "type": "integer" }, "timeFrom": { "type": "string" @@ -3110,7 +2926,8 @@ "timeShift": { "type": "string" } - } + }, + "additionalProperties": false }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardQueryVariableKind": { "description": "Query variable kind", @@ -3121,50 +2938,13 @@ ], "properties": { "kind": { - "type": "string", - "default": "" + "type": "string" }, "spec": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardQueryVariableSpec" - } - ] + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardQueryVariableSpec" } - } - }, - "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardQueryVariableKindOrTextVariableKindOrConstantVariableKindOrDatasourceVariableKindOrIntervalVariableKindOrCustomVariableKindOrGroupByVariableKindOrAdhocVariableKindOrSwitchVariableKind": { - "type": "object", - "properties": { - "AdhocVariableKind": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardAdhocVariableKind" - }, - "ConstantVariableKind": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardConstantVariableKind" - }, - "CustomVariableKind": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardCustomVariableKind" - }, - "DatasourceVariableKind": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardDatasourceVariableKind" - }, - "GroupByVariableKind": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardGroupByVariableKind" - }, - "IntervalVariableKind": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardIntervalVariableKind" - }, - "QueryVariableKind": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardQueryVariableKind" - }, - "SwitchVariableKind": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardSwitchVariableKind" - }, - "TextVariableKind": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardTextVariableKind" - } - } + }, + "additionalProperties": false }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardQueryVariableSpec": { "description": "Query variable specification", @@ -3189,15 +2969,10 @@ }, "allowCustomValue": { "type": "boolean", - "default": false + "default": true }, "current": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardVariableOption" - } - ] + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardVariableOption" }, "datasource": { "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardDataSourceRef" @@ -3209,8 +2984,7 @@ "type": "string" }, "hide": { - "type": "string", - "default": "" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardVariableHide" }, "includeAll": { "type": "boolean", @@ -3230,28 +3004,17 @@ "options": { "type": "array", "items": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardVariableOption" - } - ] + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardVariableOption" } }, "placeholder": { "type": "string" }, "query": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardDataQueryKind" - } - ] + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardDataQueryKind" }, "refresh": { - "type": "string", - "default": "" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardVariableRefresh" }, "regex": { "type": "string", @@ -3262,27 +3025,27 @@ "default": false }, "sort": { - "type": "string", - "default": "" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardVariableSort" }, "staticOptions": { "type": "array", "items": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardVariableOption" - } - ] + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardVariableOption" } }, "staticOptionsOrder": { - "type": "string" + "type": "string", + "enum": [ + "before", + "after", + "sorted" + ] } - } + }, + "additionalProperties": false }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardRangeMap": { - "description": "Maps numerical ranges to a display text and color. For example, if a value is within a certain range, you can configure a range value mapping to display Low or High rather than the number.", + "description": "Maps numerical ranges to a display text and color.\nFor example, if a value is within a certain range, you can configure a range value mapping to display Low or High rather than the number.", "type": "object", "required": [ "type", @@ -3291,21 +3054,35 @@ "properties": { "options": { "description": "Range to match against and the result to apply when the value is within the range", - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardV2alpha1RangeMapOptions" + "type": "object", + "required": [ + "from", + "to", + "result" + ], + "properties": { + "from": { + "description": "Min value of the range. It can be null which means -Infinity", + "type": "number" + }, + "result": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardValueMappingResult" + }, + "to": { + "description": "Max value of the range. It can be null which means +Infinity", + "type": "number" } - ] + }, + "additionalProperties": false }, "type": { - "type": "string", - "default": "" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardMappingType" } - } + }, + "additionalProperties": false }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardRegexMap": { - "description": "Maps regular expressions to replacement text and a color. For example, if a value is www.example.com, you can configure a regex value mapping so that Grafana displays www and truncates the domain.", + "description": "Maps regular expressions to replacement text and a color.\nFor example, if a value is www.example.com, you can configure a regex value mapping so that Grafana displays www and truncates the domain.", "type": "object", "required": [ "type", @@ -3314,18 +3091,31 @@ "properties": { "options": { "description": "Regular expression to match against and the result to apply when the value matches the regex", - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardV2alpha1RegexMapOptions" + "type": "object", + "required": [ + "pattern", + "result" + ], + "properties": { + "pattern": { + "description": "Regular expression to match against", + "type": "string" + }, + "result": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardValueMappingResult" } - ] + }, + "additionalProperties": false }, "type": { - "type": "string", - "default": "" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardMappingType" } - } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardRepeatMode": { + "description": "other repeat modes will be added in the future: label, frame", + "type": "string" }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardRepeatOptions": { "type": "object", @@ -3335,21 +3125,23 @@ ], "properties": { "direction": { - "type": "string" + "type": "string", + "enum": [ + "h", + "v" + ] }, "maxPerRow": { - "type": "integer", - "format": "int64" + "type": "integer" }, "mode": { - "type": "string", - "default": "" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardRepeatMode" }, "value": { - "type": "string", - "default": "" + "type": "string" } - } + }, + "additionalProperties": false }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardRowRepeatOptions": { "type": "object", @@ -3359,14 +3151,13 @@ ], "properties": { "mode": { - "type": "string", - "default": "" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardRepeatMode" }, "value": { - "type": "string", - "default": "" + "type": "string" } - } + }, + "additionalProperties": false }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardRowsLayoutKind": { "type": "object", @@ -3376,18 +3167,13 @@ ], "properties": { "kind": { - "type": "string", - "default": "" + "type": "string" }, "spec": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardRowsLayoutSpec" - } - ] + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardRowsLayoutSpec" } - } + }, + "additionalProperties": false }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardRowsLayoutRowKind": { "type": "object", @@ -3397,18 +3183,13 @@ ], "properties": { "kind": { - "type": "string", - "default": "" + "type": "string" }, "spec": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardRowsLayoutRowSpec" - } - ] + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardRowsLayoutRowSpec" } - } + }, + "additionalProperties": false }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardRowsLayoutRowSpec": { "type": "object", @@ -3429,7 +3210,20 @@ "type": "boolean" }, "layout": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardGridLayoutKindOrAutoGridLayoutKindOrTabsLayoutKindOrRowsLayoutKind" + "oneOf": [ + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardGridLayoutKind" + }, + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardAutoGridLayoutKind" + }, + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardTabsLayoutKind" + }, + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardRowsLayoutKind" + } + ] }, "repeat": { "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardRowRepeatOptions" @@ -3437,7 +3231,8 @@ "title": { "type": "string" } - } + }, + "additionalProperties": false }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardRowsLayoutSpec": { "type": "object", @@ -3448,15 +3243,11 @@ "rows": { "type": "array", "items": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardRowsLayoutRowKind" - } - ] + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardRowsLayoutRowKind" } } - } + }, + "additionalProperties": false }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardSpec": { "type": "object", @@ -3476,18 +3267,11 @@ "annotations": { "type": "array", "items": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardAnnotationQueryKind" - } - ] + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardAnnotationQueryKind" } }, "cursorSync": { - "description": "Configuration of dashboard cursor sync behavior. \"Off\" for no shared crosshair or tooltip (default). \"Crosshair\" for shared crosshair. \"Tooltip\" for shared crosshair AND shared tooltip.", - "type": "string", - "default": "" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardDashboardCursorSync" }, "description": { "description": "Description of dashboard.", @@ -3495,31 +3279,40 @@ }, "editable": { "description": "Whether a dashboard is editable or not.", - "type": "boolean" + "type": "boolean", + "default": true }, "elements": { "type": "object", "additionalProperties": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardPanelKindOrLibraryPanelKind" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardElement" } }, "layout": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardGridLayoutKindOrRowsLayoutKindOrAutoGridLayoutKindOrTabsLayoutKind" + "oneOf": [ + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardGridLayoutKind" + }, + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardRowsLayoutKind" + }, + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardAutoGridLayoutKind" + }, + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardTabsLayoutKind" + } + ] }, "links": { "description": "Links with references to other dashboards or external websites.", "type": "array", "items": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardDashboardLink" - } - ] + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardDashboardLink" } }, "liveNow": { - "description": "When set to true, the dashboard will redraw panels at an interval matching the pixel width. This will keep data \"moving left\" regardless of the query refresh rate. This setting helps avoid dashboards presenting stale live data.", + "description": "When set to true, the dashboard will redraw panels at an interval matching the pixel width.\nThis will keep data \"moving left\" regardless of the query refresh rate. This setting helps\navoid dashboards presenting stale live data.", "type": "boolean" }, "preload": { @@ -3528,42 +3321,35 @@ "default": false }, "revision": { - "description": "Plugins only. The version of the dashboard installed together with the plugin. This is used to determine if the dashboard should be updated when the plugin is updated.", - "type": "integer", - "format": "int32" + "description": "Plugins only. The version of the dashboard installed together with the plugin.\nThis is used to determine if the dashboard should be updated when the plugin is updated.", + "type": "integer" }, "tags": { "description": "Tags associated with dashboard.", "type": "array", "items": { - "type": "string", - "default": "" + "type": "string" } }, "timeSettings": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardTimeSettingsSpec" - } - ] + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardTimeSettingsSpec" }, "title": { "description": "Title of dashboard.", - "type": "string", - "default": "" + "type": "string" }, "variables": { "description": "Configured template variables.", "type": "array", "items": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardQueryVariableKindOrTextVariableKindOrConstantVariableKindOrDatasourceVariableKindOrIntervalVariableKindOrCustomVariableKindOrGroupByVariableKindOrAdhocVariableKindOrSwitchVariableKind" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardVariableKind" } } - } + }, + "additionalProperties": false }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardSpecialValueMap": { - "description": "Maps special values like Null, NaN (not a number), and boolean values like true and false to a display text and color. See SpecialValueMatch to see the list of special values. For example, you can configure a special value mapping so that null values appear as N/A.", + "description": "Maps special values like Null, NaN (not a number), and boolean values like true and false to a display text and color.\nSee SpecialValueMatch to see the list of special values.\nFor example, you can configure a special value mapping so that null values appear as N/A.", "type": "object", "required": [ "type", @@ -3571,58 +3357,47 @@ ], "properties": { "options": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardV2alpha1SpecialValueMapOptions" + "type": "object", + "required": [ + "match", + "result" + ], + "properties": { + "match": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardSpecialValueMatch" + }, + "result": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardValueMappingResult" } - ] + }, + "additionalProperties": false }, "type": { - "type": "string", - "default": "" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardMappingType" } - } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardSpecialValueMatch": { + "description": "Special value types supported by the `SpecialValueMap`", + "type": "string", + "enum": [ + "true", + "false", + "null", + "nan", + "null+nan", + "empty" + ] }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardStatus": { "type": "object", "properties": { "conversion": { - "description": "Optional conversion status.", - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardConversionStatus" - } - ] + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardConversionStatus" } - } - }, - "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardStringOrArrayOfString": { - "type": "object", - "properties": { - "ArrayOfString": { - "type": "array", - "items": { - "type": "string", - "default": "" - } - }, - "String": { - "type": "string" - } - } - }, - "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardStringOrFloat64": { - "type": "object", - "properties": { - "Float64": { - "type": "number", - "format": "double" - }, - "String": { - "type": "string" - } - } + }, + "additionalProperties": false }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardSwitchVariableKind": { "type": "object", @@ -3632,18 +3407,13 @@ ], "properties": { "kind": { - "type": "string", - "default": "" + "type": "string" }, "spec": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardSwitchVariableSpec" - } - ] + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardSwitchVariableSpec" } - } + }, + "additionalProperties": false }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardSwitchVariableSpec": { "description": "Switch variable specification", @@ -3659,22 +3429,21 @@ "properties": { "current": { "type": "string", - "default": "" + "default": "false" }, "description": { "type": "string" }, "disabledValue": { "type": "string", - "default": "" + "default": "false" }, "enabledValue": { "type": "string", - "default": "" + "default": "true" }, "hide": { - "type": "string", - "default": "" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardVariableHide" }, "label": { "type": "string" @@ -3687,7 +3456,8 @@ "type": "boolean", "default": false } - } + }, + "additionalProperties": false }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardTabRepeatOptions": { "type": "object", @@ -3697,14 +3467,13 @@ ], "properties": { "mode": { - "type": "string", - "default": "" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardRepeatMode" }, "value": { - "type": "string", - "default": "" + "type": "string" } - } + }, + "additionalProperties": false }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardTabsLayoutKind": { "type": "object", @@ -3714,18 +3483,13 @@ ], "properties": { "kind": { - "type": "string", - "default": "" + "type": "string" }, "spec": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardTabsLayoutSpec" - } - ] + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardTabsLayoutSpec" } - } + }, + "additionalProperties": false }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardTabsLayoutSpec": { "type": "object", @@ -3736,15 +3500,11 @@ "tabs": { "type": "array", "items": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardTabsLayoutTabKind" - } - ] + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardTabsLayoutTabKind" } } - } + }, + "additionalProperties": false }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardTabsLayoutTabKind": { "type": "object", @@ -3754,18 +3514,13 @@ ], "properties": { "kind": { - "type": "string", - "default": "" + "type": "string" }, "spec": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardTabsLayoutTabSpec" - } - ] + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardTabsLayoutTabSpec" } - } + }, + "additionalProperties": false }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardTabsLayoutTabSpec": { "type": "object", @@ -3777,7 +3532,20 @@ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardConditionalRenderingGroupKind" }, "layout": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardGridLayoutKindOrRowsLayoutKindOrAutoGridLayoutKindOrTabsLayoutKind" + "oneOf": [ + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardGridLayoutKind" + }, + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardRowsLayoutKind" + }, + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardAutoGridLayoutKind" + }, + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardTabsLayoutKind" + } + ] }, "repeat": { "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardTabRepeatOptions" @@ -3785,7 +3553,8 @@ "title": { "type": "string" } - } + }, + "additionalProperties": false }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardTextVariableKind": { "description": "Text variable kind", @@ -3796,18 +3565,13 @@ ], "properties": { "kind": { - "type": "string", - "default": "" + "type": "string" }, "spec": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardTextVariableSpec" - } - ] + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardTextVariableSpec" } - } + }, + "additionalProperties": false }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardTextVariableSpec": { "description": "Text variable specification", @@ -3821,19 +3585,13 @@ ], "properties": { "current": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardVariableOption" - } - ] + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardVariableOption" }, "description": { "type": "string" }, "hide": { - "type": "string", - "default": "" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardVariableHide" }, "label": { "type": "string" @@ -3850,7 +3608,8 @@ "type": "boolean", "default": false } - } + }, + "additionalProperties": false }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardThreshold": { "type": "object", @@ -3860,15 +3619,14 @@ ], "properties": { "color": { - "type": "string", - "default": "" + "type": "string" }, "value": { "description": "Value null means -Infinity", - "type": "number", - "format": "double" + "type": "number" } - } + }, + "additionalProperties": false }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardThresholdsConfig": { "type": "object", @@ -3878,21 +3636,23 @@ ], "properties": { "mode": { - "type": "string", - "default": "" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardThresholdsMode" }, "steps": { "type": "array", "items": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardThreshold" - } - ] + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardThreshold" } } - } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardThresholdsMode": { + "type": "string", + "enum": [ + "absolute", + "percentage" + ] }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardTimeRangeOption": { "type": "object", @@ -3904,20 +3664,21 @@ "properties": { "display": { "type": "string", - "default": "" + "default": "Last 6 hours" }, "from": { "type": "string", - "default": "" + "default": "now-6h" }, "to": { "type": "string", - "default": "" + "default": "now" } - } + }, + "additionalProperties": false }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardTimeSettingsSpec": { - "description": "Time configuration It defines the default time config for the time picker, the refresh picker for the specific dashboard.", + "description": "Time configuration\nIt defines the default time config for the time picker, the refresh picker for the specific dashboard.", "type": "object", "required": [ "from", @@ -3929,64 +3690,76 @@ ], "properties": { "autoRefresh": { - "description": "Refresh rate of dashboard. Represented via interval string, e.g. \"5s\", \"1m\", \"1h\", \"1d\". v1: refresh", + "description": "Refresh rate of dashboard. Represented via interval string, e.g. \"5s\", \"1m\", \"1h\", \"1d\".\nv1: refresh", "type": "string", "default": "" }, "autoRefreshIntervals": { - "description": "Interval options available in the refresh picker dropdown. v1: timepicker.refresh_intervals", + "description": "Interval options available in the refresh picker dropdown.\nv1: timepicker.refresh_intervals", "type": "array", + "default": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], "items": { - "type": "string", - "default": "" + "type": "string" } }, "fiscalYearStartMonth": { "description": "The month that the fiscal year starts on. 0 = January, 11 = December", "type": "integer", - "format": "int64", "default": 0 }, "from": { - "description": "Start time range for dashboard. Accepted values are relative time strings like \"now-6h\" or absolute time strings like \"2020-07-10T08:00:00.000Z\".", + "description": "Start time range for dashboard.\nAccepted values are relative time strings like \"now-6h\" or absolute time strings like \"2020-07-10T08:00:00.000Z\".", "type": "string", - "default": "" + "default": "now-6h" }, "hideTimepicker": { - "description": "Whether timepicker is visible or not. v1: timepicker.hidden", + "description": "Whether timepicker is visible or not.\nv1: timepicker.hidden", "type": "boolean", "default": false }, "nowDelay": { - "description": "Override the now time by entering a time delay. Use this option to accommodate known delays in data aggregation to avoid null values. v1: timepicker.nowDelay", + "description": "Override the now time by entering a time delay. Use this option to accommodate known delays in data aggregation to avoid null values.\nv1: timepicker.nowDelay", "type": "string" }, "quickRanges": { - "description": "Selectable options available in the time picker dropdown. Has no effect on provisioned dashboard. v1: timepicker.quick_ranges , not exposed in the UI", + "description": "Selectable options available in the time picker dropdown. Has no effect on provisioned dashboard.\nv1: timepicker.quick_ranges , not exposed in the UI", "type": "array", "items": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardTimeRangeOption" - } - ] + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardTimeRangeOption" } }, "timezone": { "description": "Timezone of dashboard. Accepted values are IANA TZDB zone ID or \"browser\" or \"utc\".", - "type": "string" + "type": "string", + "default": "browser" }, "to": { - "description": "End time range for dashboard. Accepted values are relative time strings like \"now-6h\" or absolute time strings like \"2020-07-10T08:00:00.000Z\".", + "description": "End time range for dashboard.\nAccepted values are relative time strings like \"now-6h\" or absolute time strings like \"2020-07-10T08:00:00.000Z\".", "type": "string", - "default": "" + "default": "now" }, "weekStart": { "description": "Day when the week starts. Expressed by the name of the day in lowercase, e.g. \"monday\".", - "type": "string" + "type": "string", + "enum": [ + "saturday", + "monday", + "sunday" + ] } - } + }, + "additionalProperties": false }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardTransformationKind": { "type": "object", @@ -3997,136 +3770,16 @@ "properties": { "kind": { "description": "The kind of a TransformationKind is the transformation ID", - "type": "string", - "default": "" + "type": "string" }, "spec": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardDataTransformerConfig" - } - ] + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardDataTransformerConfig" } - } - }, - "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardV2alpha1ActionStyle": { - "type": "object", - "properties": { - "backgroundColor": { - "type": "string" - } - } - }, - "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardV2alpha1FieldConfigSourceOverrides": { - "type": "object", - "required": [ - "matcher", - "properties" - ], - "properties": { - "__systemRef": { - "description": "Describes config override rules created when interacting with Grafana.", - "type": "string" - }, - "matcher": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardMatcherConfig" - } - ] - }, - "properties": { - "type": "array", - "items": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardDynamicConfigValue" - } - ] - } - } - } - }, - "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardV2alpha1RangeMapOptions": { - "type": "object", - "required": [ - "from", - "to", - "result" - ], - "properties": { - "from": { - "description": "Min value of the range. It can be null which means -Infinity", - "type": "number", - "format": "double" - }, - "result": { - "description": "Config to apply when the value is within the range", - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardValueMappingResult" - } - ] - }, - "to": { - "description": "Max value of the range. It can be null which means +Infinity", - "type": "number", - "format": "double" - } - } - }, - "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardV2alpha1RegexMapOptions": { - "type": "object", - "required": [ - "pattern", - "result" - ], - "properties": { - "pattern": { - "description": "Regular expression to match against", - "type": "string", - "default": "" - }, - "result": { - "description": "Config to apply when the value matches the regex", - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardValueMappingResult" - } - ] - } - } - }, - "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardV2alpha1SpecialValueMapOptions": { - "type": "object", - "required": [ - "match", - "result" - ], - "properties": { - "match": { - "description": "Special value to match against", - "type": "string", - "default": "" - }, - "result": { - "description": "Config to apply when the value matches the special value", - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardValueMappingResult" - } - ] - } - } + }, + "additionalProperties": false }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardValueMap": { - "description": "Maps text values to a color or different display text and color. For example, you can configure a value mapping so that all instances of the value 10 appear as Perfection! rather than the number.", + "description": "Maps text values to a color or different display text and color.\nFor example, you can configure a value mapping so that all instances of the value 10 appear as Perfection! rather than the number.", "type": "object", "required": [ "type", @@ -4134,39 +3787,33 @@ ], "properties": { "options": { - "description": "Map with \u003cvalue_to_match\u003e: ValueMappingResult. For example: { \"10\": { text: \"Perfection!\", color: \"green\" } }", + "description": "Map with : ValueMappingResult. For example: { \"10\": { text: \"Perfection!\", color: \"green\" } }", "type": "object", "additionalProperties": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardValueMappingResult" - } - ] + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardValueMappingResult" } }, "type": { - "type": "string", - "default": "" + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardMappingType" } - } + }, + "additionalProperties": false }, - "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardValueMapOrRangeMapOrRegexMapOrSpecialValueMap": { - "type": "object", - "properties": { - "RangeMap": { + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardValueMapping": { + "oneOf": [ + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardValueMap" + }, + { "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardRangeMap" }, - "RegexMap": { + { "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardRegexMap" }, - "SpecialValueMap": { + { "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardSpecialValueMap" - }, - "ValueMap": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardValueMap" } - } + ] }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardValueMappingResult": { "description": "Result used as replacement with text and color when the value matches", @@ -4182,14 +3829,54 @@ }, "index": { "description": "Position in the mapping array. Only used internally.", - "type": "integer", - "format": "int32" + "type": "integer" }, "text": { "description": "Text to display when the value matches", "type": "string" } - } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardVariableHide": { + "description": "Determine if the variable shows on dashboard\nAccepted values are `dontHide` (show label and value), `hideLabel` (show value only), `hideVariable` (show nothing).", + "type": "string", + "enum": [ + "dontHide", + "hideLabel", + "hideVariable" + ] + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardVariableKind": { + "oneOf": [ + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardQueryVariableKind" + }, + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardTextVariableKind" + }, + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardConstantVariableKind" + }, + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardDatasourceVariableKind" + }, + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardIntervalVariableKind" + }, + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardCustomVariableKind" + }, + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardGroupByVariableKind" + }, + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardAdhocVariableKind" + }, + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardSwitchVariableKind" + } + ] }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardVariableOption": { "description": "Variable option specification", @@ -4205,21 +3892,58 @@ }, "text": { "description": "Text to be displayed for the option", - "allOf": [ + "oneOf": [ { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardStringOrArrayOfString" + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } } ] }, "value": { "description": "Value of the option", - "allOf": [ + "oneOf": [ { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardStringOrArrayOfString" + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } } ] } - } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardVariableRefresh": { + "description": "Options to config when to refresh a variable\n`never`: Never refresh the variable\n`onDashboardLoad`: Queries the data source every time the dashboard loads.\n`onTimeRangeChanged`: Queries the data source when the dashboard time range changes.", + "type": "string", + "enum": [ + "never", + "onDashboardLoad", + "onTimeRangeChanged" + ] + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardVariableSort": { + "description": "Sort variable options\nAccepted values are:\n`disabled`: No sorting\n`alphabeticalAsc`: Alphabetical ASC\n`alphabeticalDesc`: Alphabetical DESC\n`numericalAsc`: Numerical ASC\n`numericalDesc`: Numerical DESC\n`alphabeticalCaseInsensitiveAsc`: Alphabetical Case Insensitive ASC\n`alphabeticalCaseInsensitiveDesc`: Alphabetical Case Insensitive DESC\n`naturalAsc`: Natural ASC\n`naturalDesc`: Natural DESC\nVariableSort enum with default value", + "type": "string", + "enum": [ + "disabled", + "alphabeticalAsc", + "alphabeticalDesc", + "numericalAsc", + "numericalDesc", + "alphabeticalCaseInsensitiveAsc", + "alphabeticalCaseInsensitiveDesc", + "naturalAsc", + "naturalDesc" + ] }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardVizConfigKind": { "type": "object", @@ -4230,18 +3954,13 @@ "properties": { "kind": { "description": "The kind of a VizConfigKind is the plugin ID", - "type": "string", - "default": "" + "type": "string" }, "spec": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardVizConfigSpec" - } - ] + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardVizConfigSpec" } - } + }, + "additionalProperties": false }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardVizConfigSpec": { "description": "--- Kinds ---", @@ -4253,24 +3972,20 @@ ], "properties": { "fieldConfig": { - "default": {}, - "allOf": [ - { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardFieldConfigSource" - } - ] + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardFieldConfigSource" }, "options": { "type": "object", "additionalProperties": { - "type": "object" + "type": "object", + "additionalProperties": {} } }, "pluginVersion": { - "type": "string", - "default": "" + "type": "string" } - } + }, + "additionalProperties": false }, "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardWithAccessInfo": { "description": "This is like the legacy DTO where access and metadata are all returned in a single call", @@ -4488,7 +4203,7 @@ } }, "io.k8s.apimachinery.pkg.apis.meta.v1.FieldsV1": { - "description": "FieldsV1 stores a set of fields in a data structure like a Trie, in JSON format.\n\nEach key is either a '.' representing the field itself, and will always map to an empty set, or a string representing a sub-field or item. The string will follow one of these four formats: 'f:\u003cname\u003e', where \u003cname\u003e is the name of a field in a struct, or key in a map 'v:\u003cvalue\u003e', where \u003cvalue\u003e is the exact json formatted value of a list item 'i:\u003cindex\u003e', where \u003cindex\u003e is position of a item in a list 'k:\u003ckeys\u003e', where \u003ckeys\u003e is a map of a list item's key fields to their unique values If a key maps to an empty Fields value, the field that key represents is part of the set.\n\nThe exact format is defined in sigs.k8s.io/structured-merge-diff", + "description": "FieldsV1 stores a set of fields in a data structure like a Trie, in JSON format.\n\nEach key is either a '.' representing the field itself, and will always map to an empty set, or a string representing a sub-field or item. The string will follow one of these four formats: 'f:', where is the name of a field in a struct, or key in a map 'v:', where is the exact json formatted value of a list item 'i:', where is position of a item in a list 'k:', where is a map of a list item's key fields to their unique values If a key maps to an empty Fields value, the field that key represents is part of the set.\n\nThe exact format is defined in sigs.k8s.io/structured-merge-diff", "type": "object" }, "io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta": { @@ -4842,4 +4557,4 @@ } } } -} \ No newline at end of file +} diff --git a/pkg/tests/apis/openapi_snapshots/dashboard.grafana.app-v2beta1.json b/pkg/tests/apis/openapi_snapshots/dashboard.grafana.app-v2beta1.json new file mode 100644 index 00000000000..198ad3aea25 --- /dev/null +++ b/pkg/tests/apis/openapi_snapshots/dashboard.grafana.app-v2beta1.json @@ -0,0 +1,4591 @@ +{ + "openapi": "3.0.0", + "info": { + "description": "Grafana dashboards as resources", + "title": "dashboard.grafana.app/v2beta1" + }, + "paths": { + "/apis/dashboard.grafana.app/v2beta1/": { + "get": { + "tags": [ + "API Discovery" + ], + "description": "Describe the available kubernetes resources", + "operationId": "getAPIResources", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.APIResourceList" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.APIResourceList" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.APIResourceList" + } + } + } + } + } + } + }, + "/apis/dashboard.grafana.app/v2beta1/namespaces/{namespace}/dashboards": { + "get": { + "tags": [ + "Dashboard" + ], + "description": "list objects of kind Dashboard", + "operationId": "listDashboard", + "parameters": [ + { + "name": "allowWatchBookmarks", + "in": "query", + "description": "allowWatchBookmarks requests watch events with type \"BOOKMARK\". Servers that do not implement bookmarks may ignore this flag and bookmarks are sent at the server's discretion. Clients should not assume bookmarks are returned at any specific interval, nor may they assume the server will send any BOOKMARK event during a session. If this is not a watch, this field is ignored.", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "name": "continue", + "in": "query", + "description": "The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the \"next key\".\n\nThis field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications.", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "fieldSelector", + "in": "query", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "labelSelector", + "in": "query", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "limit", + "in": "query", + "description": "limit is a maximum number of responses to return for a list call. If more items exist, the server will set the `continue` field on the list metadata to a value that can be used with the same initial query to retrieve the next set of results. Setting a limit may return fewer than the requested amount of items (up to zero items) in the event all requested objects are filtered out and clients should only use the presence of the continue field to determine whether more results are available. Servers may choose not to support the limit argument and will return all of the available results. If limit is specified and the continue field is empty, clients may assume that no more results are available. This field is not supported if watch is true.\n\nThe server guarantees that the objects returned when using continue will be identical to issuing a single list call without a limit - that is, no objects created, modified, or deleted after the first request is issued will be included in any subsequent continued requests. This is sometimes referred to as a consistent snapshot, and ensures that a client that is using limit to receive smaller chunks of a very large result can ensure they see all possible objects. If objects are updated during a chunked list the version of the object that was present at the time the first list result was calculated is returned.", + "schema": { + "type": "integer", + "uniqueItems": true + } + }, + { + "name": "resourceVersion", + "in": "query", + "description": "resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "resourceVersionMatch", + "in": "query", + "description": "resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "sendInitialEvents", + "in": "query", + "description": "`sendInitialEvents=true` may be set together with `watch=true`. In that case, the watch stream will begin with synthetic events to produce the current state of objects in the collection. Once all such events have been sent, a synthetic \"Bookmark\" event will be sent. The bookmark will report the ResourceVersion (RV) corresponding to the set of objects, and be marked with `\"k8s.io/initial-events-end\": \"true\"` annotation. Afterwards, the watch stream will proceed as usual, sending watch events corresponding to changes (subsequent to the RV) to objects watched.\n\nWhen `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan\n is interpreted as \"data at least as new as the provided `resourceVersion`\"\n and the bookmark event is send when the state is synced\n to a `resourceVersion` at least as fresh as the one provided by the ListOptions.\n If `resourceVersion` is unset, this is interpreted as \"consistent read\" and the\n bookmark event is send when the state is synced at least to the moment\n when request started being processed.\n- `resourceVersionMatch` set to any other value or unset\n Invalid error is returned.\n\nDefaults to true if `resourceVersion=\"\"` or `resourceVersion=\"0\"` (for backward compatibility reasons) and to false otherwise.", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "name": "timeoutSeconds", + "in": "query", + "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", + "schema": { + "type": "integer", + "uniqueItems": true + } + }, + { + "name": "watch", + "in": "query", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "schema": { + "type": "boolean", + "uniqueItems": true + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardList" + } + }, + "application/json;stream=watch": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardList" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardList" + } + }, + "application/vnd.kubernetes.protobuf;stream=watch": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardList" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardList" + } + } + } + } + }, + "x-kubernetes-action": "list", + "x-kubernetes-group-version-kind": { + "group": "dashboard.grafana.app", + "version": "v2beta1", + "kind": "Dashboard" + } + }, + "post": { + "tags": [ + "Dashboard" + ], + "description": "create a Dashboard", + "operationId": "createDashboard", + "parameters": [ + { + "name": "dryRun", + "in": "query", + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "fieldManager", + "in": "query", + "description": "fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint.", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "fieldValidation", + "in": "query", + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "schema": { + "type": "string", + "uniqueItems": true + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.Dashboard" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.Dashboard" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.Dashboard" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.Dashboard" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.Dashboard" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.Dashboard" + } + } + } + }, + "201": { + "description": "Created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.Dashboard" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.Dashboard" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.Dashboard" + } + } + } + }, + "202": { + "description": "Accepted", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.Dashboard" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.Dashboard" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.Dashboard" + } + } + } + } + }, + "x-kubernetes-action": "post", + "x-kubernetes-group-version-kind": { + "group": "dashboard.grafana.app", + "version": "v2beta1", + "kind": "Dashboard" + } + }, + "delete": { + "tags": [ + "Dashboard" + ], + "description": "delete collection of Dashboard", + "operationId": "deletecollectionDashboard", + "parameters": [ + { + "name": "continue", + "in": "query", + "description": "The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the \"next key\".\n\nThis field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications.", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "dryRun", + "in": "query", + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "fieldSelector", + "in": "query", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "gracePeriodSeconds", + "in": "query", + "description": "The duration in seconds before the object should be deleted. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period for the specified type will be used. Defaults to a per object value if not specified. zero means delete immediately.", + "schema": { + "type": "integer", + "uniqueItems": true + } + }, + { + "name": "ignoreStoreReadErrorWithClusterBreakingPotential", + "in": "query", + "description": "if set to true, it will trigger an unsafe deletion of the resource in case the normal deletion flow fails with a corrupt object error. A resource is considered corrupt if it can not be retrieved from the underlying storage successfully because of a) its data can not be transformed e.g. decryption failure, or b) it fails to decode into an object. NOTE: unsafe deletion ignores finalizer constraints, skips precondition checks, and removes the object from the storage. WARNING: This may potentially break the cluster if the workload associated with the resource being unsafe-deleted relies on normal deletion flow. Use only if you REALLY know what you are doing. The default value is false, and the user must opt in to enable it", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "name": "labelSelector", + "in": "query", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "limit", + "in": "query", + "description": "limit is a maximum number of responses to return for a list call. If more items exist, the server will set the `continue` field on the list metadata to a value that can be used with the same initial query to retrieve the next set of results. Setting a limit may return fewer than the requested amount of items (up to zero items) in the event all requested objects are filtered out and clients should only use the presence of the continue field to determine whether more results are available. Servers may choose not to support the limit argument and will return all of the available results. If limit is specified and the continue field is empty, clients may assume that no more results are available. This field is not supported if watch is true.\n\nThe server guarantees that the objects returned when using continue will be identical to issuing a single list call without a limit - that is, no objects created, modified, or deleted after the first request is issued will be included in any subsequent continued requests. This is sometimes referred to as a consistent snapshot, and ensures that a client that is using limit to receive smaller chunks of a very large result can ensure they see all possible objects. If objects are updated during a chunked list the version of the object that was present at the time the first list result was calculated is returned.", + "schema": { + "type": "integer", + "uniqueItems": true + } + }, + { + "name": "orphanDependents", + "in": "query", + "description": "Deprecated: please use the PropagationPolicy, this field will be deprecated in 1.7. Should the dependent objects be orphaned. If true/false, the \"orphan\" finalizer will be added to/removed from the object's finalizers list. Either this field or PropagationPolicy may be set, but not both.", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "name": "propagationPolicy", + "in": "query", + "description": "Whether and how garbage collection will be performed. Either this field or OrphanDependents may be set, but not both. The default policy is decided by the existing finalizer set in the metadata.finalizers and the resource-specific default policy. Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - allow the garbage collector to delete the dependents in the background; 'Foreground' - a cascading policy that deletes all dependents in the foreground.", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "resourceVersion", + "in": "query", + "description": "resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "resourceVersionMatch", + "in": "query", + "description": "resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "sendInitialEvents", + "in": "query", + "description": "`sendInitialEvents=true` may be set together with `watch=true`. In that case, the watch stream will begin with synthetic events to produce the current state of objects in the collection. Once all such events have been sent, a synthetic \"Bookmark\" event will be sent. The bookmark will report the ResourceVersion (RV) corresponding to the set of objects, and be marked with `\"k8s.io/initial-events-end\": \"true\"` annotation. Afterwards, the watch stream will proceed as usual, sending watch events corresponding to changes (subsequent to the RV) to objects watched.\n\nWhen `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan\n is interpreted as \"data at least as new as the provided `resourceVersion`\"\n and the bookmark event is send when the state is synced\n to a `resourceVersion` at least as fresh as the one provided by the ListOptions.\n If `resourceVersion` is unset, this is interpreted as \"consistent read\" and the\n bookmark event is send when the state is synced at least to the moment\n when request started being processed.\n- `resourceVersionMatch` set to any other value or unset\n Invalid error is returned.\n\nDefaults to true if `resourceVersion=\"\"` or `resourceVersion=\"0\"` (for backward compatibility reasons) and to false otherwise.", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "name": "timeoutSeconds", + "in": "query", + "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", + "schema": { + "type": "integer", + "uniqueItems": true + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + } + } + } + }, + "x-kubernetes-action": "deletecollection", + "x-kubernetes-group-version-kind": { + "group": "dashboard.grafana.app", + "version": "v2beta1", + "kind": "Dashboard" + } + }, + "parameters": [ + { + "name": "namespace", + "in": "path", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "pretty", + "in": "query", + "description": "If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget).", + "schema": { + "type": "string", + "uniqueItems": true + } + } + ] + }, + "/apis/dashboard.grafana.app/v2beta1/namespaces/{namespace}/dashboards/{name}": { + "get": { + "tags": [ + "Dashboard" + ], + "description": "read the specified Dashboard", + "operationId": "getDashboard", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.Dashboard" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.Dashboard" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.Dashboard" + } + } + } + } + }, + "x-kubernetes-action": "get", + "x-kubernetes-group-version-kind": { + "group": "dashboard.grafana.app", + "version": "v2beta1", + "kind": "Dashboard" + } + }, + "put": { + "tags": [ + "Dashboard" + ], + "description": "replace the specified Dashboard", + "operationId": "replaceDashboard", + "parameters": [ + { + "name": "dryRun", + "in": "query", + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "fieldManager", + "in": "query", + "description": "fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint.", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "fieldValidation", + "in": "query", + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "schema": { + "type": "string", + "uniqueItems": true + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.Dashboard" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.Dashboard" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.Dashboard" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.Dashboard" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.Dashboard" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.Dashboard" + } + } + } + }, + "201": { + "description": "Created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.Dashboard" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.Dashboard" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.Dashboard" + } + } + } + } + }, + "x-kubernetes-action": "put", + "x-kubernetes-group-version-kind": { + "group": "dashboard.grafana.app", + "version": "v2beta1", + "kind": "Dashboard" + } + }, + "delete": { + "tags": [ + "Dashboard" + ], + "description": "delete a Dashboard", + "operationId": "deleteDashboard", + "parameters": [ + { + "name": "dryRun", + "in": "query", + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "gracePeriodSeconds", + "in": "query", + "description": "The duration in seconds before the object should be deleted. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period for the specified type will be used. Defaults to a per object value if not specified. zero means delete immediately.", + "schema": { + "type": "integer", + "uniqueItems": true + } + }, + { + "name": "ignoreStoreReadErrorWithClusterBreakingPotential", + "in": "query", + "description": "if set to true, it will trigger an unsafe deletion of the resource in case the normal deletion flow fails with a corrupt object error. A resource is considered corrupt if it can not be retrieved from the underlying storage successfully because of a) its data can not be transformed e.g. decryption failure, or b) it fails to decode into an object. NOTE: unsafe deletion ignores finalizer constraints, skips precondition checks, and removes the object from the storage. WARNING: This may potentially break the cluster if the workload associated with the resource being unsafe-deleted relies on normal deletion flow. Use only if you REALLY know what you are doing. The default value is false, and the user must opt in to enable it", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "name": "orphanDependents", + "in": "query", + "description": "Deprecated: please use the PropagationPolicy, this field will be deprecated in 1.7. Should the dependent objects be orphaned. If true/false, the \"orphan\" finalizer will be added to/removed from the object's finalizers list. Either this field or PropagationPolicy may be set, but not both.", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "name": "propagationPolicy", + "in": "query", + "description": "Whether and how garbage collection will be performed. Either this field or OrphanDependents may be set, but not both. The default policy is decided by the existing finalizer set in the metadata.finalizers and the resource-specific default policy. Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - allow the garbage collector to delete the dependents in the background; 'Foreground' - a cascading policy that deletes all dependents in the foreground.", + "schema": { + "type": "string", + "uniqueItems": true + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + } + } + }, + "202": { + "description": "Accepted", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + } + } + } + }, + "x-kubernetes-action": "delete", + "x-kubernetes-group-version-kind": { + "group": "dashboard.grafana.app", + "version": "v2beta1", + "kind": "Dashboard" + } + }, + "patch": { + "tags": [ + "Dashboard" + ], + "description": "partially update the specified Dashboard", + "operationId": "updateDashboard", + "parameters": [ + { + "name": "dryRun", + "in": "query", + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "fieldManager", + "in": "query", + "description": "fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint. This field is required for apply requests (application/apply-patch) but optional for non-apply patch types (JsonPatch, MergePatch, StrategicMergePatch).", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "fieldValidation", + "in": "query", + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "force", + "in": "query", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "schema": { + "type": "boolean", + "uniqueItems": true + } + } + ], + "requestBody": { + "content": { + "application/apply-patch+yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + }, + "application/json-patch+json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + }, + "application/merge-patch+json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + }, + "application/strategic-merge-patch+json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.Dashboard" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.Dashboard" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.Dashboard" + } + } + } + }, + "201": { + "description": "Created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.Dashboard" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.Dashboard" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.Dashboard" + } + } + } + } + }, + "x-kubernetes-action": "patch", + "x-kubernetes-group-version-kind": { + "group": "dashboard.grafana.app", + "version": "v2beta1", + "kind": "Dashboard" + } + }, + "parameters": [ + { + "name": "name", + "in": "path", + "description": "name of the Dashboard", + "required": true, + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "namespace", + "in": "path", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "pretty", + "in": "query", + "description": "If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget).", + "schema": { + "type": "string", + "uniqueItems": true + } + } + ] + }, + "/apis/dashboard.grafana.app/v2beta1/namespaces/{namespace}/dashboards/{name}/dto": { + "get": { + "tags": [ + "Dashboard" + ], + "description": "connect GET requests to dto of Dashboard", + "operationId": "getDashboardDto", + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardWithAccessInfo" + } + } + } + } + }, + "x-kubernetes-action": "connect", + "x-kubernetes-group-version-kind": { + "group": "dashboard.grafana.app", + "version": "v2beta1", + "kind": "DashboardWithAccessInfo" + } + }, + "parameters": [ + { + "name": "name", + "in": "path", + "description": "name of the DashboardWithAccessInfo", + "required": true, + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "namespace", + "in": "path", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "schema": { + "type": "string", + "uniqueItems": true + } + } + ] + } + }, + "components": { + "schemas": { + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.AnnotationActions": { + "type": "object", + "required": [ + "canAdd", + "canEdit", + "canDelete" + ], + "properties": { + "canAdd": { + "type": "boolean", + "default": false + }, + "canDelete": { + "type": "boolean", + "default": false + }, + "canEdit": { + "type": "boolean", + "default": false + } + } + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.AnnotationPermission": { + "type": "object", + "required": [ + "dashboard", + "organization" + ], + "properties": { + "dashboard": { + "default": {}, + "allOf": [ + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.AnnotationActions" + } + ] + }, + "organization": { + "default": {}, + "allOf": [ + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.AnnotationActions" + } + ] + } + } + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.Dashboard": { + "type": "object", + "required": [ + "kind", + "apiVersion", + "metadata", + "spec" + ], + "properties": { + "apiVersion": { + "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" + }, + "kind": { + "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" + }, + "metadata": { + "default": {}, + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta" + } + ] + }, + "spec": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardSpec" + }, + "status": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardStatus" + } + }, + "x-kubernetes-group-version-kind": [ + { + "group": "dashboard.grafana.app", + "kind": "Dashboard", + "version": "v2beta1" + } + ] + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardAccess": { + "description": "Information about how the requesting user can use a given dashboard", + "type": "object", + "required": [ + "isPublic", + "canSave", + "canEdit", + "canAdmin", + "canStar", + "canDelete", + "annotationsPermissions" + ], + "properties": { + "annotationsPermissions": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.AnnotationPermission" + }, + "canAdmin": { + "type": "boolean", + "default": false + }, + "canDelete": { + "type": "boolean", + "default": false + }, + "canEdit": { + "type": "boolean", + "default": false + }, + "canSave": { + "description": "The permissions part", + "type": "boolean", + "default": false + }, + "canStar": { + "type": "boolean", + "default": false + }, + "isPublic": { + "type": "boolean", + "default": false + }, + "slug": { + "description": "Metadata fields", + "type": "string" + }, + "url": { + "type": "string" + } + } + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardAction": { + "type": "object", + "required": [ + "type", + "title" + ], + "properties": { + "confirmation": { + "type": "string" + }, + "fetch": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardFetchOptions" + }, + "infinity": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardInfinityOptions" + }, + "oneClick": { + "type": "boolean" + }, + "style": { + "type": "object", + "properties": { + "backgroundColor": { + "type": "string" + } + }, + "additionalProperties": false + }, + "title": { + "type": "string" + }, + "type": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardActionType" + }, + "variables": { + "type": "array", + "items": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardActionVariable" + } + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardActionType": { + "type": "string", + "enum": [ + "fetch", + "infinity" + ] + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardActionVariable": { + "type": "object", + "required": [ + "key", + "name", + "type" + ], + "properties": { + "key": { + "type": "string" + }, + "name": { + "type": "string" + }, + "type": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardActionVariableType" + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardActionVariableType": { + "description": "Action variable type", + "type": "string" + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardAdHocFilterWithLabels": { + "description": "Define the AdHocFilterWithLabels type", + "type": "object", + "required": [ + "key", + "operator", + "value" + ], + "properties": { + "condition": { + "description": "@deprecated", + "type": "string" + }, + "forceEdit": { + "type": "boolean" + }, + "key": { + "type": "string" + }, + "keyLabel": { + "type": "string" + }, + "operator": { + "type": "string" + }, + "origin": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardFilterOrigin" + }, + "value": { + "type": "string" + }, + "valueLabels": { + "type": "array", + "items": { + "type": "string" + } + }, + "values": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardAdhocVariableKind": { + "description": "Adhoc variable kind", + "type": "object", + "required": [ + "kind", + "group", + "spec" + ], + "properties": { + "datasource": { + "type": "object", + "properties": { + "name": { + "type": "string" + } + }, + "additionalProperties": false + }, + "group": { + "type": "string" + }, + "kind": { + "type": "string" + }, + "spec": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardAdhocVariableSpec" + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardAdhocVariableSpec": { + "description": "Adhoc variable specification", + "type": "object", + "required": [ + "name", + "baseFilters", + "filters", + "defaultKeys", + "hide", + "skipUrlSync", + "allowCustomValue" + ], + "properties": { + "allowCustomValue": { + "type": "boolean", + "default": true + }, + "baseFilters": { + "type": "array", + "items": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardAdHocFilterWithLabels" + } + }, + "defaultKeys": { + "type": "array", + "items": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardMetricFindValue" + } + }, + "description": { + "type": "string" + }, + "filters": { + "type": "array", + "items": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardAdHocFilterWithLabels" + } + }, + "hide": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardVariableHide" + }, + "label": { + "type": "string" + }, + "name": { + "type": "string", + "default": "" + }, + "skipUrlSync": { + "type": "boolean", + "default": false + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardAnnotationEventFieldMapping": { + "description": "Annotation event field mapping. Defines how to map a data frame field to an annotation event field.", + "type": "object", + "properties": { + "regex": { + "description": "Regular expression to apply to the field value", + "type": "string" + }, + "source": { + "description": "Source type for the field value", + "type": "string", + "default": "field" + }, + "value": { + "description": "Constant value to use when source is \"text\"", + "type": "string" + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardAnnotationPanelFilter": { + "type": "object", + "required": [ + "ids" + ], + "properties": { + "exclude": { + "description": "Should the specified panels be included or excluded", + "type": "boolean", + "default": false + }, + "ids": { + "description": "Panel IDs that should be included or excluded", + "type": "array", + "items": { + "type": "integer" + } + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardAnnotationQueryKind": { + "type": "object", + "required": [ + "kind", + "spec" + ], + "properties": { + "kind": { + "type": "string" + }, + "spec": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardAnnotationQuerySpec" + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardAnnotationQueryPlacement": { + "description": "Annotation Query placement. Defines where the annotation query should be displayed.\n- \"inControlsMenu\" renders the annotation query in the dashboard controls dropdown menu", + "type": "string" + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardAnnotationQuerySpec": { + "type": "object", + "required": [ + "query", + "enable", + "hide", + "iconColor", + "name" + ], + "properties": { + "builtIn": { + "type": "boolean", + "default": false + }, + "enable": { + "type": "boolean" + }, + "filter": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardAnnotationPanelFilter" + }, + "hide": { + "type": "boolean" + }, + "iconColor": { + "type": "string" + }, + "legacyOptions": { + "description": "Catch-all field for datasource-specific properties. Should not be available in as code tooling.", + "type": "object", + "additionalProperties": true + }, + "mappings": { + "description": "Mappings define how to convert data frame fields to annotation event fields.", + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardAnnotationEventFieldMapping" + } + }, + "name": { + "type": "string" + }, + "placement": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardAnnotationQueryPlacement" + }, + "query": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardDataQueryKind" + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardAutoGridLayoutItemKind": { + "type": "object", + "required": [ + "kind", + "spec" + ], + "properties": { + "kind": { + "type": "string" + }, + "spec": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardAutoGridLayoutItemSpec" + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardAutoGridLayoutItemSpec": { + "type": "object", + "required": [ + "element" + ], + "properties": { + "conditionalRendering": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardConditionalRenderingGroupKind" + }, + "element": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardElementReference" + }, + "repeat": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardAutoGridRepeatOptions" + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardAutoGridLayoutKind": { + "type": "object", + "required": [ + "kind", + "spec" + ], + "properties": { + "kind": { + "type": "string" + }, + "spec": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardAutoGridLayoutSpec" + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardAutoGridLayoutSpec": { + "type": "object", + "required": [ + "columnWidthMode", + "rowHeightMode", + "items" + ], + "properties": { + "columnWidth": { + "type": "number" + }, + "columnWidthMode": { + "type": "string", + "default": "standard", + "enum": [ + "narrow", + "standard", + "wide", + "custom" + ] + }, + "fillScreen": { + "type": "boolean", + "default": false + }, + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardAutoGridLayoutItemKind" + } + }, + "maxColumnCount": { + "type": "number", + "default": 3 + }, + "rowHeight": { + "type": "number" + }, + "rowHeightMode": { + "type": "string", + "default": "standard", + "enum": [ + "short", + "standard", + "tall", + "custom" + ] + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardAutoGridRepeatOptions": { + "type": "object", + "required": [ + "mode", + "value" + ], + "properties": { + "mode": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardRepeatMode" + }, + "value": { + "type": "string" + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardConditionalRenderingDataKind": { + "type": "object", + "required": [ + "kind", + "spec" + ], + "properties": { + "kind": { + "type": "string" + }, + "spec": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardConditionalRenderingDataSpec" + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardConditionalRenderingDataSpec": { + "type": "object", + "required": [ + "value" + ], + "properties": { + "value": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardConditionalRenderingGroupKind": { + "type": "object", + "required": [ + "kind", + "spec" + ], + "properties": { + "kind": { + "type": "string" + }, + "spec": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardConditionalRenderingGroupSpec" + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardConditionalRenderingGroupSpec": { + "type": "object", + "required": [ + "visibility", + "condition", + "items" + ], + "properties": { + "condition": { + "type": "string", + "enum": [ + "and", + "or" + ] + }, + "items": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardConditionalRenderingVariableKind" + }, + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardConditionalRenderingDataKind" + }, + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardConditionalRenderingTimeRangeSizeKind" + } + ] + } + }, + "visibility": { + "type": "string", + "enum": [ + "show", + "hide" + ] + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardConditionalRenderingTimeRangeSizeKind": { + "type": "object", + "required": [ + "kind", + "spec" + ], + "properties": { + "kind": { + "type": "string" + }, + "spec": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardConditionalRenderingTimeRangeSizeSpec" + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardConditionalRenderingTimeRangeSizeSpec": { + "type": "object", + "required": [ + "value" + ], + "properties": { + "value": { + "type": "string" + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardConditionalRenderingVariableKind": { + "type": "object", + "required": [ + "kind", + "spec" + ], + "properties": { + "kind": { + "type": "string" + }, + "spec": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardConditionalRenderingVariableSpec" + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardConditionalRenderingVariableSpec": { + "type": "object", + "required": [ + "variable", + "operator", + "value" + ], + "properties": { + "operator": { + "type": "string", + "enum": [ + "equals", + "notEquals", + "matches", + "notMatches" + ] + }, + "value": { + "type": "string" + }, + "variable": { + "type": "string" + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardConstantVariableKind": { + "description": "Constant variable kind", + "type": "object", + "required": [ + "kind", + "spec" + ], + "properties": { + "kind": { + "type": "string" + }, + "spec": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardConstantVariableSpec" + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardConstantVariableSpec": { + "description": "Constant variable specification", + "type": "object", + "required": [ + "name", + "query", + "current", + "hide", + "skipUrlSync" + ], + "properties": { + "current": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardVariableOption" + }, + "description": { + "type": "string" + }, + "hide": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardVariableHide" + }, + "label": { + "type": "string" + }, + "name": { + "type": "string", + "default": "" + }, + "query": { + "type": "string", + "default": "" + }, + "skipUrlSync": { + "type": "boolean", + "default": false + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardConversionStatus": { + "description": "ConversionStatus is the status of the conversion of the dashboard.", + "type": "object", + "required": [ + "failed" + ], + "properties": { + "error": { + "description": "The error message from the conversion.\nEmpty if the conversion has not failed.", + "type": "string" + }, + "failed": { + "description": "Whether from another version has failed.\nIf true, means that the dashboard is not valid,\nand the caller should instead fetch the stored version.", + "type": "boolean" + }, + "source": { + "description": "The original value map[string]any", + "type": "object", + "additionalProperties": {} + }, + "storedVersion": { + "description": "The version which was stored when the dashboard was created / updated.\nFetching this version should always succeed.", + "type": "string" + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardCustomVariableKind": { + "description": "Custom variable kind", + "type": "object", + "required": [ + "kind", + "spec" + ], + "properties": { + "kind": { + "type": "string" + }, + "spec": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardCustomVariableSpec" + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardCustomVariableSpec": { + "description": "Custom variable specification", + "type": "object", + "required": [ + "name", + "query", + "current", + "options", + "multi", + "includeAll", + "hide", + "skipUrlSync", + "allowCustomValue" + ], + "properties": { + "allValue": { + "type": "string" + }, + "allowCustomValue": { + "type": "boolean", + "default": true + }, + "current": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardVariableOption" + }, + "description": { + "type": "string" + }, + "hide": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardVariableHide" + }, + "includeAll": { + "type": "boolean", + "default": false + }, + "label": { + "type": "string" + }, + "multi": { + "type": "boolean", + "default": false + }, + "name": { + "type": "string", + "default": "" + }, + "options": { + "type": "array", + "items": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardVariableOption" + } + }, + "query": { + "type": "string", + "default": "" + }, + "skipUrlSync": { + "type": "boolean", + "default": false + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardDashboardCursorSync": { + "description": "\"Off\" for no shared crosshair or tooltip (default).\n\"Crosshair\" for shared crosshair.\n\"Tooltip\" for shared crosshair AND shared tooltip.", + "type": "string", + "enum": [ + "Crosshair", + "Tooltip", + "Off" + ] + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardDashboardLink": { + "description": "Links with references to other dashboards or external resources", + "type": "object", + "required": [ + "title", + "type", + "icon", + "tooltip", + "tags", + "asDropdown", + "targetBlank", + "includeVars", + "keepTime" + ], + "properties": { + "asDropdown": { + "description": "If true, all dashboards links will be displayed in a dropdown. If false, all dashboards links will be displayed side by side. Only valid if the type is dashboards", + "type": "boolean", + "default": false + }, + "icon": { + "description": "Icon name to be displayed with the link", + "type": "string" + }, + "includeVars": { + "description": "If true, includes current template variables values in the link as query params", + "type": "boolean", + "default": false + }, + "keepTime": { + "description": "If true, includes current time range in the link as query params", + "type": "boolean", + "default": false + }, + "placement": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardDashboardLinkPlacement" + }, + "tags": { + "description": "List of tags to limit the linked dashboards. If empty, all dashboards will be displayed. Only valid if the type is dashboards", + "type": "array", + "items": { + "type": "string" + } + }, + "targetBlank": { + "description": "If true, the link will be opened in a new tab", + "type": "boolean", + "default": false + }, + "title": { + "description": "Title to display with the link", + "type": "string" + }, + "tooltip": { + "description": "Tooltip to display when the user hovers their mouse over it", + "type": "string" + }, + "type": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardDashboardLinkType" + }, + "url": { + "description": "Link URL. Only required/valid if the type is link", + "type": "string" + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardDashboardLinkPlacement": { + "description": "Dashboard Link placement. Defines where the link should be displayed.\n- \"inControlsMenu\" renders the link in bottom part of the dashboard controls dropdown menu", + "type": "string" + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardDashboardLinkType": { + "description": "Dashboard Link type. Accepted values are dashboards (to refer to another dashboard) and link (to refer to an external resource)", + "type": "string", + "enum": [ + "link", + "dashboards" + ] + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardDataLink": { + "type": "object", + "required": [ + "title", + "url" + ], + "properties": { + "targetBlank": { + "type": "boolean" + }, + "title": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardDataQueryKind": { + "type": "object", + "required": [ + "kind", + "group", + "version", + "spec" + ], + "properties": { + "datasource": { + "description": "New type for datasource reference\nNot creating a new type until we figure out how to handle DS refs for group by, adhoc, and every place that uses DataSourceRef in TS.", + "type": "object", + "properties": { + "name": { + "type": "string" + } + }, + "additionalProperties": false + }, + "group": { + "type": "string" + }, + "kind": { + "type": "string" + }, + "spec": { + "type": "object", + "additionalProperties": { + "type": "object", + "additionalProperties": {} + } + }, + "version": { + "type": "string", + "default": "v0" + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardDataTopic": { + "description": "A topic is attached to DataFrame metadata in query results.\nThis specifies where the data should be used.", + "type": "string", + "enum": [ + "series", + "annotations", + "alertStates" + ] + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardDataTransformerConfig": { + "description": "Transformations allow to manipulate data returned by a query before the system applies a visualization.\nUsing transformations you can: rename fields, join time series data, perform mathematical operations across queries,\nuse the output of one transformation as the input to another transformation, etc.", + "type": "object", + "required": [ + "id", + "options" + ], + "properties": { + "disabled": { + "description": "Disabled transformations are skipped", + "type": "boolean" + }, + "filter": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardMatcherConfig" + }, + "id": { + "description": "Unique identifier of transformer", + "type": "string" + }, + "options": { + "description": "Options to be passed to the transformer\nValid options depend on the transformer id", + "type": "object", + "additionalProperties": {} + }, + "topic": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardDataTopic" + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardDatasourceVariableKind": { + "description": "Datasource variable kind", + "type": "object", + "required": [ + "kind", + "spec" + ], + "properties": { + "kind": { + "type": "string" + }, + "spec": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardDatasourceVariableSpec" + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardDatasourceVariableSpec": { + "description": "Datasource variable specification", + "type": "object", + "required": [ + "name", + "pluginId", + "refresh", + "regex", + "current", + "options", + "multi", + "includeAll", + "hide", + "skipUrlSync", + "allowCustomValue" + ], + "properties": { + "allValue": { + "type": "string" + }, + "allowCustomValue": { + "type": "boolean", + "default": true + }, + "current": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardVariableOption" + }, + "description": { + "type": "string" + }, + "hide": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardVariableHide" + }, + "includeAll": { + "type": "boolean", + "default": false + }, + "label": { + "type": "string" + }, + "multi": { + "type": "boolean", + "default": false + }, + "name": { + "type": "string", + "default": "" + }, + "options": { + "type": "array", + "items": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardVariableOption" + } + }, + "pluginId": { + "type": "string", + "default": "" + }, + "refresh": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardVariableRefresh" + }, + "regex": { + "type": "string", + "default": "" + }, + "skipUrlSync": { + "type": "boolean", + "default": false + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardDynamicConfigValue": { + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "type": "string", + "default": "" + }, + "value": { + "type": "object", + "additionalProperties": {} + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardElement": { + "description": "Supported dashboard elements\n|* more element types in the future", + "oneOf": [ + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardPanelKind" + }, + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardLibraryPanelKind" + } + ] + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardElementReference": { + "type": "object", + "required": [ + "kind", + "name" + ], + "properties": { + "kind": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardFetchOptions": { + "type": "object", + "required": [ + "method", + "url" + ], + "properties": { + "body": { + "type": "string" + }, + "headers": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "method": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardHttpRequestMethod" + }, + "queryParams": { + "description": "These are 2D arrays of strings, each representing a key-value pair\nWe are defining them this way because we can't generate a go struct that\nthat would have exactly two strings in each sub-array", + "type": "array", + "items": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardFieldColor": { + "description": "Map a field to a color.", + "type": "object", + "required": [ + "mode" + ], + "properties": { + "fixedColor": { + "description": "The fixed color value for fixed or shades color modes.", + "type": "string" + }, + "mode": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardFieldColorModeId" + }, + "seriesBy": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardFieldColorSeriesByMode" + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardFieldColorModeId": { + "description": "Color mode for a field. You can specify a single color, or select a continuous (gradient) color schemes, based on a value.\nContinuous color interpolates a color using the percentage of a value relative to min and max.\nAccepted values are:\n`thresholds`: From thresholds. Informs Grafana to take the color from the matching threshold\n`palette-classic`: Classic palette. Grafana will assign color by looking up a color in a palette by series index. Useful for Graphs and pie charts and other categorical data visualizations\n`palette-classic-by-name`: Classic palette (by name). Grafana will assign color by looking up a color in a palette by series name. Useful for Graphs and pie charts and other categorical data visualizations\n`continuous-viridis`: Continuous Viridis palette mode\n`continuous-magma`: Continuous Magma palette mode\n`continuous-plasma`: Continuous Plasma palette mode\n`continuous-inferno`: Continuous Inferno palette mode\n`continuous-cividis`: Continuous Cividis palette mode\n`continuous-GrYlRd`: Continuous Green-Yellow-Red palette mode\n`continuous-RdYlGr`: Continuous Red-Yellow-Green palette mode\n`continuous-BlYlRd`: Continuous Blue-Yellow-Red palette mode\n`continuous-YlRd`: Continuous Yellow-Red palette mode\n`continuous-BlPu`: Continuous Blue-Purple palette mode\n`continuous-YlBl`: Continuous Yellow-Blue palette mode\n`continuous-blues`: Continuous Blue palette mode\n`continuous-reds`: Continuous Red palette mode\n`continuous-greens`: Continuous Green palette mode\n`continuous-purples`: Continuous Purple palette mode\n`shades`: Shades of a single color. Specify a single color, useful in an override rule.\n`fixed`: Fixed color mode. Specify a single color, useful in an override rule.", + "type": "string", + "enum": [ + "thresholds", + "palette-classic", + "palette-classic-by-name", + "continuous-viridis", + "continuous-magma", + "continuous-plasma", + "continuous-inferno", + "continuous-cividis", + "continuous-GrYlRd", + "continuous-RdYlGr", + "continuous-BlYlRd", + "continuous-YlRd", + "continuous-BlPu", + "continuous-YlBl", + "continuous-blues", + "continuous-reds", + "continuous-greens", + "continuous-purples", + "fixed", + "shades" + ] + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardFieldColorSeriesByMode": { + "description": "Defines how to assign a series color from \"by value\" color schemes. For example for an aggregated data points like a timeseries, the color can be assigned by the min, max or last value.", + "type": "string", + "enum": [ + "min", + "max", + "last" + ] + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardFieldConfig": { + "description": "The data model used in Grafana, namely the data frame, is a columnar-oriented table structure that unifies both time series and table query results.\nEach column within this structure is called a field. A field can represent a single time series or table column.\nField options allow you to change how the data is displayed in your visualizations.", + "type": "object", + "properties": { + "actions": { + "description": "Define interactive HTTP requests that can be triggered from data visualizations.", + "type": "array", + "items": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardAction" + } + }, + "color": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardFieldColor" + }, + "custom": { + "description": "custom is specified by the FieldConfig field\nin panel plugin schemas.", + "type": "object", + "additionalProperties": { + "type": "object", + "additionalProperties": {} + } + }, + "decimals": { + "description": "Specify the number of decimals Grafana includes in the rendered value.\nIf you leave this field blank, Grafana automatically truncates the number of decimals based on the value.\nFor example 1.1234 will display as 1.12 and 100.456 will display as 100.\nTo display all decimals, set the unit to `String`.", + "type": "number" + }, + "description": { + "description": "Human readable field metadata", + "type": "string" + }, + "displayName": { + "description": "The display value for this field. This supports template variables blank is auto", + "type": "string" + }, + "displayNameFromDS": { + "description": "This can be used by data sources that return and explicit naming structure for values and labels\nWhen this property is configured, this value is used rather than the default naming strategy.", + "type": "string" + }, + "filterable": { + "description": "True if data source field supports ad-hoc filters", + "type": "boolean" + }, + "links": { + "description": "The behavior when clicking on a result", + "type": "array", + "items": { + "type": "object", + "additionalProperties": {} + } + }, + "mappings": { + "description": "Convert input values into a display string", + "type": "array", + "items": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardValueMapping" + } + }, + "max": { + "description": "The maximum value used in percentage threshold calculations. Leave blank for auto calculation based on all series and fields.", + "type": "number" + }, + "min": { + "description": "The minimum value used in percentage threshold calculations. Leave blank for auto calculation based on all series and fields.", + "type": "number" + }, + "noValue": { + "description": "Alternative to empty string", + "type": "string" + }, + "path": { + "description": "An explicit path to the field in the datasource. When the frame meta includes a path,\nThis will default to `${frame.meta.path}/${field.name}\n\nWhen defined, this value can be used as an identifier within the datasource scope, and\nmay be used to update the results", + "type": "string" + }, + "thresholds": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardThresholdsConfig" + }, + "unit": { + "description": "Unit a field should use. The unit you select is applied to all fields except time.\nYou can use the units ID availables in Grafana or a custom unit.\nAvailable units in Grafana: https://github.com/grafana/grafana/blob/main/packages/grafana-data/src/valueFormats/categories.ts\nAs custom unit, you can use the following formats:\n`suffix:` for custom unit that should go after value.\n`prefix:` for custom unit that should go before value.\n`time:` For custom date time formats type for example `time:YYYY-MM-DD`.\n`si:` for custom SI units. For example: `si: mF`. This one is a bit more advanced as you can specify both a unit and the source data scale. So if your source data is represented as milli (thousands of) something prefix the unit with that SI scale character.\n`count:` for a custom count unit.\n`currency:` for custom a currency unit.", + "type": "string" + }, + "writeable": { + "description": "True if data source can write a value to the path. Auth/authz are supported separately", + "type": "boolean" + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardFieldConfigSource": { + "description": "The data model used in Grafana, namely the data frame, is a columnar-oriented table structure that unifies both time series and table query results.\nEach column within this structure is called a field. A field can represent a single time series or table column.\nField options allow you to change how the data is displayed in your visualizations.", + "type": "object", + "required": [ + "defaults", + "overrides" + ], + "properties": { + "defaults": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardFieldConfig" + }, + "overrides": { + "description": "Overrides are the options applied to specific fields overriding the defaults.", + "type": "array", + "items": { + "type": "object", + "required": [ + "matcher", + "properties" + ], + "properties": { + "__systemRef": { + "description": "Describes config override rules created when interacting with Grafana.", + "type": "string" + }, + "matcher": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardMatcherConfig" + }, + "properties": { + "type": "array", + "items": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardDynamicConfigValue" + } + } + }, + "additionalProperties": false + } + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardFilterOrigin": { + "description": "Determine the origin of the adhoc variable filter", + "type": "string" + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardGridLayoutItemKind": { + "type": "object", + "required": [ + "kind", + "spec" + ], + "properties": { + "kind": { + "type": "string" + }, + "spec": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardGridLayoutItemSpec" + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardGridLayoutItemSpec": { + "type": "object", + "required": [ + "x", + "y", + "width", + "height", + "element" + ], + "properties": { + "element": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardElementReference" + }, + "height": { + "type": "integer" + }, + "repeat": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardRepeatOptions" + }, + "width": { + "type": "integer" + }, + "x": { + "type": "integer" + }, + "y": { + "type": "integer" + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardGridLayoutKind": { + "type": "object", + "required": [ + "kind", + "spec" + ], + "properties": { + "kind": { + "type": "string" + }, + "spec": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardGridLayoutSpec" + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardGridLayoutSpec": { + "type": "object", + "required": [ + "items" + ], + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardGridLayoutItemKind" + } + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardGroupByVariableKind": { + "description": "Group variable kind", + "type": "object", + "required": [ + "kind", + "group", + "spec" + ], + "properties": { + "datasource": { + "type": "object", + "properties": { + "name": { + "type": "string" + } + }, + "additionalProperties": false + }, + "group": { + "type": "string" + }, + "kind": { + "type": "string" + }, + "spec": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardGroupByVariableSpec" + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardGroupByVariableSpec": { + "description": "GroupBy variable specification", + "type": "object", + "required": [ + "name", + "current", + "options", + "multi", + "hide", + "skipUrlSync" + ], + "properties": { + "current": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardVariableOption" + }, + "defaultValue": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardVariableOption" + }, + "description": { + "type": "string" + }, + "hide": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardVariableHide" + }, + "label": { + "type": "string" + }, + "multi": { + "type": "boolean", + "default": false + }, + "name": { + "type": "string", + "default": "" + }, + "options": { + "type": "array", + "items": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardVariableOption" + } + }, + "skipUrlSync": { + "type": "boolean", + "default": false + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardHttpRequestMethod": { + "type": "string", + "enum": [ + "GET", + "PUT", + "POST", + "DELETE", + "PATCH" + ] + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardInfinityOptions": { + "type": "object", + "required": [ + "method", + "url", + "datasourceUid" + ], + "properties": { + "body": { + "type": "string" + }, + "datasourceUid": { + "type": "string" + }, + "headers": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "method": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardHttpRequestMethod" + }, + "queryParams": { + "description": "These are 2D arrays of strings, each representing a key-value pair\nWe are defining them this way because we can't generate a go struct that\nthat would have exactly two strings in each sub-array", + "type": "array", + "items": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardIntervalVariableKind": { + "description": "Interval variable kind", + "type": "object", + "required": [ + "kind", + "spec" + ], + "properties": { + "kind": { + "type": "string" + }, + "spec": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardIntervalVariableSpec" + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardIntervalVariableSpec": { + "description": "Interval variable specification", + "type": "object", + "required": [ + "name", + "query", + "current", + "options", + "auto", + "auto_min", + "auto_count", + "refresh", + "hide", + "skipUrlSync" + ], + "properties": { + "auto": { + "type": "boolean", + "default": false + }, + "auto_count": { + "type": "integer", + "default": 0 + }, + "auto_min": { + "type": "string", + "default": "" + }, + "current": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardVariableOption" + }, + "description": { + "type": "string" + }, + "hide": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardVariableHide" + }, + "label": { + "type": "string" + }, + "name": { + "type": "string", + "default": "" + }, + "options": { + "type": "array", + "items": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardVariableOption" + } + }, + "query": { + "type": "string", + "default": "" + }, + "refresh": { + "type": "string" + }, + "skipUrlSync": { + "type": "boolean", + "default": false + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardLibraryPanelKind": { + "type": "object", + "required": [ + "kind", + "spec" + ], + "properties": { + "kind": { + "type": "string" + }, + "spec": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardLibraryPanelKindSpec" + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardLibraryPanelKindSpec": { + "type": "object", + "required": [ + "id", + "title", + "libraryPanel" + ], + "properties": { + "id": { + "description": "Panel ID for the library panel in the dashboard", + "type": "number" + }, + "libraryPanel": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardLibraryPanelRef" + }, + "title": { + "description": "Title for the library panel in the dashboard", + "type": "string" + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardLibraryPanelRef": { + "description": "A library panel is a reusable panel that you can use in any dashboard.\nWhen you make a change to a library panel, that change propagates to all instances of where the panel is used.\nLibrary panels streamline reuse of panels across multiple dashboards.", + "type": "object", + "required": [ + "name", + "uid" + ], + "properties": { + "name": { + "description": "Library panel name", + "type": "string" + }, + "uid": { + "description": "Library panel uid", + "type": "string" + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardList": { + "type": "object", + "required": [ + "metadata", + "items" + ], + "properties": { + "apiVersion": { + "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" + }, + "items": { + "type": "array", + "items": { + "default": {}, + "allOf": [ + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.Dashboard" + } + ] + } + }, + "kind": { + "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" + }, + "metadata": { + "default": {}, + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta" + } + ] + } + }, + "x-kubernetes-group-version-kind": [ + { + "group": "dashboard.grafana.app", + "kind": "DashboardList", + "version": "v2beta1" + } + ] + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardMappingType": { + "description": "Supported value mapping types\n`value`: Maps text values to a color or different display text and color. For example, you can configure a value mapping so that all instances of the value 10 appear as Perfection! rather than the number.\n`range`: Maps numerical ranges to a display text and color. For example, if a value is within a certain range, you can configure a range value mapping to display Low or High rather than the number.\n`regex`: Maps regular expressions to replacement text and a color. For example, if a value is www.example.com, you can configure a regex value mapping so that Grafana displays www and truncates the domain.\n`special`: Maps special values like Null, NaN (not a number), and boolean values like true and false to a display text and color. See SpecialValueMatch to see the list of special values. For example, you can configure a special value mapping so that null values appear as N/A.", + "type": "string", + "enum": [ + "value", + "range", + "regex", + "special" + ] + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardMatcherConfig": { + "description": "Matcher is a predicate configuration. Based on the config a set of field(s) or values is filtered in order to apply override / transformation.\nIt comes with in id ( to resolve implementation from registry) and a configuration that’s specific to a particular matcher type.", + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "description": "The matcher id. This is used to find the matcher implementation from registry.", + "type": "string", + "default": "" + }, + "options": { + "description": "The matcher options. This is specific to the matcher implementation.", + "type": "object", + "additionalProperties": {} + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardMetricFindValue": { + "description": "Define the MetricFindValue type", + "type": "object", + "required": [ + "text" + ], + "properties": { + "expandable": { + "type": "boolean" + }, + "group": { + "type": "string" + }, + "text": { + "type": "string" + }, + "value": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "number" + } + ] + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardPanelKind": { + "type": "object", + "required": [ + "kind", + "spec" + ], + "properties": { + "kind": { + "type": "string" + }, + "spec": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardPanelSpec" + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardPanelQueryKind": { + "type": "object", + "required": [ + "kind", + "spec" + ], + "properties": { + "kind": { + "type": "string" + }, + "spec": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardPanelQuerySpec" + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardPanelQuerySpec": { + "type": "object", + "required": [ + "query", + "refId", + "hidden" + ], + "properties": { + "hidden": { + "type": "boolean" + }, + "query": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardDataQueryKind" + }, + "refId": { + "type": "string", + "default": "A" + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardPanelSpec": { + "type": "object", + "required": [ + "id", + "title", + "description", + "links", + "data", + "vizConfig" + ], + "properties": { + "data": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardQueryGroupKind" + }, + "description": { + "type": "string" + }, + "id": { + "type": "number" + }, + "links": { + "type": "array", + "items": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardDataLink" + } + }, + "title": { + "type": "string" + }, + "transparent": { + "type": "boolean" + }, + "vizConfig": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardVizConfigKind" + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardQueryGroupKind": { + "type": "object", + "required": [ + "kind", + "spec" + ], + "properties": { + "kind": { + "type": "string" + }, + "spec": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardQueryGroupSpec" + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardQueryGroupSpec": { + "type": "object", + "required": [ + "queries", + "transformations", + "queryOptions" + ], + "properties": { + "queries": { + "type": "array", + "items": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardPanelQueryKind" + } + }, + "queryOptions": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardQueryOptionsSpec" + }, + "transformations": { + "type": "array", + "items": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardTransformationKind" + } + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardQueryOptionsSpec": { + "type": "object", + "properties": { + "cacheTimeout": { + "type": "string" + }, + "hideTimeOverride": { + "type": "boolean" + }, + "interval": { + "type": "string" + }, + "maxDataPoints": { + "type": "integer" + }, + "queryCachingTTL": { + "type": "integer" + }, + "timeCompare": { + "type": "string" + }, + "timeFrom": { + "type": "string" + }, + "timeShift": { + "type": "string" + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardQueryVariableKind": { + "description": "Query variable kind", + "type": "object", + "required": [ + "kind", + "spec" + ], + "properties": { + "kind": { + "type": "string" + }, + "spec": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardQueryVariableSpec" + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardQueryVariableSpec": { + "description": "Query variable specification", + "type": "object", + "required": [ + "name", + "current", + "hide", + "refresh", + "skipUrlSync", + "query", + "regex", + "sort", + "options", + "multi", + "includeAll", + "allowCustomValue" + ], + "properties": { + "allValue": { + "type": "string" + }, + "allowCustomValue": { + "type": "boolean", + "default": true + }, + "current": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardVariableOption" + }, + "definition": { + "type": "string" + }, + "description": { + "type": "string" + }, + "hide": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardVariableHide" + }, + "includeAll": { + "type": "boolean", + "default": false + }, + "label": { + "type": "string" + }, + "multi": { + "type": "boolean", + "default": false + }, + "name": { + "type": "string", + "default": "" + }, + "options": { + "type": "array", + "items": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardVariableOption" + } + }, + "placeholder": { + "type": "string" + }, + "query": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardDataQueryKind" + }, + "refresh": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardVariableRefresh" + }, + "regex": { + "type": "string", + "default": "" + }, + "skipUrlSync": { + "type": "boolean", + "default": false + }, + "sort": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardVariableSort" + }, + "staticOptions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardVariableOption" + } + }, + "staticOptionsOrder": { + "type": "string", + "enum": [ + "before", + "after", + "sorted" + ] + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardRangeMap": { + "description": "Maps numerical ranges to a display text and color.\nFor example, if a value is within a certain range, you can configure a range value mapping to display Low or High rather than the number.", + "type": "object", + "required": [ + "type", + "options" + ], + "properties": { + "options": { + "description": "Range to match against and the result to apply when the value is within the range", + "type": "object", + "required": [ + "from", + "to", + "result" + ], + "properties": { + "from": { + "description": "Min value of the range. It can be null which means -Infinity", + "type": "number" + }, + "result": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardValueMappingResult" + }, + "to": { + "description": "Max value of the range. It can be null which means +Infinity", + "type": "number" + } + }, + "additionalProperties": false + }, + "type": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardMappingType" + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardRegexMap": { + "description": "Maps regular expressions to replacement text and a color.\nFor example, if a value is www.example.com, you can configure a regex value mapping so that Grafana displays www and truncates the domain.", + "type": "object", + "required": [ + "type", + "options" + ], + "properties": { + "options": { + "description": "Regular expression to match against and the result to apply when the value matches the regex", + "type": "object", + "required": [ + "pattern", + "result" + ], + "properties": { + "pattern": { + "description": "Regular expression to match against", + "type": "string" + }, + "result": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardValueMappingResult" + } + }, + "additionalProperties": false + }, + "type": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardMappingType" + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardRepeatMode": { + "description": "other repeat modes will be added in the future: label, frame", + "type": "string" + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardRepeatOptions": { + "type": "object", + "required": [ + "mode", + "value" + ], + "properties": { + "direction": { + "type": "string", + "enum": [ + "h", + "v" + ] + }, + "maxPerRow": { + "type": "integer" + }, + "mode": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardRepeatMode" + }, + "value": { + "type": "string" + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardRowRepeatOptions": { + "type": "object", + "required": [ + "mode", + "value" + ], + "properties": { + "mode": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardRepeatMode" + }, + "value": { + "type": "string" + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardRowsLayoutKind": { + "type": "object", + "required": [ + "kind", + "spec" + ], + "properties": { + "kind": { + "type": "string" + }, + "spec": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardRowsLayoutSpec" + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardRowsLayoutRowKind": { + "type": "object", + "required": [ + "kind", + "spec" + ], + "properties": { + "kind": { + "type": "string" + }, + "spec": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardRowsLayoutRowSpec" + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardRowsLayoutRowSpec": { + "type": "object", + "required": [ + "layout" + ], + "properties": { + "collapse": { + "type": "boolean" + }, + "conditionalRendering": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardConditionalRenderingGroupKind" + }, + "fillScreen": { + "type": "boolean" + }, + "hideHeader": { + "type": "boolean" + }, + "layout": { + "oneOf": [ + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardGridLayoutKind" + }, + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardAutoGridLayoutKind" + }, + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardTabsLayoutKind" + }, + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardRowsLayoutKind" + } + ] + }, + "repeat": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardRowRepeatOptions" + }, + "title": { + "type": "string" + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardRowsLayoutSpec": { + "type": "object", + "required": [ + "rows" + ], + "properties": { + "rows": { + "type": "array", + "items": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardRowsLayoutRowKind" + } + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardSpec": { + "type": "object", + "required": [ + "annotations", + "cursorSync", + "elements", + "layout", + "links", + "preload", + "tags", + "timeSettings", + "title", + "variables" + ], + "properties": { + "annotations": { + "type": "array", + "items": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardAnnotationQueryKind" + } + }, + "cursorSync": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardDashboardCursorSync" + }, + "description": { + "description": "Description of dashboard.", + "type": "string" + }, + "editable": { + "description": "Whether a dashboard is editable or not.", + "type": "boolean", + "default": true + }, + "elements": { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardElement" + } + }, + "layout": { + "oneOf": [ + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardGridLayoutKind" + }, + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardRowsLayoutKind" + }, + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardAutoGridLayoutKind" + }, + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardTabsLayoutKind" + } + ] + }, + "links": { + "description": "Links with references to other dashboards or external websites.", + "type": "array", + "items": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardDashboardLink" + } + }, + "liveNow": { + "description": "When set to true, the dashboard will redraw panels at an interval matching the pixel width.\nThis will keep data \"moving left\" regardless of the query refresh rate. This setting helps\navoid dashboards presenting stale live data.", + "type": "boolean" + }, + "preload": { + "description": "When set to true, the dashboard will load all panels in the dashboard when it's loaded.", + "type": "boolean", + "default": false + }, + "revision": { + "description": "Plugins only. The version of the dashboard installed together with the plugin.\nThis is used to determine if the dashboard should be updated when the plugin is updated.", + "type": "integer" + }, + "tags": { + "description": "Tags associated with dashboard.", + "type": "array", + "items": { + "type": "string" + } + }, + "timeSettings": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardTimeSettingsSpec" + }, + "title": { + "description": "Title of dashboard.", + "type": "string" + }, + "variables": { + "description": "Configured template variables.", + "type": "array", + "items": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardVariableKind" + } + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardSpecialValueMap": { + "description": "Maps special values like Null, NaN (not a number), and boolean values like true and false to a display text and color.\nSee SpecialValueMatch to see the list of special values.\nFor example, you can configure a special value mapping so that null values appear as N/A.", + "type": "object", + "required": [ + "type", + "options" + ], + "properties": { + "options": { + "type": "object", + "required": [ + "match", + "result" + ], + "properties": { + "match": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardSpecialValueMatch" + }, + "result": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardValueMappingResult" + } + }, + "additionalProperties": false + }, + "type": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardMappingType" + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardSpecialValueMatch": { + "description": "Special value types supported by the `SpecialValueMap`", + "type": "string", + "enum": [ + "true", + "false", + "null", + "nan", + "null+nan", + "empty" + ] + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardStatus": { + "type": "object", + "properties": { + "conversion": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardConversionStatus" + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardSwitchVariableKind": { + "type": "object", + "required": [ + "kind", + "spec" + ], + "properties": { + "kind": { + "type": "string" + }, + "spec": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardSwitchVariableSpec" + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardSwitchVariableSpec": { + "type": "object", + "required": [ + "name", + "current", + "enabledValue", + "disabledValue", + "hide", + "skipUrlSync" + ], + "properties": { + "current": { + "type": "string", + "default": "false" + }, + "description": { + "type": "string" + }, + "disabledValue": { + "type": "string", + "default": "false" + }, + "enabledValue": { + "type": "string", + "default": "true" + }, + "hide": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardVariableHide" + }, + "label": { + "type": "string" + }, + "name": { + "type": "string", + "default": "" + }, + "skipUrlSync": { + "type": "boolean", + "default": false + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardTabRepeatOptions": { + "type": "object", + "required": [ + "mode", + "value" + ], + "properties": { + "mode": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardRepeatMode" + }, + "value": { + "type": "string" + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardTabsLayoutKind": { + "type": "object", + "required": [ + "kind", + "spec" + ], + "properties": { + "kind": { + "type": "string" + }, + "spec": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardTabsLayoutSpec" + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardTabsLayoutSpec": { + "type": "object", + "required": [ + "tabs" + ], + "properties": { + "tabs": { + "type": "array", + "items": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardTabsLayoutTabKind" + } + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardTabsLayoutTabKind": { + "type": "object", + "required": [ + "kind", + "spec" + ], + "properties": { + "kind": { + "type": "string" + }, + "spec": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardTabsLayoutTabSpec" + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardTabsLayoutTabSpec": { + "type": "object", + "required": [ + "layout" + ], + "properties": { + "conditionalRendering": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardConditionalRenderingGroupKind" + }, + "layout": { + "oneOf": [ + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardGridLayoutKind" + }, + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardRowsLayoutKind" + }, + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardAutoGridLayoutKind" + }, + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardTabsLayoutKind" + } + ] + }, + "repeat": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardTabRepeatOptions" + }, + "title": { + "type": "string" + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardTextVariableKind": { + "description": "Text variable kind", + "type": "object", + "required": [ + "kind", + "spec" + ], + "properties": { + "kind": { + "type": "string" + }, + "spec": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardTextVariableSpec" + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardTextVariableSpec": { + "description": "Text variable specification", + "type": "object", + "required": [ + "name", + "current", + "query", + "hide", + "skipUrlSync" + ], + "properties": { + "current": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardVariableOption" + }, + "description": { + "type": "string" + }, + "hide": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardVariableHide" + }, + "label": { + "type": "string" + }, + "name": { + "type": "string", + "default": "" + }, + "query": { + "type": "string", + "default": "" + }, + "skipUrlSync": { + "type": "boolean", + "default": false + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardThreshold": { + "type": "object", + "required": [ + "value", + "color" + ], + "properties": { + "color": { + "type": "string" + }, + "value": { + "description": "Value null means -Infinity", + "type": "number" + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardThresholdsConfig": { + "type": "object", + "required": [ + "mode", + "steps" + ], + "properties": { + "mode": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardThresholdsMode" + }, + "steps": { + "type": "array", + "items": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardThreshold" + } + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardThresholdsMode": { + "type": "string", + "enum": [ + "absolute", + "percentage" + ] + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardTimeRangeOption": { + "type": "object", + "required": [ + "display", + "from", + "to" + ], + "properties": { + "display": { + "type": "string", + "default": "Last 6 hours" + }, + "from": { + "type": "string", + "default": "now-6h" + }, + "to": { + "type": "string", + "default": "now" + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardTimeSettingsSpec": { + "description": "Time configuration\nIt defines the default time config for the time picker, the refresh picker for the specific dashboard.", + "type": "object", + "required": [ + "from", + "to", + "autoRefresh", + "autoRefreshIntervals", + "hideTimepicker", + "fiscalYearStartMonth" + ], + "properties": { + "autoRefresh": { + "description": "Refresh rate of dashboard. Represented via interval string, e.g. \"5s\", \"1m\", \"1h\", \"1d\".\nv1: refresh", + "type": "string", + "default": "" + }, + "autoRefreshIntervals": { + "description": "Interval options available in the refresh picker dropdown.\nv1: timepicker.refresh_intervals", + "type": "array", + "default": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "items": { + "type": "string" + } + }, + "fiscalYearStartMonth": { + "description": "The month that the fiscal year starts on. 0 = January, 11 = December", + "type": "integer", + "default": 0 + }, + "from": { + "description": "Start time range for dashboard.\nAccepted values are relative time strings like \"now-6h\" or absolute time strings like \"2020-07-10T08:00:00.000Z\".", + "type": "string", + "default": "now-6h" + }, + "hideTimepicker": { + "description": "Whether timepicker is visible or not.\nv1: timepicker.hidden", + "type": "boolean", + "default": false + }, + "nowDelay": { + "description": "Override the now time by entering a time delay. Use this option to accommodate known delays in data aggregation to avoid null values.\nv1: timepicker.nowDelay", + "type": "string" + }, + "quickRanges": { + "description": "Selectable options available in the time picker dropdown. Has no effect on provisioned dashboard.\nv1: timepicker.quick_ranges , not exposed in the UI", + "type": "array", + "items": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardTimeRangeOption" + } + }, + "timezone": { + "description": "Timezone of dashboard. Accepted values are IANA TZDB zone ID or \"browser\" or \"utc\".", + "type": "string", + "default": "browser" + }, + "to": { + "description": "End time range for dashboard.\nAccepted values are relative time strings like \"now-6h\" or absolute time strings like \"2020-07-10T08:00:00.000Z\".", + "type": "string", + "default": "now" + }, + "weekStart": { + "description": "Day when the week starts. Expressed by the name of the day in lowercase, e.g. \"monday\".", + "type": "string", + "enum": [ + "saturday", + "monday", + "sunday" + ] + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardTransformationKind": { + "type": "object", + "required": [ + "kind", + "spec" + ], + "properties": { + "kind": { + "description": "The kind of a TransformationKind is the transformation ID", + "type": "string" + }, + "spec": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardDataTransformerConfig" + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardValueMap": { + "description": "Maps text values to a color or different display text and color.\nFor example, you can configure a value mapping so that all instances of the value 10 appear as Perfection! rather than the number.", + "type": "object", + "required": [ + "type", + "options" + ], + "properties": { + "options": { + "description": "Map with : ValueMappingResult. For example: { \"10\": { text: \"Perfection!\", color: \"green\" } }", + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardValueMappingResult" + } + }, + "type": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardMappingType" + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardValueMapping": { + "oneOf": [ + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardValueMap" + }, + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardRangeMap" + }, + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardRegexMap" + }, + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardSpecialValueMap" + } + ] + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardValueMappingResult": { + "description": "Result used as replacement with text and color when the value matches", + "type": "object", + "properties": { + "color": { + "description": "Text to use when the value matches", + "type": "string" + }, + "icon": { + "description": "Icon to display when the value matches. Only specific visualizations.", + "type": "string" + }, + "index": { + "description": "Position in the mapping array. Only used internally.", + "type": "integer" + }, + "text": { + "description": "Text to display when the value matches", + "type": "string" + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardVariableHide": { + "description": "Determine if the variable shows on dashboard\nAccepted values are `dontHide` (show label and value), `hideLabel` (show value only), `hideVariable` (show nothing), `inControlsMenu` (show in a drop-down menu).", + "type": "string", + "enum": [ + "dontHide", + "hideLabel", + "hideVariable", + "inControlsMenu" + ] + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardVariableKind": { + "oneOf": [ + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardQueryVariableKind" + }, + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardTextVariableKind" + }, + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardConstantVariableKind" + }, + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardDatasourceVariableKind" + }, + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardIntervalVariableKind" + }, + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardCustomVariableKind" + }, + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardGroupByVariableKind" + }, + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardAdhocVariableKind" + }, + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardSwitchVariableKind" + } + ] + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardVariableOption": { + "description": "Variable option specification", + "type": "object", + "required": [ + "text", + "value" + ], + "properties": { + "selected": { + "description": "Whether the option is selected or not", + "type": "boolean" + }, + "text": { + "description": "Text to be displayed for the option", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "value": { + "description": "Value of the option", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardVariableRefresh": { + "description": "Options to config when to refresh a variable\n`never`: Never refresh the variable\n`onDashboardLoad`: Queries the data source every time the dashboard loads.\n`onTimeRangeChanged`: Queries the data source when the dashboard time range changes.", + "type": "string", + "enum": [ + "never", + "onDashboardLoad", + "onTimeRangeChanged" + ] + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardVariableSort": { + "description": "Sort variable options\nAccepted values are:\n`disabled`: No sorting\n`alphabeticalAsc`: Alphabetical ASC\n`alphabeticalDesc`: Alphabetical DESC\n`numericalAsc`: Numerical ASC\n`numericalDesc`: Numerical DESC\n`alphabeticalCaseInsensitiveAsc`: Alphabetical Case Insensitive ASC\n`alphabeticalCaseInsensitiveDesc`: Alphabetical Case Insensitive DESC\n`naturalAsc`: Natural ASC\n`naturalDesc`: Natural DESC\nVariableSort enum with default value", + "type": "string", + "enum": [ + "disabled", + "alphabeticalAsc", + "alphabeticalDesc", + "numericalAsc", + "numericalDesc", + "alphabeticalCaseInsensitiveAsc", + "alphabeticalCaseInsensitiveDesc", + "naturalAsc", + "naturalDesc" + ] + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardVizConfigKind": { + "type": "object", + "required": [ + "kind", + "group", + "version", + "spec" + ], + "properties": { + "group": { + "description": "The group is the plugin ID", + "type": "string" + }, + "kind": { + "type": "string" + }, + "spec": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardVizConfigSpec" + }, + "version": { + "type": "string" + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardVizConfigSpec": { + "description": "--- Kinds ---", + "type": "object", + "required": [ + "options", + "fieldConfig" + ], + "properties": { + "fieldConfig": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardFieldConfigSource" + }, + "options": { + "type": "object", + "additionalProperties": { + "type": "object", + "additionalProperties": {} + } + } + }, + "additionalProperties": false + }, + "com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardWithAccessInfo": { + "description": "This is like the legacy DTO where access and metadata are all returned in a single call", + "type": "object", + "required": [ + "metadata", + "spec", + "status", + "access" + ], + "properties": { + "access": { + "default": {}, + "allOf": [ + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardAccess" + } + ] + }, + "apiVersion": { + "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" + }, + "kind": { + "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" + }, + "metadata": { + "default": {}, + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta" + } + ] + }, + "spec": { + "description": "Spec is the spec of the Dashboard", + "default": {}, + "allOf": [ + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardSpec" + } + ] + }, + "status": { + "default": {}, + "allOf": [ + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardStatus" + } + ] + } + }, + "x-kubernetes-group-version-kind": [ + { + "group": "dashboard.grafana.app", + "kind": "DashboardWithAccessInfo", + "version": "v2beta1" + } + ] + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.APIResource": { + "description": "APIResource specifies the name of a resource and whether it is namespaced.", + "type": "object", + "required": [ + "name", + "singularName", + "namespaced", + "kind", + "verbs" + ], + "properties": { + "categories": { + "description": "categories is a list of the grouped resources this resource belongs to (e.g. 'all')", + "type": "array", + "items": { + "type": "string", + "default": "" + }, + "x-kubernetes-list-type": "atomic" + }, + "group": { + "description": "group is the preferred group of the resource. Empty implies the group of the containing resource list. For subresources, this may have a different value, for example: Scale\".", + "type": "string" + }, + "kind": { + "description": "kind is the kind for the resource (e.g. 'Foo' is the kind for a resource 'foo')", + "type": "string", + "default": "" + }, + "name": { + "description": "name is the plural name of the resource.", + "type": "string", + "default": "" + }, + "namespaced": { + "description": "namespaced indicates if a resource is namespaced or not.", + "type": "boolean", + "default": false + }, + "shortNames": { + "description": "shortNames is a list of suggested short names of the resource.", + "type": "array", + "items": { + "type": "string", + "default": "" + }, + "x-kubernetes-list-type": "atomic" + }, + "singularName": { + "description": "singularName is the singular name of the resource. This allows clients to handle plural and singular opaquely. The singularName is more correct for reporting status on a single item and both singular and plural are allowed from the kubectl CLI interface.", + "type": "string", + "default": "" + }, + "storageVersionHash": { + "description": "The hash value of the storage version, the version this resource is converted to when written to the data store. Value must be treated as opaque by clients. Only equality comparison on the value is valid. This is an alpha feature and may change or be removed in the future. The field is populated by the apiserver only if the StorageVersionHash feature gate is enabled. This field will remain optional even if it graduates.", + "type": "string" + }, + "verbs": { + "description": "verbs is a list of supported kube verbs (this includes get, list, watch, create, update, patch, delete, deletecollection, and proxy)", + "type": "array", + "items": { + "type": "string", + "default": "" + } + }, + "version": { + "description": "version is the preferred version of the resource. Empty implies the version of the containing resource list For subresources, this may have a different value, for example: v1 (while inside a v1beta1 version of the core resource's group)\".", + "type": "string" + } + } + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.APIResourceList": { + "description": "APIResourceList is a list of APIResource, it is used to expose the name of the resources supported in a specific group and version, and if the resource is namespaced.", + "type": "object", + "required": [ + "groupVersion", + "resources" + ], + "properties": { + "apiVersion": { + "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" + }, + "groupVersion": { + "description": "groupVersion is the group and version this APIResourceList is for.", + "type": "string", + "default": "" + }, + "kind": { + "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" + }, + "resources": { + "description": "resources contains the name of the resources and if they are namespaced.", + "type": "array", + "items": { + "default": {}, + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.APIResource" + } + ] + }, + "x-kubernetes-list-type": "atomic" + } + } + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.DeleteOptions": { + "description": "DeleteOptions may be provided when deleting an API object.", + "type": "object", + "properties": { + "apiVersion": { + "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" + }, + "dryRun": { + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "type": "array", + "items": { + "type": "string", + "default": "" + }, + "x-kubernetes-list-type": "atomic" + }, + "gracePeriodSeconds": { + "description": "The duration in seconds before the object should be deleted. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period for the specified type will be used. Defaults to a per object value if not specified. zero means delete immediately.", + "type": "integer", + "format": "int64" + }, + "ignoreStoreReadErrorWithClusterBreakingPotential": { + "description": "if set to true, it will trigger an unsafe deletion of the resource in case the normal deletion flow fails with a corrupt object error. A resource is considered corrupt if it can not be retrieved from the underlying storage successfully because of a) its data can not be transformed e.g. decryption failure, or b) it fails to decode into an object. NOTE: unsafe deletion ignores finalizer constraints, skips precondition checks, and removes the object from the storage. WARNING: This may potentially break the cluster if the workload associated with the resource being unsafe-deleted relies on normal deletion flow. Use only if you REALLY know what you are doing. The default value is false, and the user must opt in to enable it", + "type": "boolean" + }, + "kind": { + "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" + }, + "orphanDependents": { + "description": "Deprecated: please use the PropagationPolicy, this field will be deprecated in 1.7. Should the dependent objects be orphaned. If true/false, the \"orphan\" finalizer will be added to/removed from the object's finalizers list. Either this field or PropagationPolicy may be set, but not both.", + "type": "boolean" + }, + "preconditions": { + "description": "Must be fulfilled before a deletion is carried out. If not possible, a 409 Conflict status will be returned.", + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Preconditions" + } + ] + }, + "propagationPolicy": { + "description": "Whether and how garbage collection will be performed. Either this field or OrphanDependents may be set, but not both. The default policy is decided by the existing finalizer set in the metadata.finalizers and the resource-specific default policy. Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - allow the garbage collector to delete the dependents in the background; 'Foreground' - a cascading policy that deletes all dependents in the foreground.", + "type": "string" + } + } + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.FieldsV1": { + "description": "FieldsV1 stores a set of fields in a data structure like a Trie, in JSON format.\n\nEach key is either a '.' representing the field itself, and will always map to an empty set, or a string representing a sub-field or item. The string will follow one of these four formats: 'f:', where is the name of a field in a struct, or key in a map 'v:', where is the exact json formatted value of a list item 'i:', where is position of a item in a list 'k:', where is a map of a list item's key fields to their unique values If a key maps to an empty Fields value, the field that key represents is part of the set.\n\nThe exact format is defined in sigs.k8s.io/structured-merge-diff", + "type": "object" + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta": { + "description": "ListMeta describes metadata that synthetic resources must have, including lists and various status objects. A resource may have only one of {ObjectMeta, ListMeta}.", + "type": "object", + "properties": { + "continue": { + "description": "continue may be set if the user set a limit on the number of items returned, and indicates that the server has more data available. The value is opaque and may be used to issue another request to the endpoint that served this list to retrieve the next set of available objects. Continuing a consistent list may not be possible if the server configuration has changed or more than a few minutes have passed. The resourceVersion field returned when using this continue value will be identical to the value in the first response, unless you have received this token from an error message.", + "type": "string" + }, + "remainingItemCount": { + "description": "remainingItemCount is the number of subsequent items in the list which are not included in this list response. If the list request contained label or field selectors, then the number of remaining items is unknown and the field will be left unset and omitted during serialization. If the list is complete (either because it is not chunking or because this is the last chunk), then there are no more remaining items and this field will be left unset and omitted during serialization. Servers older than v1.15 do not set this field. The intended use of the remainingItemCount is *estimating* the size of a collection. Clients should not rely on the remainingItemCount to be set or to be exact.", + "type": "integer", + "format": "int64" + }, + "resourceVersion": { + "description": "String that identifies the server's internal version of this object that can be used by clients to determine when objects have changed. Value must be treated as opaque by clients and passed unmodified back to the server. Populated by the system. Read-only. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency", + "type": "string" + }, + "selfLink": { + "description": "Deprecated: selfLink is a legacy read-only field that is no longer populated by the system.", + "type": "string" + } + } + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.ManagedFieldsEntry": { + "description": "ManagedFieldsEntry is a workflow-id, a FieldSet and the group version of the resource that the fieldset applies to.", + "type": "object", + "properties": { + "apiVersion": { + "description": "APIVersion defines the version of this resource that this field set applies to. The format is \"group/version\" just like the top-level APIVersion field. It is necessary to track the version of a field set because it cannot be automatically converted.", + "type": "string" + }, + "fieldsType": { + "description": "FieldsType is the discriminator for the different fields format and version. There is currently only one possible value: \"FieldsV1\"", + "type": "string" + }, + "fieldsV1": { + "description": "FieldsV1 holds the first JSON version format as described in the \"FieldsV1\" type.", + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.FieldsV1" + } + ] + }, + "manager": { + "description": "Manager is an identifier of the workflow managing these fields.", + "type": "string" + }, + "operation": { + "description": "Operation is the type of operation which lead to this ManagedFieldsEntry being created. The only valid values for this field are 'Apply' and 'Update'.", + "type": "string" + }, + "subresource": { + "description": "Subresource is the name of the subresource used to update that object, or empty string if the object was updated through the main resource. The value of this field is used to distinguish between managers, even if they share the same name. For example, a status update will be distinct from a regular update using the same manager name. Note that the APIVersion field is not related to the Subresource field and it always corresponds to the version of the main resource.", + "type": "string" + }, + "time": { + "description": "Time is the timestamp of when the ManagedFields entry was added. The timestamp will also be updated if a field is added, the manager changes any of the owned fields value or removes a field. The timestamp does not update when a field is removed from the entry because another manager took it over.", + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Time" + } + ] + } + } + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta": { + "description": "ObjectMeta is metadata that all persisted resources must have, which includes all objects users must create.", + "type": "object", + "properties": { + "annotations": { + "description": "Annotations is an unstructured key value map stored with a resource that may be set by external tools to store and retrieve arbitrary metadata. They are not queryable and should be preserved when modifying objects. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations", + "type": "object", + "additionalProperties": { + "type": "string", + "default": "" + } + }, + "creationTimestamp": { + "description": "CreationTimestamp is a timestamp representing the server time when this object was created. It is not guaranteed to be set in happens-before order across separate operations. Clients may not set this value. It is represented in RFC3339 form and is in UTC.\n\nPopulated by the system. Read-only. Null for lists. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Time" + } + ] + }, + "deletionGracePeriodSeconds": { + "description": "Number of seconds allowed for this object to gracefully terminate before it will be removed from the system. Only set when deletionTimestamp is also set. May only be shortened. Read-only.", + "type": "integer", + "format": "int64" + }, + "deletionTimestamp": { + "description": "DeletionTimestamp is RFC 3339 date and time at which this resource will be deleted. This field is set by the server when a graceful deletion is requested by the user, and is not directly settable by a client. The resource is expected to be deleted (no longer visible from resource lists, and not reachable by name) after the time in this field, once the finalizers list is empty. As long as the finalizers list contains items, deletion is blocked. Once the deletionTimestamp is set, this value may not be unset or be set further into the future, although it may be shortened or the resource may be deleted prior to this time. For example, a user may request that a pod is deleted in 30 seconds. The Kubelet will react by sending a graceful termination signal to the containers in the pod. After that 30 seconds, the Kubelet will send a hard termination signal (SIGKILL) to the container and after cleanup, remove the pod from the API. In the presence of network partitions, this object may still exist after this timestamp, until an administrator or automated process can determine the resource is fully terminated. If not set, graceful deletion of the object has not been requested.\n\nPopulated by the system when a graceful deletion is requested. Read-only. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Time" + } + ] + }, + "finalizers": { + "description": "Must be empty before the object is deleted from the registry. Each entry is an identifier for the responsible component that will remove the entry from the list. If the deletionTimestamp of the object is non-nil, entries in this list can only be removed. Finalizers may be processed and removed in any order. Order is NOT enforced because it introduces significant risk of stuck finalizers. finalizers is a shared field, any actor with permission can reorder it. If the finalizer list is processed in order, then this can lead to a situation in which the component responsible for the first finalizer in the list is waiting for a signal (field value, external system, or other) produced by a component responsible for a finalizer later in the list, resulting in a deadlock. Without enforced ordering finalizers are free to order amongst themselves and are not vulnerable to ordering changes in the list.", + "type": "array", + "items": { + "type": "string", + "default": "" + }, + "x-kubernetes-list-type": "set", + "x-kubernetes-patch-strategy": "merge" + }, + "generateName": { + "description": "GenerateName is an optional prefix, used by the server, to generate a unique name ONLY IF the Name field has not been provided. If this field is used, the name returned to the client will be different than the name passed. This value will also be combined with a unique suffix. The provided value has the same validation rules as the Name field, and may be truncated by the length of the suffix required to make the value unique on the server.\n\nIf this field is specified and the generated name exists, the server will return a 409.\n\nApplied only if Name is not specified. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#idempotency", + "type": "string" + }, + "generation": { + "description": "A sequence number representing a specific generation of the desired state. Populated by the system. Read-only.", + "type": "integer", + "format": "int64" + }, + "labels": { + "description": "Map of string keys and values that can be used to organize and categorize (scope and select) objects. May match selectors of replication controllers and services. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels", + "type": "object", + "additionalProperties": { + "type": "string", + "default": "" + } + }, + "managedFields": { + "description": "ManagedFields maps workflow-id and version to the set of fields that are managed by that workflow. This is mostly for internal housekeeping, and users typically shouldn't need to set or understand this field. A workflow can be the user's name, a controller's name, or the name of a specific apply path like \"ci-cd\". The set of fields is always in the version that the workflow used when modifying the object.", + "type": "array", + "items": { + "default": {}, + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ManagedFieldsEntry" + } + ] + }, + "x-kubernetes-list-type": "atomic" + }, + "name": { + "description": "Name must be unique within a namespace. Is required when creating resources, although some resources may allow a client to request the generation of an appropriate name automatically. Name is primarily intended for creation idempotence and configuration definition. Cannot be updated. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#names", + "type": "string" + }, + "namespace": { + "description": "Namespace defines the space within which each name must be unique. An empty namespace is equivalent to the \"default\" namespace, but \"default\" is the canonical representation. Not all objects are required to be scoped to a namespace - the value of this field for those objects will be empty.\n\nMust be a DNS_LABEL. Cannot be updated. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces", + "type": "string" + }, + "ownerReferences": { + "description": "List of objects depended by this object. If ALL objects in the list have been deleted, this object will be garbage collected. If this object is managed by a controller, then an entry in this list will point to this controller, with the controller field set to true. There cannot be more than one managing controller.", + "type": "array", + "items": { + "default": {}, + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.OwnerReference" + } + ] + }, + "x-kubernetes-list-map-keys": [ + "uid" + ], + "x-kubernetes-list-type": "map", + "x-kubernetes-patch-merge-key": "uid", + "x-kubernetes-patch-strategy": "merge" + }, + "resourceVersion": { + "description": "An opaque value that represents the internal version of this object that can be used by clients to determine when objects have changed. May be used for optimistic concurrency, change detection, and the watch operation on a resource or set of resources. Clients must treat these values as opaque and passed unmodified back to the server. They may only be valid for a particular resource or set of resources.\n\nPopulated by the system. Read-only. Value must be treated as opaque by clients and . More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency", + "type": "string" + }, + "selfLink": { + "description": "Deprecated: selfLink is a legacy read-only field that is no longer populated by the system.", + "type": "string" + }, + "uid": { + "description": "UID is the unique in time and space value for this object. It is typically generated by the server on successful creation of a resource and is not allowed to change on PUT operations.\n\nPopulated by the system. Read-only. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#uids", + "type": "string" + } + } + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.OwnerReference": { + "description": "OwnerReference contains enough information to let you identify an owning object. An owning object must be in the same namespace as the dependent, or be cluster-scoped, so there is no namespace field.", + "type": "object", + "required": [ + "apiVersion", + "kind", + "name", + "uid" + ], + "properties": { + "apiVersion": { + "description": "API version of the referent.", + "type": "string", + "default": "" + }, + "blockOwnerDeletion": { + "description": "If true, AND if the owner has the \"foregroundDeletion\" finalizer, then the owner cannot be deleted from the key-value store until this reference is removed. See https://kubernetes.io/docs/concepts/architecture/garbage-collection/#foreground-deletion for how the garbage collector interacts with this field and enforces the foreground deletion. Defaults to false. To set this field, a user needs \"delete\" permission of the owner, otherwise 422 (Unprocessable Entity) will be returned.", + "type": "boolean" + }, + "controller": { + "description": "If true, this reference points to the managing controller.", + "type": "boolean" + }, + "kind": { + "description": "Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string", + "default": "" + }, + "name": { + "description": "Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#names", + "type": "string", + "default": "" + }, + "uid": { + "description": "UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#uids", + "type": "string", + "default": "" + } + }, + "x-kubernetes-map-type": "atomic" + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.Patch": { + "description": "Patch is provided to give a concrete name and type to the Kubernetes PATCH request body.", + "type": "object" + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.Preconditions": { + "description": "Preconditions must be fulfilled before an operation (update, delete, etc.) is carried out.", + "type": "object", + "properties": { + "resourceVersion": { + "description": "Specifies the target ResourceVersion", + "type": "string" + }, + "uid": { + "description": "Specifies the target UID.", + "type": "string" + } + } + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.Status": { + "description": "Status is a return value for calls that don't return other objects.", + "type": "object", + "properties": { + "apiVersion": { + "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" + }, + "code": { + "description": "Suggested HTTP return code for this status, 0 if not set.", + "type": "integer", + "format": "int32" + }, + "details": { + "description": "Extended data associated with the reason. Each reason may define its own extended details. This field is optional and the data returned is not guaranteed to conform to any schema except that defined by the reason type.", + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.StatusDetails" + } + ], + "x-kubernetes-list-type": "atomic" + }, + "kind": { + "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" + }, + "message": { + "description": "A human-readable description of the status of this operation.", + "type": "string" + }, + "metadata": { + "description": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "default": {}, + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta" + } + ] + }, + "reason": { + "description": "A machine-readable description of why this operation is in the \"Failure\" status. If this value is empty there is no information available. A Reason clarifies an HTTP status code but does not override it.", + "type": "string" + }, + "status": { + "description": "Status of the operation. One of: \"Success\" or \"Failure\". More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status", + "type": "string" + } + } + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.StatusCause": { + "description": "StatusCause provides more information about an api.Status failure, including cases when multiple errors are encountered.", + "type": "object", + "properties": { + "field": { + "description": "The field of the resource that has caused this error, as named by its JSON serialization. May include dot and postfix notation for nested attributes. Arrays are zero-indexed. Fields may appear more than once in an array of causes due to fields having multiple errors. Optional.\n\nExamples:\n \"name\" - the field \"name\" on the current resource\n \"items[0].name\" - the field \"name\" on the first array entry in \"items\"", + "type": "string" + }, + "message": { + "description": "A human-readable description of the cause of the error. This field may be presented as-is to a reader.", + "type": "string" + }, + "reason": { + "description": "A machine-readable description of the cause of the error. If this value is empty there is no information available.", + "type": "string" + } + } + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.StatusDetails": { + "description": "StatusDetails is a set of additional properties that MAY be set by the server to provide additional information about a response. The Reason field of a Status object defines what attributes will be set. Clients must ignore fields that do not match the defined type of each attribute, and should assume that any attribute may be empty, invalid, or under defined.", + "type": "object", + "properties": { + "causes": { + "description": "The Causes array includes more details associated with the StatusReason failure. Not all StatusReasons may provide detailed causes.", + "type": "array", + "items": { + "default": {}, + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.StatusCause" + } + ] + }, + "x-kubernetes-list-type": "atomic" + }, + "group": { + "description": "The group attribute of the resource associated with the status StatusReason.", + "type": "string" + }, + "kind": { + "description": "The kind attribute of the resource associated with the status StatusReason. On some operations may differ from the requested resource Kind. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "name": { + "description": "The name attribute of the resource associated with the status StatusReason (when there is a single name which can be described).", + "type": "string" + }, + "retryAfterSeconds": { + "description": "If specified, the time in seconds before the operation should be retried. Some errors may indicate the client must take an alternate action - for those errors this field may indicate how long to wait before taking the alternate action.", + "type": "integer", + "format": "int32" + }, + "uid": { + "description": "UID of the resource. (when there is a single resource which can be described). More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#uids", + "type": "string" + } + } + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.Time": { + "description": "Time is a wrapper around time.Time which supports correct marshaling to YAML and JSON. Wrappers are provided for many of the factory methods that the time package offers.", + "type": "string", + "format": "date-time" + } + } + } +} diff --git a/pkg/tests/apis/openapi_test.go b/pkg/tests/apis/openapi_test.go index 3e02a985eb1..b9d0283e37a 100644 --- a/pkg/tests/apis/openapi_test.go +++ b/pkg/tests/apis/openapi_test.go @@ -78,6 +78,9 @@ func TestIntegrationOpenAPIs(t *testing.T) { }, { Group: "dashboard.grafana.app", Version: "v2alpha1", + }, { + Group: "dashboard.grafana.app", + Version: "v2beta1", }, { Group: "folder.grafana.app", Version: "v1beta1", From 592c599ca6d6e7c000ee98c6b0b1a93d30b02d53 Mon Sep 17 00:00:00 2001 From: Steve Simpson Date: Sat, 6 Dec 2025 10:37:20 +0100 Subject: [PATCH 074/110] Alerting: Add configurable transport to historian app (#114935) --- .../historian/pkg/app/config/config.go | 10 ++++-- .../historian/pkg/app/config/config_test.go | 36 +++++++++++-------- .../pkg/app/notification/lokireader.go | 10 ++++-- .../apps/alerting/historian/register.go | 4 ++- 4 files changed, 40 insertions(+), 20 deletions(-) diff --git a/apps/alerting/historian/pkg/app/config/config.go b/apps/alerting/historian/pkg/app/config/config.go index 5d8027d933b..cb3b3caa711 100644 --- a/apps/alerting/historian/pkg/app/config/config.go +++ b/apps/alerting/historian/pkg/app/config/config.go @@ -1,6 +1,7 @@ package config import ( + "net/http" "net/url" "time" @@ -15,9 +16,14 @@ const ( lokiDefaultMaxQuerySize = 65536 // 64kb ) +type LokiConfig struct { + lokiclient.LokiConfig + Transport http.RoundTripper +} + type NotificationConfig struct { Enabled bool - Loki lokiclient.LokiConfig + Loki LokiConfig } type RuntimeConfig struct { @@ -27,7 +33,7 @@ type RuntimeConfig struct { func (n *NotificationConfig) AddFlagsWithPrefix(prefix string, flags *pflag.FlagSet) { flags.BoolVar(&n.Enabled, prefix+".enabled", false, "Enable notification query endpoints") - addLokiFlags(&n.Loki, prefix+".loki", flags) + addLokiFlags(&n.Loki.LokiConfig, prefix+".loki", flags) } func (r *RuntimeConfig) AddFlagsWithPrefix(prefix string, flags *pflag.FlagSet) { diff --git a/apps/alerting/historian/pkg/app/config/config_test.go b/apps/alerting/historian/pkg/app/config/config_test.go index 8234f4c945f..7f8ab623a7a 100644 --- a/apps/alerting/historian/pkg/app/config/config_test.go +++ b/apps/alerting/historian/pkg/app/config/config_test.go @@ -24,10 +24,12 @@ func TestRuntimeConfig(t *testing.T) { expected: RuntimeConfig{ Notification: NotificationConfig{ Enabled: false, - Loki: lokiclient.LokiConfig{ - ReadPathURL: nil, - MaxQueryLength: 721 * time.Hour, - MaxQuerySize: 65536, + Loki: LokiConfig{ + LokiConfig: lokiclient.LokiConfig{ + ReadPathURL: nil, + MaxQueryLength: 721 * time.Hour, + MaxQuerySize: 65536, + }, }, }, }, @@ -38,10 +40,12 @@ func TestRuntimeConfig(t *testing.T) { expected: RuntimeConfig{ Notification: NotificationConfig{ Enabled: true, - Loki: lokiclient.LokiConfig{ - ReadPathURL: nil, - MaxQueryLength: 721 * time.Hour, - MaxQuerySize: 65536, + Loki: LokiConfig{ + LokiConfig: lokiclient.LokiConfig{ + ReadPathURL: nil, + MaxQueryLength: 721 * time.Hour, + MaxQuerySize: 65536, + }, }, }, }, @@ -57,13 +61,15 @@ func TestRuntimeConfig(t *testing.T) { expected: RuntimeConfig{ Notification: NotificationConfig{ Enabled: false, - Loki: lokiclient.LokiConfig{ - ReadPathURL: lokiURL, - BasicAuthUser: "foo", - BasicAuthPassword: "bar", - TenantID: "baz", - MaxQueryLength: 721 * time.Hour, - MaxQuerySize: 65536, + Loki: LokiConfig{ + LokiConfig: lokiclient.LokiConfig{ + ReadPathURL: lokiURL, + BasicAuthUser: "foo", + BasicAuthPassword: "bar", + TenantID: "baz", + MaxQueryLength: 721 * time.Hour, + MaxQuerySize: 65536, + }, }, }, }, diff --git a/apps/alerting/historian/pkg/app/notification/lokireader.go b/apps/alerting/historian/pkg/app/notification/lokireader.go index e8cea23dda7..c26519e59b4 100644 --- a/apps/alerting/historian/pkg/app/notification/lokireader.go +++ b/apps/alerting/historian/pkg/app/notification/lokireader.go @@ -5,6 +5,7 @@ import ( "encoding/json" "errors" "fmt" + "net/http" "regexp" "sort" "strings" @@ -19,6 +20,7 @@ import ( "go.opentelemetry.io/otel/trace" "github.com/grafana/grafana/apps/alerting/historian/pkg/apis/alertinghistorian/v0alpha1" + "github.com/grafana/grafana/apps/alerting/historian/pkg/app/config" "github.com/grafana/grafana/apps/alerting/historian/pkg/app/logutil" ) @@ -47,7 +49,7 @@ type LokiReader struct { logger logging.Logger } -func NewLokiReader(cfg lokiclient.LokiConfig, reg prometheus.Registerer, logger logging.Logger, tracer trace.Tracer) *LokiReader { +func NewLokiReader(cfg config.LokiConfig, reg prometheus.Registerer, logger logging.Logger, tracer trace.Tracer) *LokiReader { duration := instrument.NewHistogramCollector(promauto.With(reg).NewHistogramVec(prometheus.HistogramOpts{ Namespace: Namespace, Subsystem: Subsystem, @@ -56,9 +58,13 @@ func NewLokiReader(cfg lokiclient.LokiConfig, reg prometheus.Registerer, logger Buckets: instrument.DefBuckets, }, instrument.HistogramCollectorBuckets)) + requester := &http.Client{ + Transport: cfg.Transport, + } + gkLogger := logutil.ToGoKitLogger(logger) return &LokiReader{ - client: lokiclient.NewLokiClient(cfg, lokiclient.NewRequester(), nil, duration, gkLogger, tracer, LokiClientSpanName), + client: lokiclient.NewLokiClient(cfg.LokiConfig, requester, nil, duration, gkLogger, tracer, LokiClientSpanName), logger: logger, } } diff --git a/pkg/registry/apps/alerting/historian/register.go b/pkg/registry/apps/alerting/historian/register.go index 7fc2176d758..68830dcd0ef 100644 --- a/pkg/registry/apps/alerting/historian/register.go +++ b/pkg/registry/apps/alerting/historian/register.go @@ -42,7 +42,9 @@ func RegisterAppInstaller( appSpecificConfig.Notification = historianAppConfig.NotificationConfig{ Enabled: nhCfg.Enabled, - Loki: lokiConfig, + Loki: historianAppConfig.LokiConfig{ + LokiConfig: lokiConfig, + }, } } } From 78b1ae4f27c0d8ae5473faf55523da791bfccbff Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Sat, 6 Dec 2025 16:45:18 +0300 Subject: [PATCH 075/110] Search: Fix field selector parsing (#114940) --- pkg/storage/unified/apistore/util.go | 2 +- pkg/storage/unified/apistore/util_test.go | 43 +++++++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/pkg/storage/unified/apistore/util.go b/pkg/storage/unified/apistore/util.go index d3763f652a6..6cb4c5b31f8 100644 --- a/pkg/storage/unified/apistore/util.go +++ b/pkg/storage/unified/apistore/util.go @@ -124,7 +124,7 @@ func toListRequest(k *resourcepb.ResourceKey, opts storage.ListOptions) (*resour if r.Value != "" { requirement.Values = append(requirement.Values, r.Value) } - req.Options.Labels = append(req.Options.Labels, requirement) + req.Options.Fields = append(req.Options.Fields, requirement) } } diff --git a/pkg/storage/unified/apistore/util_test.go b/pkg/storage/unified/apistore/util_test.go index 0bee8adf75e..68ca1ad462f 100644 --- a/pkg/storage/unified/apistore/util_test.go +++ b/pkg/storage/unified/apistore/util_test.go @@ -117,6 +117,49 @@ func TestToListRequest(t *testing.T) { }, wantErr: nil, }, + { + name: "with field selector", + key: &resourcepb.ResourceKey{ + Group: "test", + Resource: "test", + Namespace: "default", + }, + opts: storage.ListOptions{ + Predicate: storage.SelectionPredicate{ + Label: labels.SelectorFromSet(labels.Set{"label": "A"}), + Field: fields.SelectorFromSet(fields.Set{"field": "B"}), + }, + }, + want: &resourcepb.ListRequest{ + VersionMatchV2: 1, + Options: &resourcepb.ListOptions{ + Key: &resourcepb.ResourceKey{ + Group: "test", + Resource: "test", + Namespace: "default", + }, + Labels: []*resourcepb.Requirement{ + { + Key: "label", + Operator: string(selection.Equals), + Values: []string{"A"}, + }, + }, + Fields: []*resourcepb.Requirement{ + { + Key: "field", + Operator: string(selection.Equals), + Values: []string{"B"}, + }, + }, + }, + }, + wantPredicate: storage.SelectionPredicate{ + Label: labels.SelectorFromSet(labels.Set{"label": "A"}), + Field: fields.SelectorFromSet(fields.Set{"field": "B"}), + }, + wantErr: nil, + }, { name: "with trash label", key: &resourcepb.ResourceKey{ From d0977b524561d8bfcc20687f0d10cd5e2c5a0754 Mon Sep 17 00:00:00 2001 From: Gabriel MABILLE Date: Mon, 8 Dec 2025 09:22:28 +0100 Subject: [PATCH 076/110] `grafana-iam`: Add role apis to the standalone app (#114897) --- pkg/registry/apis/iam/authorizer.go | 2 +- pkg/registry/apis/iam/register.go | 41 ++++++++++++++++++++++------- 2 files changed, 33 insertions(+), 10 deletions(-) diff --git a/pkg/registry/apis/iam/authorizer.go b/pkg/registry/apis/iam/authorizer.go index 0ec018d86de..05c8da97c2e 100644 --- a/pkg/registry/apis/iam/authorizer.go +++ b/pkg/registry/apis/iam/authorizer.go @@ -44,7 +44,7 @@ func newIAMAuthorizer(accessClient authlib.AccessClient, legacyAccessClient auth authorizer := gfauthorizer.NewResourceAuthorizer(accessClient) resourceAuthorizer[iamv0.CoreRoleInfo.GetName()] = iamauthorizer.NewCoreRoleAuthorizer(accessClient) resourceAuthorizer[iamv0.RoleInfo.GetName()] = authorizer - resourceAuthorizer[iamv0.ResourcePermissionInfo.GetName()] = allowAuthorizer // Handled at storage layer + resourceAuthorizer[iamv0.ResourcePermissionInfo.GetName()] = allowAuthorizer // Handled by the backend wrapper resourceAuthorizer[iamv0.RoleBindingInfo.GetName()] = authorizer resourceAuthorizer[iamv0.ServiceAccountResourceInfo.GetName()] = authorizer resourceAuthorizer[iamv0.UserResourceInfo.GetName()] = authorizer diff --git a/pkg/registry/apis/iam/register.go b/pkg/registry/apis/iam/register.go index 786635fa19a..32e2c9fefef 100644 --- a/pkg/registry/apis/iam/register.go +++ b/pkg/registry/apis/iam/register.go @@ -7,6 +7,7 @@ import ( "strings" "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" @@ -23,7 +24,6 @@ import ( "github.com/grafana/authlib/types" iamv0 "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1" - "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/apimachinery/utils" legacyiamv0 "github.com/grafana/grafana/pkg/apis/iam/v0alpha1" grafanaregistry "github.com/grafana/grafana/pkg/apiserver/registry/generic" @@ -32,6 +32,7 @@ import ( iamauthorizer "github.com/grafana/grafana/pkg/registry/apis/iam/authorizer" "github.com/grafana/grafana/pkg/registry/apis/iam/externalgroupmapping" "github.com/grafana/grafana/pkg/registry/apis/iam/legacy" + "github.com/grafana/grafana/pkg/registry/apis/iam/noopstorage" "github.com/grafana/grafana/pkg/registry/apis/iam/resourcepermission" "github.com/grafana/grafana/pkg/registry/apis/iam/serviceaccount" "github.com/grafana/grafana/pkg/registry/apis/iam/sso" @@ -39,6 +40,7 @@ import ( "github.com/grafana/grafana/pkg/registry/apis/iam/teambinding" "github.com/grafana/grafana/pkg/registry/apis/iam/user" "github.com/grafana/grafana/pkg/services/accesscontrol" + gfauthorizer "github.com/grafana/grafana/pkg/services/apiserver/auth/authorizer" "github.com/grafana/grafana/pkg/services/apiserver/auth/authorizer/storewrapper" "github.com/grafana/grafana/pkg/services/apiserver/builder" "github.com/grafana/grafana/pkg/services/authz/zanzana" @@ -116,6 +118,8 @@ func RegisterAPIService( func NewAPIService( accessClient types.AccessClient, dbProvider legacysql.LegacyDatabaseProvider, + coreRoleStorage CoreRoleStorageBackend, + roleStorage RoleStorageBackend, features featuremgmt.FeatureToggles, zClient zanzana.Client, reg prometheus.Registerer, @@ -123,10 +127,17 @@ func NewAPIService( store := legacy.NewLegacySQLStores(dbProvider) resourcePermissionsStorage := resourcepermission.ProvideStorageBackend(dbProvider) registerMetrics(reg) + + resourceAuthorizer := gfauthorizer.NewResourceAuthorizer(accessClient) + coreRoleAuthorizer := iamauthorizer.NewCoreRoleAuthorizer(accessClient) + return &IdentityAccessManagementAPIBuilder{ store: store, display: user.NewLegacyDisplayREST(store), resourcePermissionsStorage: resourcePermissionsStorage, + rolesStorage: roleStorage, + coreRolesStorage: coreRoleStorage, + roleBindingsStorage: noopstorage.ProvideStorageBackend(), // TODO: add a proper storage backend logger: log.New("iam.apis"), features: features, accessClient: accessClient, @@ -135,20 +146,32 @@ func NewAPIService( reg: reg, authorizer: authorizer.AuthorizerFunc( func(ctx context.Context, a authorizer.Attributes) (authorizer.Decision, string, error) { + user, ok := types.AuthInfoFrom(ctx) + if !ok { + return authorizer.DecisionDeny, "no identity found", apierrors.NewUnauthorized("no identity found in context") + } + + if a.GetResource() == "coreroles" { + if user.GetIdentityType() != types.TypeAccessPolicy { + return authorizer.DecisionDeny, "only access policy identities have access for now", nil + } + return coreRoleAuthorizer.Authorize(ctx, a) + } + // For now only authorize resourcepermissions resource if a.GetResource() == "resourcepermissions" { - // Authorization is handled at the storage layer + // Authorization is handled by the backend wrapper return authorizer.DecisionAllow, "", nil } - user, err := identity.GetRequester(ctx) - if err != nil { - return authorizer.DecisionDeny, "no identity found", err + if a.GetResource() == "roles" { + if user.GetIdentityType() != types.TypeAccessPolicy { + return authorizer.DecisionDeny, "only access policy identities have access for now", nil + } + return resourceAuthorizer.Authorize(ctx, a) } - if user.GetIsGrafanaAdmin() { - return authorizer.DecisionAllow, "", nil - } - return authorizer.DecisionDeny, "only grafana admins have access for now", nil + + return authorizer.DecisionDeny, "access denied", nil }), } } From 8bf3ac97108cd826c76a845d1544541a6e632622 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 8 Dec 2025 10:13:56 +0100 Subject: [PATCH 077/110] SelectBase: Use standard portal container (#114844) * SelectBase: Use standard portal container * Fixed positioning issue --- packages/grafana-ui/src/components/Select/SelectBase.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/grafana-ui/src/components/Select/SelectBase.tsx b/packages/grafana-ui/src/components/Select/SelectBase.tsx index 8609c307c25..cad0eb10e20 100644 --- a/packages/grafana-ui/src/components/Select/SelectBase.tsx +++ b/packages/grafana-ui/src/components/Select/SelectBase.tsx @@ -16,6 +16,7 @@ import { t, Trans } from '@grafana/i18n'; import { useTheme2 } from '../../themes/ThemeContext'; import { Icon } from '../Icon/Icon'; +import { getPortalContainer } from '../Portal/Portal'; import { CustomInput } from './CustomInput'; import { DropdownIndicator } from './DropdownIndicator'; @@ -123,7 +124,7 @@ export function SelectBase({ minMenuHeight, maxVisibleValues, menuPlacement = 'auto', - menuPosition, + menuPosition = 'fixed', menuShouldPortal = true, noOptionsMessage = t('grafana-ui.select.no-options-label', 'No options found'), onBlur, @@ -255,9 +256,9 @@ export function SelectBase({ maxVisibleValues, menuIsOpen: isOpen, menuPlacement: menuPlacement === 'auto' && closeToBottom ? 'top' : menuPlacement, - menuPosition, + menuPosition: menuShouldPortal ? 'fixed' : menuPosition, menuShouldBlockScroll: true, - menuPortalTarget: menuShouldPortal && typeof document !== 'undefined' ? document.body : undefined, + menuPortalTarget: menuShouldPortal && getPortalContainer(), menuShouldScrollIntoView: false, onBlur, onChange: onChangeWithEmpty, From 3490c3b0fdc8cab4fc40ccce89b4be9d82aec6d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20H=C3=A4ggmark?= Date: Mon, 8 Dec 2025 10:19:44 +0100 Subject: [PATCH 078/110] e2e: add tests for translations (#114390) e2e: add tests for translations --- devenv/plugins.yaml | 4 + .../components/App/App.tsx | 3 +- .../grafana-extensionstest-app/constants.ts | 1 + .../i18next.config.ts | 13 +++ .../en-US/grafana-extensionstest-app.json | 7 ++ .../es-ES/grafana-extensionstest-app.json | 7 ++ .../sv-SE/grafana-extensionstest-app.json | 7 ++ .../grafana-extensionstest-app/module.tsx | 3 + .../grafana-extensionstest-app/package.json | 10 ++- .../pages/Config.tsx | 17 ++++ .../pages/index.tsx | 1 + .../grafana-extensionstest-app/plugin.json | 5 +- .../tests/translations/french.spec.ts | 12 +++ .../tests/translations/swedish.spec.ts | 12 +++ .../webpack.config.ts | 1 + .../components/ConfigEditor.tsx | 27 ++++-- .../grafana-test-datasource/i18next.config.ts | 13 +++ .../en-US/grafana-e2etest-datasource.json | 23 +++++ .../es-ES/grafana-e2etest-datasource.json | 23 +++++ .../sv-SE/grafana-e2etest-datasource.json | 23 +++++ .../grafana-test-datasource/module.ts | 4 + .../grafana-test-datasource/package.json | 9 +- .../grafana-test-datasource/plugin.json | 5 +- .../tests/translations/french.spec.ts | 11 +++ .../tests/translations/swedish.spec.ts | 11 +++ .../grafana-test-datasource/webpack.config.ts | 1 + .../grafana-test-panel/CHANGELOG.md | 1 + .../test-plugins/grafana-test-panel/README.md | 0 .../components/SimplePanel.tsx | 83 +++++++++++++++++++ .../grafana-test-panel/i18next.config.ts | 13 +++ .../grafana-test-panel/img/logo.svg | 1 + .../locales/en-US/grafana-e2etest-panel.json | 30 +++++++ .../locales/es-ES/grafana-e2etest-panel.json | 30 +++++++ .../locales/sv-SE/grafana-e2etest-panel.json | 30 +++++++ .../test-plugins/grafana-test-panel/module.ts | 46 ++++++++++ .../grafana-test-panel/package.json | 50 +++++++++++ .../grafana-test-panel/plugin.json | 26 ++++++ .../tests/translations/french.spec.ts | 13 +++ .../tests/translations/swedish.spec.ts | 13 +++ .../grafana-test-panel/tsconfig.json | 8 ++ .../test-plugins/grafana-test-panel/types.ts | 7 ++ .../grafana-test-panel/webpack.config.ts | 45 ++++++++++ pkg/build/e2e-playwright/main.go | 5 ++ playwright.config.ts | 4 + scripts/grafana-server/custom.ini | 2 +- yarn.lock | 38 +++++++++ 46 files changed, 678 insertions(+), 20 deletions(-) create mode 100644 e2e-playwright/test-plugins/grafana-extensionstest-app/i18next.config.ts create mode 100644 e2e-playwright/test-plugins/grafana-extensionstest-app/locales/en-US/grafana-extensionstest-app.json create mode 100644 e2e-playwright/test-plugins/grafana-extensionstest-app/locales/es-ES/grafana-extensionstest-app.json create mode 100644 e2e-playwright/test-plugins/grafana-extensionstest-app/locales/sv-SE/grafana-extensionstest-app.json create mode 100644 e2e-playwright/test-plugins/grafana-extensionstest-app/pages/Config.tsx create mode 100644 e2e-playwright/test-plugins/grafana-extensionstest-app/tests/translations/french.spec.ts create mode 100644 e2e-playwright/test-plugins/grafana-extensionstest-app/tests/translations/swedish.spec.ts create mode 100644 e2e-playwright/test-plugins/grafana-test-datasource/i18next.config.ts create mode 100644 e2e-playwright/test-plugins/grafana-test-datasource/locales/en-US/grafana-e2etest-datasource.json create mode 100644 e2e-playwright/test-plugins/grafana-test-datasource/locales/es-ES/grafana-e2etest-datasource.json create mode 100644 e2e-playwright/test-plugins/grafana-test-datasource/locales/sv-SE/grafana-e2etest-datasource.json create mode 100644 e2e-playwright/test-plugins/grafana-test-datasource/tests/translations/french.spec.ts create mode 100644 e2e-playwright/test-plugins/grafana-test-datasource/tests/translations/swedish.spec.ts create mode 100644 e2e-playwright/test-plugins/grafana-test-panel/CHANGELOG.md create mode 100644 e2e-playwright/test-plugins/grafana-test-panel/README.md create mode 100644 e2e-playwright/test-plugins/grafana-test-panel/components/SimplePanel.tsx create mode 100644 e2e-playwright/test-plugins/grafana-test-panel/i18next.config.ts create mode 100644 e2e-playwright/test-plugins/grafana-test-panel/img/logo.svg create mode 100644 e2e-playwright/test-plugins/grafana-test-panel/locales/en-US/grafana-e2etest-panel.json create mode 100644 e2e-playwright/test-plugins/grafana-test-panel/locales/es-ES/grafana-e2etest-panel.json create mode 100644 e2e-playwright/test-plugins/grafana-test-panel/locales/sv-SE/grafana-e2etest-panel.json create mode 100644 e2e-playwright/test-plugins/grafana-test-panel/module.ts create mode 100644 e2e-playwright/test-plugins/grafana-test-panel/package.json create mode 100644 e2e-playwright/test-plugins/grafana-test-panel/plugin.json create mode 100644 e2e-playwright/test-plugins/grafana-test-panel/tests/translations/french.spec.ts create mode 100644 e2e-playwright/test-plugins/grafana-test-panel/tests/translations/swedish.spec.ts create mode 100644 e2e-playwright/test-plugins/grafana-test-panel/tsconfig.json create mode 100644 e2e-playwright/test-plugins/grafana-test-panel/types.ts create mode 100644 e2e-playwright/test-plugins/grafana-test-panel/webpack.config.ts diff --git a/devenv/plugins.yaml b/devenv/plugins.yaml index 554a4828cff..0f292e324f1 100644 --- a/devenv/plugins.yaml +++ b/devenv/plugins.yaml @@ -21,3 +21,7 @@ apps: org_id: 1 org_name: Main Org. disabled: false +panels: + - type: grafana-e2etest-panel + org_id: 1 + org_name: Main Org. diff --git a/e2e-playwright/test-plugins/grafana-extensionstest-app/components/App/App.tsx b/e2e-playwright/test-plugins/grafana-extensionstest-app/components/App/App.tsx index d57a1476e19..26d7b201466 100644 --- a/e2e-playwright/test-plugins/grafana-extensionstest-app/components/App/App.tsx +++ b/e2e-playwright/test-plugins/grafana-extensionstest-app/components/App/App.tsx @@ -3,7 +3,7 @@ import { Route, Routes } from 'react-router-dom'; import { AppRootProps } from '@grafana/data'; import { ROUTES } from '../../constants'; -import { AddedComponents, AddedLinks, ExposedComponents } from '../../pages'; +import { AddedComponents, AddedLinks, Config, ExposedComponents } from '../../pages'; import { testIds } from '../../testIds'; export function App(props: AppRootProps) { @@ -13,6 +13,7 @@ export function App(props: AppRootProps) { } /> } /> } /> + } /> } /> diff --git a/e2e-playwright/test-plugins/grafana-extensionstest-app/constants.ts b/e2e-playwright/test-plugins/grafana-extensionstest-app/constants.ts index 120eb5d8191..c208781acf3 100644 --- a/e2e-playwright/test-plugins/grafana-extensionstest-app/constants.ts +++ b/e2e-playwright/test-plugins/grafana-extensionstest-app/constants.ts @@ -8,4 +8,5 @@ export enum ROUTES { ExposedComponents = 'exposed-components', AddedComponents = 'added-components', AddedLinks = 'added-links', + Config = 'config', } diff --git a/e2e-playwright/test-plugins/grafana-extensionstest-app/i18next.config.ts b/e2e-playwright/test-plugins/grafana-extensionstest-app/i18next.config.ts new file mode 100644 index 00000000000..ba1645d38c9 --- /dev/null +++ b/e2e-playwright/test-plugins/grafana-extensionstest-app/i18next.config.ts @@ -0,0 +1,13 @@ +import { defineConfig } from 'i18next-cli'; +import pluginJson from './plugin.json'; + +export default defineConfig({ + locales: pluginJson.languages, + extract: { + input: ['**/*.{tsx,ts}'], + output: 'locales/{{language}}/{{namespace}}.json', + defaultNS: pluginJson.id, + functions: ['t', '*.t'], + transComponents: ['Trans'], + }, +}); diff --git a/e2e-playwright/test-plugins/grafana-extensionstest-app/locales/en-US/grafana-extensionstest-app.json b/e2e-playwright/test-plugins/grafana-extensionstest-app/locales/en-US/grafana-extensionstest-app.json new file mode 100644 index 00000000000..2fc28958864 --- /dev/null +++ b/e2e-playwright/test-plugins/grafana-extensionstest-app/locales/en-US/grafana-extensionstest-app.json @@ -0,0 +1,7 @@ +{ + "config-page": { + "header": { + "text": "Is this translated" + } + } +} diff --git a/e2e-playwright/test-plugins/grafana-extensionstest-app/locales/es-ES/grafana-extensionstest-app.json b/e2e-playwright/test-plugins/grafana-extensionstest-app/locales/es-ES/grafana-extensionstest-app.json new file mode 100644 index 00000000000..2c2f51a239d --- /dev/null +++ b/e2e-playwright/test-plugins/grafana-extensionstest-app/locales/es-ES/grafana-extensionstest-app.json @@ -0,0 +1,7 @@ +{ + "config-page": { + "header": { + "text": "¿Está traducido?" + } + } +} diff --git a/e2e-playwright/test-plugins/grafana-extensionstest-app/locales/sv-SE/grafana-extensionstest-app.json b/e2e-playwright/test-plugins/grafana-extensionstest-app/locales/sv-SE/grafana-extensionstest-app.json new file mode 100644 index 00000000000..8bae86f58aa --- /dev/null +++ b/e2e-playwright/test-plugins/grafana-extensionstest-app/locales/sv-SE/grafana-extensionstest-app.json @@ -0,0 +1,7 @@ +{ + "config-page": { + "header": { + "text": "Det här är översatt" + } + } +} diff --git a/e2e-playwright/test-plugins/grafana-extensionstest-app/module.tsx b/e2e-playwright/test-plugins/grafana-extensionstest-app/module.tsx index 89ed1af12c4..9f585c0d5f7 100644 --- a/e2e-playwright/test-plugins/grafana-extensionstest-app/module.tsx +++ b/e2e-playwright/test-plugins/grafana-extensionstest-app/module.tsx @@ -3,6 +3,9 @@ import { App } from './components/App'; import { QueryModal } from './components/QueryModal'; import { selectQuery } from './utils/utils'; import pluginJson from './plugin.json'; +import { initPluginTranslations } from '@grafana/i18n'; + +await initPluginTranslations(pluginJson.id); export const plugin = new AppPlugin<{}>() .setRootPage(App) diff --git a/e2e-playwright/test-plugins/grafana-extensionstest-app/package.json b/e2e-playwright/test-plugins/grafana-extensionstest-app/package.json index f89721aaac9..7f094c82b9b 100644 --- a/e2e-playwright/test-plugins/grafana-extensionstest-app/package.json +++ b/e2e-playwright/test-plugins/grafana-extensionstest-app/package.json @@ -6,7 +6,8 @@ "build": "NODE_OPTIONS='--experimental-strip-types --no-warnings=ExperimentalWarning' webpack -c ./webpack.config.ts --env production", "dev": "NODE_OPTIONS='--experimental-strip-types --no-warnings=ExperimentalWarning' webpack -w -c ./webpack.config.ts --env development", "typecheck": "tsc --noEmit", - "lint": "eslint --cache --ignore-path ./.gitignore --ext .js,.jsx,.ts,.tsx ." + "lint": "eslint --cache --ignore-path ./.gitignore --ext .js,.jsx,.ts,.tsx .", + "i18n-extract": "i18next-cli extract --sync-primary" }, "author": "Grafana Labs", "license": "Apache-2.0", @@ -20,17 +21,19 @@ "@types/semver": "7.5.8", "@types/uuid": "9.0.8", "glob": "10.5.0", + "i18next-cli": "^1.24.22", "ts-node": "10.9.2", "typescript": "5.5.4", "webpack": "5.95.0", "webpack-merge": "5.10.0" }, "engines": { - "node": ">=20" + "node": ">= 22 <25" }, "dependencies": { "@emotion/css": "11.11.2", "@grafana/data": "workspace:*", + "@grafana/i18n": "workspace:*", "@grafana/runtime": "workspace:*", "@grafana/schema": "workspace:*", "@grafana/ui": "workspace:*", @@ -42,5 +45,6 @@ }, "peerDependencies": { "@grafana/runtime": "*" - } + }, + "packageManager": "yarn@4.11.0" } diff --git a/e2e-playwright/test-plugins/grafana-extensionstest-app/pages/Config.tsx b/e2e-playwright/test-plugins/grafana-extensionstest-app/pages/Config.tsx new file mode 100644 index 00000000000..8b31e490242 --- /dev/null +++ b/e2e-playwright/test-plugins/grafana-extensionstest-app/pages/Config.tsx @@ -0,0 +1,17 @@ +import { Trans } from '@grafana/i18n'; +import { PluginPage } from '@grafana/runtime'; +import { Stack } from '@grafana/ui'; + +export function Config() { + return ( + + +
+

+ Is this translated +

+
+
+
+ ); +} diff --git a/e2e-playwright/test-plugins/grafana-extensionstest-app/pages/index.tsx b/e2e-playwright/test-plugins/grafana-extensionstest-app/pages/index.tsx index 1326d3c7bdf..84ddfc8e6ea 100644 --- a/e2e-playwright/test-plugins/grafana-extensionstest-app/pages/index.tsx +++ b/e2e-playwright/test-plugins/grafana-extensionstest-app/pages/index.tsx @@ -1,3 +1,4 @@ export { ExposedComponents } from './ExposedComponents'; export { AddedComponents } from './AddedComponents'; export { AddedLinks } from './AddedLinks'; +export { Config } from './Config'; diff --git a/e2e-playwright/test-plugins/grafana-extensionstest-app/plugin.json b/e2e-playwright/test-plugins/grafana-extensionstest-app/plugin.json index 3f5adfa215a..c5ce29f2dc4 100644 --- a/e2e-playwright/test-plugins/grafana-extensionstest-app/plugin.json +++ b/e2e-playwright/test-plugins/grafana-extensionstest-app/plugin.json @@ -82,10 +82,11 @@ ] }, "dependencies": { - "grafanaDependency": ">=10.4.0", + "grafanaDependency": ">=12.0.0", "plugins": [], "extensions": { "exposedComponents": ["grafana-extensionexample1-app/reusable-component/v1", "grafana/add-to-dashboard-form/v1"] } - } + }, + "languages": ["en-US", "es-ES", "sv-SE"] } diff --git a/e2e-playwright/test-plugins/grafana-extensionstest-app/tests/translations/french.spec.ts b/e2e-playwright/test-plugins/grafana-extensionstest-app/tests/translations/french.spec.ts new file mode 100644 index 00000000000..a991453b0cc --- /dev/null +++ b/e2e-playwright/test-plugins/grafana-extensionstest-app/tests/translations/french.spec.ts @@ -0,0 +1,12 @@ +import { FRENCH_FRANCE } from '@grafana/i18n'; +import { expect, test } from '@grafana/plugin-e2e'; +import pluginJson from '../../plugin.json'; +import { ROUTES } from '../../constants'; + +test.use({ userPreferences: { language: FRENCH_FRANCE } }); + +test('should display default translation (en-US)', async ({ gotoAppPage }) => { + const configPage = await gotoAppPage({ pluginId: pluginJson.id, path: ROUTES.Config }); + + await expect(configPage.ctx.page.getByText('Is this translated')).toBeVisible(); +}); diff --git a/e2e-playwright/test-plugins/grafana-extensionstest-app/tests/translations/swedish.spec.ts b/e2e-playwright/test-plugins/grafana-extensionstest-app/tests/translations/swedish.spec.ts new file mode 100644 index 00000000000..404f5053948 --- /dev/null +++ b/e2e-playwright/test-plugins/grafana-extensionstest-app/tests/translations/swedish.spec.ts @@ -0,0 +1,12 @@ +import { SWEDISH_SWEDEN } from '@grafana/i18n'; +import { expect, test } from '@grafana/plugin-e2e'; +import pluginJson from '../../plugin.json'; +import { ROUTES } from '../../constants'; + +test.use({ userPreferences: { language: SWEDISH_SWEDEN } }); + +test('should display correct translation', async ({ gotoAppPage }) => { + const configPage = await gotoAppPage({ pluginId: pluginJson.id, path: ROUTES.Config }); + + await expect(configPage.ctx.page.getByText('Det här är översatt')).toBeVisible(); +}); diff --git a/e2e-playwright/test-plugins/grafana-extensionstest-app/webpack.config.ts b/e2e-playwright/test-plugins/grafana-extensionstest-app/webpack.config.ts index 564555396a5..ceff913dbba 100644 --- a/e2e-playwright/test-plugins/grafana-extensionstest-app/webpack.config.ts +++ b/e2e-playwright/test-plugins/grafana-extensionstest-app/webpack.config.ts @@ -34,6 +34,7 @@ const config = async (env: Env): Promise => { ], }), ], + externals: [...(baseConfig.externals as any), 'i18next'], }; return mergeWithCustomize({ diff --git a/e2e-playwright/test-plugins/grafana-test-datasource/components/ConfigEditor.tsx b/e2e-playwright/test-plugins/grafana-test-datasource/components/ConfigEditor.tsx index 2c46992a5d9..06101e6e050 100644 --- a/e2e-playwright/test-plugins/grafana-test-datasource/components/ConfigEditor.tsx +++ b/e2e-playwright/test-plugins/grafana-test-datasource/components/ConfigEditor.tsx @@ -1,6 +1,7 @@ import { ChangeEvent } from 'react'; import { Checkbox, InlineField, InlineSwitch, Input, SecretInput, Select } from '@grafana/ui'; import { DataSourcePluginOptionsEditorProps, SelectableValue, toOption } from '@grafana/data'; +import { t } from '@grafana/i18n'; import { MyDataSourceOptions, MySecureJsonData } from '../types'; interface Props extends DataSourcePluginOptionsEditorProps {} @@ -45,36 +46,46 @@ export function ConfigEditor(props: Props) { return ( <> - + ) => onJsonDataChange('path', e.target.value)} value={jsonData.path} - placeholder="Enter the path, e.g. /api/v1" + placeholder={t('config-editor.path.placeholder', 'Enter the path, e.g. /api/v1')} width={40} /> - + ) => onSecureJsonDataChange('path', e.target.value)} /> - + ) => onJsonDataChange('switchEnabled', e.target.checked)} /> - + ) => onJsonDataChange('checkboxEnabled', e.target.checked)} /> - + - + + render={({ field: { ref, value, onChange, ...field } }) => ( + {inputs.dataSources && inputs.dataSources.map((input: DataSourceInput) => { if (input.pluginId === ExpressionDatasourceRef.type) { @@ -102,6 +100,7 @@ export const ImportDashboardFormV2 = ({ key={input.pluginId} invalid={!!errors[dataSourceOption]} error={errors[dataSourceOption] ? 'Please select a data source' : undefined} + noMargin > name={dataSourceOption} @@ -133,7 +132,7 @@ export const ImportDashboardFormV2 = ({ ); })} - + - + ); }; diff --git a/public/app/features/dashboard-scene/v2schema/ImportDashboardOverviewV2.tsx b/public/app/features/dashboard-scene/v2schema/ImportDashboardOverviewV2.tsx index 9f87a602d3f..e71b419b8e4 100644 --- a/public/app/features/dashboard-scene/v2schema/ImportDashboardOverviewV2.tsx +++ b/public/app/features/dashboard-scene/v2schema/ImportDashboardOverviewV2.tsx @@ -1,5 +1,3 @@ -import { useState } from 'react'; - import { locationUtil } from '@grafana/data'; import { locationService, reportInteraction } from '@grafana/runtime'; import { @@ -20,7 +18,6 @@ const IMPORT_FINISHED_EVENT_NAME = 'dashboard_import_imported'; type FormData = SaveDashboardCommand & { [key: `datasource-${string}`]: string }; export function ImportDashboardOverviewV2() { - const [uidReset, setUidReset] = useState(false); const dispatch = useDispatch(); // Get state from Redux store @@ -29,10 +26,6 @@ export function ImportDashboardOverviewV2() { const inputs = useSelector((state: StoreState) => state.importDashboard.inputs); const folder = searchObj.folderUid ? { uid: String(searchObj.folderUid) } : { uid: '' }; - function onUidReset() { - setUidReset(true); - } - function onCancel() { dispatch(clearLoadedDashboard()); } @@ -180,7 +173,7 @@ export function ImportDashboardOverviewV2() { <> onSubmit={onSubmit} - defaultValues={{ dashboard, k8s: { annotations: { 'grafana.app/folder': folder.uid } } }} + defaultValues={{ dashboard, folderUid: folder.uid, k8s: { annotations: { 'grafana.app/folder': folder.uid } } }} validateOnMount validateOn="onChange" > @@ -191,9 +184,7 @@ export function ImportDashboardOverviewV2() { errors={errors} control={control} getValues={getValues} - uidReset={uidReset} onCancel={onCancel} - onUidReset={onUidReset} onSubmit={onSubmit} watch={watch} />