({
display: 'flex',
flex: 1,
flexDirection: 'column',
+ overflow: 'hidden',
}),
controlledLogsContainer: css({
height: '100%',
diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json
index c0061d4b664..339cb81d0d1 100644
--- a/public/locales/en-US/grafana.json
+++ b/public/locales/en-US/grafana.json
@@ -9362,6 +9362,15 @@
"show-more": "show more",
"tooltip-error": "Error: {{errorMessage}}"
},
+ "log-line-context": {
+ "center-matched-line": "Center matched line",
+ "newer-logs": "newer",
+ "no-more-logs-available": "No more logs available.",
+ "older-logs": "older",
+ "open-in-split-view": "Open in split view",
+ "title-log-context": "Log context",
+ "title-log-line": "Referenced log line"
+ },
"log-line-details": {
"clear-search": "Clear",
"close": "Close log details",
From 63093dfb261bca650890379c86b0510cac589332 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Fri, 1 Aug 2025 12:34:32 +0100
Subject: [PATCH 15/89] Update dependency @types/eslint-scope to v8 (#109034)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
package.json | 2 +-
yarn.lock | 12 +++++++++++-
2 files changed, 12 insertions(+), 2 deletions(-)
diff --git a/package.json b/package.json
index cc339866f02..eb94c9eb369 100644
--- a/package.json
+++ b/package.json
@@ -121,7 +121,7 @@
"@types/d3-scale-chromatic": "3.1.0",
"@types/debounce-promise": "3.1.9",
"@types/eslint": "9.6.1",
- "@types/eslint-scope": "^3.7.7",
+ "@types/eslint-scope": "^8.0.0",
"@types/file-saver": "2.0.7",
"@types/glob": "^8.0.0",
"@types/google.analytics": "^0.0.46",
diff --git a/yarn.lock b/yarn.lock
index a9cb48e452e..e3c9d0fdd0e 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -9357,6 +9357,16 @@ __metadata:
languageName: node
linkType: hard
+"@types/eslint-scope@npm:^8.0.0":
+ version: 8.3.1
+ resolution: "@types/eslint-scope@npm:8.3.1"
+ dependencies:
+ "@types/eslint": "npm:*"
+ "@types/estree": "npm:*"
+ checksum: 10/54404a6473928b513b9ab3de9de34a52ed3b0524d010b0b068023539bd834617073b74e3052fc51d3c9bff2093d6b85c0af61de55e47c83d6685d4fa0d363d2b
+ languageName: node
+ linkType: hard
+
"@types/eslint@npm:*, @types/eslint@npm:9.6.1":
version: 9.6.1
resolution: "@types/eslint@npm:9.6.1"
@@ -18309,7 +18319,7 @@ __metadata:
"@types/d3-scale-chromatic": "npm:3.1.0"
"@types/debounce-promise": "npm:3.1.9"
"@types/eslint": "npm:9.6.1"
- "@types/eslint-scope": "npm:^3.7.7"
+ "@types/eslint-scope": "npm:^8.0.0"
"@types/file-saver": "npm:2.0.7"
"@types/glob": "npm:^8.0.0"
"@types/google.analytics": "npm:^0.0.46"
From d96cd46272ff9212f00d130876bcac01efc41d2f Mon Sep 17 00:00:00 2001
From: Levente Balogh
Date: Fri, 1 Aug 2025 13:42:07 +0200
Subject: [PATCH 16/89] Plugin Extensions: Support core plugins (#108685)
* feat(extensions): allow core plugins to use core grafana extension points
* fix: don't validate the plugin.json for core plugins
---
.../extensions/usePluginComponents.test.tsx | 77 +++++++++++++++++-
.../extensions/usePluginComponents.tsx | 13 +++-
.../plugins/extensions/usePluginFunctions.tsx | 13 +++-
.../extensions/usePluginLinks.test.tsx | 78 ++++++++++++++++++-
.../plugins/extensions/usePluginLinks.tsx | 13 +++-
.../plugins/extensions/validators.test.tsx | 14 ++++
.../features/plugins/extensions/validators.ts | 4 +-
7 files changed, 203 insertions(+), 9 deletions(-)
diff --git a/public/app/features/plugins/extensions/usePluginComponents.test.tsx b/public/app/features/plugins/extensions/usePluginComponents.test.tsx
index f40955c3f54..255a8a6161c 100644
--- a/public/app/features/plugins/extensions/usePluginComponents.test.tsx
+++ b/public/app/features/plugins/extensions/usePluginComponents.test.tsx
@@ -1,7 +1,13 @@
import { act, render, renderHook, screen } from '@testing-library/react';
import React from 'react';
-import { PluginContextProvider, PluginExtensionPoints, PluginMeta, PluginType } from '@grafana/data';
+import {
+ PluginContextProvider,
+ PluginExtensionPoints,
+ PluginLoadingStrategy,
+ PluginMeta,
+ PluginType,
+} from '@grafana/data';
import { config } from '@grafana/runtime';
import { ExtensionRegistriesProvider } from './ExtensionRegistriesContext';
@@ -105,6 +111,32 @@ describe('usePluginComponents()', () => {
},
};
+ config.apps[pluginId] = {
+ id: pluginId,
+ path: '',
+ version: '',
+ preload: false,
+ angular: {
+ detected: false,
+ hideDeprecation: false,
+ },
+ loadingStrategy: PluginLoadingStrategy.fetch,
+ dependencies: {
+ grafanaVersion: '8.0.0',
+ plugins: [],
+ extensions: {
+ exposedComponents: [],
+ },
+ },
+ extensions: {
+ addedLinks: [],
+ addedComponents: [],
+ addedFunctions: [],
+ exposedComponents: [],
+ extensionPoints: [],
+ },
+ };
+
wrapper = ({ children }: { children: React.ReactNode }) => (
{children}
@@ -459,6 +491,49 @@ describe('usePluginComponents()', () => {
expect(log.error).not.toHaveBeenCalled();
});
+ // It can happen that core Grafana plugins (e.g. traces) reuse core components which implement extension points.
+ it('should not validate the extension point meta-info for core plugins', () => {
+ jest.mocked(isGrafanaDevMode).mockReturnValue(true);
+
+ const componentConfig = {
+ targets: extensionPointId,
+ title: '1',
+ description: '1',
+ component: () => Component
,
+ };
+
+ // The `AddedComponentsRegistry` is validating if the link is registered in the plugin metadata (config.apps).
+ config.apps[pluginId].extensions.addedComponents = [componentConfig];
+
+ wrapper = ({ children }: { children: React.ReactNode }) => (
+
+ {children}
+
+ );
+
+ registries.addedComponentsRegistry.register({
+ pluginId,
+ configs: [componentConfig],
+ });
+
+ // Trying to render an extension point that is not defined in the plugin meta
+ // (No restrictions due to being a core plugin)
+ let { result } = renderHook(() => usePluginComponents({ extensionPointId }), { wrapper });
+ expect(result.current.components.length).toBe(1);
+ expect(log.error).not.toHaveBeenCalled();
+ });
+
it('should not validate the extension point id in production mode', () => {
// Empty list of extension points in the plugin meta (from plugin.json)
wrapper = ({ children }: { children: React.ReactNode }) => (
diff --git a/public/app/features/plugins/extensions/usePluginComponents.tsx b/public/app/features/plugins/extensions/usePluginComponents.tsx
index 6f37f3bdbba..d07a2e5ac07 100644
--- a/public/app/features/plugins/extensions/usePluginComponents.tsx
+++ b/public/app/features/plugins/extensions/usePluginComponents.tsx
@@ -29,6 +29,7 @@ export function usePluginComponents({
return useMemo(() => {
const isInsidePlugin = Boolean(pluginContext);
+ const isCoreGrafanaPlugin = pluginContext?.meta.module.startsWith('core:') ?? false;
const components: Array> = [];
const extensionsByPlugin: Record = {};
const pluginId = pluginContext?.meta.id ?? '';
@@ -38,7 +39,10 @@ export function usePluginComponents({
});
// Don't show extensions if the extension-point id is invalid in DEV mode
- if (isGrafanaDevMode() && !isExtensionPointIdValid({ extensionPointId, pluginId, isInsidePlugin, log: pointLog })) {
+ if (
+ isGrafanaDevMode() &&
+ !isExtensionPointIdValid({ extensionPointId, pluginId, isInsidePlugin, isCoreGrafanaPlugin, log: pointLog })
+ ) {
return {
isLoading: false,
components: [],
@@ -46,7 +50,12 @@ export function usePluginComponents({
}
// Don't show extensions if the extension-point misses meta info (plugin.json) in DEV mode
- if (isGrafanaDevMode() && pluginContext && isExtensionPointMetaInfoMissing(extensionPointId, pluginContext)) {
+ if (
+ isGrafanaDevMode() &&
+ !isCoreGrafanaPlugin &&
+ pluginContext &&
+ isExtensionPointMetaInfoMissing(extensionPointId, pluginContext)
+ ) {
pointLog.error(errors.EXTENSION_POINT_META_INFO_MISSING);
return {
isLoading: false,
diff --git a/public/app/features/plugins/extensions/usePluginFunctions.tsx b/public/app/features/plugins/extensions/usePluginFunctions.tsx
index 18a8abd7aef..8f8c989e316 100644
--- a/public/app/features/plugins/extensions/usePluginFunctions.tsx
+++ b/public/app/features/plugins/extensions/usePluginFunctions.tsx
@@ -24,6 +24,7 @@ export function usePluginFunctions({
return useMemo(() => {
const isInsidePlugin = Boolean(pluginContext);
+ const isCoreGrafanaPlugin = pluginContext?.meta.module.startsWith('core:') ?? false;
const results: Array> = [];
const extensionsByPlugin: Record = {};
const pluginId = pluginContext?.meta.id ?? '';
@@ -32,14 +33,22 @@ export function usePluginFunctions({
extensionPointId,
});
- if (isGrafanaDevMode() && !isExtensionPointIdValid({ extensionPointId, pluginId, isInsidePlugin, log: pointLog })) {
+ if (
+ isGrafanaDevMode() &&
+ !isExtensionPointIdValid({ extensionPointId, pluginId, isInsidePlugin, isCoreGrafanaPlugin, log: pointLog })
+ ) {
return {
isLoading: false,
functions: [],
};
}
- if (isGrafanaDevMode() && pluginContext && isExtensionPointMetaInfoMissing(extensionPointId, pluginContext)) {
+ if (
+ isGrafanaDevMode() &&
+ !isCoreGrafanaPlugin &&
+ pluginContext &&
+ isExtensionPointMetaInfoMissing(extensionPointId, pluginContext)
+ ) {
pointLog.error(errors.EXTENSION_POINT_META_INFO_MISSING);
return {
isLoading: false,
diff --git a/public/app/features/plugins/extensions/usePluginLinks.test.tsx b/public/app/features/plugins/extensions/usePluginLinks.test.tsx
index b5d984b165c..697c9dfd91f 100644
--- a/public/app/features/plugins/extensions/usePluginLinks.test.tsx
+++ b/public/app/features/plugins/extensions/usePluginLinks.test.tsx
@@ -1,6 +1,13 @@
import { act, renderHook } from '@testing-library/react';
-import { PluginContextProvider, PluginExtensionPoints, PluginMeta, PluginType } from '@grafana/data';
+import {
+ PluginContextProvider,
+ PluginExtensionPoints,
+ PluginLoadingStrategy,
+ PluginMeta,
+ PluginType,
+} from '@grafana/data';
+import { config } from '@grafana/runtime';
import { ExtensionRegistriesProvider } from './ExtensionRegistriesContext';
import { log } from './logs/log';
@@ -98,6 +105,32 @@ describe('usePluginLinks()', () => {
},
};
+ config.apps[pluginId] = {
+ id: pluginId,
+ path: '',
+ version: '',
+ preload: false,
+ angular: {
+ detected: false,
+ hideDeprecation: false,
+ },
+ loadingStrategy: PluginLoadingStrategy.fetch,
+ dependencies: {
+ grafanaVersion: '8.0.0',
+ plugins: [],
+ extensions: {
+ exposedComponents: [],
+ },
+ },
+ extensions: {
+ addedLinks: [],
+ addedComponents: [],
+ addedFunctions: [],
+ exposedComponents: [],
+ extensionPoints: [],
+ },
+ };
+
wrapper = ({ children }: { children: React.ReactNode }) => (
{children}
@@ -219,6 +252,49 @@ describe('usePluginLinks()', () => {
expect(log.warning).not.toHaveBeenCalled();
});
+ // It can happen that core Grafana plugins (e.g. traces) reuse core components which implement extension points.
+ it('should not validate the extension point meta-info for core plugins', () => {
+ jest.mocked(isGrafanaDevMode).mockReturnValue(true);
+
+ const linkConfig = {
+ targets: extensionPointId,
+ title: '1',
+ description: '1',
+ path: `/a/${pluginId}/2`,
+ };
+
+ // The `AddedLinksRegistry` is validating if the link is registered in the plugin metadata (config.apps).
+ config.apps[pluginId].extensions.addedLinks = [linkConfig];
+
+ wrapper = ({ children }: { children: React.ReactNode }) => (
+
+ {children}
+
+ );
+
+ registries.addedLinksRegistry.register({
+ pluginId,
+ configs: [linkConfig],
+ });
+
+ // Trying to render an extension point that is not defined in the plugin meta
+ // (No restrictions due to being a core plugin)
+ let { result } = renderHook(() => usePluginLinks({ extensionPointId }), { wrapper });
+ expect(result.current.links.length).toBe(1);
+ expect(log.warning).not.toHaveBeenCalled();
+ });
+
it('should not validate the extension point id in production mode', () => {
// Empty list of extension points in the plugin meta (from plugin.json)
wrapper = ({ children }: { children: React.ReactNode }) => (
diff --git a/public/app/features/plugins/extensions/usePluginLinks.tsx b/public/app/features/plugins/extensions/usePluginLinks.tsx
index adec8114582..5d2b193a77c 100644
--- a/public/app/features/plugins/extensions/usePluginLinks.tsx
+++ b/public/app/features/plugins/extensions/usePluginLinks.tsx
@@ -34,19 +34,28 @@ export function usePluginLinks({
return useMemo(() => {
const isInsidePlugin = Boolean(pluginContext);
const pluginId = pluginContext?.meta.id ?? '';
+ const isCoreGrafanaPlugin = pluginContext?.meta.module.startsWith('core:') ?? false;
const pointLog = log.child({
pluginId,
extensionPointId,
});
- if (isGrafanaDevMode() && !isExtensionPointIdValid({ extensionPointId, pluginId, isInsidePlugin, log: pointLog })) {
+ if (
+ isGrafanaDevMode() &&
+ !isExtensionPointIdValid({ extensionPointId, pluginId, isInsidePlugin, isCoreGrafanaPlugin, log: pointLog })
+ ) {
return {
isLoading: false,
links: [],
};
}
- if (isGrafanaDevMode() && pluginContext && isExtensionPointMetaInfoMissing(extensionPointId, pluginContext)) {
+ if (
+ isGrafanaDevMode() &&
+ !isCoreGrafanaPlugin &&
+ pluginContext &&
+ isExtensionPointMetaInfoMissing(extensionPointId, pluginContext)
+ ) {
pointLog.error(errors.EXTENSION_POINT_META_INFO_MISSING);
return {
isLoading: false,
diff --git a/public/app/features/plugins/extensions/validators.test.tsx b/public/app/features/plugins/extensions/validators.test.tsx
index 1b7e71eba7b..2f1713d3f34 100644
--- a/public/app/features/plugins/extensions/validators.test.tsx
+++ b/public/app/features/plugins/extensions/validators.test.tsx
@@ -217,6 +217,7 @@ describe('Plugin Extension Validators', () => {
extensionPointId,
pluginId,
isInsidePlugin: pluginId !== 'grafana' && pluginId !== '',
+ isCoreGrafanaPlugin: false,
log: createLogMock(),
})
).toBe(true);
@@ -244,10 +245,23 @@ describe('Plugin Extension Validators', () => {
extensionPointId,
pluginId,
isInsidePlugin: pluginId !== 'grafana' && pluginId !== '',
+ isCoreGrafanaPlugin: false,
log: createLogMock(),
})
).toBe(false);
});
+
+ it('should return FALSE true if the extension point id is set by a core plugin', () => {
+ expect(
+ isExtensionPointIdValid({
+ extensionPointId: 'traces',
+ pluginId: 'traces',
+ isInsidePlugin: true,
+ isCoreGrafanaPlugin: true,
+ log: createLogMock(),
+ })
+ ).toBe(true);
+ });
});
describe('isAddedLinkMetaInfoMissing()', () => {
diff --git a/public/app/features/plugins/extensions/validators.ts b/public/app/features/plugins/extensions/validators.ts
index 081ac98fbab..df808a34576 100644
--- a/public/app/features/plugins/extensions/validators.ts
+++ b/public/app/features/plugins/extensions/validators.ts
@@ -69,17 +69,19 @@ export function isExtensionPointIdValid({
extensionPointId,
pluginId,
isInsidePlugin,
+ isCoreGrafanaPlugin,
log,
}: {
extensionPointId: string;
pluginId: string;
isInsidePlugin: boolean;
+ isCoreGrafanaPlugin: boolean;
log: ExtensionsLog;
}) {
const startsWithPluginId =
extensionPointId.startsWith(`${pluginId}/`) || extensionPointId.startsWith(`plugins/${pluginId}/`);
- if (isInsidePlugin && !startsWithPluginId) {
+ if (isInsidePlugin && !isCoreGrafanaPlugin && !startsWithPluginId) {
log.error(errors.INVALID_EXTENSION_POINT_ID_PLUGIN(pluginId, extensionPointId));
return false;
}
From 7374df7945dbc5661f82ac77e1ec85159c5f615a Mon Sep 17 00:00:00 2001
From: Matheus Macabu
Date: Fri, 1 Aug 2025 13:57:51 +0200
Subject: [PATCH 17/89] Secrets: Add inline secure value create method
(#108987)
---
.../secret/service/inline_secure_value.go | 64 ++++++++-
.../service/inline_secure_value_test.go | 127 ++++++++++++++++++
.../apis/secret/testutils/testutils.go | 32 ++++-
.../secret/metadata/secure_value_test.go | 4 +-
4 files changed, 223 insertions(+), 4 deletions(-)
diff --git a/pkg/registry/apis/secret/service/inline_secure_value.go b/pkg/registry/apis/secret/service/inline_secure_value.go
index 062f601ba6e..c2a6b97e451 100644
--- a/pkg/registry/apis/secret/service/inline_secure_value.go
+++ b/pkg/registry/apis/secret/service/inline_secure_value.go
@@ -5,9 +5,11 @@ import (
"errors"
"fmt"
+ "github.com/grafana/authlib/authn"
authlib "github.com/grafana/authlib/types"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
secretv1beta1 "github.com/grafana/grafana/apps/secret/pkg/apis/secret/v1beta1"
@@ -15,6 +17,7 @@ import (
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/registry/apis/secret/contracts"
"github.com/grafana/grafana/pkg/registry/apis/secret/xkube"
+ "github.com/grafana/grafana/pkg/util"
)
type inlineSecureValueService struct {
@@ -158,7 +161,66 @@ func (s *inlineSecureValueService) canIdentityReadSecureValue(ctx context.Contex
}
func (s *inlineSecureValueService) CreateInline(ctx context.Context, owner common.ObjectReference, value common.RawSecureValue) (string, error) {
- return "", fmt.Errorf("not implemented yet")
+ ctx, span := s.tracer.Start(ctx, "InlineSecureValueService.CreateInline", trace.WithAttributes(
+ attribute.String("owner.namespace", owner.Namespace),
+ attribute.String("owner.apiGroup", owner.APIGroup),
+ attribute.String("owner.apiVersion", owner.APIVersion),
+ attribute.String("owner.kind", owner.Kind),
+ attribute.String("owner.name", owner.Name),
+ ))
+ defer span.End()
+
+ authInfo, ok := authlib.AuthInfoFrom(ctx)
+ if !ok {
+ return "", fmt.Errorf("missing auth info in context")
+ }
+
+ if authInfo.GetIdentityType() != authlib.TypeUser && authInfo.GetIdentityType() != authlib.TypeServiceAccount {
+ return "", fmt.Errorf("identity type %s not allowed, expected either %s or %s", authInfo.GetIdentityType(), authlib.TypeUser, authlib.TypeServiceAccount)
+ }
+
+ serviceIdentityList, ok := authInfo.GetExtra()[authn.ServiceIdentityKey]
+ if !ok || len(serviceIdentityList) != 1 {
+ return "", fmt.Errorf("expected exactly one service identity, found %d", len(serviceIdentityList))
+ }
+ serviceIdentity := serviceIdentityList[0]
+
+ if owner.Namespace == "" || !authlib.NamespaceMatches(authInfo.GetNamespace(), owner.Namespace) {
+ return "", fmt.Errorf("owner namespace %s does not match auth info namespace %s", owner.Namespace, authInfo.GetNamespace())
+ }
+
+ if owner.APIGroup == "" || owner.APIVersion == "" || owner.Kind == "" || owner.Name == "" {
+ return "", fmt.Errorf("owner reference must have a valid API group, API version, kind and name")
+ }
+
+ if value.IsZero() {
+ return "", fmt.Errorf("trying to create an inline secure value with empty value")
+ }
+
+ // TODO(2025-07-31): when we migrate to using the common type, we don't need this conversion.
+ secret := secretv1beta1.ExposedSecureValue(value)
+
+ spec := &secretv1beta1.SecureValue{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "sv-" + util.GenerateShortUID(),
+ Namespace: owner.Namespace,
+ OwnerReferences: []metav1.OwnerReference{owner.ToOwnerReference()},
+ },
+ Spec: secretv1beta1.SecureValueSpec{
+ Description: fmt.Sprintf("Inline secure value for %s/%s in %s/%s", owner.Kind, owner.Name, owner.APIVersion, owner.APIVersion),
+ Value: &secret,
+ Decrypters: []string{
+ serviceIdentity,
+ },
+ },
+ }
+
+ createdSv, err := s.secureValueService.Create(ctx, spec, authInfo.GetUID())
+ if err != nil {
+ return "", fmt.Errorf("error creating secure value %s for owner %v: %w", spec.Name, owner, err)
+ }
+
+ return createdSv.GetName(), nil
}
func (s *inlineSecureValueService) DeleteWhenOwnedByResource(ctx context.Context, owner common.ObjectReference, name string) error {
diff --git a/pkg/registry/apis/secret/service/inline_secure_value_test.go b/pkg/registry/apis/secret/service/inline_secure_value_test.go
index a358ff13cbd..fe486df5a9b 100644
--- a/pkg/registry/apis/secret/service/inline_secure_value_test.go
+++ b/pkg/registry/apis/secret/service/inline_secure_value_test.go
@@ -292,3 +292,130 @@ func TestIntegration_InlineSecureValue_CanReference(t *testing.T) {
require.Error(t, err)
})
}
+
+func TestIntegration_InlineSecureValue_CreateInline(t *testing.T) {
+ t.Parallel()
+
+ tracer := noop.NewTracerProvider().Tracer("test")
+
+ defaultNs := "org-1234"
+ owner := common.ObjectReference{
+ APIGroup: "prometheus.datasource.grafana.app",
+ APIVersion: "v1alpha1",
+ Kind: "DataSourceConfig",
+ Name: "test-datasource",
+ Namespace: defaultNs,
+ }
+
+ t.Run("happy path creates an inline secure value", func(t *testing.T) {
+ t.Parallel()
+
+ tu := testutils.Setup(t)
+
+ secret := common.NewSecretValue("test-value")
+
+ serviceIdentity := "service-identity"
+
+ createAuthCtx := testutils.CreateOBOAuthContext(t.Context(), serviceIdentity, owner.Namespace, nil, nil)
+
+ svc := service.ProvideInlineSecureValueService(tracer, tu.SecureValueService, nil)
+
+ createdName, err := svc.CreateInline(createAuthCtx, owner, secret)
+ require.NoError(t, err)
+ require.NotEmpty(t, createdName)
+
+ decryptAuthCtx := testutils.CreateServiceAuthContext(t.Context(), serviceIdentity, owner.Namespace, []string{"secret.grafana.app/securevalues:decrypt"})
+
+ decryptedValues, err := tu.DecryptService.Decrypt(decryptAuthCtx, owner.Namespace, createdName)
+ require.NoError(t, err)
+
+ decryptedResult, ok := decryptedValues[createdName]
+ require.True(t, ok)
+ require.Equal(t, decryptedResult.Value().DangerouslyExposeAndConsumeValue(), secret.DangerouslyExposeAndConsumeValue())
+ })
+
+ t.Run("when the auth info is missing it returns an error", func(t *testing.T) {
+ t.Parallel()
+
+ svc := service.ProvideInlineSecureValueService(tracer, nil, nil)
+ _, err := svc.CreateInline(t.Context(), common.ObjectReference{}, "")
+ require.Error(t, err)
+ })
+
+ t.Run("when the request identity is not a user nor a service account, it returns an error", func(t *testing.T) {
+ t.Parallel()
+
+ svc := service.ProvideInlineSecureValueService(tracer, nil, nil)
+
+ createAuthCtx := testutils.CreateServiceAuthContext(t.Context(), "service-identity", defaultNs, nil)
+
+ _, err := svc.CreateInline(createAuthCtx, common.ObjectReference{}, "")
+ require.Error(t, err)
+ })
+
+ t.Run("when the owner namespace does not match auth info namespace it returns an error", func(t *testing.T) {
+ t.Parallel()
+
+ svc := service.ProvideInlineSecureValueService(tracer, nil, nil)
+
+ reqNs := "org-2345"
+ createAuthCtx := testutils.CreateOBOAuthContext(t.Context(), "service-identity", reqNs, nil, nil)
+
+ _, err := svc.CreateInline(createAuthCtx, owner, "")
+ require.Error(t, err)
+ })
+
+ t.Run("when the owner namespace is empty it returns an error", func(t *testing.T) {
+ t.Parallel()
+
+ svc := service.ProvideInlineSecureValueService(tracer, nil, nil)
+
+ createAuthCtx := testutils.CreateOBOAuthContext(t.Context(), "service-identity", defaultNs, nil, nil)
+
+ _, err := svc.CreateInline(createAuthCtx, common.ObjectReference{}, "")
+ require.Error(t, err)
+ })
+
+ t.Run("when the owner reference has empty fields it returns an error", func(t *testing.T) {
+ t.Parallel()
+
+ svc := service.ProvideInlineSecureValueService(tracer, nil, nil)
+
+ owner := common.ObjectReference{
+ Namespace: defaultNs,
+ }
+
+ createAuthCtx := testutils.CreateOBOAuthContext(t.Context(), "service-identity", defaultNs, nil, nil)
+
+ _, err := svc.CreateInline(createAuthCtx, owner, "")
+ require.Error(t, err)
+
+ owner.APIGroup = "prometheus.datasource.grafana.app"
+ _, err = svc.CreateInline(createAuthCtx, owner, "")
+ require.Error(t, err)
+
+ owner.APIVersion = "v1alpha1"
+ _, err = svc.CreateInline(createAuthCtx, owner, "")
+ require.Error(t, err)
+
+ owner.Kind = "DataSourceConfig"
+ _, err = svc.CreateInline(createAuthCtx, owner, "")
+ require.Error(t, err)
+ owner.Kind = ""
+
+ owner.Name = "test-datasource"
+ _, err = svc.CreateInline(createAuthCtx, owner, "")
+ require.Error(t, err)
+ })
+
+ t.Run("when an empty secret is provided it returns an error", func(t *testing.T) {
+ t.Parallel()
+
+ svc := service.ProvideInlineSecureValueService(tracer, nil, nil)
+
+ createAuthCtx := testutils.CreateOBOAuthContext(t.Context(), "service-identity", defaultNs, nil, nil)
+
+ _, err := svc.CreateInline(createAuthCtx, owner, "")
+ require.Error(t, err)
+ })
+}
diff --git a/pkg/registry/apis/secret/testutils/testutils.go b/pkg/registry/apis/secret/testutils/testutils.go
index ebe8b109b81..3e742439bb0 100644
--- a/pkg/registry/apis/secret/testutils/testutils.go
+++ b/pkg/registry/apis/secret/testutils/testutils.go
@@ -244,8 +244,9 @@ func CreateUserAuthContext(ctx context.Context, namespace string, permissions ma
return types.WithAuthInfo(ctx, requester)
}
-func CreateServiceAuthContext(ctx context.Context, serviceIdentity string, permissions []string) context.Context {
+func CreateServiceAuthContext(ctx context.Context, serviceIdentity string, namespace string, permissions []string) context.Context {
requester := &identity.StaticRequester{
+ Namespace: namespace,
AccessTokenClaims: &authn.Claims[authn.AccessTokenClaims]{
Rest: authn.AccessTokenClaims{
Permissions: permissions,
@@ -256,3 +257,32 @@ func CreateServiceAuthContext(ctx context.Context, serviceIdentity string, permi
return types.WithAuthInfo(ctx, requester)
}
+
+// CreateOBOAuthContext emulates a context where the request is made on-behalf-of (OBO) a user, with an access token.
+func CreateOBOAuthContext(
+ ctx context.Context,
+ serviceIdentity string,
+ namespace string,
+ userPermissions map[string][]string,
+ delegatedPermissions []string,
+) context.Context {
+ requester := &identity.StaticRequester{
+ Namespace: namespace,
+ Type: types.TypeUser,
+ UserID: 1,
+ Permissions: map[int64]map[string][]string{
+ 1: userPermissions,
+ },
+ AccessTokenClaims: &authn.Claims[authn.AccessTokenClaims]{
+ Rest: authn.AccessTokenClaims{
+ ServiceIdentity: serviceIdentity,
+ DelegatedPermissions: delegatedPermissions,
+ Actor: &authn.ActorClaims{
+ Subject: "user:1",
+ },
+ },
+ },
+ }
+
+ return types.WithAuthInfo(ctx, requester)
+}
diff --git a/pkg/storage/secret/metadata/secure_value_test.go b/pkg/storage/secret/metadata/secure_value_test.go
index 9400735cdd1..ce1513d95cf 100644
--- a/pkg/storage/secret/metadata/secure_value_test.go
+++ b/pkg/storage/secret/metadata/secure_value_test.go
@@ -403,7 +403,7 @@ func TestStateMachine(t *testing.T) {
},
"decrypt": func(t *rapid.T) {
input := decryptGen.Draw(t, "decryptInput")
- authCtx := testutils.CreateServiceAuthContext(t.Context(), input.decrypter, []string{fmt.Sprintf("secret.grafana.app/securevalues/%+v:decrypt", input.name)})
+ authCtx := testutils.CreateServiceAuthContext(t.Context(), input.decrypter, input.namespace, []string{fmt.Sprintf("secret.grafana.app/securevalues/%+v:decrypt", input.name)})
modelResult, modelErr := model.decrypt(input.decrypter, input.namespace, input.name)
result, err := sut.DecryptService.Decrypt(authCtx, input.namespace, input.name)
if err != nil || modelErr != nil {
@@ -440,7 +440,7 @@ func TestSecureValueServiceExampleBased(t *testing.T) {
require.NoError(t, err)
require.Equal(t, sv.Status.Version, deletedSv.Status.Version)
- authCtx := testutils.CreateServiceAuthContext(t.Context(), sv.Spec.Decrypters[0], []string{fmt.Sprintf("secret.grafana.app/securevalues/%+v:decrypt", sv.Name)})
+ authCtx := testutils.CreateServiceAuthContext(t.Context(), sv.Spec.Decrypters[0], sv.Namespace, []string{fmt.Sprintf("secret.grafana.app/securevalues/%+v:decrypt", sv.Name)})
result, err := sut.DecryptService.Decrypt(authCtx, sv.Namespace, sv.Name)
require.NoError(t, err)
require.Equal(t, 1, len(result))
From 988439e0b8fba04643aac137fd32645094667cbe Mon Sep 17 00:00:00 2001
From: Matheus Macabu
Date: Fri, 1 Aug 2025 14:00:01 +0200
Subject: [PATCH 18/89] Secrets: Simplify CanReference interface to only pass
secure value names (#109030)
---
pkg/registry/apis/secret/contracts/inline.go | 4 +-
.../secret/service/inline_secure_value.go | 27 ++---
.../service/inline_secure_value_test.go | 102 +++---------------
3 files changed, 27 insertions(+), 106 deletions(-)
diff --git a/pkg/registry/apis/secret/contracts/inline.go b/pkg/registry/apis/secret/contracts/inline.go
index 4f677fa5c79..f2f3de71568 100644
--- a/pkg/registry/apis/secret/contracts/inline.go
+++ b/pkg/registry/apis/secret/contracts/inline.go
@@ -7,8 +7,8 @@ import (
)
type InlineSecureValueSupport interface {
- // Check that the request user can reference a secret in the context of a given resource (owner)
- CanReference(ctx context.Context, owner common.ObjectReference, values common.InlineSecureValues) error
+ // Check that the request user can reference secure value names in the context of a given resource (owner)
+ CanReference(ctx context.Context, owner common.ObjectReference, names ...string) error
// CreateInline creates a secret that is owned by the referenced object
// returns the name of the created secret or an error
diff --git a/pkg/registry/apis/secret/service/inline_secure_value.go b/pkg/registry/apis/secret/service/inline_secure_value.go
index c2a6b97e451..9e0cb9fce41 100644
--- a/pkg/registry/apis/secret/service/inline_secure_value.go
+++ b/pkg/registry/apis/secret/service/inline_secure_value.go
@@ -38,13 +38,14 @@ func ProvideInlineSecureValueService(
}
}
-func (s *inlineSecureValueService) CanReference(ctx context.Context, owner common.ObjectReference, values common.InlineSecureValues) error {
+func (s *inlineSecureValueService) CanReference(ctx context.Context, owner common.ObjectReference, names ...string) error {
ctx, span := s.tracer.Start(ctx, "InlineSecureValueService.CanReference", trace.WithAttributes(
attribute.String("owner.namespace", owner.Namespace),
attribute.String("owner.apiGroup", owner.APIGroup),
attribute.String("owner.apiVersion", owner.APIVersion),
attribute.String("owner.kind", owner.Kind),
attribute.String("owner.name", owner.Name),
+ attribute.StringSlice("secureValueNames", names),
))
defer span.End()
@@ -61,31 +62,23 @@ func (s *inlineSecureValueService) CanReference(ctx context.Context, owner commo
return fmt.Errorf("owner reference must have a valid API group, API version, kind and name")
}
- if len(values) == 0 {
+ if len(names) == 0 {
return fmt.Errorf("no inline secure values provided")
}
- for field, value := range values {
- if value.Name == "" {
- return fmt.Errorf("field %s has an empty secure value name", field)
+ for _, name := range names {
+ if name == "" {
+ return fmt.Errorf("empty secure value name")
}
- if !value.Create.IsZero() {
- return fmt.Errorf("field %s has 'create' set, which is not allowed", field)
- }
-
- if value.Remove {
- return fmt.Errorf("field %s has 'remove' set, which is not allowed", field)
- }
-
- owned, err := s.isSecureValueOwnedByResource(ctx, owner, value.Name)
+ owned, err := s.isSecureValueOwnedByResource(ctx, owner, name)
if err != nil {
- return fmt.Errorf("field %s had an error checking secure value ownership: %w", field, err)
+ return err
}
if !owned {
- if err := s.canIdentityReadSecureValue(ctx, xkube.Namespace(owner.Namespace), value.Name); err != nil {
- return fmt.Errorf("field %s: identity cannot read secure value %s: %w", field, value.Name, err)
+ if err := s.canIdentityReadSecureValue(ctx, xkube.Namespace(owner.Namespace), name); err != nil {
+ return err
}
}
}
diff --git a/pkg/registry/apis/secret/service/inline_secure_value_test.go b/pkg/registry/apis/secret/service/inline_secure_value_test.go
index fe486df5a9b..d108ce8f047 100644
--- a/pkg/registry/apis/secret/service/inline_secure_value_test.go
+++ b/pkg/registry/apis/secret/service/inline_secure_value_test.go
@@ -48,18 +48,13 @@ func TestIntegration_InlineSecureValue_CanReference(t *testing.T) {
require.NoError(t, err)
require.NotNil(t, createdSv2)
- values := common.InlineSecureValues{
- "fieldA": {Name: sv1},
- "fieldB": {Name: sv2},
- }
-
ctx := testutils.CreateUserAuthContext(t.Context(), defaultNs, map[string][]string{
"securevalues:read": {"securevalues:uid:" + sv2},
})
svc := service.ProvideInlineSecureValueService(tracer, tu.SecureValueService, tu.AccessClient)
- err = svc.CanReference(ctx, owner, values)
+ err = svc.CanReference(ctx, owner, sv1, sv2)
require.NoError(t, err)
})
@@ -67,7 +62,7 @@ func TestIntegration_InlineSecureValue_CanReference(t *testing.T) {
t.Parallel()
svc := service.ProvideInlineSecureValueService(tracer, nil, nil)
- err := svc.CanReference(t.Context(), common.ObjectReference{}, common.InlineSecureValues{})
+ err := svc.CanReference(t.Context(), common.ObjectReference{})
require.Error(t, err)
})
@@ -79,7 +74,7 @@ func TestIntegration_InlineSecureValue_CanReference(t *testing.T) {
reqNs := "org-2345"
ctx := testutils.CreateUserAuthContext(t.Context(), reqNs, map[string][]string{})
- err := svc.CanReference(ctx, owner, common.InlineSecureValues{})
+ err := svc.CanReference(ctx, owner)
require.Error(t, err)
})
@@ -90,7 +85,7 @@ func TestIntegration_InlineSecureValue_CanReference(t *testing.T) {
ctx := testutils.CreateUserAuthContext(t.Context(), defaultNs, map[string][]string{})
- err := svc.CanReference(ctx, common.ObjectReference{}, common.InlineSecureValues{})
+ err := svc.CanReference(ctx, common.ObjectReference{})
require.Error(t, err)
})
@@ -105,23 +100,23 @@ func TestIntegration_InlineSecureValue_CanReference(t *testing.T) {
ctx := testutils.CreateUserAuthContext(t.Context(), defaultNs, map[string][]string{})
- err := svc.CanReference(ctx, owner, common.InlineSecureValues{})
+ err := svc.CanReference(ctx, owner)
require.Error(t, err)
owner.APIGroup = "prometheus.datasource.grafana.app"
- require.Error(t, svc.CanReference(ctx, owner, common.InlineSecureValues{}))
+ require.Error(t, svc.CanReference(ctx, owner))
owner.APIGroup = ""
owner.APIVersion = "v1alpha1"
- require.Error(t, svc.CanReference(ctx, owner, common.InlineSecureValues{}))
+ require.Error(t, svc.CanReference(ctx, owner))
owner.APIVersion = ""
owner.Kind = "DataSourceConfig"
- require.Error(t, svc.CanReference(ctx, owner, common.InlineSecureValues{}))
+ require.Error(t, svc.CanReference(ctx, owner))
owner.Kind = ""
owner.Name = "test-datasource"
- require.Error(t, svc.CanReference(ctx, owner, common.InlineSecureValues{}))
+ require.Error(t, svc.CanReference(ctx, owner))
owner.Name = ""
})
@@ -132,58 +127,7 @@ func TestIntegration_InlineSecureValue_CanReference(t *testing.T) {
ctx := testutils.CreateUserAuthContext(t.Context(), defaultNs, map[string][]string{})
- err := svc.CanReference(ctx, owner, common.InlineSecureValues{})
- require.Error(t, err)
- })
-
- t.Run("when one of the secure values does not have a `name`, it returns an error", func(t *testing.T) {
- t.Parallel()
-
- svc := service.ProvideInlineSecureValueService(tracer, nil, nil)
-
- values := common.InlineSecureValues{
- "fieldA": {Name: ""},
- }
-
- ctx := testutils.CreateUserAuthContext(t.Context(), defaultNs, map[string][]string{})
-
- err := svc.CanReference(ctx, owner, values)
- require.Error(t, err)
- })
-
- t.Run("when one of the secure values has `create` field set, it returns an error", func(t *testing.T) {
- t.Parallel()
-
- svc := service.ProvideInlineSecureValueService(tracer, nil, nil)
-
- values := common.InlineSecureValues{
- "fieldA": {
- Name: "test-sv",
- Create: common.NewSecretValue("test"),
- },
- }
-
- ctx := testutils.CreateUserAuthContext(t.Context(), defaultNs, map[string][]string{})
-
- err := svc.CanReference(ctx, owner, values)
- require.Error(t, err)
- })
-
- t.Run("when one of the secure values has `remove` field set, it returns an error", func(t *testing.T) {
- t.Parallel()
-
- svc := service.ProvideInlineSecureValueService(tracer, nil, nil)
-
- values := common.InlineSecureValues{
- "fieldA": {
- Name: "test-sv",
- Remove: true,
- },
- }
-
- ctx := testutils.CreateUserAuthContext(t.Context(), defaultNs, map[string][]string{})
-
- err := svc.CanReference(ctx, owner, values)
+ err := svc.CanReference(ctx, owner)
require.Error(t, err)
})
@@ -193,13 +137,9 @@ func TestIntegration_InlineSecureValue_CanReference(t *testing.T) {
tu := testutils.Setup(t)
svc := service.ProvideInlineSecureValueService(tracer, tu.SecureValueService, nil)
- values := common.InlineSecureValues{
- "fieldA": {Name: "non-existent-sv"},
- }
-
ctx := testutils.CreateUserAuthContext(t.Context(), defaultNs, map[string][]string{})
- err := svc.CanReference(ctx, owner, values)
+ err := svc.CanReference(ctx, owner, "non-existent-sv")
require.Error(t, err)
})
@@ -225,15 +165,11 @@ func TestIntegration_InlineSecureValue_CanReference(t *testing.T) {
require.NoError(t, err)
require.NotNil(t, createdSv1)
- values := common.InlineSecureValues{
- "fieldA": {Name: sv1},
- }
-
ctx := testutils.CreateUserAuthContext(t.Context(), defaultNs, map[string][]string{})
svc := service.ProvideInlineSecureValueService(tracer, tu.SecureValueService, nil)
- err = svc.CanReference(ctx, owner, values)
+ err = svc.CanReference(ctx, owner, sv1)
require.Error(t, err)
})
@@ -249,15 +185,11 @@ func TestIntegration_InlineSecureValue_CanReference(t *testing.T) {
})
require.NoError(t, err)
- values := common.InlineSecureValues{
- "fieldA": {Name: sv1},
- }
-
ctx := identity.WithServiceIdentityContext(t.Context(), 1234)
svc := service.ProvideInlineSecureValueService(tracer, tu.SecureValueService, nil)
- err = svc.CanReference(ctx, owner, values)
+ err = svc.CanReference(ctx, owner, sv1)
require.Error(t, err)
})
@@ -273,22 +205,18 @@ func TestIntegration_InlineSecureValue_CanReference(t *testing.T) {
})
require.NoError(t, err)
- values := common.InlineSecureValues{
- "fieldA": {Name: sv1},
- }
-
svc := service.ProvideInlineSecureValueService(tracer, tu.SecureValueService, tu.AccessClient)
ctx := testutils.CreateUserAuthContext(t.Context(), defaultNs, map[string][]string{
"securevalues:read": {"securevalues:uid:another-sv"}, // can read, but another resource!
})
- err = svc.CanReference(ctx, owner, values)
+ err = svc.CanReference(ctx, owner, sv1)
require.Error(t, err)
ctx = testutils.CreateUserAuthContext(t.Context(), defaultNs, nil)
- err = svc.CanReference(ctx, owner, values)
+ err = svc.CanReference(ctx, owner, sv1)
require.Error(t, err)
})
}
From 1130f69ef779217b42f0a5b12616869a39bf54c0 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Fri, 1 Aug 2025 12:00:34 +0000
Subject: [PATCH 19/89] Update dependency @types/glob to v9 (#109043)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
package.json | 2 +-
yarn.lock | 47 +++++++++++++++++++++++------------------------
2 files changed, 24 insertions(+), 25 deletions(-)
diff --git a/package.json b/package.json
index eb94c9eb369..2728eafd9a0 100644
--- a/package.json
+++ b/package.json
@@ -123,7 +123,7 @@
"@types/eslint": "9.6.1",
"@types/eslint-scope": "^8.0.0",
"@types/file-saver": "2.0.7",
- "@types/glob": "^8.0.0",
+ "@types/glob": "^9.0.0",
"@types/google.analytics": "^0.0.46",
"@types/gtag.js": "^0.0.20",
"@types/history": "4.7.11",
diff --git a/yarn.lock b/yarn.lock
index e3c9d0fdd0e..a78abcf6911 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -9458,13 +9458,12 @@ __metadata:
languageName: node
linkType: hard
-"@types/glob@npm:^8.0.0":
- version: 8.1.0
- resolution: "@types/glob@npm:8.1.0"
+"@types/glob@npm:^9.0.0":
+ version: 9.0.0
+ resolution: "@types/glob@npm:9.0.0"
dependencies:
- "@types/minimatch": "npm:^5.1.2"
- "@types/node": "npm:*"
- checksum: 10/9101f3a9061e40137190f70626aa0e202369b5ec4012c3fabe6f5d229cce04772db9a94fa5a0eb39655e2e4ad105c38afbb4af56a56c0996a8c7d4fc72350e3d
+ glob: "npm:*"
+ checksum: 10/a9ea3afe1eafbc8fb303d2d39cd786084aece75fd8eeae1bad8febbf6e0323b429145f31e779a3d68fa693b2a53648ec2c639ee4858fb29132f801c74678051c
languageName: node
linkType: hard
@@ -9714,7 +9713,7 @@ __metadata:
languageName: node
linkType: hard
-"@types/minimatch@npm:*, @types/minimatch@npm:^5.1.2":
+"@types/minimatch@npm:*":
version: 5.1.2
resolution: "@types/minimatch@npm:5.1.2"
checksum: 10/94db5060d20df2b80d77b74dd384df3115f01889b5b6c40fa2dfa27cfc03a68fb0ff7c1f2a0366070263eb2e9d6bfd8c87111d4bc3ae93c3f291297c1bf56c85
@@ -17974,22 +17973,7 @@ __metadata:
languageName: node
linkType: hard
-"glob@npm:10.4.1, glob@npm:^10.2.2, glob@npm:^10.3.10":
- version: 10.4.1
- resolution: "glob@npm:10.4.1"
- dependencies:
- foreground-child: "npm:^3.1.0"
- jackspeak: "npm:^3.1.2"
- minimatch: "npm:^9.0.4"
- minipass: "npm:^7.1.2"
- path-scurry: "npm:^1.11.1"
- bin:
- glob: dist/esm/bin.mjs
- checksum: 10/d7bb49d2b413f77bdd59fea4ca86dcc12450deee221af0ca93e09534b81b9ef68fe341345751d8ff0c5b54bad422307e0e44266ff8ad7fbbd0c200e8ec258b16
- languageName: node
- linkType: hard
-
-"glob@npm:11.0.3, glob@npm:^11.0.0":
+"glob@npm:*, glob@npm:11.0.3, glob@npm:^11.0.0":
version: 11.0.3
resolution: "glob@npm:11.0.3"
dependencies:
@@ -18005,6 +17989,21 @@ __metadata:
languageName: node
linkType: hard
+"glob@npm:10.4.1, glob@npm:^10.2.2, glob@npm:^10.3.10":
+ version: 10.4.1
+ resolution: "glob@npm:10.4.1"
+ dependencies:
+ foreground-child: "npm:^3.1.0"
+ jackspeak: "npm:^3.1.2"
+ minimatch: "npm:^9.0.4"
+ minipass: "npm:^7.1.2"
+ path-scurry: "npm:^1.11.1"
+ bin:
+ glob: dist/esm/bin.mjs
+ checksum: 10/d7bb49d2b413f77bdd59fea4ca86dcc12450deee221af0ca93e09534b81b9ef68fe341345751d8ff0c5b54bad422307e0e44266ff8ad7fbbd0c200e8ec258b16
+ languageName: node
+ linkType: hard
+
"glob@npm:^7.0.3, glob@npm:^7.1.2, glob@npm:^7.1.3, glob@npm:^7.1.4, glob@npm:^7.1.6":
version: 7.2.3
resolution: "glob@npm:7.2.3"
@@ -18321,7 +18320,7 @@ __metadata:
"@types/eslint": "npm:9.6.1"
"@types/eslint-scope": "npm:^8.0.0"
"@types/file-saver": "npm:2.0.7"
- "@types/glob": "npm:^8.0.0"
+ "@types/glob": "npm:^9.0.0"
"@types/google.analytics": "npm:^0.0.46"
"@types/gtag.js": "npm:^0.0.20"
"@types/history": "npm:4.7.11"
From 4f30a3b62ba28769990d87c8b7d3ecaa571685b0 Mon Sep 17 00:00:00 2001
From: Tom Ratcliffe
Date: Fri, 1 Aug 2025 13:34:33 +0100
Subject: [PATCH 20/89] Folders: Use app platform search endpoint and update
tests (#108814)
* Update test utils package with mock folder + search endpoints
* Add dashboards v0alpha1 api client
* Update hook to use app platform search API
* Update tests and fixtures
---
.../src/fixtures/folders.ts | 130 +++++++++++++++
.../src/handlers/all-handlers.ts | 4 +-
.../src/handlers/api/folders/handlers.ts | 52 ++++++
.../v0alpha1/handlers.ts | 52 ++++++
.../src/types/browse-dashboards.ts | 58 +++++++
packages/grafana-test-utils/src/unstable.ts | 3 +
.../api/clients/dashboard/v0alpha1/baseAPI.ts | 14 ++
.../dashboard/v0alpha1/endpoints.gen.ts | 53 +++++++
.../api/clients/dashboard/v0alpha1/index.ts | 27 ++++
.../NestedFolderPicker.test.tsx | 149 ++++++------------
.../useFoldersQuery.test.tsx | 118 +++++---------
.../useFoldersQueryAppPlatform.ts | 26 +--
public/app/core/reducers/root.ts | 2 +
.../BrowseDashboardsPage.test.tsx | 33 +---
.../BrowseFolderLibraryPanelsPage.test.tsx | 33 ++--
.../BrowseActions/MoveModal.test.tsx | 82 ++--------
.../components/BrowseView.test.tsx | 5 +-
.../fixtures/dashboardsTreeItem.fixture.ts | 58 +------
public/app/store/configureStore.ts | 2 +
scripts/generate-rtk-apis.ts | 11 ++
20 files changed, 539 insertions(+), 373 deletions(-)
create mode 100644 packages/grafana-test-utils/src/fixtures/folders.ts
create mode 100644 packages/grafana-test-utils/src/handlers/api/folders/handlers.ts
create mode 100644 packages/grafana-test-utils/src/handlers/apis/dashboard.grafana.app/v0alpha1/handlers.ts
create mode 100644 packages/grafana-test-utils/src/types/browse-dashboards.ts
create mode 100644 public/app/api/clients/dashboard/v0alpha1/baseAPI.ts
create mode 100644 public/app/api/clients/dashboard/v0alpha1/endpoints.gen.ts
create mode 100644 public/app/api/clients/dashboard/v0alpha1/index.ts
diff --git a/packages/grafana-test-utils/src/fixtures/folders.ts b/packages/grafana-test-utils/src/fixtures/folders.ts
new file mode 100644
index 00000000000..7fc9bed9e20
--- /dev/null
+++ b/packages/grafana-test-utils/src/fixtures/folders.ts
@@ -0,0 +1,130 @@
+import { Chance } from 'chance';
+
+import { DashboardsTreeItem, DashboardViewItem, UIDashboardViewItem } from '../types/browse-dashboards';
+
+function wellFormedEmptyFolder(
+ seed = 1,
+ partial?: Partial>
+): DashboardsTreeItem {
+ const random = Chance(seed);
+
+ return {
+ item: {
+ kind: 'ui',
+ uiKind: 'empty-folder',
+ uid: random.guid(),
+ },
+ level: 0,
+ isOpen: false,
+ ...partial,
+ };
+}
+
+function wellFormedDashboard(
+ seed = 1,
+ partial?: Partial>,
+ itemPartial?: Partial
+): DashboardsTreeItem {
+ const random = Chance(seed);
+
+ return {
+ item: {
+ kind: 'dashboard',
+ title: random.sentence({ words: 3 }),
+ uid: random.guid(),
+ tags: [random.word()],
+ ...itemPartial,
+ },
+ level: 0,
+ isOpen: false,
+ ...partial,
+ };
+}
+
+function wellFormedFolder(
+ seed = 1,
+ partial?: Partial>,
+ itemPartial?: Partial
+): DashboardsTreeItem {
+ const random = Chance(seed);
+ const uid = random.guid();
+
+ return {
+ item: {
+ kind: 'folder',
+ title: random.sentence({ words: 3 }),
+ uid,
+ url: `/dashboards/f/${uid}`,
+ ...itemPartial,
+ },
+ level: 0,
+ isOpen: false,
+ ...partial,
+ };
+}
+
+export function treeViewersCanEdit() {
+ const [, { folderA, folderC }] = wellFormedTree();
+
+ return [
+ [folderA, folderC],
+ {
+ folderA,
+ folderC,
+ },
+ ] as const;
+}
+
+export function wellFormedTree() {
+ let seed = 1;
+
+ const folderA = wellFormedFolder(seed++);
+ const folderA_folderA = wellFormedFolder(seed++, { level: 1 }, { parentUID: folderA.item.uid });
+ const folderA_folderB = wellFormedFolder(seed++, { level: 1 }, { parentUID: folderA.item.uid });
+ const folderA_folderB_dashbdA = wellFormedDashboard(seed++, { level: 2 }, { parentUID: folderA_folderB.item.uid });
+ const folderA_folderB_dashbdB = wellFormedDashboard(seed++, { level: 2 }, { parentUID: folderA_folderB.item.uid });
+ const folderA_folderC = wellFormedFolder(seed++, { level: 1 }, { parentUID: folderA.item.uid });
+ const folderA_folderC_dashbdA = wellFormedDashboard(seed++, { level: 2 }, { parentUID: folderA_folderC.item.uid });
+ const folderA_folderC_dashbdB = wellFormedDashboard(seed++, { level: 2 }, { parentUID: folderA_folderC.item.uid });
+ const folderA_dashbdD = wellFormedDashboard(seed++, { level: 1 }, { parentUID: folderA.item.uid });
+ const folderB = wellFormedFolder(seed++);
+ const folderB_empty = wellFormedEmptyFolder(seed++);
+ const folderC = wellFormedFolder(seed++);
+ const dashbdD = wellFormedDashboard(seed++);
+ const dashbdE = wellFormedDashboard(seed++);
+
+ return [
+ [
+ folderA,
+ folderA_folderA,
+ folderA_folderB,
+ folderA_folderB_dashbdA,
+ folderA_folderB_dashbdB,
+ folderA_folderC,
+ folderA_folderC_dashbdA,
+ folderA_folderC_dashbdB,
+ folderA_dashbdD,
+ folderB,
+ folderB_empty,
+ folderC,
+ dashbdD,
+ dashbdE,
+ ],
+ {
+ folderA,
+ folderA_folderA,
+ folderA_folderB,
+ folderA_folderB_dashbdA,
+ folderA_folderB_dashbdB,
+ folderA_folderC,
+ folderA_folderC_dashbdA,
+ folderA_folderC_dashbdB,
+ folderA_dashbdD,
+ folderB,
+ folderB_empty,
+ folderC,
+ dashbdD,
+ dashbdE,
+ },
+ ] as const;
+}
diff --git a/packages/grafana-test-utils/src/handlers/all-handlers.ts b/packages/grafana-test-utils/src/handlers/all-handlers.ts
index edf4c592ef2..3e626ba021b 100644
--- a/packages/grafana-test-utils/src/handlers/all-handlers.ts
+++ b/packages/grafana-test-utils/src/handlers/all-handlers.ts
@@ -1,7 +1,9 @@
import { HttpHandler } from 'msw';
+import folderHandlers from './api/folders/handlers';
import teamsHandlers from './api/teams/handlers';
+import appPlatformFolderHandlers from './apis/dashboard.grafana.app/v0alpha1/handlers';
-const allHandlers: HttpHandler[] = [...teamsHandlers];
+const allHandlers: HttpHandler[] = [...teamsHandlers, ...folderHandlers, ...appPlatformFolderHandlers];
export default allHandlers;
diff --git a/packages/grafana-test-utils/src/handlers/api/folders/handlers.ts b/packages/grafana-test-utils/src/handlers/api/folders/handlers.ts
new file mode 100644
index 00000000000..103b816ce3a
--- /dev/null
+++ b/packages/grafana-test-utils/src/handlers/api/folders/handlers.ts
@@ -0,0 +1,52 @@
+import { HttpResponse, http } from 'msw';
+
+import { treeViewersCanEdit, wellFormedTree } from '../../../fixtures/folders';
+
+const [mockTree] = wellFormedTree();
+const [mockTreeThatViewersCanEdit] = treeViewersCanEdit();
+const collator = new Intl.Collator();
+
+const listFoldersHandler = () =>
+ http.get('/api/folders', ({ request }) => {
+ const url = new URL(request.url);
+ const parentUid = url.searchParams.get('parentUid') ?? undefined;
+ const permission = url.searchParams.get('permission');
+
+ const limit = parseInt(url.searchParams.get('limit') ?? '1000', 10);
+ const page = parseInt(url.searchParams.get('page') ?? '1', 10);
+
+ const tree = permission === 'Edit' ? mockTreeThatViewersCanEdit : mockTree;
+
+ // reconstruct a folder API response from the flat tree fixture
+ const folders = tree
+ .filter((v) => v.item.kind === 'folder' && v.item.parentUID === parentUid)
+ .map((folder) => {
+ return {
+ uid: folder.item.uid,
+ title: folder.item.kind === 'folder' ? folder.item.title : "invalid - this shouldn't happen",
+ };
+ })
+ .sort((a, b) => collator.compare(a.title, b.title)) // API always sorts by title
+ .slice(limit * (page - 1), limit * page);
+
+ return HttpResponse.json(folders);
+ });
+
+const getFolderHandler = () =>
+ http.get('/api/folders/:uid', ({ params }) => {
+ const { uid } = params;
+
+ const folder = mockTree.find((v) => v.item.uid === uid);
+ if (!folder) {
+ return HttpResponse.json({ message: 'folder not found', status: 'not-found' }, { status: 404 });
+ }
+
+ return HttpResponse.json({
+ title: folder?.item.title,
+ uid: folder?.item.uid,
+ });
+ });
+
+const handlers = [listFoldersHandler(), getFolderHandler()];
+
+export default handlers;
diff --git a/packages/grafana-test-utils/src/handlers/apis/dashboard.grafana.app/v0alpha1/handlers.ts b/packages/grafana-test-utils/src/handlers/apis/dashboard.grafana.app/v0alpha1/handlers.ts
new file mode 100644
index 00000000000..43813d6f4f8
--- /dev/null
+++ b/packages/grafana-test-utils/src/handlers/apis/dashboard.grafana.app/v0alpha1/handlers.ts
@@ -0,0 +1,52 @@
+import { Chance } from 'chance';
+import { HttpResponse, http } from 'msw';
+
+import { wellFormedTree } from '../../../../fixtures/folders';
+
+const [mockTree] = wellFormedTree();
+
+type FilterArray = Array<(v: (typeof mockTree)[number]) => boolean>;
+
+const getSearchHandler = () =>
+ http.get('/apis/dashboard.grafana.app/v0alpha1/namespaces/default/search', ({ request }) => {
+ const folderFilter = new URL(request.url).searchParams.get('folder') || null;
+ const typeFilter = new URL(request.url).searchParams.get('type') || null;
+ const response = mockTree
+ .filter((filterItem) => {
+ const filters: FilterArray = [];
+ if (folderFilter && folderFilter !== 'general') {
+ filters.push(({ item }) => item.kind === 'folder' && item.parentUID === folderFilter);
+ }
+
+ if (folderFilter === 'general') {
+ filters.push(({ item }) => item.kind === 'folder' && item.parentUID === undefined);
+ }
+
+ if (typeFilter) {
+ filters.push(({ item }) => item.kind === typeFilter);
+ }
+
+ return filters.every((filterPredicate) => filterPredicate(filterItem));
+ })
+
+ .map(({ item }) => {
+ const random = Chance(item.uid);
+ return {
+ resource: 'folders',
+ name: item.uid,
+ title: item.title,
+ field: {
+ // Generate mock deprecated IDs only in the mock handlers - not generating in
+ // mock data as it would require updating/tracking in the types as well
+ 'grafana.app/deprecatedInternalID': random.integer({ min: 1, max: 1000 }),
+ },
+ };
+ });
+
+ return HttpResponse.json({
+ totalHits: response.length,
+ hits: response,
+ });
+ });
+
+export default [getSearchHandler()];
diff --git a/packages/grafana-test-utils/src/types/browse-dashboards.ts b/packages/grafana-test-utils/src/types/browse-dashboards.ts
new file mode 100644
index 00000000000..5757b4a2ec0
--- /dev/null
+++ b/packages/grafana-test-utils/src/types/browse-dashboards.ts
@@ -0,0 +1,58 @@
+// FIXME: This file is a duplication of types within the core code
+// Where should these live long term?
+// @grafana/schema?
+// New package @grafana/core? @grafana/types?
+
+enum ManagerKind {
+ Repo = 'repo',
+ Terraform = 'terraform',
+ Kubectl = 'kubectl',
+ Plugin = 'plugin',
+}
+
+type DashboardViewItemKind = 'folder' | 'dashboard' | 'panel';
+
+type DashboardViewItemWithUIItems = DashboardViewItem | UIDashboardViewItem;
+
+export interface DashboardsTreeItem {
+ item: T;
+ level: number;
+ isOpen: boolean;
+ parentUID?: string;
+}
+
+export interface UIDashboardViewItem {
+ kind: 'ui';
+ uiKind: 'empty-folder' | 'pagination-placeholder' | 'divider';
+ uid: string;
+ // Optional title to make mock data easier to work with
+ title?: string;
+}
+
+/**
+ * Type used in the folder view components
+ */
+export interface DashboardViewItem {
+ kind: DashboardViewItemKind;
+ uid: string;
+ title: string;
+ url?: string;
+ tags?: string[];
+
+ icon?: string;
+
+ parentUID?: string;
+ /** @deprecated Not used in new Browse UI */
+ parentTitle?: string;
+ /** @deprecated Not used in new Browse UI */
+ parentKind?: string;
+
+ // Used only for psuedo-folders, such as Starred or Recent
+ /** @deprecated Not used in new Browse UI */
+ itemsUIDs?: string[];
+
+ // For enterprise sort options
+ sortMeta?: number | string; // value sorted by
+ sortMetaName?: string; // name of the value being sorted e.g. 'Views'
+ managedBy?: ManagerKind;
+}
diff --git a/packages/grafana-test-utils/src/unstable.ts b/packages/grafana-test-utils/src/unstable.ts
index 5b60f83479c..8758e294464 100644
--- a/packages/grafana-test-utils/src/unstable.ts
+++ b/packages/grafana-test-utils/src/unstable.ts
@@ -1 +1,4 @@
+import { wellFormedTree } from './fixtures/folders';
+
+export const getFolderFixtures = wellFormedTree;
export { MOCK_TEAMS } from './fixtures/teams';
diff --git a/public/app/api/clients/dashboard/v0alpha1/baseAPI.ts b/public/app/api/clients/dashboard/v0alpha1/baseAPI.ts
new file mode 100644
index 00000000000..dca53563fa0
--- /dev/null
+++ b/public/app/api/clients/dashboard/v0alpha1/baseAPI.ts
@@ -0,0 +1,14 @@
+import { createApi } from '@reduxjs/toolkit/query/react';
+
+import { createBaseQuery } from 'app/api/createBaseQuery';
+import { getAPIBaseURL } from 'app/api/utils';
+
+export const BASE_URL = getAPIBaseURL('dashboard.grafana.app', 'v0alpha1');
+
+export const api = createApi({
+ reducerPath: 'dashboardAPIv0alpha1',
+ baseQuery: createBaseQuery({
+ baseURL: BASE_URL,
+ }),
+ endpoints: () => ({}),
+});
diff --git a/public/app/api/clients/dashboard/v0alpha1/endpoints.gen.ts b/public/app/api/clients/dashboard/v0alpha1/endpoints.gen.ts
new file mode 100644
index 00000000000..8730157dedf
--- /dev/null
+++ b/public/app/api/clients/dashboard/v0alpha1/endpoints.gen.ts
@@ -0,0 +1,53 @@
+import { api } from './baseAPI';
+export const addTagTypes = ['Search'] as const;
+const injectedRtkApi = api
+ .enhanceEndpoints({
+ addTagTypes,
+ })
+ .injectEndpoints({
+ endpoints: (build) => ({
+ getSearch: build.query({
+ query: (queryArg) => ({
+ url: `/search`,
+ params: {
+ query: queryArg.query,
+ folder: queryArg.folder,
+ sort: queryArg.sort,
+ },
+ }),
+ providesTags: ['Search'],
+ }),
+ }),
+ overrideExisting: false,
+ });
+export { injectedRtkApi as generatedAPI };
+export type GetSearchApiResponse = /** status 200 undefined */ {
+ /** 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 */
+ apiVersion?: string;
+ /** Facet results */
+ facets?: {
+ [key: string]: any;
+ };
+ /** The dashboard body (unstructured for now) */
+ hits: any[];
+ /** 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 */
+ kind?: string;
+ /** Max score */
+ maxScore?: number;
+ /** Where the query started from */
+ offset?: number;
+ /** Cost of running the query */
+ queryCost?: number;
+ /** How are the results sorted */
+ sortBy?: any;
+ /** The number of matching results */
+ totalHits: number;
+};
+export type GetSearchApiArg = {
+ /** user query string */
+ query?: string;
+ /** search/list within a folder (not recursive) */
+ folder?: string;
+ /** sortable field */
+ sort?: string;
+};
diff --git a/public/app/api/clients/dashboard/v0alpha1/index.ts b/public/app/api/clients/dashboard/v0alpha1/index.ts
new file mode 100644
index 00000000000..f61b46bc1f9
--- /dev/null
+++ b/public/app/api/clients/dashboard/v0alpha1/index.ts
@@ -0,0 +1,27 @@
+import { generatedAPI, GetSearchApiArg } from './endpoints.gen';
+
+type OverrideGetSearchRequestOptions = GetSearchApiArg & {
+ type: string;
+};
+
+export const dashboardAPIv0alpha1 = generatedAPI.enhanceEndpoints({
+ addTagTypes: ['Folder', 'Dashboard'],
+ endpoints: {
+ getSearch: (endpointDefinition) => {
+ const originalQuery = endpointDefinition.query;
+ endpointDefinition.providesTags = ['Search', 'Folder', 'Dashboard'];
+ if (originalQuery) {
+ // TODO: Remove once API spec is updated with `type`
+ endpointDefinition.query = (requestOptions: OverrideGetSearchRequestOptions) => ({
+ ...originalQuery(requestOptions),
+ params: {
+ ...requestOptions,
+ type: requestOptions.type,
+ },
+ });
+ }
+ },
+ },
+});
+
+export const { useGetSearchQuery } = dashboardAPIv0alpha1;
diff --git a/public/app/core/components/NestedFolderPicker/NestedFolderPicker.test.tsx b/public/app/core/components/NestedFolderPicker/NestedFolderPicker.test.tsx
index 226583c54e1..38674034a0d 100644
--- a/public/app/core/components/NestedFolderPicker/NestedFolderPicker.test.tsx
+++ b/public/app/core/components/NestedFolderPicker/NestedFolderPicker.test.tsx
@@ -1,89 +1,31 @@
-import { fireEvent, render as rtlRender, screen } from '@testing-library/react';
-import userEvent from '@testing-library/user-event';
-import { HttpResponse, http } from 'msw';
-import { SetupServer, setupServer } from 'msw/node';
-import { TestProvider } from 'test/helpers/TestProvider';
+import { fireEvent, render, screen } from 'test/test-utils';
-import { config } from '@grafana/runtime';
+import { config, setBackendSrv } from '@grafana/runtime';
+import { setupMockServer } from '@grafana/test-utils/server';
+import { getFolderFixtures } from '@grafana/test-utils/unstable';
import { backendSrv } from 'app/core/services/backend_srv';
-import {
- treeViewersCanEdit,
- wellFormedTree,
-} from '../../../features/browse-dashboards/fixtures/dashboardsTreeItem.fixture';
-
import { NestedFolderPicker } from './NestedFolderPicker';
-const [mockTree, { folderA, folderB, folderC, folderA_folderA, folderA_folderB }] = wellFormedTree();
-const [mockTreeThatViewersCanEdit /* shares folders with wellFormedTree */] = treeViewersCanEdit();
+const [_, { folderA, folderB, folderC, folderA_folderA, folderA_folderB, folderA_folderC }] = getFolderFixtures();
-jest.mock('@grafana/runtime', () => ({
- ...jest.requireActual('@grafana/runtime'),
- getBackendSrv: () => backendSrv,
-}));
-
-function render(...[ui, options]: Parameters) {
- rtlRender({ui}, options);
-}
+setupMockServer();
+setBackendSrv(backendSrv);
describe('NestedFolderPicker', () => {
const mockOnChange = jest.fn();
const originalScrollIntoView = window.HTMLElement.prototype.scrollIntoView;
- let server: SetupServer;
beforeAll(() => {
window.HTMLElement.prototype.scrollIntoView = function () {};
-
- server = setupServer(
- http.get('/api/folders/:uid', () => {
- return HttpResponse.json({
- title: folderA.item.title,
- uid: folderA.item.uid,
- });
- }),
-
- http.get('/apis/provisioning.grafana.app/v0alpha1/namespaces/default/settings', () => {
- return HttpResponse.json({
- items: [],
- });
- }),
-
- http.get('/api/folders', ({ request }) => {
- const url = new URL(request.url);
- const parentUid = url.searchParams.get('parentUid') ?? undefined;
- const permission = url.searchParams.get('permission');
-
- const limit = parseInt(url.searchParams.get('limit') ?? '1000', 10);
- const page = parseInt(url.searchParams.get('page') ?? '1', 10);
-
- const tree = permission === 'Edit' ? mockTreeThatViewersCanEdit : mockTree;
-
- // reconstruct a folder API response from the flat tree fixture
- const folders = tree
- .filter((v) => v.item.kind === 'folder' && v.item.parentUID === parentUid)
- .map((folder) => {
- return {
- uid: folder.item.uid,
- title: folder.item.kind === 'folder' ? folder.item.title : "invalid - this shouldn't happen",
- };
- })
- .slice(limit * (page - 1), limit * page);
-
- return HttpResponse.json(folders);
- })
- );
-
- server.listen();
});
afterAll(() => {
- server.close();
window.HTMLElement.prototype.scrollIntoView = originalScrollIntoView;
});
afterEach(() => {
jest.resetAllMocks();
- server.resetHandlers();
});
it('renders a button with the correct label when no folder is selected', async () => {
@@ -92,18 +34,18 @@ describe('NestedFolderPicker', () => {
});
it('renders a button with the correct label when a folder is selected', async () => {
- render();
+ render();
expect(
await screen.findByRole('button', { name: `Select folder: ${folderA.item.title} currently selected` })
).toBeInTheDocument();
});
it('clicking the button opens the folder picker', async () => {
- render();
+ const { user } = render();
// Open the picker and wait for children to load
const button = await screen.findByRole('button', { name: 'Select folder' });
- await userEvent.click(button);
+ await user.click(button);
await screen.findByLabelText(folderA.item.title);
// Select folder button is no longer visible
@@ -118,73 +60,73 @@ describe('NestedFolderPicker', () => {
});
it('can select a folder from the picker', async () => {
- render();
+ const { user } = render();
// Open the picker and wait for children to load
const button = await screen.findByRole('button', { name: 'Select folder' });
- await userEvent.click(button);
+ await user.click(button);
await screen.findByLabelText(folderA.item.title);
- await userEvent.click(screen.getByLabelText(folderA.item.title));
+ await user.click(screen.getByLabelText(folderA.item.title));
expect(mockOnChange).toHaveBeenCalledWith(folderA.item.uid, folderA.item.title);
});
it('can clear a selection if clearable is specified', async () => {
- render();
+ const { user } = render();
- await userEvent.click(await screen.findByRole('button', { name: 'Clear selection' }));
+ await user.click(await screen.findByRole('button', { name: 'Clear selection' }));
expect(mockOnChange).toHaveBeenCalledWith(undefined, undefined);
});
it('can select a folder from the picker with the keyboard', async () => {
- render();
+ const { user } = render();
const button = await screen.findByRole('button', { name: 'Select folder' });
- await userEvent.click(button);
+ await user.click(button);
- await userEvent.keyboard('{ArrowDown}{ArrowDown}{Enter}');
- expect(mockOnChange).toHaveBeenCalledWith(folderA.item.uid, folderA.item.title);
+ await user.keyboard('{ArrowDown}{ArrowDown}{Enter}');
+ expect(mockOnChange).toHaveBeenCalledWith(folderC.item.uid, folderC.item.title);
});
it('shows the root folder by default', async () => {
- render();
+ const { user } = render();
// Open the picker and wait for children to load
const button = await screen.findByRole('button', { name: 'Select folder' });
- await userEvent.click(button);
+ await user.click(button);
await screen.findByLabelText(folderA.item.title);
- await userEvent.click(screen.getByLabelText('Dashboards'));
+ await user.click(screen.getByLabelText('Dashboards'));
expect(mockOnChange).toHaveBeenCalledWith('', 'Dashboards');
});
it('hides the root folder if the prop says so', async () => {
- render();
+ const { user } = render();
// Open the picker and wait for children to load
const button = await screen.findByRole('button', { name: 'Select folder' });
- await userEvent.click(button);
+ await user.click(button);
await screen.findByLabelText(folderA.item.title);
expect(screen.queryByLabelText('Dashboards')).not.toBeInTheDocument();
});
it('hides folders specififed by UID', async () => {
- render();
+ const { user } = render();
// Open the picker and wait for children to load
const button = await screen.findByRole('button', { name: 'Select folder' });
- await userEvent.click(button);
+ await user.click(button);
await screen.findByLabelText(folderA.item.title);
expect(screen.queryByLabelText(folderC.item.title)).not.toBeInTheDocument();
});
it('by default only shows items the user can edit', async () => {
- render();
+ const { user } = render();
const button = await screen.findByRole('button', { name: 'Select folder' });
- await userEvent.click(button);
+ await user.click(button);
await screen.findByLabelText(folderA.item.title);
expect(screen.queryByLabelText(folderB.item.title)).not.toBeInTheDocument(); // folderB is not editable
@@ -192,10 +134,10 @@ describe('NestedFolderPicker', () => {
});
it('shows items the user can view, with the prop', async () => {
- render();
+ const { user } = render();
const button = await screen.findByRole('button', { name: 'Select folder' });
- await userEvent.click(button);
+ await user.click(button);
await screen.findByLabelText(folderA.item.title);
expect(screen.getByLabelText(folderB.item.title)).toBeInTheDocument();
@@ -214,11 +156,11 @@ describe('NestedFolderPicker', () => {
});
it('can expand and collapse a folder to show its children', async () => {
- render();
+ const { user } = render();
// Open the picker and wait for children to load
const button = await screen.findByRole('button', { name: 'Select folder' });
- await userEvent.click(button);
+ await user.click(button);
await screen.findByLabelText(folderA.item.title);
// Expand Folder A
@@ -240,34 +182,35 @@ describe('NestedFolderPicker', () => {
fireEvent.mouseDown(screen.getByRole('button', { name: `Expand folder ${folderA.item.title}` }));
// Select the first child
- await userEvent.click(screen.getByLabelText(folderA_folderA.item.title));
+ await user.click(screen.getByLabelText(folderA_folderA.item.title));
expect(mockOnChange).toHaveBeenCalledWith(folderA_folderA.item.uid, folderA_folderA.item.title);
});
it('can expand and collapse a folder to show its children with the keyboard', async () => {
- render();
+ const { user } = render();
const button = await screen.findByRole('button', { name: 'Select folder' });
- await userEvent.click(button);
+ await user.click(button);
// Expand Folder A
- await userEvent.keyboard('{ArrowDown}{ArrowDown}{ArrowRight}');
+ await user.keyboard('{ArrowDown}{ArrowDown}{ArrowDown}{ArrowDown}{ArrowRight}');
// Folder A's children are visible
expect(await screen.findByLabelText(folderA_folderA.item.title)).toBeInTheDocument();
expect(await screen.findByLabelText(folderA_folderB.item.title)).toBeInTheDocument();
+ expect(await screen.findByLabelText(folderA_folderC.item.title)).toBeInTheDocument();
// Collapse Folder A
- await userEvent.keyboard('{ArrowLeft}');
+ await user.keyboard('{ArrowLeft}');
expect(screen.queryByLabelText(folderA_folderA.item.title)).not.toBeInTheDocument();
expect(screen.queryByLabelText(folderA_folderB.item.title)).not.toBeInTheDocument();
// Expand Folder A again
- await userEvent.keyboard('{ArrowRight}');
+ await user.keyboard('{ArrowRight}');
// Select the first child
- await userEvent.keyboard('{ArrowDown}{Enter}');
- expect(mockOnChange).toHaveBeenCalledWith(folderA_folderA.item.uid, folderA_folderA.item.title);
+ await user.keyboard('{ArrowDown}{Enter}');
+ expect(mockOnChange).toHaveBeenCalledWith(folderA_folderC.item.uid, folderA_folderC.item.title);
});
});
@@ -283,11 +226,11 @@ describe('NestedFolderPicker', () => {
});
it('does not show an expand button', async () => {
- render();
+ const { user } = render();
// Open the picker and wait for children to load
const button = await screen.findByRole('button', { name: 'Select folder' });
- await userEvent.click(button);
+ await user.click(button);
await screen.findByLabelText(folderA.item.title);
// There should be no expand button
@@ -296,13 +239,13 @@ describe('NestedFolderPicker', () => {
});
it('does not expand a folder with the keyboard', async () => {
- render();
+ const { user } = render();
const button = await screen.findByRole('button', { name: 'Select folder' });
- await userEvent.click(button);
+ await user.click(button);
// try to expand Folder A
- await userEvent.keyboard('{ArrowDown}{ArrowDown}{ArrowRight}');
+ await user.keyboard('{ArrowDown}{ArrowDown}{ArrowRight}');
// Folder A's children are not visible
expect(screen.queryByLabelText(folderA_folderA.item.title)).not.toBeInTheDocument();
diff --git a/public/app/core/components/NestedFolderPicker/useFoldersQuery.test.tsx b/public/app/core/components/NestedFolderPicker/useFoldersQuery.test.tsx
index 18d83068b5e..cf0f0f67af1 100644
--- a/public/app/core/components/NestedFolderPicker/useFoldersQuery.test.tsx
+++ b/public/app/core/components/NestedFolderPicker/useFoldersQuery.test.tsx
@@ -1,82 +1,26 @@
-import { act, renderHook } from '@testing-library/react';
+import { ReactNode } from 'react';
+import { act, getWrapper, renderHook, waitFor } from 'test/test-utils';
import { GrafanaConfig } from '@grafana/data';
import * as runtime from '@grafana/runtime';
-import { DashboardsTreeItem } from 'app/features/browse-dashboards/types';
+import { setupMockServer } from '@grafana/test-utils/server';
+import { getFolderFixtures } from '@grafana/test-utils/unstable';
+import { backendSrv } from 'app/core/services/backend_srv';
import { DashboardViewItem } from '../../../features/search/types';
import { useFoldersQuery } from './useFoldersQuery';
import { getRootFolderItem } from './utils';
-const PAGE_SIZE = 10;
+const [_, { folderA, folderB, folderC }] = getFolderFixtures();
-const legacyResponse = {
- status: 'fulfilled',
- originalArgs: { parentUid: undefined, page: 1, limit: PAGE_SIZE, permission: 'Edit' },
- data: [{ title: 'Legacy Folder', uid: 'legacy1', managedBy: undefined }],
+runtime.setBackendSrv(backendSrv);
+setupMockServer();
+
+const wrapper = ({ children }: { children: ReactNode }) => {
+ const ProviderWrapper = getWrapper({ renderWithRouter: true });
+ return {children};
};
-// Mock the legacy API client
-jest.mock('app/features/browse-dashboards/api/browseDashboardsAPI', () => {
- const PAGE_SIZE = 10;
- return {
- PAGE_SIZE,
- browseDashboardsAPI: {
- endpoints: {
- listFolders: {
- select: jest.fn(() => () => legacyResponse),
- initiate: jest.fn(() => ({
- arg: { parentUid: undefined, page: 1, limit: PAGE_SIZE, permission: 'Edit' },
- unsubscribe: jest.fn(),
- })),
- },
- },
- },
- };
-});
-
-const appPlatfromResponse = {
- status: 'fulfilled',
- originalArgs: { name: 'general' },
- data: {
- items: [
- {
- metadata: { name: 'app1', annotations: {} },
- spec: { title: 'AppPlatform Folder' },
- },
- ],
- },
-};
-
-// Mock the appPlatform API client
-jest.mock('app/api/clients/folder/v1beta1', () => ({
- folderAPIv1beta1: {
- endpoints: {
- getFolderChildren: {
- select: jest.fn(() => () => appPlatfromResponse),
- initiate: jest.fn((arg: unknown) => ({
- arg,
- unsubscribe: jest.fn(),
- })),
- },
- },
- },
-}));
-
-// Mock getPaginationPlaceholders to return empty array for simplicity
-jest.mock('app/features/browse-dashboards/state/utils', () => ({
- getPaginationPlaceholders: jest.fn((): DashboardsTreeItem[] => []),
-}));
-
-// Mock useDispatch and useSelector to just pass through
-jest.mock('app/types/store', () => {
- const mod = jest.requireActual('app/types/store');
- return {
- ...mod,
- useDispatch: () => (val: unknown) => val,
- useSelector: (selector: Function) => selector(),
- };
-});
describe('useFoldersQuery', () => {
let configBackup: GrafanaConfig;
@@ -89,28 +33,40 @@ describe('useFoldersQuery', () => {
runtime.config.featureToggles = configBackup.featureToggles;
});
- it('returns data using legacy api', () => {
- runtime.config.featureToggles.foldersAppPlatformAPI = false;
- const items = testFn();
- expect((items[1].item as DashboardViewItem).title).toBe('Legacy Folder');
- });
+ describe.each([
+ // foldersAppPlatformAPI enabled
+ true,
+ // foldersAppPlatformAPI disabled
+ false,
+ ])('foldersAppPlatformAPI feature toggle set to %s', (featureToggleState) => {
+ it('returns data using legacy api', async () => {
+ runtime.config.featureToggles.foldersAppPlatformAPI = featureToggleState;
+ const [_dashboardsContainer, ...items] = await testFn();
- it('returns appPlatform hook result when foldersAppPlatformAPI is on', () => {
- runtime.config.featureToggles.foldersAppPlatformAPI = true;
- const items = testFn();
- expect((items[1].item as DashboardViewItem).title).toBe('AppPlatform Folder');
+ const sortedItemTitles = items.map((item) => (item.item as DashboardViewItem).title).sort();
+ const expectedTitles = [folderA.item.title, folderB.item.title, folderC.item.title].sort();
+
+ expect(sortedItemTitles).toEqual(expectedTitles);
+ });
});
});
-function testFn() {
- const { result } = renderHook(() => useFoldersQuery(true, {}));
+async function testFn() {
+ const { result } = renderHook(() => useFoldersQuery(true, {}), { wrapper });
- expect(result.current.items).toEqual([getRootFolderItem()]);
+ expect(result.current.items[0]).toEqual(getRootFolderItem());
expect(result.current.isLoading).toBe(false);
+
act(() => {
result.current.requestNextPage(undefined);
});
- expect(result.current.items.length).toBe(2);
+ expect(result.current.isLoading).toBe(true);
+
+ await waitFor(() => {
+ const withoutPaginationPlaceholders = result.current.items.filter((item) => item.item.kind !== 'ui');
+ return expect(withoutPaginationPlaceholders.length).toBeGreaterThan(1);
+ });
+
return result.current.items;
}
diff --git a/public/app/core/components/NestedFolderPicker/useFoldersQueryAppPlatform.ts b/public/app/core/components/NestedFolderPicker/useFoldersQueryAppPlatform.ts
index 4828417c10c..b877c69aba0 100644
--- a/public/app/core/components/NestedFolderPicker/useFoldersQueryAppPlatform.ts
+++ b/public/app/core/components/NestedFolderPicker/useFoldersQueryAppPlatform.ts
@@ -2,7 +2,7 @@ import { createSelector } from '@reduxjs/toolkit';
import { QueryStatus } from '@reduxjs/toolkit/query';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
-import { folderAPIv1beta1 } from 'app/api/clients/folder/v1beta1';
+import { dashboardAPIv0alpha1 } from 'app/api/clients/dashboard/v0alpha1';
import { DashboardViewItemWithUIItems, DashboardsTreeItem } from 'app/features/browse-dashboards/types';
import { useDispatch, useSelector } from 'app/types/store';
@@ -12,7 +12,7 @@ import { getPaginationPlaceholders } from '../../../features/browse-dashboards/s
import { getRootFolderItem } from './utils';
-type GetFolderChildrenQuery = ReturnType>;
+type GetFolderChildrenQuery = ReturnType>;
type GetFolderChildrenRequest = {
unsubscribe: () => void;
};
@@ -32,9 +32,9 @@ export function useFoldersQueryAppPlatform(isBrowsing: boolean, openFolders: Rec
const requestsRef = useRef([]);
// Keep a list of selectors for dynamic state selection
- const [selectors, setSelectors] = useState<
- Array>
- >([]);
+ const [selectors, setSelectors] = useState>>(
+ []
+ );
// This is an aggregated dynamic selector of all the selectors for all the request issued while loading the folder
// tree and returns the whole tree that was loaded so far.
@@ -50,7 +50,7 @@ export function useFoldersQueryAppPlatform(isBrowsing: boolean, openFolders: Rec
isLoading = true;
}
- const parentName = response.originalArgs?.name;
+ const parentName = response.originalArgs?.folder;
if (parentName) {
responseByParent[parentName] = response;
}
@@ -77,13 +77,13 @@ export function useFoldersQueryAppPlatform(isBrowsing: boolean, openFolders: Rec
return;
}
- const args = { name: finalParentUid };
+ const args = { folder: finalParentUid, type: 'folder' };
// Make a request
- const subscription = dispatch(folderAPIv1beta1.endpoints.getFolderChildren.initiate(args));
+ const subscription = dispatch(dashboardAPIv0alpha1.endpoints.getSearch.initiate(args));
// Add selector for the response to the list so we can then have an aggregated selector for all the folders
- const selector = folderAPIv1beta1.endpoints.getFolderChildren.select(args);
+ const selector = dashboardAPIv0alpha1.endpoints.getSearch.select(args);
setSelectors((selectors) => selectors.concat(selector));
// the subscriptions are saved in a ref so they can be unsubscribed on unmount
@@ -113,18 +113,18 @@ export function useFoldersQueryAppPlatform(isBrowsing: boolean, openFolders: Rec
response: GetFolderChildrenQuery | undefined,
level: number
): Array> {
- let folders = response?.data?.items ? [...response.data.items] : [];
- folders.sort((a, b) => collator.compare(a.spec.title, b.spec.title));
+ let folders = response?.data?.hits ? [...response.data.hits] : [];
+ folders.sort((a, b) => collator.compare(a.title, b.title));
const list = folders.flatMap((item) => {
- const name = item.metadata.name!;
+ const name = item.name;
const folderIsOpen = openFolders[name];
const flatItem: DashboardsTreeItem = {
isOpen: Boolean(folderIsOpen),
level: level,
item: {
kind: 'folder' as const,
- title: item.spec.title,
+ title: item.title,
// We use resource name as UID because well, not sure what metadata.uid would be used for now as you cannot
// query by it.
uid: name,
diff --git a/public/app/core/reducers/root.ts b/public/app/core/reducers/root.ts
index 67078fdbd27..bc296ee14b6 100644
--- a/public/app/core/reducers/root.ts
+++ b/public/app/core/reducers/root.ts
@@ -2,6 +2,7 @@ import { ReducersMapObject } from '@reduxjs/toolkit';
import { AnyAction, combineReducers } from 'redux';
import { alertingAPI as alertingPackageAPI } from '@grafana/alerting/unstable';
+import { dashboardAPIv0alpha1 } from 'app/api/clients/dashboard/v0alpha1';
import sharedReducers from 'app/core/reducers';
import ldapReducers from 'app/features/admin/state/reducers';
import alertingReducers from 'app/features/alerting/state/reducers';
@@ -71,6 +72,7 @@ const rootReducers = {
[provisioningAPIv0alpha1.reducerPath]: provisioningAPIv0alpha1.reducer,
[folderAPIv1beta1.reducerPath]: folderAPIv1beta1.reducer,
[advisorAPIv0alpha1.reducerPath]: advisorAPIv0alpha1.reducer,
+ [dashboardAPIv0alpha1.reducerPath]: dashboardAPIv0alpha1.reducer,
// PLOP_INJECT_REDUCER
// Used by the API client generator
};
diff --git a/public/app/features/browse-dashboards/BrowseDashboardsPage.test.tsx b/public/app/features/browse-dashboards/BrowseDashboardsPage.test.tsx
index 4a2c9f4a82b..a9a0dda6274 100644
--- a/public/app/features/browse-dashboards/BrowseDashboardsPage.test.tsx
+++ b/public/app/features/browse-dashboards/BrowseDashboardsPage.test.tsx
@@ -1,7 +1,6 @@
import { render as rtlRender, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { HttpResponse, http } from 'msw';
-import { setupServer, SetupServer } from 'msw/node';
import { ComponentProps } from 'react';
import * as React from 'react';
import { useParams } from 'react-router-dom-v5-compat';
@@ -9,13 +8,16 @@ import AutoSizer from 'react-virtualized-auto-sizer';
import { TestProvider } from 'test/helpers/TestProvider';
import { selectors } from '@grafana/e2e-selectors';
+import server, { setupMockServer } from '@grafana/test-utils/server';
+import { getFolderFixtures } from '@grafana/test-utils/unstable';
import { contextSrv } from 'app/core/core';
import { backendSrv } from 'app/core/services/backend_srv';
import BrowseDashboardsPage from './BrowseDashboardsPage';
-import { wellFormedTree } from './fixtures/dashboardsTreeItem.fixture';
import * as permissions from './permissions';
-const [mockTree, { dashbdD, folderA, folderA_folderA }] = wellFormedTree();
+
+setupMockServer();
+const [mockTree, { dashbdD, folderA, folderA_folderA }] = getFolderFixtures();
jest.mock('@grafana/runtime', () => ({
...jest.requireActual('@grafana/runtime'),
@@ -111,7 +113,6 @@ jest.mock('app/features/browse-dashboards/api/services', () => {
});
describe('browse-dashboards BrowseDashboardsPage', () => {
- let server: SetupServer;
const mockPermissions = {
canCreateDashboards: true,
canEditDashboards: true,
@@ -123,33 +124,14 @@ describe('browse-dashboards BrowseDashboardsPage', () => {
canDeleteDashboards: true,
};
- beforeAll(() => {
- server = setupServer(
- http.get('/api/folders/:uid', () => {
- return HttpResponse.json({
- title: folderA.item.title,
- uid: folderA.item.uid,
- });
- }),
- http.get('/api/search', () => {
- return HttpResponse.json({});
- }),
+ beforeEach(() => {
+ server.use(
http.get('/api/search/sorting', () => {
return HttpResponse.json({
sortOptions: [],
});
- }),
- http.get('/apis/provisioning.grafana.app/v0alpha1/namespaces/default/settings', () => {
- return HttpResponse.json({
- items: [],
- });
})
);
- server.listen();
- });
-
- afterAll(() => {
- server.close();
});
beforeEach(() => {
@@ -170,7 +152,6 @@ describe('browse-dashboards BrowseDashboardsPage', () => {
canDeleteDashboards: true,
});
jest.restoreAllMocks();
- server.resetHandlers();
});
describe('at the root level', () => {
diff --git a/public/app/features/browse-dashboards/BrowseFolderLibraryPanelsPage.test.tsx b/public/app/features/browse-dashboards/BrowseFolderLibraryPanelsPage.test.tsx
index c60a8a8de56..9b96316add6 100644
--- a/public/app/features/browse-dashboards/BrowseFolderLibraryPanelsPage.test.tsx
+++ b/public/app/features/browse-dashboards/BrowseFolderLibraryPanelsPage.test.tsx
@@ -1,9 +1,9 @@
-import { render as rtlRender, screen } from '@testing-library/react';
import { http, HttpResponse } from 'msw';
-import { SetupServer, setupServer } from 'msw/node';
import { useParams } from 'react-router-dom-v5-compat';
-import { TestProvider } from 'test/helpers/TestProvider';
+import { render, screen } from 'test/test-utils';
+import server, { setupMockServer } from '@grafana/test-utils/server';
+import { getFolderFixtures } from '@grafana/test-utils/unstable';
import { contextSrv } from 'app/core/core';
import { backendSrv } from 'app/core/services/backend_srv';
@@ -11,10 +11,7 @@ import BrowseFolderLibraryPanelsPage from './BrowseFolderLibraryPanelsPage';
import { getLibraryElementsResponse } from './fixtures/libraryElements.fixture';
import * as permissions from './permissions';
-function render(...[ui, options]: Parameters) {
- rtlRender({ui}, options);
-}
-
+setupMockServer();
jest.mock('@grafana/runtime', () => ({
...jest.requireActual('@grafana/runtime'),
getBackendSrv: () => backendSrv,
@@ -28,15 +25,15 @@ jest.mock('react-router-dom-v5-compat', () => ({
useParams: jest.fn(),
}));
-const mockFolderName = 'myFolder';
-const mockFolderUid = '12345';
+const [_, { folderA }] = getFolderFixtures();
+const mockFolderName = folderA.item.title;
+const mockFolderUid = folderA.item.uid;
const mockLibraryElementsResponse = getLibraryElementsResponse(1, {
folderUid: mockFolderUid,
});
describe('browse-dashboards BrowseFolderLibraryPanelsPage', () => {
(useParams as jest.Mock).mockReturnValue({ uid: mockFolderUid });
- let server: SetupServer;
const mockPermissions = {
canCreateDashboards: true,
canEditDashboards: true,
@@ -48,14 +45,8 @@ describe('browse-dashboards BrowseFolderLibraryPanelsPage', () => {
canDeleteDashboards: true,
};
- beforeAll(() => {
- server = setupServer(
- http.get('/api/folders/:uid', () => {
- return HttpResponse.json({
- title: mockFolderName,
- uid: mockFolderUid,
- });
- }),
+ beforeEach(() => {
+ server.use(
http.get('/api/library-elements', () => {
return HttpResponse.json({
result: mockLibraryElementsResponse,
@@ -65,11 +56,6 @@ describe('browse-dashboards BrowseFolderLibraryPanelsPage', () => {
return HttpResponse.json({});
})
);
- server.listen();
- });
-
- afterAll(() => {
- server.close();
});
beforeEach(() => {
@@ -79,7 +65,6 @@ describe('browse-dashboards BrowseFolderLibraryPanelsPage', () => {
afterEach(() => {
jest.restoreAllMocks();
- server.resetHandlers();
});
it('displays the folder title', async () => {
diff --git a/public/app/features/browse-dashboards/components/BrowseActions/MoveModal.test.tsx b/public/app/features/browse-dashboards/components/BrowseActions/MoveModal.test.tsx
index fb1a160d8b1..c941cd76150 100644
--- a/public/app/features/browse-dashboards/components/BrowseActions/MoveModal.test.tsx
+++ b/public/app/features/browse-dashboards/components/BrowseActions/MoveModal.test.tsx
@@ -1,38 +1,26 @@
-import userEvent from '@testing-library/user-event';
import { HttpResponse, http } from 'msw';
-import { SetupServer, setupServer } from 'msw/node';
import { render, screen } from 'test/test-utils';
+import { setBackendSrv } from '@grafana/runtime';
+import server, { setupMockServer } from '@grafana/test-utils/server';
+import { getFolderFixtures } from '@grafana/test-utils/unstable';
import { backendSrv } from 'app/core/services/backend_srv';
-import { treeViewersCanEdit, wellFormedTree } from '../../fixtures/dashboardsTreeItem.fixture';
-
import { MoveModal, Props } from './MoveModal';
-const [mockTree, { folderA }] = wellFormedTree();
-const [mockTreeThatViewersCanEdit /* shares folders with wellFormedTree */] = treeViewersCanEdit();
+const [_, { folderA }] = getFolderFixtures();
-jest.mock('@grafana/runtime', () => ({
- ...jest.requireActual('@grafana/runtime'),
- getBackendSrv: () => backendSrv,
-}));
+setBackendSrv(backendSrv);
+setupMockServer();
describe('browse-dashboards MoveModal', () => {
const mockOnDismiss = jest.fn();
const mockOnConfirm = jest.fn();
let props: Props;
- let server: SetupServer;
window.HTMLElement.prototype.scrollIntoView = () => {};
- beforeAll(() => {
- server = setupServer(
- http.get('/api/folders/:uid', () => {
- return HttpResponse.json({
- title: folderA.item.title,
- uid: folderA.item.uid,
- });
- }),
-
+ beforeEach(() => {
+ server.use(
http.get('/api/folders/:uid/counts', () => {
return HttpResponse.json({
folder: 1,
@@ -40,43 +28,9 @@ describe('browse-dashboards MoveModal', () => {
librarypanel: 3,
alertrule: 4,
});
- }),
-
- http.get('/apis/provisioning.grafana.app/v0alpha1/namespaces/default/settings', () => {
- return HttpResponse.json({
- items: [],
- });
- }),
-
- http.get('/api/folders', ({ request }) => {
- const url = new URL(request.url);
- const parentUid = url.searchParams.get('parentUid') ?? undefined;
- const permission = url.searchParams.get('permission');
-
- const limit = parseInt(url.searchParams.get('limit') ?? '1000', 10);
- const page = parseInt(url.searchParams.get('page') ?? '1', 10);
-
- const tree = permission === 'Edit' ? mockTreeThatViewersCanEdit : mockTree;
-
- // reconstruct a folder API response from the flat tree fixture
- const folders = tree
- .filter((v) => v.item.kind === 'folder' && v.item.parentUID === parentUid)
- .map((folder) => {
- return {
- uid: folder.item.uid,
- title: folder.item.kind === 'folder' ? folder.item.title : "invalid - this shouldn't happen",
- };
- })
- .slice(limit * (page - 1), limit * page);
-
- return HttpResponse.json(folders);
})
);
- server.listen();
- });
-
- beforeEach(() => {
props = {
isOpen: true,
onConfirm: mockOnConfirm,
@@ -90,10 +44,6 @@ describe('browse-dashboards MoveModal', () => {
};
});
- afterAll(() => {
- server.close();
- });
-
it('renders a dialog with the correct title', async () => {
render();
@@ -130,36 +80,36 @@ describe('browse-dashboards MoveModal', () => {
});
it('enables the `Move` button once a folder is selected', async () => {
- render();
+ const { user } = render();
expect(await screen.findByRole('button', { name: 'Move' })).toBeDisabled();
// Open the picker and wait for children to load
const folderPicker = await screen.findByRole('button', { name: 'Select folder' });
- await userEvent.click(folderPicker);
+ await user.click(folderPicker);
await screen.findByLabelText(folderA.item.title);
// Select the folder
- await userEvent.click(screen.getByLabelText(folderA.item.title));
+ await user.click(screen.getByLabelText(folderA.item.title));
const moveButton = await screen.findByRole('button', { name: 'Move' });
expect(moveButton).toBeEnabled();
- await userEvent.click(moveButton);
+ await user.click(moveButton);
expect(mockOnConfirm).toHaveBeenCalledWith(folderA.item.uid);
});
it('calls onDismiss when clicking the `Cancel` button', async () => {
- render();
+ const { user } = render();
- await userEvent.click(await screen.findByRole('button', { name: 'Cancel' }));
+ await user.click(await screen.findByRole('button', { name: 'Cancel' }));
expect(mockOnDismiss).toHaveBeenCalled();
});
it('calls onDismiss when clicking the X', async () => {
- render();
+ const { user } = render();
- await userEvent.click(await screen.findByRole('button', { name: 'Close' }));
+ await user.click(await screen.findByRole('button', { name: 'Close' }));
expect(mockOnDismiss).toHaveBeenCalled();
});
});
diff --git a/public/app/features/browse-dashboards/components/BrowseView.test.tsx b/public/app/features/browse-dashboards/components/BrowseView.test.tsx
index 1a19d2b5b61..929f5bbf53c 100644
--- a/public/app/features/browse-dashboards/components/BrowseView.test.tsx
+++ b/public/app/features/browse-dashboards/components/BrowseView.test.tsx
@@ -3,14 +3,13 @@ import userEvent from '@testing-library/user-event';
import { TestProvider } from 'test/helpers/TestProvider';
import { selectors } from '@grafana/e2e-selectors';
+import { getFolderFixtures } from '@grafana/test-utils/unstable';
import { DashboardViewItem } from 'app/features/search/types';
-import { wellFormedTree } from '../fixtures/dashboardsTreeItem.fixture';
-
import { BrowseView } from './BrowseView';
const [mockTree, { folderA, folderA_folderA, folderA_folderB, folderA_folderB_dashbdB, dashbdD, folderB_empty }] =
- wellFormedTree();
+ getFolderFixtures();
function render(...[ui, options]: Parameters) {
rtlRender({ui}, options);
diff --git a/public/app/features/browse-dashboards/fixtures/dashboardsTreeItem.fixture.ts b/public/app/features/browse-dashboards/fixtures/dashboardsTreeItem.fixture.ts
index 4ac6515c726..7b5d17a2825 100644
--- a/public/app/features/browse-dashboards/fixtures/dashboardsTreeItem.fixture.ts
+++ b/public/app/features/browse-dashboards/fixtures/dashboardsTreeItem.fixture.ts
@@ -1,5 +1,6 @@
import { Chance } from 'chance';
+import { getFolderFixtures } from '@grafana/test-utils/unstable';
import { DashboardViewItem } from 'app/features/search/types';
import { DashboardsTreeItem, UIDashboardViewItem } from '../types';
@@ -74,7 +75,7 @@ export function sharedWithMeFolder(seed = 1): DashboardsTreeItem) {
provisioningAPIv0alpha1.middleware,
folderAPIv1beta1.middleware,
advisorAPIv0alpha1.middleware,
+ dashboardAPIv0alpha1.middleware,
// PLOP_INJECT_MIDDLEWARE
// Used by the API client generator
...extraMiddleware
diff --git a/scripts/generate-rtk-apis.ts b/scripts/generate-rtk-apis.ts
index 792594a91e1..2082e222156 100644
--- a/scripts/generate-rtk-apis.ts
+++ b/scripts/generate-rtk-apis.ts
@@ -79,6 +79,17 @@ const config: ConfigFile = {
filterEndpoints: ['listPlaylist', 'getPlaylist', 'createPlaylist', 'deletePlaylist', 'replacePlaylist'],
tag: true,
},
+ '../public/app/api/clients/dashboard/v0alpha1/endpoints.gen.ts': {
+ apiFile: '../public/app/api/clients/dashboard/v0alpha1/baseAPI.ts',
+ schemaFile: '../data/openapi/dashboard.grafana.app-v0alpha1.json',
+ filterEndpoints: [
+ // Do not use any other endpoints from this version
+ // If other endpoints are required, they must be used from a newer version of the dashboard API
+ 'getSearch',
+ ],
+ tag: true,
+ },
+
// PLOP_INJECT_API_CLIENT - Used by the API client generator
},
};
From 491c74b6886f9dc57b48fdcf3423b7bf9f13b539 Mon Sep 17 00:00:00 2001
From: Matheus Macabu
Date: Fri, 1 Aug 2025 14:45:44 +0200
Subject: [PATCH 21/89] Secrets: Add inline secure value delete when owned
method (#108993)
---
.../secret/service/inline_secure_value.go | 37 +++-
.../service/inline_secure_value_test.go | 163 ++++++++++++++++++
2 files changed, 199 insertions(+), 1 deletion(-)
diff --git a/pkg/registry/apis/secret/service/inline_secure_value.go b/pkg/registry/apis/secret/service/inline_secure_value.go
index 9e0cb9fce41..665bc622f54 100644
--- a/pkg/registry/apis/secret/service/inline_secure_value.go
+++ b/pkg/registry/apis/secret/service/inline_secure_value.go
@@ -217,5 +217,40 @@ func (s *inlineSecureValueService) CreateInline(ctx context.Context, owner commo
}
func (s *inlineSecureValueService) DeleteWhenOwnedByResource(ctx context.Context, owner common.ObjectReference, name string) error {
- return fmt.Errorf("not implemented yet")
+ ctx, span := s.tracer.Start(ctx, "InlineSecureValueService.DeleteWhenOwnedByResource", trace.WithAttributes(
+ attribute.String("owner.namespace", owner.Namespace),
+ attribute.String("owner.apiGroup", owner.APIGroup),
+ attribute.String("owner.apiVersion", owner.APIVersion),
+ attribute.String("owner.kind", owner.Kind),
+ attribute.String("owner.name", owner.Name),
+ attribute.String("secureValue.name", name),
+ ))
+ defer span.End()
+
+ authInfo, ok := authlib.AuthInfoFrom(ctx)
+ if !ok {
+ return fmt.Errorf("missing auth info in context")
+ }
+
+ if owner.Namespace == "" || !authlib.NamespaceMatches(authInfo.GetNamespace(), owner.Namespace) {
+ return fmt.Errorf("owner namespace %s does not match auth info namespace %s", owner.Namespace, authInfo.GetNamespace())
+ }
+
+ if owner.APIGroup == "" || owner.APIVersion == "" || owner.Kind == "" || owner.Name == "" {
+ return fmt.Errorf("owner reference must have a valid API group, API version, kind and name")
+ }
+
+ owned, err := s.isSecureValueOwnedByResource(ctx, owner, name)
+ if err != nil {
+ return fmt.Errorf("error checking if secure value %s is owned by %v: %w", name, owner, err)
+ }
+
+ if owned {
+ if _, err := s.secureValueService.Delete(ctx, xkube.Namespace(owner.Namespace), name); err != nil {
+ return fmt.Errorf("error deleting secure value %s for owner %v: %w", name, owner, err)
+ }
+ }
+
+ // if it is not owned, this is a no-op
+ return nil
}
diff --git a/pkg/registry/apis/secret/service/inline_secure_value_test.go b/pkg/registry/apis/secret/service/inline_secure_value_test.go
index d108ce8f047..3c8742bffb3 100644
--- a/pkg/registry/apis/secret/service/inline_secure_value_test.go
+++ b/pkg/registry/apis/secret/service/inline_secure_value_test.go
@@ -5,8 +5,10 @@ import (
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
"github.com/grafana/grafana/pkg/apimachinery/identity"
+ "github.com/grafana/grafana/pkg/registry/apis/secret/contracts"
"github.com/grafana/grafana/pkg/registry/apis/secret/service"
"github.com/grafana/grafana/pkg/registry/apis/secret/testutils"
+ "github.com/grafana/grafana/pkg/registry/apis/secret/xkube"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/trace/noop"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -347,3 +349,164 @@ func TestIntegration_InlineSecureValue_CreateInline(t *testing.T) {
require.Error(t, err)
})
}
+
+func TestIntegration_InlineSecureValue_DeleteWhenOwnedByResource(t *testing.T) {
+ t.Parallel()
+
+ tracer := noop.NewTracerProvider().Tracer("test")
+
+ defaultNs := "org-1234"
+ owner := common.ObjectReference{
+ APIGroup: "prometheus.datasource.grafana.app",
+ APIVersion: "v1alpha1",
+ Kind: "DataSourceConfig",
+ Name: "test-datasource",
+ Namespace: defaultNs,
+ }
+
+ t.Run("happy path deletes an owned secure value", func(t *testing.T) {
+ t.Parallel()
+
+ tu := testutils.Setup(t)
+
+ sv1 := "test-secure-value-1"
+ createdSv1, err := tu.CreateSv(t.Context(), func(cfg *testutils.CreateSvConfig) {
+ cfg.Sv.Name = sv1
+ cfg.Sv.Namespace = defaultNs
+ cfg.Sv.OwnerReferences = []metav1.OwnerReference{owner.ToOwnerReference()}
+ })
+ require.NoError(t, err)
+ require.NotNil(t, createdSv1)
+
+ svc := service.ProvideInlineSecureValueService(tracer, tu.SecureValueService, nil)
+
+ ctx := testutils.CreateServiceAuthContext(t.Context(), "", defaultNs, nil)
+
+ err = svc.DeleteWhenOwnedByResource(ctx, owner, sv1)
+ require.NoError(t, err)
+
+ // make sure it got deleted
+ sv, err := tu.SecureValueService.Read(ctx, xkube.Namespace(owner.Namespace), sv1)
+ require.ErrorIs(t, err, contracts.ErrSecureValueNotFound)
+ require.Nil(t, sv)
+ })
+
+ t.Run("when the auth info is missing it returns an error", func(t *testing.T) {
+ t.Parallel()
+
+ svc := service.ProvideInlineSecureValueService(tracer, nil, nil)
+ err := svc.DeleteWhenOwnedByResource(t.Context(), common.ObjectReference{}, "")
+ require.Error(t, err)
+ })
+
+ t.Run("when the owner namespace does not match auth info namespace it returns an error", func(t *testing.T) {
+ t.Parallel()
+
+ svc := service.ProvideInlineSecureValueService(tracer, nil, nil)
+
+ reqNs := "org-2345"
+ ctx := testutils.CreateUserAuthContext(t.Context(), reqNs, map[string][]string{})
+
+ err := svc.DeleteWhenOwnedByResource(ctx, owner, "")
+ require.Error(t, err)
+ })
+
+ t.Run("when the owner namespace is empty it returns an error", func(t *testing.T) {
+ t.Parallel()
+
+ svc := service.ProvideInlineSecureValueService(tracer, nil, nil)
+
+ ctx := testutils.CreateUserAuthContext(t.Context(), defaultNs, map[string][]string{})
+
+ err := svc.DeleteWhenOwnedByResource(ctx, common.ObjectReference{}, "")
+ require.Error(t, err)
+ })
+
+ t.Run("when the owner reference has empty fields it returns an error", func(t *testing.T) {
+ t.Parallel()
+
+ svc := service.ProvideInlineSecureValueService(tracer, nil, nil)
+
+ owner := common.ObjectReference{
+ Namespace: defaultNs,
+ }
+
+ createAuthCtx := testutils.CreateOBOAuthContext(t.Context(), "service-identity", defaultNs, nil, nil)
+
+ require.Error(t, svc.DeleteWhenOwnedByResource(createAuthCtx, owner, ""))
+
+ owner.APIGroup = "prometheus.datasource.grafana.app"
+ require.Error(t, svc.DeleteWhenOwnedByResource(createAuthCtx, owner, ""))
+
+ owner.APIVersion = "v1alpha1"
+ require.Error(t, svc.DeleteWhenOwnedByResource(createAuthCtx, owner, ""))
+
+ owner.Kind = "DataSourceConfig"
+ require.Error(t, svc.DeleteWhenOwnedByResource(createAuthCtx, owner, ""))
+ owner.Kind = ""
+
+ owner.Name = "test-datasource"
+ require.Error(t, svc.DeleteWhenOwnedByResource(createAuthCtx, owner, ""))
+ })
+
+ t.Run("when the secure value exists but the owner does not match, it returns an error", func(t *testing.T) {
+ t.Parallel()
+
+ tu := testutils.Setup(t)
+
+ sv1 := "test-secure-value-1"
+ createdSv1, err := tu.CreateSv(t.Context(), func(cfg *testutils.CreateSvConfig) {
+ cfg.Sv.Name = sv1
+ cfg.Sv.Namespace = defaultNs
+ cfg.Sv.OwnerReferences = []metav1.OwnerReference{
+ {
+ APIVersion: "another.example.com/v0alpha1",
+ Kind: "another-kind",
+ Name: "another-name",
+ },
+ }
+ })
+ require.NoError(t, err)
+ require.NotNil(t, createdSv1)
+
+ svc := service.ProvideInlineSecureValueService(tracer, tu.SecureValueService, nil)
+
+ ctx := testutils.CreateServiceAuthContext(t.Context(), "", defaultNs, nil)
+
+ err = svc.DeleteWhenOwnedByResource(ctx, owner, sv1)
+ require.Error(t, err)
+
+ // make sure it still exists
+ sv, err := tu.SecureValueService.Read(ctx, xkube.Namespace(owner.Namespace), sv1)
+ require.NoError(t, err)
+ require.NotNil(t, sv)
+ require.Equal(t, sv1, sv.GetName())
+ })
+
+ t.Run("when the secure value exists but it is shared (no owner), it does not return an error (noop)", func(t *testing.T) {
+ t.Parallel()
+
+ tu := testutils.Setup(t)
+
+ sv1 := "test-secure-value-1"
+ createdSv1, err := tu.CreateSv(t.Context(), func(cfg *testutils.CreateSvConfig) {
+ cfg.Sv.Name = sv1
+ cfg.Sv.Namespace = defaultNs
+ })
+ require.NoError(t, err)
+ require.NotNil(t, createdSv1)
+
+ svc := service.ProvideInlineSecureValueService(tracer, tu.SecureValueService, nil)
+
+ ctx := testutils.CreateServiceAuthContext(t.Context(), "", defaultNs, nil)
+
+ err = svc.DeleteWhenOwnedByResource(ctx, owner, sv1)
+ require.NoError(t, err)
+
+ // make sure it still exists
+ sv, err := tu.SecureValueService.Read(ctx, xkube.Namespace(owner.Namespace), sv1)
+ require.NoError(t, err)
+ require.NotNil(t, sv)
+ require.Equal(t, sv1, sv.GetName())
+ })
+}
From 7b5288c28ac89523c68cfc5d8c521754aea8af21 Mon Sep 17 00:00:00 2001
From: Gareth
Date: Fri, 1 Aug 2025 13:56:31 +0100
Subject: [PATCH 22/89] Fix: Preserve Jaeger base path when constructing search
request (#109045)
* fix url construction
* consistency
* update error response
* error source
* add test case
---
pkg/tsdb/jaeger/client.go | 12 ++++++++----
pkg/tsdb/jaeger/client_test.go | 18 ++++++++++++++++++
2 files changed, 26 insertions(+), 4 deletions(-)
diff --git a/pkg/tsdb/jaeger/client.go b/pkg/tsdb/jaeger/client.go
index 653c0463892..d858aa48f56 100644
--- a/pkg/tsdb/jaeger/client.go
+++ b/pkg/tsdb/jaeger/client.go
@@ -115,11 +115,15 @@ func (j *JaegerClient) Operations(s string) ([]string, error) {
}
func (j *JaegerClient) Search(query *JaegerQuery, start, end int64) ([]TraceResponse, error) {
- jaegerURL, err := url.Parse(j.url)
+ u, err := url.JoinPath(j.url, "/api/traces")
if err != nil {
- return []TraceResponse{}, fmt.Errorf("failed to parse Jaeger URL: %w", err)
+ return []TraceResponse{}, backend.DownstreamError(fmt.Errorf("failed to join url path: %w", err))
+ }
+
+ jaegerURL, err := url.Parse(u)
+ if err != nil {
+ return []TraceResponse{}, backend.DownstreamError(fmt.Errorf("failed to parse Jaeger URL: %w", err))
}
- jaegerURL.Path = "/api/traces"
var queryTags string
if query.Tags != "" {
@@ -135,7 +139,7 @@ func (j *JaegerClient) Search(query *JaegerQuery, start, end int64) ([]TraceResp
marshaledTags, err := json.Marshal(tagMap)
if err != nil {
- return []TraceResponse{}, fmt.Errorf("failed to convert tags to JSON: %w", err)
+ return []TraceResponse{}, backend.DownstreamError(fmt.Errorf("failed to convert tags to JSON: %w", err))
}
queryTags = string(marshaledTags)
diff --git a/pkg/tsdb/jaeger/client_test.go b/pkg/tsdb/jaeger/client_test.go
index afabdc4bccc..eceb103ab82 100644
--- a/pkg/tsdb/jaeger/client_test.go
+++ b/pkg/tsdb/jaeger/client_test.go
@@ -186,6 +186,19 @@ func TestJaegerClient_Search(t *testing.T) {
expectError bool
expectedError error
}{
+ {
+ name: "Preserves base path in Jaeger URL",
+ query: &JaegerQuery{
+ Service: "test-service",
+ },
+ start: 1735689600000000,
+ end: 1738368000000000,
+ mockResponse: `{"data":[{"traceID":"test-trace-id"}]}`,
+ mockStatusCode: http.StatusOK,
+ expectedURL: "/abc/api/traces?end=1738368000000000&service=test-service&start=1735689600000000",
+ expectError: false,
+ expectedError: nil,
+ },
{
name: "Successful search with all parameters",
query: &JaegerQuery{
@@ -245,6 +258,11 @@ func TestJaegerClient_Search(t *testing.T) {
settings := backend.DataSourceInstanceSettings{
URL: server.URL,
}
+
+ if tt.name == "Preserves base path in Jaeger URL" {
+ settings.URL = server.URL + "/abc"
+ }
+
client, err := New(server.Client(), log.NewNullLogger(), settings)
assert.NoError(t, err)
traces, err := client.Search(tt.query, tt.start, tt.end)
From b1cdd45ca4b258a57ddb3a23266e501cc8704425 Mon Sep 17 00:00:00 2001
From: Konrad Lalik
Date: Fri, 1 Aug 2025 15:36:46 +0200
Subject: [PATCH 23/89] Alerting: List V2 - datasource icons for rules
(#109033)
Add datasource icons to the rule list item components
---
.../unified/rule-list/GrafanaRuleListItem.tsx | 1 +
.../components/AlertRuleListItem.tsx | 72 +++++++++++++++++--
.../unified/rule-list/components/ListItem.tsx | 4 +-
3 files changed, 71 insertions(+), 6 deletions(-)
diff --git a/public/app/features/alerting/unified/rule-list/GrafanaRuleListItem.tsx b/public/app/features/alerting/unified/rule-list/GrafanaRuleListItem.tsx
index 5667069082b..ed90116bcaa 100644
--- a/public/app/features/alerting/unified/rule-list/GrafanaRuleListItem.tsx
+++ b/public/app/features/alerting/unified/rule-list/GrafanaRuleListItem.tsx
@@ -53,6 +53,7 @@ export function GrafanaRuleListItem({
isPaused: rule?.isPaused,
application: 'grafana' as const,
actions: ,
+ querySourceUIDs: rule?.queriedDatasourceUIDs,
};
if (prometheusRuleType.grafana.alertingRule(rule)) {
diff --git a/public/app/features/alerting/unified/rule-list/components/AlertRuleListItem.tsx b/public/app/features/alerting/unified/rule-list/components/AlertRuleListItem.tsx
index 5117a8516bd..2694bfc580d 100644
--- a/public/app/features/alerting/unified/rule-list/components/AlertRuleListItem.tsx
+++ b/public/app/features/alerting/unified/rule-list/components/AlertRuleListItem.tsx
@@ -1,8 +1,8 @@
-import { css } from '@emotion/css';
+import { css, cx } from '@emotion/css';
import pluralize from 'pluralize';
-import { ReactNode, useEffect, useId } from 'react';
+import { ReactNode, forwardRef, memo, useEffect, useId } from 'react';
-import { GrafanaTheme2 } from '@grafana/data';
+import { DataSourceInstanceSettings, GrafanaTheme2 } from '@grafana/data';
import { Trans, t } from '@grafana/i18n';
import { Alert, Stack, Text, TextLink, Tooltip, useStyles2 } from '@grafana/ui';
import { Rule, RuleGroupIdentifierV2, RuleHealth, RulesSourceIdentifier } from 'app/types/unified-alerting';
@@ -13,7 +13,7 @@ import { AlertLabels } from '../../components/AlertLabels';
import { MetaText } from '../../components/MetaText';
import { ProvisioningBadge } from '../../components/Provisioning';
import { PluginOriginBadge } from '../../plugins/PluginOriginBadge';
-import { GRAFANA_RULES_SOURCE_NAME } from '../../utils/datasource';
+import { GRAFANA_RULES_SOURCE_NAME, getDataSourceByUid } from '../../utils/datasource';
import { getGroupOriginName } from '../../utils/groupIdentifier';
import { labelsSize } from '../../utils/labels';
import { createContactPointSearchLink } from '../../utils/misc';
@@ -49,6 +49,7 @@ export interface AlertRuleListItemProps {
operation?: RuleOperation;
// the grouped view doesn't need to show the location again – it's redundant
showLocation?: boolean;
+ querySourceUIDs?: string[];
}
export const AlertRuleListItem = (props: AlertRuleListItemProps) => {
@@ -75,6 +76,7 @@ export const AlertRuleListItem = (props: AlertRuleListItemProps) => {
actions = null,
operation,
showLocation = true,
+ querySourceUIDs = [],
} = props;
const listItemAriaId = useId();
@@ -94,6 +96,10 @@ export const AlertRuleListItem = (props: AlertRuleListItemProps) => {
);
}
+ if (querySourceUIDs.length > 0) {
+ metadata.push();
+ }
+
if (!isPaused) {
if (lastEvaluation && evaluationInterval) {
metadata.push(
@@ -179,6 +185,7 @@ export function RecordingRuleListItem({
origin,
actions,
showLocation = true,
+ querySourceUIDs = [],
}: RecordingRuleListItemProps) {
const metadata: ReactNode[] = [];
if (namespace && group && showLocation) {
@@ -195,6 +202,10 @@ export function RecordingRuleListItem({
);
}
+ if (querySourceUIDs.length > 0) {
+ metadata.push();
+ }
+
return (
ds !== undefined);
+
+ return (
+
+ {dataSources.map((dataSource) => {
+ return (
+
+
+
+ );
+ })}
+
+ );
+});
+
function RuleLabels({ labels }: { labels: Labels }) {
const styles = useStyles2(getStyles);
@@ -417,3 +451,33 @@ export type RuleListItemCommonProps = Pick<
AlertRuleListItemProps,
Extract
>;
+
+interface DataSourceLogoProps {
+ dataSource: DataSourceInstanceSettings;
+}
+
+const DataSourceLogo = forwardRef(({ dataSource }, ref) => {
+ const styles = useStyles2(dataSourceLogoStyles);
+
+ return (
+
+ );
+});
+
+const dataSourceLogoStyles = (theme: GrafanaTheme2) => ({
+ logo: css({
+ height: '14px',
+ width: '14px',
+ borderRadius: theme.shape.radius.default,
+ }),
+ filter: css({
+ filter: `invert(${theme.isLight ? 1 : 0})`,
+ }),
+});
diff --git a/public/app/features/alerting/unified/rule-list/components/ListItem.tsx b/public/app/features/alerting/unified/rule-list/components/ListItem.tsx
index ce3ea7f6fb6..0e15e5fb696 100644
--- a/public/app/features/alerting/unified/rule-list/components/ListItem.tsx
+++ b/public/app/features/alerting/unified/rule-list/components/ListItem.tsx
@@ -39,7 +39,7 @@ export const ListItem = (props: ListItemProps) => {
{/* metadata */}
-
+
{meta?.map((item, index) => (
{index > 0 && }
@@ -72,7 +72,7 @@ export const SkeletonListItem = () => {
const Separator = () => (
- {'·'}
+ {'|'}
);
From 772f647210cfe3c4ac95002e21888db9c0ed9f5d Mon Sep 17 00:00:00 2001
From: Serge Zaitsev
Date: Fri, 1 Aug 2025 16:01:13 +0200
Subject: [PATCH 24/89] Chore: Use proper database type from env in testinfra
integration tests (#108845)
use database type from env in testinfra
---
pkg/services/sqlstore/sqlutil/sqlutil.go | 15 ++++++++++
.../apis/provisioning/provisioning_test.go | 29 ++++++++++++-------
pkg/tests/testinfra/testinfra.go | 15 ++++++++++
3 files changed, 49 insertions(+), 10 deletions(-)
diff --git a/pkg/services/sqlstore/sqlutil/sqlutil.go b/pkg/services/sqlstore/sqlutil/sqlutil.go
index 8506acdab79..7d3d09a5ff3 100644
--- a/pkg/services/sqlstore/sqlutil/sqlutil.go
+++ b/pkg/services/sqlstore/sqlutil/sqlutil.go
@@ -22,6 +22,11 @@ type TestDB struct {
DriverName string
ConnStr string
Path string
+ Host string
+ Port string
+ User string
+ Password string
+ Database string
Cleanup func()
}
@@ -132,6 +137,11 @@ func mySQLTestDB() (*TestDB, error) {
return &TestDB{
DriverName: "mysql",
ConnStr: conn_str,
+ Host: host,
+ Port: port,
+ User: "grafana",
+ Password: "password",
+ Database: "grafana_tests",
Cleanup: func() {},
}, nil
}
@@ -149,6 +159,11 @@ func postgresTestDB() (*TestDB, error) {
return &TestDB{
DriverName: "postgres",
ConnStr: connStr,
+ Host: host,
+ Port: port,
+ User: "grafanatest",
+ Password: "grafanatest",
+ Database: "grafanatest",
Cleanup: func() {},
}, nil
}
diff --git a/pkg/tests/apis/provisioning/provisioning_test.go b/pkg/tests/apis/provisioning/provisioning_test.go
index e04406787fe..f9a5c40a14e 100644
--- a/pkg/tests/apis/provisioning/provisioning_test.go
+++ b/pkg/tests/apis/provisioning/provisioning_test.go
@@ -161,14 +161,21 @@ func TestIntegrationProvisioning_CreatingAndGetting(t *testing.T) {
// Viewer can see settings listing
t.Run("viewer has access to list", func(t *testing.T) {
settings := &provisioning.RepositoryViewList{}
- rsp := helper.ViewerREST.Get().
- Namespace("default").
- Suffix("settings").
- Do(context.Background())
- require.NoError(t, rsp.Error())
- err := rsp.Into(settings)
- require.NoError(t, err)
- require.Len(t, settings.Items, len(inputFiles))
+ // Wait for unified storage to make the data available
+ require.Eventually(t, func() bool {
+ rsp := helper.ViewerREST.Get().
+ Namespace("default").
+ Suffix("settings").
+ Do(context.Background())
+ if rsp.Error() != nil {
+ return false
+ }
+ err := rsp.Into(settings)
+ if err != nil {
+ return false
+ }
+ return len(settings.Items) == len(inputFiles)
+ }, time.Second*10, time.Millisecond*100, "Expected settings to have len(inputFiles) items")
// FIXME: this should be an enterprise integration test
if extensions.IsEnterprise {
@@ -1825,8 +1832,10 @@ func TestIntegrationProvisioning_MoveResources(t *testing.T) {
// Verify dashboard still exists in Grafana with same content but may have updated path references
helper.SyncAndWait(t, repo, nil)
- _, err = helper.DashboardsV1.Resource.Get(ctx, allPanelsUID, metav1.GetOptions{})
- require.NoError(t, err, "dashboard should still exist in Grafana after move")
+ require.Eventually(t, func() bool {
+ _, err = helper.DashboardsV1.Resource.Get(ctx, allPanelsUID, metav1.GetOptions{})
+ return err == nil
+ }, 10*time.Second, 100*time.Millisecond, "dashboard should still exist in Grafana after move") // Using Eventually to account for potential delays in dashboards APIs.
})
t.Run("move file to nested path without ref", func(t *testing.T) {
diff --git a/pkg/tests/testinfra/testinfra.go b/pkg/tests/testinfra/testinfra.go
index eb9b460c6b8..56f9f3000e2 100644
--- a/pkg/tests/testinfra/testinfra.go
+++ b/pkg/tests/testinfra/testinfra.go
@@ -13,6 +13,7 @@ import (
"time"
"github.com/grafana/grafana/pkg/services/featuremgmt"
+ "github.com/grafana/grafana/pkg/services/sqlstore/sqlutil"
"github.com/prometheus/client_golang/prometheus"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -85,6 +86,20 @@ func StartGrafanaEnv(t *testing.T, grafDir, cfgPath string) (string, *server.Tes
err = featuremgmt.InitOpenFeatureWithCfg(cfg)
require.NoError(t, err)
+
+ // Use proper database type based on the environment variable GRAFANA_TEST_DB in tests
+ testDB, err := sqlutil.GetTestDB(sqlutil.GetTestDBType())
+ require.NoError(t, err)
+ t.Cleanup(testDB.Cleanup)
+
+ dbCfg := cfg.Raw.Section("database")
+ dbCfg.Key("type").SetValue(testDB.DriverName)
+ dbCfg.Key("host").SetValue(testDB.Host)
+ dbCfg.Key("port").SetValue(testDB.Port)
+ dbCfg.Key("user").SetValue(testDB.User)
+ dbCfg.Key("password").SetValue(testDB.Password)
+ dbCfg.Key("name").SetValue(testDB.Database)
+
env, err := server.InitializeForTest(t, t, cfg, serverOpts, apiServerOpts)
require.NoError(t, err)
From 1831953f7fd114c1db235fe35b87986f17def271 Mon Sep 17 00:00:00 2001
From: Todd Treece <360020+toddtreece@users.noreply.github.com>
Date: Fri, 1 Aug 2025 10:02:01 -0400
Subject: [PATCH 25/89] K8s: Add API Enablement for apps (#109019)
---
pkg/registry/apps/playlist/register.go | 8 -----
.../apiserver/appinstaller/installer.go | 19 +++++------
.../apiserver/appinstaller/resourceconfig.go | 32 +++++++++++++++++++
pkg/services/apiserver/appinstaller/server.go | 8 +++++
pkg/services/apiserver/config.go | 7 ++++
pkg/services/apiserver/options/options.go | 3 ++
pkg/services/apiserver/service.go | 9 ++++++
7 files changed, 67 insertions(+), 19 deletions(-)
create mode 100644 pkg/services/apiserver/appinstaller/resourceconfig.go
diff --git a/pkg/registry/apps/playlist/register.go b/pkg/registry/apps/playlist/register.go
index 4374c0265dd..123974c2dcc 100644
--- a/pkg/registry/apps/playlist/register.go
+++ b/pkg/registry/apps/playlist/register.go
@@ -26,7 +26,6 @@ import (
var (
_ appsdkapiserver.AppInstaller = (*PlaylistAppInstaller)(nil)
_ appinstaller.LegacyStorageProvider = (*PlaylistAppInstaller)(nil)
- _ appinstaller.APIEnablementProvider = (*PlaylistAppInstaller)(nil)
)
type PlaylistAppInstaller struct {
@@ -102,10 +101,3 @@ func (p *PlaylistAppInstaller) GetLegacyStorage(requested schema.GroupVersionRes
)
return legacyStore
}
-
-// GetAllowedV0Alpha1Resources returns the list of resources that are allowed to be accessed in v0alpha1.
-func (p *PlaylistAppInstaller) GetAllowedV0Alpha1Resources() []string {
- return []string{
- playlistv0alpha1.PlaylistKind().Plural(),
- }
-}
diff --git a/pkg/services/apiserver/appinstaller/installer.go b/pkg/services/apiserver/appinstaller/installer.go
index fb5427b761f..9817f7513bc 100644
--- a/pkg/services/apiserver/appinstaller/installer.go
+++ b/pkg/services/apiserver/appinstaller/installer.go
@@ -8,17 +8,19 @@ import (
appsdkapiserver "github.com/grafana/grafana-app-sdk/k8s/apiserver"
"github.com/grafana/grafana-app-sdk/logging"
- grafanarest "github.com/grafana/grafana/pkg/apiserver/rest"
- "github.com/grafana/grafana/pkg/services/apiserver/builder"
- "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
- grafanaapiserveroptions "github.com/grafana/grafana/pkg/services/apiserver/options"
"github.com/grafana/grafana/pkg/storage/legacysql/dualwrite"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/authorization/authorizer"
"k8s.io/apiserver/pkg/registry/generic"
genericapiserver "k8s.io/apiserver/pkg/server"
+ serverstore "k8s.io/apiserver/pkg/server/storage"
"k8s.io/kube-openapi/pkg/common"
+
+ grafanarest "github.com/grafana/grafana/pkg/apiserver/rest"
+ "github.com/grafana/grafana/pkg/services/apiserver/builder"
+ "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
+ grafanaapiserveroptions "github.com/grafana/grafana/pkg/services/apiserver/options"
)
type LegacyStorageGetterFunc func(schema.GroupVersionResource) grafanarest.Storage
@@ -31,13 +33,6 @@ type AuthorizerProvider interface {
GetAuthorizer() authorizer.Authorizer
}
-type APIEnablementProvider interface {
- // Do not implement this unless you have special circumstances! This is a list of resources that are allowed to be accessed in v0alpha1,
- // to prevent accidental exposure of experimental APIs. While developing, use the feature flag `grafanaAPIServerWithExperimentalAPIs`.
- // And then, when you're ready to expose this to the end user, go to v1beta1 instead.
- GetAllowedV0Alpha1Resources() []string
-}
-
type AppInstallerConfig struct {
CustomConfig any
AllowedV0Alpha1Resources []string
@@ -132,6 +127,7 @@ func InstallAPIs(
dualWriteService dualwrite.Service,
dualWriterMetrics *grafanarest.DualWriterMetrics,
builderMetrics *builder.BuilderMetrics,
+ apiResourceConfig *serverstore.ResourceConfig,
) error {
logger := logging.FromContext(ctx)
for _, installer := range appInstallers {
@@ -148,6 +144,7 @@ func InstallAPIs(
dualWriteService: dualWriteService,
dualWriterMetrics: dualWriterMetrics,
builderMetrics: builderMetrics,
+ apiResourceConfig: apiResourceConfig,
}
if err := installer.InstallAPIs(wrapper, restOpsGetter); err != nil {
return fmt.Errorf("failed to install APIs for app %s: %w", installer.ManifestData().AppName, err)
diff --git a/pkg/services/apiserver/appinstaller/resourceconfig.go b/pkg/services/apiserver/appinstaller/resourceconfig.go
new file mode 100644
index 00000000000..c2e45eb74e9
--- /dev/null
+++ b/pkg/services/apiserver/appinstaller/resourceconfig.go
@@ -0,0 +1,32 @@
+package appinstaller
+
+import (
+ appsdkapiserver "github.com/grafana/grafana-app-sdk/k8s/apiserver"
+ "k8s.io/apimachinery/pkg/runtime/schema"
+ serverstorage "k8s.io/apiserver/pkg/server/storage"
+)
+
+func NewAPIResourceConfig(installers []appsdkapiserver.AppInstaller) *serverstorage.ResourceConfig {
+ ret := serverstorage.NewResourceConfig()
+ enable := []schema.GroupVersion{}
+ disable := []schema.GroupVersion{}
+
+ for _, installer := range installers {
+ for _, version := range installer.ManifestData().Versions {
+ gv := schema.GroupVersion{
+ Group: installer.ManifestData().Group,
+ Version: version.Name,
+ }
+ if version.Served {
+ enable = append(enable, gv)
+ } else {
+ disable = append(disable, gv)
+ }
+ }
+ }
+
+ ret.EnableVersions(enable...)
+ ret.DisableVersions(disable...)
+
+ return ret
+}
diff --git a/pkg/services/apiserver/appinstaller/server.go b/pkg/services/apiserver/appinstaller/server.go
index ea9b88e031e..20792eeab0d 100644
--- a/pkg/services/apiserver/appinstaller/server.go
+++ b/pkg/services/apiserver/appinstaller/server.go
@@ -10,6 +10,7 @@ import (
genericregistry "k8s.io/apiserver/pkg/registry/generic/registry"
genericrest "k8s.io/apiserver/pkg/registry/rest"
genericapiserver "k8s.io/apiserver/pkg/server"
+ serverstorage "k8s.io/apiserver/pkg/server/storage"
appsdkapiserver "github.com/grafana/grafana-app-sdk/k8s/apiserver"
"github.com/grafana/grafana-app-sdk/logging"
@@ -35,6 +36,7 @@ type serverWrapper struct {
dualWriteService dualwrite.Service
dualWriterMetrics *grafanarest.DualWriterMetrics
builderMetrics *builder.BuilderMetrics
+ apiResourceConfig *serverstorage.ResourceConfig
}
func (s *serverWrapper) InstallAPIGroup(apiGroupInfo *genericapiserver.APIGroupInfo) error {
@@ -50,6 +52,12 @@ func (s *serverWrapper) InstallAPIGroup(apiGroupInfo *genericapiserver.APIGroupI
Group: s.installer.ManifestData().Group,
Resource: resource,
}
+ gvr := gr.WithVersion(v)
+ if s.apiResourceConfig != nil && !s.apiResourceConfig.ResourceEnabled(gvr) {
+ log.Debug("Skipping storage for disabled resource", "gvr", gvr.String(), "storagePath", storagePath)
+ delete(apiGroupInfo.VersionedResourcesStorageMap[v], storagePath)
+ continue
+ }
storage := s.configureStorage(gr, dualWriteSupported, restStorage)
if unifiedStorage, ok := storage.(grafanarest.Storage); ok && dualWriteSupported {
log.Debug("Configuring dual writer for storage", "resource", gr.String(), "version", v, "storagePath", storagePath)
diff --git a/pkg/services/apiserver/config.go b/pkg/services/apiserver/config.go
index 8cf5789c777..dc2234e6e56 100644
--- a/pkg/services/apiserver/config.go
+++ b/pkg/services/apiserver/config.go
@@ -39,6 +39,13 @@ func applyGrafanaConfig(cfg *setting.Cfg, features featuremgmt.FeatureToggles, o
apiserverCfg := cfg.SectionWithEnvOverrides("grafana-apiserver")
+ runtimeConfig := apiserverCfg.Key("runtime_config").String()
+ if runtimeConfig != "" {
+ if err := o.APIEnablementOptions.RuntimeConfig.Set(runtimeConfig); err != nil {
+ return fmt.Errorf("failed to set runtime config: %w", err)
+ }
+ }
+
o.RecommendedOptions.Etcd.StorageConfig.Transport.ServerList = apiserverCfg.Key("etcd_servers").Strings(",")
o.RecommendedOptions.SecureServing.BindAddress = ip
diff --git a/pkg/services/apiserver/options/options.go b/pkg/services/apiserver/options/options.go
index a9209cdbc00..721f1ce570b 100644
--- a/pkg/services/apiserver/options/options.go
+++ b/pkg/services/apiserver/options/options.go
@@ -21,6 +21,7 @@ const defaultEtcdPathPrefix = "/registry/grafana.app"
type Options struct {
RecommendedOptions *genericoptions.RecommendedOptions
+ APIEnablementOptions *genericoptions.APIEnablementOptions
GrafanaAggregatorOptions *GrafanaAggregatorOptions
StorageOptions *StorageOptions
ExtraOptions *ExtraOptions
@@ -30,6 +31,7 @@ type Options struct {
func NewOptions(codec runtime.Codec) *Options {
return &Options{
RecommendedOptions: NewRecommendedOptions(codec),
+ APIEnablementOptions: genericoptions.NewAPIEnablementOptions(),
GrafanaAggregatorOptions: NewGrafanaAggregatorOptions(),
StorageOptions: NewStorageOptions(),
ExtraOptions: NewExtraOptions(),
@@ -38,6 +40,7 @@ func NewOptions(codec runtime.Codec) *Options {
func (o *Options) AddFlags(fs *pflag.FlagSet) {
o.RecommendedOptions.AddFlags(fs)
+ o.APIEnablementOptions.AddFlags(fs)
o.GrafanaAggregatorOptions.AddFlags(fs)
o.StorageOptions.AddFlags(fs)
o.ExtraOptions.AddFlags(fs)
diff --git a/pkg/services/apiserver/service.go b/pkg/services/apiserver/service.go
index 58bf5ad4ac7..be580025823 100644
--- a/pkg/services/apiserver/service.go
+++ b/pkg/services/apiserver/service.go
@@ -304,11 +304,19 @@ func (s *service) start(ctx context.Context) error {
return errs[0]
}
+ if errs := o.APIEnablementOptions.Validate(s.scheme); len(errs) != 0 {
+ return errs[0]
+ }
+
serverConfig := genericapiserver.NewRecommendedConfig(s.codecs)
if err := o.ApplyTo(serverConfig); err != nil {
return err
}
+ if err := o.APIEnablementOptions.ApplyTo(&serverConfig.Config, appinstaller.NewAPIResourceConfig(s.appInstallers), s.scheme); err != nil {
+ return err
+ }
+
serverConfig.Authorization.Authorizer = s.authorizer
serverConfig.Authentication.Authenticator = authenticator.NewAuthenticator(serverConfig.Authentication.Authenticator)
serverConfig.TracerProvider = s.tracing.GetTracerProvider()
@@ -395,6 +403,7 @@ func (s *service) start(ctx context.Context) error {
s.storageStatus,
s.dualWriterMetrics,
s.builderMetrics,
+ serverConfig.MergedResourceConfig,
); err != nil {
return err
}
From cc63af204cbfcc5d7816195a15ada0904bab8e7d Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Fri, 1 Aug 2025 15:10:30 +0100
Subject: [PATCH 26/89] Update dependency babel-loader to v10 (#109047)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
package.json | 2 +-
yarn.lock | 92 ++++------------------------------------------------
2 files changed, 8 insertions(+), 86 deletions(-)
diff --git a/package.json b/package.json
index 2728eafd9a0..6fdf6a22a28 100644
--- a/package.json
+++ b/package.json
@@ -167,7 +167,7 @@
"@typescript-eslint/eslint-plugin": "8.38.0",
"@typescript-eslint/parser": "8.38.0",
"autoprefixer": "10.4.21",
- "babel-loader": "9.2.1",
+ "babel-loader": "10.0.0",
"blob-polyfill": "9.0.20240710",
"browserslist": "^4.21.4",
"chance": "^1.1.13",
diff --git a/yarn.lock b/yarn.lock
index a78abcf6911..e09a9406eac 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -11817,16 +11817,15 @@ __metadata:
languageName: node
linkType: hard
-"babel-loader@npm:9.2.1":
- version: 9.2.1
- resolution: "babel-loader@npm:9.2.1"
+"babel-loader@npm:10.0.0":
+ version: 10.0.0
+ resolution: "babel-loader@npm:10.0.0"
dependencies:
- find-cache-dir: "npm:^4.0.0"
- schema-utils: "npm:^4.0.0"
+ find-up: "npm:^5.0.0"
peerDependencies:
"@babel/core": ^7.12.0
- webpack: ">=5"
- checksum: 10/f1f24ae3c22d488630629240b0eba9c935545f82ff843c214e8f8df66e266492b7a3d4cb34ef9c9721fb174ca222e900799951c3fd82199473bc6bac52ec03a3
+ webpack: ">=5.61.0"
+ checksum: 10/f22dc803e38a6b29cc61fbc3482f1f42a8787df2a43706dc937d328103ba6b947a223f67706b07af765d415664ad56e9fed00f85b524fe223f3ac3f00b03770b
languageName: node
linkType: hard
@@ -13391,13 +13390,6 @@ __metadata:
languageName: node
linkType: hard
-"common-path-prefix@npm:^3.0.0":
- version: 3.0.0
- resolution: "common-path-prefix@npm:3.0.0"
- checksum: 10/09c180e8d8495d42990d617f4d4b7522b5da20f6b236afe310192d401d1da8147a7835ae1ea37797ba0c2238ef3d06f3492151591451df34539fdb4b2630f2b3
- languageName: node
- linkType: hard
-
"common-tags@npm:1.8.2, common-tags@npm:^1.8.0":
version: 1.8.2
resolution: "common-tags@npm:1.8.2"
@@ -17103,16 +17095,6 @@ __metadata:
languageName: node
linkType: hard
-"find-cache-dir@npm:^4.0.0":
- version: 4.0.0
- resolution: "find-cache-dir@npm:4.0.0"
- dependencies:
- common-path-prefix: "npm:^3.0.0"
- pkg-dir: "npm:^7.0.0"
- checksum: 10/52a456a80deeb27daa3af6e06059b63bdb9cc4af4d845fc6d6229887e505ba913cd56000349caa60bc3aa59dacdb5b4c37903d4ba34c75102d83cab330b70d2f
- languageName: node
- linkType: hard
-
"find-file-up@npm:^0.1.2":
version: 0.1.3
resolution: "find-file-up@npm:0.1.3"
@@ -17181,16 +17163,6 @@ __metadata:
languageName: node
linkType: hard
-"find-up@npm:^6.3.0":
- version: 6.3.0
- resolution: "find-up@npm:6.3.0"
- dependencies:
- locate-path: "npm:^7.1.0"
- path-exists: "npm:^5.0.0"
- checksum: 10/4f3bdc30d41778c647e53f4923e72de5e5fb055157031f34501c5b36c2eb59f77b997edf9cb00165c6060cda7eaa2e3da82cb6be2e61d68ad3e07c4bc4cce67e
- languageName: node
- linkType: hard
-
"findup-sync@npm:^5.0.0":
version: 5.0.0
resolution: "findup-sync@npm:5.0.0"
@@ -18371,7 +18343,7 @@ __metadata:
"@welldone-software/why-did-you-render": "npm:8.0.3"
ansicolor: "npm:2.0.3"
autoprefixer: "npm:10.4.21"
- babel-loader: "npm:9.2.1"
+ babel-loader: "npm:10.0.0"
baron: "npm:3.0.3"
blob-polyfill: "npm:9.0.20240710"
brace: "npm:0.11.1"
@@ -22132,15 +22104,6 @@ __metadata:
languageName: node
linkType: hard
-"locate-path@npm:^7.1.0":
- version: 7.2.0
- resolution: "locate-path@npm:7.2.0"
- dependencies:
- p-locate: "npm:^6.0.0"
- checksum: 10/1c6d269d4efec555937081be964e8a9b4a136319c79ca1d45ac6382212a8466113c75bd89e44521ca8ecd1c47fb08523b56eee5c0712bc7d14fec5f729deeb42
- languageName: node
- linkType: hard
-
"lockfile@npm:^1.0.4":
version: 1.0.4
resolution: "lockfile@npm:1.0.4"
@@ -24661,15 +24624,6 @@ __metadata:
languageName: node
linkType: hard
-"p-limit@npm:^4.0.0":
- version: 4.0.0
- resolution: "p-limit@npm:4.0.0"
- dependencies:
- yocto-queue: "npm:^1.0.0"
- checksum: 10/01d9d70695187788f984226e16c903475ec6a947ee7b21948d6f597bed788e3112cc7ec2e171c1d37125057a5f45f3da21d8653e04a3a793589e12e9e80e756b
- languageName: node
- linkType: hard
-
"p-locate@npm:^2.0.0":
version: 2.0.0
resolution: "p-locate@npm:2.0.0"
@@ -24697,15 +24651,6 @@ __metadata:
languageName: node
linkType: hard
-"p-locate@npm:^6.0.0":
- version: 6.0.0
- resolution: "p-locate@npm:6.0.0"
- dependencies:
- p-limit: "npm:^4.0.0"
- checksum: 10/2bfe5234efa5e7a4e74b30a5479a193fdd9236f8f6b4d2f3f69e3d286d9a7d7ab0c118a2a50142efcf4e41625def635bd9332d6cbf9cc65d85eb0718c579ab38
- languageName: node
- linkType: hard
-
"p-map-series@npm:2.1.0":
version: 2.1.0
resolution: "p-map-series@npm:2.1.0"
@@ -25206,13 +25151,6 @@ __metadata:
languageName: node
linkType: hard
-"path-exists@npm:^5.0.0":
- version: 5.0.0
- resolution: "path-exists@npm:5.0.0"
- checksum: 10/8ca842868cab09423994596eb2c5ec2a971c17d1a3cb36dbf060592c730c725cd524b9067d7d2a1e031fef9ba7bd2ac6dc5ec9fb92aa693265f7be3987045254
- languageName: node
- linkType: hard
-
"path-is-absolute@npm:^1.0.0":
version: 1.0.1
resolution: "path-is-absolute@npm:1.0.1"
@@ -25485,15 +25423,6 @@ __metadata:
languageName: node
linkType: hard
-"pkg-dir@npm:^7.0.0":
- version: 7.0.0
- resolution: "pkg-dir@npm:7.0.0"
- dependencies:
- find-up: "npm:^6.3.0"
- checksum: 10/94298b20a446bfbbd66604474de8a0cdd3b8d251225170970f15d9646f633e056c80520dd5b4c1d1050c9fed8f6a9e5054b141c93806439452efe72e57562c03
- languageName: node
- linkType: hard
-
"playwright-core@npm:1.54.1, playwright-core@npm:>=1.2.0":
version: 1.54.1
resolution: "playwright-core@npm:1.54.1"
@@ -33274,13 +33203,6 @@ __metadata:
languageName: node
linkType: hard
-"yocto-queue@npm:^1.0.0":
- version: 1.0.0
- resolution: "yocto-queue@npm:1.0.0"
- checksum: 10/2cac84540f65c64ccc1683c267edce396b26b1e931aa429660aefac8fbe0188167b7aee815a3c22fa59a28a58d898d1a2b1825048f834d8d629f4c2a5d443801
- languageName: node
- linkType: hard
-
"yoctocolors-cjs@npm:^2.1.2":
version: 2.1.2
resolution: "yoctocolors-cjs@npm:2.1.2"
From 953afbd7841a07263871ca62754aedd5c8b64b47 Mon Sep 17 00:00:00 2001
From: Konrad Lalik
Date: Fri, 1 Aug 2025 16:29:08 +0200
Subject: [PATCH 27/89] Alerting: List V2 - Fix free form filter (#109050)
Use whole free form words for fuzzy search in rule names
---
.../alerting/unified/rule-list/hooks/filters.ts | 16 +++++-----------
1 file changed, 5 insertions(+), 11 deletions(-)
diff --git a/public/app/features/alerting/unified/rule-list/hooks/filters.ts b/public/app/features/alerting/unified/rule-list/hooks/filters.ts
index 3602081e7c0..2e9535dd97f 100644
--- a/public/app/features/alerting/unified/rule-list/hooks/filters.ts
+++ b/public/app/features/alerting/unified/rule-list/hooks/filters.ts
@@ -22,12 +22,10 @@ export function groupFilter(
const { name, file } = group;
const { namespace, groupName } = filterState;
- // Use fuzzy search for namespace
if (namespace && !fuzzyMatches(file, namespace)) {
return false;
}
- // Use fuzzy search for group name
if (groupName && !fuzzyMatches(name, groupName)) {
return false;
}
@@ -41,17 +39,17 @@ export function groupFilter(
export function ruleFilter(rule: PromRuleDTO, filterState: RulesFilter) {
const { name, labels = {}, health, type } = rule;
- // Free form words filter (uses fuzzy matching for each word)
- if (filterState.freeFormWords.length > 0 && !filterState.freeFormWords.some((word) => fuzzyMatches(name, word))) {
- return false;
+ if (filterState.freeFormWords.length > 0) {
+ const nameMatches = fuzzyMatches(name, filterState.freeFormWords.join(' '));
+ if (!nameMatches) {
+ return false;
+ }
}
- // Rule name filter (uses fuzzy matching)
if (filterState.ruleName && !fuzzyMatches(name, filterState.ruleName)) {
return false;
}
- // Labels filter
if (filterState.labels.length > 0) {
const matchers = compact(filterState.labels.map(looseParseMatcher));
const doRuleLabelsMatchQuery = matchers.length > 0 && labelsMatchMatchers(labels, matchers);
@@ -68,12 +66,10 @@ export function ruleFilter(rule: PromRuleDTO, filterState: RulesFilter) {
}
}
- // Rule type filter
if (filterState.ruleType && type !== filterState.ruleType) {
return false;
}
- // Rule state filter (for alerting rules only)
if (filterState.ruleState) {
if (!prometheusRuleType.alertingRule(rule)) {
return false;
@@ -83,7 +79,6 @@ export function ruleFilter(rule: PromRuleDTO, filterState: RulesFilter) {
}
}
- // Rule health filter
if (filterState.ruleHealth && health !== filterState.ruleHealth) {
return false;
}
@@ -102,7 +97,6 @@ export function ruleFilter(rule: PromRuleDTO, filterState: RulesFilter) {
}
}
- // Dashboard UID filter
if (filterState.dashboardUid) {
if (!prometheusRuleType.alertingRule(rule)) {
return false;
From b333b67aeaeb81b383281ecd7980b152a5744f3d Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Fri, 1 Aug 2025 15:29:06 +0000
Subject: [PATCH 28/89] Update dependency copy-webpack-plugin to v13 (#109053)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
package.json | 2 +-
packages/grafana-plugin-configs/package.json | 2 +-
yarn.lock | 54 ++++----------------
3 files changed, 11 insertions(+), 47 deletions(-)
diff --git a/package.json b/package.json
index 6fdf6a22a28..7626f9f04b1 100644
--- a/package.json
+++ b/package.json
@@ -174,7 +174,7 @@
"chrome-remote-interface": "0.33.3",
"codeowners": "^5.1.1",
"confusing-browser-globals": "^1.0.11",
- "copy-webpack-plugin": "12.0.2",
+ "copy-webpack-plugin": "13.0.0",
"core-js": "3.44.0",
"crashme": "0.0.15",
"css-loader": "7.1.2",
diff --git a/packages/grafana-plugin-configs/package.json b/packages/grafana-plugin-configs/package.json
index 7d068121b19..c22258285c0 100644
--- a/packages/grafana-plugin-configs/package.json
+++ b/packages/grafana-plugin-configs/package.json
@@ -14,7 +14,7 @@
"@swc/jest": "^0.2.26",
"@types/eslint": "9.6.1",
"@types/webpack-bundle-analyzer": "^4.7.0",
- "copy-webpack-plugin": "12.0.2",
+ "copy-webpack-plugin": "13.0.0",
"eslint": "9.32.0",
"eslint-webpack-plugin": "4.2.0",
"fork-ts-checker-webpack-plugin": "9.1.0",
diff --git a/yarn.lock b/yarn.lock
index e09a9406eac..dae3c03c7d0 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3415,7 +3415,7 @@ __metadata:
"@swc/jest": "npm:^0.2.26"
"@types/eslint": "npm:9.6.1"
"@types/webpack-bundle-analyzer": "npm:^4.7.0"
- copy-webpack-plugin: "npm:12.0.2"
+ copy-webpack-plugin: "npm:13.0.0"
eslint: "npm:9.32.0"
eslint-webpack-plugin: "npm:4.2.0"
fork-ts-checker-webpack-plugin: "npm:9.1.0"
@@ -7355,13 +7355,6 @@ __metadata:
languageName: node
linkType: hard
-"@sindresorhus/merge-streams@npm:^2.1.0":
- version: 2.3.0
- resolution: "@sindresorhus/merge-streams@npm:2.3.0"
- checksum: 10/798bcb53cd1ace9df84fcdd1ba86afdc9e0cd84f5758d26ae9b1eefd8e8887e5fc30051132b9e74daf01bb41fa5a2faf1369361f83d76a3b3d7ee938058fd71c
- languageName: node
- linkType: hard
-
"@sinonjs/commons@npm:^3.0.0":
version: 3.0.0
resolution: "@sinonjs/commons@npm:3.0.0"
@@ -13644,19 +13637,18 @@ __metadata:
languageName: node
linkType: hard
-"copy-webpack-plugin@npm:12.0.2":
- version: 12.0.2
- resolution: "copy-webpack-plugin@npm:12.0.2"
+"copy-webpack-plugin@npm:13.0.0":
+ version: 13.0.0
+ resolution: "copy-webpack-plugin@npm:13.0.0"
dependencies:
- fast-glob: "npm:^3.3.2"
glob-parent: "npm:^6.0.1"
- globby: "npm:^14.0.0"
normalize-path: "npm:^3.0.0"
schema-utils: "npm:^4.2.0"
serialize-javascript: "npm:^6.0.2"
+ tinyglobby: "npm:^0.2.12"
peerDependencies:
webpack: ^5.1.0
- checksum: 10/674725d4d9556b7b9a32bb85393532ef2bb75ffce785d942681b3575a86d900751f67cebbb089ddd050757f58c84edc18732e17880f12c45c9775ca94328526c
+ checksum: 10/209051dd3c0bc7ab97170309cdb1826e642044d2d53e0adc35bb227123c89ae1296a504409325e9b955d7b2d1a505b063f0023e924151d382dbcc92cb9325e6a
languageName: node
linkType: hard
@@ -18144,20 +18136,6 @@ __metadata:
languageName: node
linkType: hard
-"globby@npm:^14.0.0":
- version: 14.0.1
- resolution: "globby@npm:14.0.1"
- dependencies:
- "@sindresorhus/merge-streams": "npm:^2.1.0"
- fast-glob: "npm:^3.3.2"
- ignore: "npm:^5.2.4"
- path-type: "npm:^5.0.0"
- slash: "npm:^5.1.0"
- unicorn-magic: "npm:^0.1.0"
- checksum: 10/b36f57afc45a857a884d82657603c7e1663b1e6f3f9afbeb53d12e42230469fc5b26a7e14a01e51086f3f25c138f58a7002036fcc8f3ca054097b6dd7c71d639
- languageName: node
- linkType: hard
-
"globby@npm:~6.1.0":
version: 6.1.0
resolution: "globby@npm:6.1.0"
@@ -18357,7 +18335,7 @@ __metadata:
comlink: "npm:4.4.2"
common-tags: "npm:1.8.2"
confusing-browser-globals: "npm:^1.0.11"
- copy-webpack-plugin: "npm:12.0.2"
+ copy-webpack-plugin: "npm:13.0.0"
core-js: "npm:3.44.0"
crashme: "npm:0.0.15"
croner: "npm:^9.0.0"
@@ -25254,13 +25232,6 @@ __metadata:
languageName: node
linkType: hard
-"path-type@npm:^5.0.0":
- version: 5.0.0
- resolution: "path-type@npm:5.0.0"
- checksum: 10/15ec24050e8932c2c98d085b72cfa0d6b4eeb4cbde151a0a05726d8afae85784fc5544f733d8dfc68536587d5143d29c0bd793623fad03d7e61cc00067291cd5
- languageName: node
- linkType: hard
-
"pathe@npm:^2.0.2":
version: 2.0.2
resolution: "pathe@npm:2.0.2"
@@ -29173,7 +29144,7 @@ __metadata:
languageName: node
linkType: hard
-"slash@npm:^5.0.0, slash@npm:^5.1.0":
+"slash@npm:^5.0.0":
version: 5.1.0
resolution: "slash@npm:5.1.0"
checksum: 10/2c41ec6fb1414cd9bba0fa6b1dd00e8be739e3fe85d079c69d4b09ca5f2f86eafd18d9ce611c0c0f686428638a36c272a6ac14799146a8295f259c10cc45cde4
@@ -30772,7 +30743,7 @@ __metadata:
languageName: node
linkType: hard
-"tinyglobby@npm:^0.2.13":
+"tinyglobby@npm:^0.2.12, tinyglobby@npm:^0.2.13":
version: 0.2.14
resolution: "tinyglobby@npm:0.2.14"
dependencies:
@@ -31680,13 +31651,6 @@ __metadata:
languageName: node
linkType: hard
-"unicorn-magic@npm:^0.1.0":
- version: 0.1.0
- resolution: "unicorn-magic@npm:0.1.0"
- checksum: 10/9b4d0e9809807823dc91d0920a4a4c0cff2de3ebc54ee87ac1ee9bc75eafd609b09d1f14495e0173aef26e01118706196b6ab06a75fe0841028b3983a8af313f
- languageName: node
- linkType: hard
-
"union@npm:~0.5.0":
version: 0.5.0
resolution: "union@npm:0.5.0"
From 428773411422a08af08b19adc3e448fd7b56bcff Mon Sep 17 00:00:00 2001
From: Timur Olzhabayev
Date: Fri, 1 Aug 2025 17:46:29 +0200
Subject: [PATCH 29/89] Fix: Adding sparse checkout to issue triage workflow
(#109060)
* adding sparse checkout
* adding checkout step if we want to run triage
---
.github/workflows/issue-opened.yml | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/.github/workflows/issue-opened.yml b/.github/workflows/issue-opened.yml
index b54bb968b7f..2ca46dd1db2 100644
--- a/.github/workflows/issue-opened.yml
+++ b/.github/workflows/issue-opened.yml
@@ -88,7 +88,6 @@ jobs:
private-key: ${{ env.GITHUB_APP_PRIVATE_KEY }}
permission-members: read
permission-issues: write
-
- name: Check if member of grafana org
id: check-if-grafana-org-member
continue-on-error: true
@@ -96,6 +95,13 @@ jobs:
env:
GH_TOKEN: ${{ steps.generate_token.outputs.token }}
ACTOR: ${{ github.actor }}
+ - name: Checkout
+ if: steps.check-if-grafana-org-member.outputs.is_grafana_org_member != 'true' && github.event.issue.author_association != 'MEMBER' && github.event.issue.author_association != 'OWNER'
+ uses: actions/checkout@v4 # v4.2.2
+ with:
+ persist-credentials: false
+ sparse-checkout: |
+ .github/workflows/auto-triager
- name: Send issue to the auto triager action
id: auto_triage
if: steps.check-if-grafana-org-member.outputs.is_grafana_org_member != 'true' && github.event.issue.author_association != 'MEMBER' && github.event.issue.author_association != 'OWNER'
From 0faa03edbe73cc9974462e1be0acd3b3b5d053ed Mon Sep 17 00:00:00 2001
From: Jack Baldry
Date: Fri, 1 Aug 2025 16:57:18 +0100
Subject: [PATCH 30/89] Add snippets for 'Create log alert rules with Grafana
Alerting' learning journey (#109059)
---
.../alerting-rules/link-alert-rules-to-panels.md | 4 ++++
docs/sources/alerting/best-practices/_index.md | 4 ++++
.../fundamentals/alert-rule-evaluation/_index.md | 14 ++++++++++++--
.../fundamentals/alert-rules/annotation-label.md | 8 ++++++++
.../alerting/fundamentals/notifications/_index.md | 4 ++++
5 files changed, 32 insertions(+), 2 deletions(-)
diff --git a/docs/sources/alerting/alerting-rules/link-alert-rules-to-panels.md b/docs/sources/alerting/alerting-rules/link-alert-rules-to-panels.md
index 052770cc058..8be72c73270 100644
--- a/docs/sources/alerting/alerting-rules/link-alert-rules-to-panels.md
+++ b/docs/sources/alerting/alerting-rules/link-alert-rules-to-panels.md
@@ -67,6 +67,8 @@ By default, notification messages include a link to the dashboard panel. Additio
## Create alert rules from panels
+{{< shared id="create-alert-from-panel" >}}
+
To streamline alert creation, you can create an alert rule directly from a panel.
1. Navigate to a dashboard in the **Dashboards** section.
@@ -77,6 +79,8 @@ To streamline alert creation, you can create an alert rule directly from a panel
- Sets the alert rule query using the panel query.
1. Complete the alert rule configuration and click **Save rule** to initiate the alert rule.
+{{< /shared >}}
+
You can then [view the alert state on the panel](ref:view-alert-state-on-panels).
By default, notification messages include a link to the dashboard panel. Additionally, you can [enable displaying panel screenshots in notifications](ref:images-in-notifications).
diff --git a/docs/sources/alerting/best-practices/_index.md b/docs/sources/alerting/best-practices/_index.md
index 18bcf13121a..41251a25994 100644
--- a/docs/sources/alerting/best-practices/_index.md
+++ b/docs/sources/alerting/best-practices/_index.md
@@ -21,6 +21,8 @@ This section provides a set of guides and examples of best practices for Grafana
Designing and configuring an alert management set up that works takes time. Here are some additional tips on how to create an effective alert management set up:
+{{< shared id="alert-planning-fundamentals" >}}
+
**Which are the key metrics for your business that you want to monitor and alert on?**
- Find events that are important to know about and not so trivial or frequent that recipients ignore them.
@@ -44,3 +46,5 @@ Designing and configuring an alert management set up that works takes time. Here
- Avoid noisy, unnecessary alerts by using silences, mute timings, or pausing alert rule evaluation.
- Continually tune your alert rules to review effectiveness. Remove alert rules to avoid duplication or ineffective alerts.
- Continually review your thresholds and evaluation rules.
+
+{{< /shared >}}
diff --git a/docs/sources/alerting/fundamentals/alert-rule-evaluation/_index.md b/docs/sources/alerting/fundamentals/alert-rule-evaluation/_index.md
index 80a58728bc1..7ff256ac103 100644
--- a/docs/sources/alerting/fundamentals/alert-rule-evaluation/_index.md
+++ b/docs/sources/alerting/fundamentals/alert-rule-evaluation/_index.md
@@ -81,30 +81,40 @@ Alert instances are routed for [notifications](ref:notifications) in two scenari
## Evaluation group
-Every alert rule and recording rule is assigned to an evaluation group.
+{{< shared id="evaluation-group-basics" >}}
-Each evaluation group contains an **evaluation interval** that determines how frequently the rule is checked. For instance, the evaluation may occur every `10s`, `30s`, `1m`, `10m`, etc.
+Every alert rule and recording rule is assigned to an evaluation group. Each evaluation group contains an **evaluation interval** that determines how frequently the rule is checked. For instance, the evaluation may occur every `10s`, `30s`, `1m`, `10m`, etc.
+
+{{< /shared >}}
Rules can be evaluated concurrently or sequentially. For details, see [How rules are evaluated within a group](ref:evaluation-within-a-group).
## Pending period
+{{< shared id="pending-period-basics" >}}
+
You can set a **Pending period** to prevent unnecessary notifications caused by temporary issues.
When the alert condition is met, the alert instance enters the **Pending** state. It remains in this state until the condition has been continuously true for the entire **Pending period**.
This ensures the condition breach is stable before the alert transitions to the **Alerting** state and routed for notification.
+{{< /shared >}}
+
- **Normal** -> **Pending** -> **Alerting**\*
You can also set the **Pending period** to zero to skip the **Pending** state entirely and transition to **Alerting** immediately.
## Keep firing for
+{{< shared id="keep-firing-for" >}}
+
You can set a **Keep firing for** period to avoid repeated firing-resolving-firing notifications caused by flapping conditions.
When the alert condition is no longer met during the **Alerting** state, the alert instance enters the **Recovering** state.
+{{< /shared >}}
+
- **Alerting** → **Recovering** → **Normal (Resolved)**\*
- After the **Keep firing for** period elapses, the alert transitions to the **Normal** state and is marked as **Resolved**.
- If the alert condition is met again, the alert transitions back to the **Alerting** state, and no new notifications are sent.
diff --git a/docs/sources/alerting/fundamentals/alert-rules/annotation-label.md b/docs/sources/alerting/fundamentals/alert-rules/annotation-label.md
index 8d0aca31034..27ae61e3cb9 100644
--- a/docs/sources/alerting/fundamentals/alert-rules/annotation-label.md
+++ b/docs/sources/alerting/fundamentals/alert-rules/annotation-label.md
@@ -62,10 +62,14 @@ Labels and annotations add additional information about an alert using key/value
## Labels
+{{< shared id="labels-basics" >}}
+
**Labels** are unique identifiers of an [alert instance](ref:alert-instances). You can use them for searching, silencing, and routing notifications.
Examples of labels are `server=server1` or `team=backend`. Each alert rule can have more than one label and the complete set of labels for an alert rule is called its label set. It is this label set that identifies the alert.
+{{< /shared >}}
+
For example, an alert instance might have the label set `{alertname="High CPU usage",server="server1"}` while another alert instance might have the label set `{alertname="High CPU usage",server="server2"}`. These are two separate alert instances because although their `alertname` labels are the same, their `server` labels are different.
{{< figure alt="Image shows an example of an alert instance and the labels used on the alert instance." src="/static/img/docs/alerting/unified/multi-dimensional-alert.png" >}}
@@ -134,6 +138,8 @@ If multiple label keys are sanitized to the same value, the duplicates have a sh
## Annotations
+{{< shared id="annotations-basics" >}}
+
Annotations add additional information to alert instances, helping responders identify and address potential issues.
Create clear and self-explanatory annotations so that first responders can investigate without needing deeper knowledge of the alert setup.
@@ -145,6 +151,8 @@ Annotations are displayed in Grafana and are included by default in notification
- `runbook_url`: The runbook page to guide operators managing a potential incident.
- `__dashboardUid__` and `__panelId__`: [Link the alert to a dashboard and panel](ref:link-alert-rules-to-panels) to facilitate alert investigation.
+{{< /shared >}}
+
For example, you can edit the annotation `summary` to explain why the alert was triggered:
```
diff --git a/docs/sources/alerting/fundamentals/notifications/_index.md b/docs/sources/alerting/fundamentals/notifications/_index.md
index 3a0599722d3..fd8a77448b8 100644
--- a/docs/sources/alerting/fundamentals/notifications/_index.md
+++ b/docs/sources/alerting/fundamentals/notifications/_index.md
@@ -83,10 +83,14 @@ Start defining your [contact points](ref:contact-points) to specify how to recei
### Contact points
+{{< shared id="contact-points-fundamentals" >}}
+
[Contact points](ref:contact-points) contain the configuration for sending alert notifications, specifying destinations like email, Slack, IRM, webhooks, and their notification messages.
A contact point is a list of integrations, each sending a message to a specific destination.
+{{< /shared >}}
+
By default, notification messages include common alert details, such as the number of alerts, alert names, labels, annotations, and other alert information. You can also customize notification messages and use notification templates.
First, create the contact point and test the notifications. Then, configure the alert rule to send its notifications to either a contact point or through Notification Policies.
From 4a26cb92c633b025441568388abcd1a2f8122bf4 Mon Sep 17 00:00:00 2001
From: Andrew Hackmann <5140848+bossinc@users.noreply.github.com>
Date: Fri, 1 Aug 2025 11:01:25 -0500
Subject: [PATCH 31/89] ElasticSearch: Fix inline casting bug when validating
the index (#108951)
split out inline casts
---
pkg/tsdb/elasticsearch/healthcheck.go | 10 +++++++---
pkg/tsdb/elasticsearch/healthcheck_test.go | 10 ++++++++++
2 files changed, 17 insertions(+), 3 deletions(-)
diff --git a/pkg/tsdb/elasticsearch/healthcheck.go b/pkg/tsdb/elasticsearch/healthcheck.go
index a3e04aafb6f..928945691de 100644
--- a/pkg/tsdb/elasticsearch/healthcheck.go
+++ b/pkg/tsdb/elasticsearch/healthcheck.go
@@ -191,11 +191,15 @@ func validateIndex(ctx context.Context, ds *es.DatasourceInfo) (message string,
return "Failed to unmarshal field capabilities response", "error"
}
if fieldCaps["error"] != nil {
- if errorMessage, ok := fieldCaps["error"].(map[string]any)["reason"].(string); ok {
- return fmt.Sprintf("Error validating index: %s", errorMessage), "warning"
- } else {
+ errorMap, ok := fieldCaps["error"].(map[string]any)
+ if !ok {
return "Error validating index", "warning"
}
+ errorMessage, ok := errorMap["reason"].(string)
+ if !ok {
+ return "Error validating index", "warning"
+ }
+ return fmt.Sprintf("Error validating index: %s", errorMessage), "warning"
}
fields, ok := fieldCaps["fields"].(map[string]any)
diff --git a/pkg/tsdb/elasticsearch/healthcheck_test.go b/pkg/tsdb/elasticsearch/healthcheck_test.go
index b3f6dc97c93..48fd00e8adc 100644
--- a/pkg/tsdb/elasticsearch/healthcheck_test.go
+++ b/pkg/tsdb/elasticsearch/healthcheck_test.go
@@ -58,6 +58,16 @@ func Test_validateIndex_Warning_ErrorValidatingIndex(t *testing.T) {
assert.Equal(t, "Elasticsearch data source is healthy. Warning: Error validating index: index_not_found", res.Message)
}
+func Test_validateIndex_Warning_ErrorValidatingIndex2(t *testing.T) {
+ service := GetMockService(http.StatusOK, "200 OK", `{"status":"green"}`, `{"error":"not a map"}`)
+ res, _ := service.CheckHealth(mockedCfg, &backend.CheckHealthRequest{
+ PluginContext: backend.PluginContext{},
+ Headers: nil,
+ })
+ assert.Equal(t, backend.HealthStatusOk, res.Status)
+ assert.Equal(t, "Elasticsearch data source is healthy. Warning: Error validating index", res.Message)
+}
+
func Test_validateIndex_Warning_WrongTimestampType(t *testing.T) {
service := GetMockService(http.StatusOK, "200 OK", `{"status":"green"}`, `{"fields":{"timestamp":{"float":{"metadata_field":true}}}}`)
res, _ := service.CheckHealth(mockedCfg, &backend.CheckHealthRequest{
From 147df3de0893fd1f6b4866edbc0ce71c9833bc2a Mon Sep 17 00:00:00 2001
From: Eve Meelan <81647476+Eve832@users.noreply.github.com>
Date: Fri, 1 Aug 2025 12:23:58 -0400
Subject: [PATCH 32/89] Pricing update: no more Cloud Advanced (#109056)
* scrub Cloud Advanced
* prettier edit
---
.../migration-guide/manually-migrate-to-grafana-cloud.md | 2 +-
.../alerting/alerting-rules/create-grafana-managed-rule.md | 2 +-
docs/sources/introduction/grafana-enterprise.md | 2 +-
.../configure-grafana/configure-custom-branding/index.md | 5 +----
.../configure-authentication/auth-proxy/index.md | 3 ++-
.../configure-authentication/azuread/index.md | 2 +-
.../configure-authentication/generic-oauth/index.md | 2 +-
.../configure-authentication/github/index.md | 2 +-
.../configure-authentication/gitlab/index.md | 2 +-
.../configure-authentication/google/index.md | 2 +-
.../configure-authentication/keycloak/index.md | 2 +-
.../configure-authentication/okta/index.md | 2 +-
.../saml/configure-saml-team-role-mapping/_index.md | 2 +-
.../configure-authentication/saml/saml-ui/_index.md | 4 ++--
.../configure-security/configure-request-security.md | 2 +-
.../configure-security/configure-scim-provisioning/_index.md | 2 +-
.../configure-scim-with-azuread/_index.md | 4 ++--
.../configure-scim-with-okta/_index.md | 4 ++--
.../configure-scim-provisioning/manage-users-teams/_index.md | 2 +-
docs/sources/setup-grafana/configure-security/export-logs.md | 2 +-
.../configure-security/planning-iam-strategy/index.md | 2 +-
21 files changed, 25 insertions(+), 27 deletions(-)
diff --git a/docs/sources/administration/migration-guide/manually-migrate-to-grafana-cloud.md b/docs/sources/administration/migration-guide/manually-migrate-to-grafana-cloud.md
index e68083eb4a0..5f4fa75a787 100644
--- a/docs/sources/administration/migration-guide/manually-migrate-to-grafana-cloud.md
+++ b/docs/sources/administration/migration-guide/manually-migrate-to-grafana-cloud.md
@@ -290,7 +290,7 @@ The following customizations are available via support:
- Enabling [feature toggles](http://www.grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/feature-toggles).
- [Single sign-on and team sync using SAML, LDAP, or OAuth](http://www.grafana.com/docs/grafana/latest/setup-grafana/configure-security/configure-authentication).
- Enable [embedding Grafana dashboards in other applications](https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/#allow_embedding) for Grafana Cloud contracted customers.
-- [Audit logging](https://grafana.com/docs/grafana/latest/setup-grafana/configure-security/audit-grafana/) ([Usage insights logs and dashboards](https://grafana.com/docs/grafana-cloud/account-management/usage-insights/) are available in Grafana Cloud Pro and Advanced by default).
+- [Audit logging](https://grafana.com/docs/grafana/latest/setup-grafana/configure-security/audit-grafana/) ([Usage insights logs and dashboards](https://grafana.com/docs/grafana-cloud/account-management/usage-insights/) are available in select Grafana Cloud paid accounts).
Note that the following custom configurations are not supported in Grafana Cloud:
diff --git a/docs/sources/alerting/alerting-rules/create-grafana-managed-rule.md b/docs/sources/alerting/alerting-rules/create-grafana-managed-rule.md
index df7abac34b0..f7d1214271a 100644
--- a/docs/sources/alerting/alerting-rules/create-grafana-managed-rule.md
+++ b/docs/sources/alerting/alerting-rules/create-grafana-managed-rule.md
@@ -161,7 +161,7 @@ You can find the public data sources that support alert rules in the [Grafana Pl
In Grafana Cloud, the number of Grafana-managed alert rules you can create depends on your Grafana Cloud plan.
- Free Forever plan: You can create up to 100 free alert rules, with each alert rule having a maximum of 1000 alert instances.
-- All paid plans (Pro and Advanced): They have a soft limit of 2000 alert rules and support unlimited alert instances. To increase the limit, open a support ticket from the [Cloud portal](/docs/grafana-cloud/account-management/support/).
+- All paid plans: They have a soft limit of 2000 alert rules and support unlimited alert instances. To increase the limit, open a support ticket from the [Cloud portal](/docs/grafana-cloud/account-management/support/).
### Permissions
diff --git a/docs/sources/introduction/grafana-enterprise.md b/docs/sources/introduction/grafana-enterprise.md
index 3993809d076..ef8884f3c55 100644
--- a/docs/sources/introduction/grafana-enterprise.md
+++ b/docs/sources/introduction/grafana-enterprise.md
@@ -19,7 +19,7 @@ To learn more about Grafana Enterprise, refer to [our product page](/enterprise)
## Enterprise features in Grafana Cloud
-Many Grafana Enterprise features are also available in [Grafana Cloud](/docs/grafana-cloud) Free, Pro, and Advanced accounts. For details, refer to [Grafana Cloud pricing](/pricing/#featuresTable).
+Many Grafana Enterprise features are also available in paid [Grafana Cloud](/docs/grafana-cloud) accounts. For details, refer to [Grafana Cloud features](/docs/grafana-cloud/introduction/understand-grafana-cloud-features/). For pricing and plans, refer to [Grafana Cloud pricing](https://grafana.com/pricing/).
To migrate to Grafana Cloud, refer to [Migrate from Grafana Enterprise to Grafana Cloud](/docs/grafana//administration/migration-guide/)
diff --git a/docs/sources/setup-grafana/configure-grafana/configure-custom-branding/index.md b/docs/sources/setup-grafana/configure-grafana/configure-custom-branding/index.md
index 2fd80cbac5f..92004450844 100644
--- a/docs/sources/setup-grafana/configure-grafana/configure-custom-branding/index.md
+++ b/docs/sources/setup-grafana/configure-grafana/configure-custom-branding/index.md
@@ -15,10 +15,7 @@ weight: 300
Custom branding enables you to replace the Grafana Labs brand and logo with your corporate brand and logo.
{{< admonition type="note" >}}
-Available in [Grafana Enterprise](../../../introduction/grafana-enterprise/) and [Grafana Cloud](/docs/grafana-cloud). For Cloud Advanced and Enterprise customers, please provide custom elements and logos to our Support team. We will help you host your images and update your custom branding.
-
-This feature is not available for Grafana Free and Pro tiers.
-For more information on feature availability across plans, refer to our [feature comparison page](/docs/grafana-cloud/cost-management-and-billing/understand-grafana-cloud-features/)
+Available in [Grafana Enterprise](../../../introduction/grafana-enterprise/) and to customers on select Grafana Cloud plans. For pricing information, visit [pricing](https://grafana.com/pricing/) or contact our sales team. For Cloud customers, please provide custom elements and logos to our Support team. We will help you host your images and update your custom branding.
{{< /admonition >}}
diff --git a/docs/sources/setup-grafana/configure-security/configure-authentication/auth-proxy/index.md b/docs/sources/setup-grafana/configure-security/configure-authentication/auth-proxy/index.md
index 3d1c4a48a6b..a379eb82d52 100644
--- a/docs/sources/setup-grafana/configure-security/configure-authentication/auth-proxy/index.md
+++ b/docs/sources/setup-grafana/configure-security/configure-authentication/auth-proxy/index.md
@@ -237,7 +237,8 @@ If the user is deleted from Grafana, the user will be not be able to login and r
### Team Sync
{{< admonition type="note" >}}
-Available in [Grafana Enterprise](https://grafana.com/docs/grafana//introduction/grafana-enterprise/) and [Grafana Cloud Advanced](https://grafana.com/docs/grafana-cloud/).
+Available in [Grafana Enterprise](https://grafana.com/docs/grafana//introduction/grafana-enterprise/) and to customers on select Grafana Cloud plans. For pricing information, visit [pricing](https://grafana.com/pricing/) or contact our sales team.
+
{{< /admonition >}}
With Team Sync, it's possible to set up synchronization between teams in your authentication provider and Grafana. You can send Grafana values as part of an HTTP header and have Grafana map them to your team structure. This allows you to put users into specific teams automatically.
diff --git a/docs/sources/setup-grafana/configure-security/configure-authentication/azuread/index.md b/docs/sources/setup-grafana/configure-security/configure-authentication/azuread/index.md
index 69e6e1415f0..f3d2c5bea74 100644
--- a/docs/sources/setup-grafana/configure-security/configure-authentication/azuread/index.md
+++ b/docs/sources/setup-grafana/configure-security/configure-authentication/azuread/index.md
@@ -414,7 +414,7 @@ auto_login = true
### Team Sync
{{< admonition type="note" >}}
-Available in [Grafana Enterprise](https://grafana.com/docs/grafana//introduction/grafana-enterprise/) and [Grafana Cloud Advanced](https://grafana.com/docs/grafana-cloud/).
+Available in [Grafana Enterprise](https://grafana.com/docs/grafana//introduction/grafana-enterprise/) and to customers on select Grafana Cloud plans. For pricing information, visit [pricing](https://grafana.com/pricing/) or contact our sales team.
{{< /admonition >}}
With Team Sync you can map your Entra ID groups to teams in Grafana so that your users will automatically be added to
diff --git a/docs/sources/setup-grafana/configure-security/configure-authentication/generic-oauth/index.md b/docs/sources/setup-grafana/configure-security/configure-authentication/generic-oauth/index.md
index 9ebe70e6e55..f28dc35b7f6 100644
--- a/docs/sources/setup-grafana/configure-security/configure-authentication/generic-oauth/index.md
+++ b/docs/sources/setup-grafana/configure-security/configure-authentication/generic-oauth/index.md
@@ -320,7 +320,7 @@ org_mapping = org_foo:org_foo:Viewer org_bar:org_bar:Editor *:org_baz:Editor
## Configure team synchronization
{{< admonition type="note" >}}
-Available in [Grafana Enterprise](https://grafana.com/docs/grafana//introduction/grafana-enterprise/) and [Grafana Cloud Advanced](https://grafana.com/docs/grafana-cloud/).
+Available in [Grafana Enterprise](https://grafana.com/docs/grafana//introduction/grafana-enterprise/) and to customers on select Grafana Cloud plans. For pricing information, visit [pricing](https://grafana.com/pricing/) or contact our sales team.
{{< /admonition >}}
By using Team Sync, you can link your OAuth2 groups to teams within Grafana. This will automatically assign users to the appropriate teams.
diff --git a/docs/sources/setup-grafana/configure-security/configure-authentication/github/index.md b/docs/sources/setup-grafana/configure-security/configure-authentication/github/index.md
index 7a327576cfd..c11ef4fd3ab 100644
--- a/docs/sources/setup-grafana/configure-security/configure-authentication/github/index.md
+++ b/docs/sources/setup-grafana/configure-security/configure-authentication/github/index.md
@@ -211,7 +211,7 @@ role_attribute_path = [login=='octocat'][0] && 'GrafanaAdmin' || 'Viewer'
## Configure team synchronization
{{< admonition type="note" >}}
-Available in [Grafana Enterprise](https://grafana.com/docs/grafana//introduction/grafana-enterprise/) and [Grafana Cloud Advanced](https://grafana.com/docs/grafana-cloud/).
+Available in [Grafana Enterprise](https://grafana.com/docs/grafana//introduction/grafana-enterprise/) and to customers on select Grafana Cloud plans. For pricing information, visit [pricing](https://grafana.com/pricing/) or contact our sales team.
{{< /admonition >}}
By using Team Sync, you can map teams from your GitHub organization to teams within Grafana. This will automatically assign users to the appropriate teams.
diff --git a/docs/sources/setup-grafana/configure-security/configure-authentication/gitlab/index.md b/docs/sources/setup-grafana/configure-security/configure-authentication/gitlab/index.md
index 22363e59b54..ac45c634599 100644
--- a/docs/sources/setup-grafana/configure-security/configure-authentication/gitlab/index.md
+++ b/docs/sources/setup-grafana/configure-security/configure-authentication/gitlab/index.md
@@ -236,7 +236,7 @@ use_refresh_token = true
## Configure team synchronization
{{< admonition type="note" >}}
-Available in [Grafana Enterprise](https://grafana.com/docs/grafana//introduction/grafana-enterprise/) and [Grafana Cloud Advanced](https://grafana.com/docs/grafana-cloud/).
+Available in [Grafana Enterprise](https://grafana.com/docs/grafana//introduction/grafana-enterprise/) and to customers on select Grafana Cloud plans. For pricing information, visit [pricing](https://grafana.com/pricing/) or contact our sales team.
{{< /admonition >}}
By using Team Sync, you can map GitLab groups to teams within Grafana. This will automatically assign users to the appropriate teams.
diff --git a/docs/sources/setup-grafana/configure-security/configure-authentication/google/index.md b/docs/sources/setup-grafana/configure-security/configure-authentication/google/index.md
index 59f2c6951a8..367d05c068d 100644
--- a/docs/sources/setup-grafana/configure-security/configure-authentication/google/index.md
+++ b/docs/sources/setup-grafana/configure-security/configure-authentication/google/index.md
@@ -160,7 +160,7 @@ auto_login = true
### Configure team synchronization
{{< admonition type="note" >}}
-Available in [Grafana Enterprise](https://grafana.com/docs/grafana//introduction/grafana-enterprise/) and [Grafana Cloud Advanced](https://grafana.com/docs/grafana-cloud/).
+Available in [Grafana Enterprise](https://grafana.com/docs/grafana//introduction/grafana-enterprise/) and to customers on select Grafana Cloud plans. For pricing information, visit [pricing](https://grafana.com/pricing/) or contact our sales team.
{{< /admonition >}}
With team sync, you can easily add users to teams by utilizing their Google groups. To set up team sync for Google OAuth, refer to the following example.
diff --git a/docs/sources/setup-grafana/configure-security/configure-authentication/keycloak/index.md b/docs/sources/setup-grafana/configure-security/configure-authentication/keycloak/index.md
index 88adc9d4a2a..6425eb09db7 100644
--- a/docs/sources/setup-grafana/configure-security/configure-authentication/keycloak/index.md
+++ b/docs/sources/setup-grafana/configure-security/configure-authentication/keycloak/index.md
@@ -109,7 +109,7 @@ viewer
## Team sync
{{< admonition type="note" >}}
-Available in [Grafana Enterprise](https://grafana.com/docs/grafana//introduction/grafana-enterprise/) and [Grafana Cloud Advanced](https://grafana.com/docs/grafana-cloud/).
+Available in [Grafana Enterprise](https://grafana.com/docs/grafana//introduction/grafana-enterprise/) and to customers on select Grafana Cloud plans. For pricing information, visit [pricing](https://grafana.com/pricing/) or contact our sales team.
{{< /admonition >}}
[Teamsync](https://grafana.com/docs/grafana//setup-grafana/configure-security/configure-team-sync/) is a feature that allows you to map groups from your identity provider to Grafana teams. This is useful if you want to give your users access to specific dashboards or folders based on their group membership.
diff --git a/docs/sources/setup-grafana/configure-security/configure-authentication/okta/index.md b/docs/sources/setup-grafana/configure-security/configure-authentication/okta/index.md
index 8166760890f..53618823a06 100644
--- a/docs/sources/setup-grafana/configure-security/configure-authentication/okta/index.md
+++ b/docs/sources/setup-grafana/configure-security/configure-authentication/okta/index.md
@@ -236,7 +236,7 @@ org_mapping = ["Group 1:org_foo:Viewer", "Group 2:org_bar:Editor", "*:3:Editor"]
### Configure team synchronization
{{< admonition type="note" >}}
-Available in [Grafana Enterprise](https://grafana.com/docs/grafana//introduction/grafana-enterprise/) and [Grafana Cloud Advanced](https://grafana.com/docs/grafana-cloud/).
+Available in [Grafana Enterprise](https://grafana.com/docs/grafana//introduction/grafana-enterprise/) and to customers on select Grafana Cloud plans. For pricing information, visit [pricing](https://grafana.com/pricing/) or contact our sales team.
{{< /admonition >}}
By using Team Sync, you can link your Okta groups to teams within Grafana. This will automatically assign users to the appropriate teams.
diff --git a/docs/sources/setup-grafana/configure-security/configure-authentication/saml/configure-saml-team-role-mapping/_index.md b/docs/sources/setup-grafana/configure-security/configure-authentication/saml/configure-saml-team-role-mapping/_index.md
index edfedcefb9a..ad4dab8cf00 100644
--- a/docs/sources/setup-grafana/configure-security/configure-authentication/saml/configure-saml-team-role-mapping/_index.md
+++ b/docs/sources/setup-grafana/configure-security/configure-authentication/saml/configure-saml-team-role-mapping/_index.md
@@ -12,7 +12,7 @@ weight: 540
# Configure team sync for SAML
{{< admonition type="note" >}}
-Available in [Grafana Enterprise](https://grafana.com/docs/grafana//introduction/grafana-enterprise/) and [Grafana Cloud Advanced](https://grafana.com/docs/grafana-cloud/).
+Available in [Grafana Enterprise](https://grafana.com/docs/grafana//introduction/grafana-enterprise/) and to customers on select Grafana Cloud plans. For pricing information, visit [pricing](https://grafana.com/pricing/) or contact our sales team.
{{< /admonition >}}
To use SAML Team sync, set [`assertion_attribute_groups`](https://grafana.com/docs/grafana//setup-grafana/configure-grafana/enterprise-configuration/#assertion_attribute_groups) to the attribute name where you store user groups. Then Grafana will use attribute values extracted from SAML assertion to add user into the groups with the same name configured on the External group sync tab.
diff --git a/docs/sources/setup-grafana/configure-security/configure-authentication/saml/saml-ui/_index.md b/docs/sources/setup-grafana/configure-security/configure-authentication/saml/saml-ui/_index.md
index 52d2c84fbfd..dee23439664 100644
--- a/docs/sources/setup-grafana/configure-security/configure-authentication/saml/saml-ui/_index.md
+++ b/docs/sources/setup-grafana/configure-security/configure-authentication/saml/saml-ui/_index.md
@@ -14,7 +14,7 @@ weight: 510
# Configure SAML authentication using the Grafana user interface
{{< admonition type="note" >}}
-Available in [Grafana Enterprise](https://grafana.com/docs/grafana//introduction/grafana-enterprise/) version 10.0 and later, and [Grafana Cloud Pro or Advanced](https://grafana.com/docs/grafana//introduction/grafana-cloud/).
+Available in [Grafana Enterprise](https://grafana.com/docs/grafana//introduction/grafana-enterprise/) version 10.0 and later, and to customers on select Grafana Cloud plans. For pricing information, visit [pricing](https://grafana.com/pricing/) or contact our sales team.
{{< /admonition >}}
You can configure SAML authentication in Grafana through the user interface (UI) or the Grafana configuration file. For instructions on how to set up SAML using the Grafana configuration file, refer to [Configure SAML authentication using the configuration file](../#configure-saml-using-the-grafana-config-file).
@@ -40,7 +40,7 @@ To follow this guide, you need:
These permissions are granted by `fixed:authentication.config:writer` role.
By default, this role is granted to Grafana server administrator in self-hosted instances and to Organization admins in Grafana Cloud instances.
-- Grafana instance running Grafana version 10.0 or later with [Grafana Enterprise](https://grafana.com/docs/grafana//introduction/grafana-enterprise/) or [Grafana Cloud Pro or Advanced](https://grafana.com/docs/grafana//introduction/grafana-cloud/) license.
+- Grafana instance running Grafana version 10.0 or later with [Grafana Enterprise](https://grafana.com/docs/grafana//introduction/grafana-enterprise/) and to customers on select Grafana Cloud plans. For pricing information, visit [pricing](https://grafana.com/pricing/) or contact our sales team.
## Steps To Configure SAML Authentication
diff --git a/docs/sources/setup-grafana/configure-security/configure-request-security.md b/docs/sources/setup-grafana/configure-security/configure-request-security.md
index 8dca935e928..8164799a6d8 100644
--- a/docs/sources/setup-grafana/configure-security/configure-request-security.md
+++ b/docs/sources/setup-grafana/configure-security/configure-request-security.md
@@ -19,7 +19,7 @@ Request security allows you to limit requests from the Grafana server by targeti
This can be used to limit access to internal systems that the server Grafana runs on can access but that users of Grafana should not be able to access. This feature does not affect traffic from the Grafana users browser.
{{< admonition type="note" >}}
-Available in [Grafana Enterprise](../../../introduction/grafana-enterprise/) and [Grafana Cloud Pro and Advanced](/docs/grafana-cloud/).
+Available in [Grafana Enterprise](../../../introduction/grafana-enterprise/) and to users on select Grafana Cloud account plans. For pricing information, visit our [pricing page](https://grafana.com/pricing/) or contact our sales team.
{{< /admonition >}}
{{< admonition type="note" >}}
diff --git a/docs/sources/setup-grafana/configure-security/configure-scim-provisioning/_index.md b/docs/sources/setup-grafana/configure-security/configure-scim-provisioning/_index.md
index d3c2496f75b..f8e2ee47611 100644
--- a/docs/sources/setup-grafana/configure-security/configure-scim-provisioning/_index.md
+++ b/docs/sources/setup-grafana/configure-security/configure-scim-provisioning/_index.md
@@ -20,7 +20,7 @@ weight: 300
System for Cross-domain Identity Management (SCIM) is an open standard that allows automated user provisioning and management. With SCIM, you can automate the provisioning of users and groups from your identity provider to Grafana.
{{< admonition type="note" >}}
-Available in [Grafana Enterprise](/docs/grafana//introduction/grafana-enterprise/) and [Grafana Cloud Pro and Advanced](/docs/grafana-cloud/) in [public preview](https://grafana.com/docs/release-life-cycle/).
+Available in [Grafana Enterprise](/docs/grafana//introduction/grafana-enterprise/) and select Grafana Cloud plans in [public preview](https://grafana.com/docs/release-life-cycle/).
Grafana Labs offers limited support, and breaking changes might occur prior to the feature being made generally available.
{{< /admonition >}}
diff --git a/docs/sources/setup-grafana/configure-security/configure-scim-provisioning/configure-scim-with-azuread/_index.md b/docs/sources/setup-grafana/configure-security/configure-scim-provisioning/configure-scim-with-azuread/_index.md
index 1554484f45b..bc04f80fa52 100644
--- a/docs/sources/setup-grafana/configure-security/configure-scim-provisioning/configure-scim-with-azuread/_index.md
+++ b/docs/sources/setup-grafana/configure-security/configure-scim-provisioning/configure-scim-with-azuread/_index.md
@@ -21,7 +21,7 @@ weight: 320
# Configure SCIM with Azure AD
{{< admonition type="note" >}}
-Available in [Grafana Enterprise](/docs/grafana//introduction/grafana-enterprise/) and [Grafana Cloud Pro and Advanced](/docs/grafana-cloud/).
+Available in [Grafana Enterprise](/docs/grafana//introduction/grafana-enterprise/) and to customers on select Grafana Cloud plans. For pricing information, visit [pricing](https://grafana.com/pricing/) or contact our sales team.
{{< /admonition >}}
{{< admonition type="warning" >}}
@@ -49,7 +49,7 @@ Refer to the [SAML authentication with Azure AD documentation](../../configure-a
Before configuring SCIM with Azure AD, ensure you have:
-- Grafana Enterprise or Grafana Cloud Advanced
+- Grafana Enterprise or a paid Grafana Cloud account with SCIM provisioning enabled.
- Admin access to both Grafana and Azure AD
- SCIM feature enabled in Grafana
diff --git a/docs/sources/setup-grafana/configure-security/configure-scim-provisioning/configure-scim-with-okta/_index.md b/docs/sources/setup-grafana/configure-security/configure-scim-provisioning/configure-scim-with-okta/_index.md
index 1555ae72754..50434391f7e 100644
--- a/docs/sources/setup-grafana/configure-security/configure-scim-provisioning/configure-scim-with-okta/_index.md
+++ b/docs/sources/setup-grafana/configure-security/configure-scim-provisioning/configure-scim-with-okta/_index.md
@@ -19,7 +19,7 @@ weight: 320
# Configure SCIM with Okta
{{< admonition type="note" >}}
-Available in [Grafana Enterprise](/docs/grafana//introduction/grafana-enterprise/) and [Grafana Cloud Pro and Advanced](/docs/grafana-cloud/).
+Available in [Grafana Enterprise](/docs/grafana//introduction/grafana-enterprise/) and to customers on select Grafana Cloud plans. For pricing information, visit [pricing](https://grafana.com/pricing/) or contact our sales team.
{{< /admonition >}}
{{< admonition type="warning" >}}
@@ -39,7 +39,7 @@ For more information, refer to the [feature toggles documentation](/docs/grafana
Before configuring SCIM with Okta, ensure you have:
-- Grafana Enterprise or Grafana Cloud Advanced
+- Grafana Enterprise or a paid Grafana Cloud account with SCIM provisioning enabled.
- Admin access to both Grafana and Okta
- [SAML authentication configured with Okta](../../configure-authentication/saml/configure-saml-with-okta/)
- SCIM feature enabled in Grafana
diff --git a/docs/sources/setup-grafana/configure-security/configure-scim-provisioning/manage-users-teams/_index.md b/docs/sources/setup-grafana/configure-security/configure-scim-provisioning/manage-users-teams/_index.md
index 2b3b0524884..ed9592e5929 100644
--- a/docs/sources/setup-grafana/configure-security/configure-scim-provisioning/manage-users-teams/_index.md
+++ b/docs/sources/setup-grafana/configure-security/configure-scim-provisioning/manage-users-teams/_index.md
@@ -18,7 +18,7 @@ weight: 310
# Manage users and teams with SCIM
{{< admonition type="note" >}}
-Available in [Grafana Enterprise](/docs/grafana//introduction/grafana-enterprise/) and [Grafana Cloud Pro and Advanced](/docs/grafana-cloud/).
+Available in [Grafana Enterprise](/docs/grafana//introduction/grafana-enterprise/) and to customers on select Grafana Cloud plans. For pricing information, visit [pricing](https://grafana.com/pricing/) or contact our sales team.
{{< /admonition >}}
{{< admonition type="warning" >}}
diff --git a/docs/sources/setup-grafana/configure-security/export-logs.md b/docs/sources/setup-grafana/configure-security/export-logs.md
index 7bb7b453fc7..f55336f8a67 100644
--- a/docs/sources/setup-grafana/configure-security/export-logs.md
+++ b/docs/sources/setup-grafana/configure-security/export-logs.md
@@ -18,7 +18,7 @@ weight: 900
# Export logs of usage insights
{{< admonition type="note" >}}
-Available in [Grafana Enterprise](../../../introduction/grafana-enterprise/) and [Grafana Cloud Pro and Advanced](/docs/grafana-cloud/).
+Available in [Grafana Enterprise](../../../introduction/grafana-enterprise/) and to customers on select Grafana Cloud plans. For pricing information, visit [pricing](https://grafana.com/pricing/) or contact our sales team.
{{< /admonition >}}
By exporting usage logs to Loki, you can directly query them and create dashboards of the information that matters to you most, such as dashboard errors, most active organizations, or your top-10 most-used queries. This configuration is done for you in Grafana Cloud, with provisioned dashboards. Read about them in the [Grafana Cloud documentation](/docs/grafana-cloud/usage-insights/).
diff --git a/docs/sources/setup-grafana/configure-security/planning-iam-strategy/index.md b/docs/sources/setup-grafana/configure-security/planning-iam-strategy/index.md
index 34d3ccd8dcb..e4a44e54ed6 100644
--- a/docs/sources/setup-grafana/configure-security/planning-iam-strategy/index.md
+++ b/docs/sources/setup-grafana/configure-security/planning-iam-strategy/index.md
@@ -182,7 +182,7 @@ When connecting Grafana to an identity provider, it's important to think beyond
Team sync is a feature that allows you to synchronize teams or groups from your authentication provider with teams in Grafana. This means that users of specific teams or groups in LDAP, OAuth, or SAML will be automatically added or removed as members of corresponding teams in Grafana. Whenever a user logs in, Grafana will check for any changes in the teams or groups of the authentication provider and update the user's teams in Grafana accordingly. This makes it easy to manage user permissions across multiple systems.
{{< admonition type="note" >}}
-Available in [Grafana Enterprise](../../../introduction/grafana-enterprise/) and [Grafana Cloud Advanced](/docs/grafana-cloud/).
+Available in [Grafana Enterprise](../../../introduction/grafana-enterprise/) and to customers on select Grafana Cloud plans. For pricing information, visit [pricing](https://grafana.com/pricing/) or contact our sales team.
{{< /admonition >}}
{{< admonition type="note" >}}
From 7e479689395650976f735cfd5528016048ec0652 Mon Sep 17 00:00:00 2001
From: Paul Marbach
Date: Fri, 1 Aug 2025 12:27:53 -0400
Subject: [PATCH 33/89] TableNG: Wrap text for DataLinks and Pills (#108645)
* TableNG: Wrap text for DataLinks and Pills; groundwork for max wrap length
* disable editing max wrapped lines for now
* disable wrap text line limit e2e
* new i18n extract after commenting out input
* wip
* kill max wrapped lines for now
* more cleanup
* remove targeting classes added for max wrapped lines
* fix Pill test
* couple more style cleanups
* fix e2es given these updates
* add a couple tests
* wip: tests
* add tests
* bump up capital letters in lorem ipsum
* fix copy-pasta mistake
* use a local count instead of getCellLinks
* fix linting on test
---
.betterer.results | 6 +-
.../panels-suite/table-kitchenSink.spec.ts | 602 +++++++++---------
.../panels-suite/table-sparkline.spec.ts | 39 +-
.../grafana-schema/src/common/common.gen.ts | 87 +--
packages/grafana-schema/src/common/table.cue | 25 +-
.../Table/TableNG/Cells/PillCell.test.tsx | 152 ++---
.../Table/TableNG/Cells/PillCell.tsx | 92 ++-
.../src/components/Table/TableNG/TableNG.tsx | 67 +-
.../components/Table/TableNG/hooks.test.ts | 81 +--
.../src/components/Table/TableNG/hooks.ts | 13 +-
.../src/components/Table/TableNG/types.ts | 11 +-
.../components/Table/TableNG/utils.test.ts | 482 ++++++--------
.../src/components/Table/TableNG/utils.ts | 132 +++-
.../table/cells/AutoCellOptionsEditor.tsx | 2 -
.../ColorBackgroundCellOptionsEditor.tsx | 2 -
.../table/table-new/TableCellOptionEditor.tsx | 21 +-
.../table-new/cells/AutoCellOptionsEditor.tsx | 27 -
.../ColorBackgroundCellOptionsEditor.tsx | 5 +-
.../table-new/cells/TextWrapOptionsEditor.tsx | 29 +
public/locales/en-US/grafana.json | 5 +-
20 files changed, 930 insertions(+), 950 deletions(-)
delete mode 100644 public/app/plugins/panel/table/table-new/cells/AutoCellOptionsEditor.tsx
create mode 100644 public/app/plugins/panel/table/table-new/cells/TextWrapOptionsEditor.tsx
diff --git a/.betterer.results b/.betterer.results
index 239af992de5..0493a87d6d3 100644
--- a/.betterer.results
+++ b/.betterer.results
@@ -3919,9 +3919,6 @@ exports[`better eslint`] = {
"public/app/plugins/panel/table/table-new/TableCellOptionEditor.tsx:5381": [
[0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"]
],
- "public/app/plugins/panel/table/table-new/cells/AutoCellOptionsEditor.tsx:5381": [
- [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"]
- ],
"public/app/plugins/panel/table/table-new/cells/BarGaugeCellOptionsEditor.tsx:5381": [
[0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"],
[0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "1"]
@@ -3939,6 +3936,9 @@ exports[`better eslint`] = {
[0, 0, 0, "\'VerticalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"],
[0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "1"]
],
+ "public/app/plugins/panel/table/table-new/cells/TextWrapOptionsEditor.tsx:5381": [
+ [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"]
+ ],
"public/app/plugins/panel/table/table-new/migrations.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
[0, 0, 0, "Unexpected any. Specify a different type.", "1"],
diff --git a/e2e-playwright/panels-suite/table-kitchenSink.spec.ts b/e2e-playwright/panels-suite/table-kitchenSink.spec.ts
index f386fb4175c..c7947fcd281 100644
--- a/e2e-playwright/panels-suite/table-kitchenSink.spec.ts
+++ b/e2e-playwright/panels-suite/table-kitchenSink.spec.ts
@@ -4,12 +4,7 @@ import { test, expect } from '@grafana/plugin-e2e';
const DASHBOARD_UID = 'dcb9f5e9-8066-4397-889e-864b99555dbb';
-test.use({
- viewport: { width: 2000, height: 1080 },
- featureToggles: {
- tableNextGen: true,
- },
-});
+test.use({ viewport: { width: 2000, height: 1080 }, featureToggles: { tableNextGen: true } });
// helper utils
const waitForTableLoad = async (loc: Page | Locator) => {
@@ -46,337 +41,324 @@ const getColumnIdx = async (loc: Page | Locator, columnName: string) => {
return result;
};
-test.describe(
- 'Panels test: Table - Kitchen Sink',
- {
- tag: ['@panels'],
- },
- () => {
- test('Tests word wrap, hover overflow, and cell inspect', async ({ gotoDashboardPage, selectors, page }) => {
- const dashboardPage = await gotoDashboardPage({
- uid: DASHBOARD_UID,
- queryParams: new URLSearchParams({ editPanel: '1' }),
- });
+test.describe('Panels test: Table - Kitchen Sink', { tag: ['@panels', '@table'] }, () => {
+ test('Tests word wrap, hover overflow, and cell inspect', async ({ gotoDashboardPage, selectors, page }) => {
+ const dashboardPage = await gotoDashboardPage({
+ uid: DASHBOARD_UID,
+ queryParams: new URLSearchParams({ editPanel: '1' }),
+ });
- await expect(
- dashboardPage.getByGrafanaSelector(selectors.components.Panels.Panel.title('Table - Kitchen Sink'))
- ).toBeVisible();
+ await expect(
+ dashboardPage.getByGrafanaSelector(selectors.components.Panels.Panel.title('Table - Kitchen Sink'))
+ ).toBeVisible();
- // to avoid a race condition when counting up , wait for react-data-grid to finish rendering.
- await waitForTableLoad(page);
+ // to avoid a race condition when counting up , wait for react-data-grid to finish rendering.
+ await waitForTableLoad(page);
- const longTextColIdx = await getColumnIdx(page, 'Long Text');
+ const longTextColIdx = await getColumnIdx(page, 'Long Text');
- // text wrapping is enabled by default on this panel.
- await expect(getCellHeight(page, 1, longTextColIdx)).resolves.toBeGreaterThan(100);
+ // text wrapping is enabled by default on this panel.
+ await expect(getCellHeight(page, 1, longTextColIdx)).resolves.toBeGreaterThan(100);
- // toggle the lorem ipsum column's wrap text toggle and confirm that the height shrinks.
- await dashboardPage
- .getByGrafanaSelector(selectors.components.PanelEditor.OptionsPane.fieldLabel('Wrap text'))
- .last()
- .click();
- await expect(getCellHeight(page, 1, longTextColIdx)).resolves.toBeLessThan(100);
+ // FIXME very bad selector to get the correct "wrap text" toggle here.
+ // toggle the lorem ipsum column's wrap text toggle and confirm that the height shrinks.
+ await page
+ .locator('[id="Override 13"]')
+ .locator(`[aria-label="${selectors.components.PanelEditor.OptionsPane.fieldLabel('Wrap text')}"]`)
+ .click();
+ await expect(getCellHeight(page, 1, longTextColIdx)).resolves.toBeLessThan(100);
- // test that hover overflow works.
- const loremIpsumCell = await getCell(page, 1, longTextColIdx);
- await loremIpsumCell.scrollIntoViewIfNeeded();
- await loremIpsumCell.hover();
- await expect(getCellHeight(page, 1, longTextColIdx)).resolves.toBeGreaterThan(100);
- await (await getCell(page, 1, longTextColIdx + 1)).hover();
- await expect(getCellHeight(page, 1, longTextColIdx)).resolves.toBeLessThan(100);
+ // test that hover overflow works.
+ const loremIpsumCell = await getCell(page, 1, longTextColIdx);
+ await loremIpsumCell.scrollIntoViewIfNeeded();
+ await loremIpsumCell.hover();
+ await expect(getCellHeight(page, 1, longTextColIdx)).resolves.toBeGreaterThan(100);
+ await (await getCell(page, 1, longTextColIdx + 1)).hover();
+ await expect(getCellHeight(page, 1, longTextColIdx)).resolves.toBeLessThan(100);
- // enable cell inspect, confirm that hover no longer triggers.
+ // enable cell inspect, confirm that hover no longer triggers.
+ await dashboardPage
+ .getByGrafanaSelector(selectors.components.PanelEditor.OptionsPane.fieldLabel('Cell options Cell value inspect'))
+ .first()
+ .locator('label[for="custom.inspect"]')
+ .click();
+ await loremIpsumCell.hover();
+ await expect(getCellHeight(page, 1, longTextColIdx)).resolves.toBeLessThan(100);
+
+ // click cell inspect, check that cell inspection pops open in the side as we'd expect.
+ await loremIpsumCell.getByLabel('Inspect value').click();
+ const loremIpsumText = await loremIpsumCell.textContent();
+ expect(loremIpsumText).toBeDefined();
+ await expect(page.getByRole('dialog').getByText(loremIpsumText!)).toBeVisible();
+ });
+
+ test('Tests visibility and display name via overrides', async ({ gotoDashboardPage, selectors, page }) => {
+ const dashboardPage = await gotoDashboardPage({
+ uid: DASHBOARD_UID,
+ queryParams: new URLSearchParams({ editPanel: '1' }),
+ });
+
+ await expect(
+ dashboardPage.getByGrafanaSelector(selectors.components.Panels.Panel.title('Table - Kitchen Sink'))
+ ).toBeVisible();
+
+ // confirm that "State" column is hidden by default.
+ expect(page.getByRole('row').nth(0)).not.toContainText('State');
+
+ // toggle the "State" column visibility and test that it appears before re-hiding it.
+ // FIXME this selector is utterly godawful, but there's no way to give testIds or aria-labels or anything to
+ // the panel editor builder. we should fix that to make e2e's easier to write for our team.
+ const hideStateColumnSwitch = page.locator('[id="Override 12"]').locator('label').last();
+ await hideStateColumnSwitch.click();
+ expect(page.getByRole('row').nth(0)).toContainText('State');
+
+ // now change the display name of the "State" column.
+ // FIXME it would be good to have a better selector here too.
+ const displayNameInput = page.locator('[id="Override 12"]').locator('input[value="State"]').last();
+ await displayNameInput.fill('State (renamed)');
+ await displayNameInput.press('Enter');
+ expect(page.getByRole('row').nth(0)).toContainText('State (renamed)');
+ });
+
+ // we test niche cases for sorting, filtering, pagination, etc. in a unit tests already.
+ // we mainly want to test the happiest paths for these in e2es as well to check for integration
+ // issues, but the unit tests can confirm that the internal logic works as expected much more quickly and thoroughly.
+ // hashtag testing pyramid.
+ test('Tests sorting by column', async ({ gotoDashboardPage, selectors, page }) => {
+ const dashboardPage = await gotoDashboardPage({
+ uid: DASHBOARD_UID,
+ queryParams: new URLSearchParams({ editPanel: '1' }),
+ });
+
+ await expect(
+ dashboardPage.getByGrafanaSelector(selectors.components.Panels.Panel.title('Table - Kitchen Sink'))
+ ).toBeVisible();
+
+ // click the "State" column header to sort it.
+ const stateColumnHeader = await getCell(page, 0, 1);
+
+ await stateColumnHeader.getByText('Info').click();
+ await expect(stateColumnHeader).toHaveAttribute('aria-sort', 'ascending');
+ expect(getCell(page, 1, 1)).resolves.toContainText('down'); // down or down fast
+
+ await stateColumnHeader.getByText('Info').click();
+ await expect(stateColumnHeader).toHaveAttribute('aria-sort', 'descending');
+ expect(getCell(page, 1, 1)).resolves.toContainText('up'); // up or up fast
+
+ await stateColumnHeader.getByText('Info').click();
+ await expect(stateColumnHeader).not.toHaveAttribute('aria-sort');
+ });
+
+ test('Tests filtering within a column', async ({ gotoDashboardPage, selectors, page }) => {
+ const dashboardPage = await gotoDashboardPage({
+ uid: DASHBOARD_UID,
+ queryParams: new URLSearchParams({ editPanel: '1' }),
+ });
+
+ await expect(
+ dashboardPage.getByGrafanaSelector(selectors.components.Panels.Panel.title('Table - Kitchen Sink'))
+ ).toBeVisible();
+
+ await waitForTableLoad(page);
+
+ const infoColumnIdx = await getColumnIdx(page, 'Info');
+
+ const stateColumnHeader = page.getByRole('columnheader').nth(infoColumnIdx);
+
+ // get the first value in the "State" column, filter it out, then check that it went away.
+ const firstStateValue = (await (await getCell(page, 1, infoColumnIdx)).textContent())!;
+ await stateColumnHeader.getByTestId(selectors.components.Panels.Visualization.TableNG.Filters.HeaderButton).click();
+ const filterContainer = dashboardPage.getByGrafanaSelector(
+ selectors.components.Panels.Visualization.TableNG.Filters.Container
+ );
+
+ await expect(filterContainer).toBeVisible();
+
+ // select all, then click the first value to unselect it, filtering it out.
+ await filterContainer.getByTestId(selectors.components.Panels.Visualization.TableNG.Filters.SelectAll).click();
+ await filterContainer.getByTitle(firstStateValue, { exact: true }).locator('label').click();
+ await filterContainer.getByRole('button', { name: 'Ok' }).click();
+
+ // make sure the filter container closed when we clicked "Ok".
+ await expect(filterContainer).not.toBeVisible();
+
+ // did it actually filter out our value?
+ await expect(getCell(page, 1, infoColumnIdx)).resolves.not.toHaveText(firstStateValue);
+ });
+
+ test('Tests pagination, row height adjustment', async ({ gotoDashboardPage, selectors, page }) => {
+ const rowRe = /([\d]+) - ([\d]+) of ([\d]+) rows/;
+ const getRowStatus = async (page: Page | Locator) => {
+ const text = (await page.getByText(rowRe).textContent()) ?? '';
+ const match = text.match(rowRe);
+ return {
+ start: parseInt(match?.[1] ?? '0', 10),
+ end: parseInt(match?.[2] ?? '0', 10),
+ total: parseInt(match?.[3] ?? '0', 10),
+ };
+ };
+
+ const dashboardPage = await gotoDashboardPage({
+ uid: DASHBOARD_UID,
+ queryParams: new URLSearchParams({ editPanel: '1' }),
+ });
+
+ await expect(
+ dashboardPage.getByGrafanaSelector(selectors.components.Panels.Panel.title('Table - Kitchen Sink'))
+ ).toBeVisible();
+
+ await page
+ .getByLabel(selectors.components.PanelEditor.OptionsPane.fieldLabel(`Enable pagination`), { exact: true })
+ .click();
+
+ // because of text wrapping, we're guaranteed to only be showing a single row when we enable pagination.
+ await expect(page.getByText(/([\d]+) - ([\d]+) of ([\d]+) rows/)).toBeVisible();
+
+ // FIXME horrible selector for the "Wrap text" toggle for the "Long text" column.
+ await page
+ .locator('[id="Override 13"]')
+ .locator(`[aria-label="${selectors.components.PanelEditor.OptionsPane.fieldLabel('Wrap text')}"]`)
+ .click();
+
+ // any number of rows that is not "1" is allowed here, we don't want to police the exact number of rows that
+ // are rendered since there are tons of factors which could effect this. we do want to grab this number for comparison
+ // in a second, though.
+ const smallRowStatus = await getRowStatus(page);
+ expect(smallRowStatus.end).toBeGreaterThan(1);
+ expect(page.getByRole('grid').getByRole('row')).toHaveCount(smallRowStatus.end + 1);
+
+ // change cell height to Large
+ await dashboardPage
+ .getByGrafanaSelector(selectors.components.PanelEditor.OptionsPane.fieldLabel('Table Cell height'))
+ .locator('input')
+ .last()
+ .click();
+ const largeRowStatus = await getRowStatus(page);
+ expect(largeRowStatus.end).toBeLessThan(smallRowStatus.end);
+ expect(page.getByRole('grid').getByRole('row')).toHaveCount(largeRowStatus.end + 1);
+
+ // click a page over with the directional nav
+ await page.getByLabel('next page').click();
+ const nextPageStatus = await getRowStatus(page);
+ expect(nextPageStatus.start).toBe(largeRowStatus.end + 1);
+ expect(nextPageStatus.end).toBe(largeRowStatus.end * 2);
+ expect(nextPageStatus.total).toBe(largeRowStatus.total);
+
+ // click a page number
+ await page.getByTestId('data-testid panel content').getByRole('navigation').getByText('4', { exact: true }).click();
+ const fourthPageStatus = await getRowStatus(page);
+ expect(fourthPageStatus.start).toBe(largeRowStatus.end * 3 + 1);
+ expect(fourthPageStatus.end).toBe(largeRowStatus.end * 4);
+ expect(fourthPageStatus.total).toBe(largeRowStatus.total);
+ });
+
+ test.skip('Tests DataLinks (single and multi) and actions', async ({ gotoDashboardPage, selectors, page }) => {
+ const addDataLink = async (title: string, url: string) => {
await dashboardPage
.getByGrafanaSelector(
- selectors.components.PanelEditor.OptionsPane.fieldLabel('Cell options Cell value inspect')
+ selectors.components.PanelEditor.OptionsPane.fieldLabel('Data links and actions Data links')
)
- .first()
- .locator('label[for="custom.inspect"]')
+ .locator('button')
+ .filter({ hasText: 'Add link' })
.click();
- await loremIpsumCell.hover();
- await expect(getCellHeight(page, 1, longTextColIdx)).resolves.toBeLessThan(100);
- // click cell inspect, check that cell inspection pops open in the side as we'd expect.
- await loremIpsumCell.getByLabel('Inspect value').click();
- const loremIpsumText = await loremIpsumCell.textContent();
- expect(loremIpsumText).toBeDefined();
- await expect(page.getByRole('dialog').getByText(loremIpsumText!)).toBeVisible();
+ // DataLinks dialog has popped open - fill it in and add a global datalink.
+ await expect(page.getByRole('dialog')).toBeVisible();
+ await page.getByRole('dialog').locator('#link-title').fill(title);
+ await page.getByRole('dialog').locator('#data-link-input [contenteditable="true"]').focus();
+ await page.getByRole('dialog').locator('#data-link-input [contenteditable="true"]').fill(url);
+ await page.getByRole('dialog').locator('#data-link-input [contenteditable="true"]').blur();
+ await page.getByRole('dialog').locator('button[aria-disabled="false"]').filter({ hasText: 'Save' }).click();
+ await expect(page.getByRole('dialog')).not.toBeVisible();
+ };
+
+ const dashboardPage = await gotoDashboardPage({
+ uid: DASHBOARD_UID,
+ queryParams: new URLSearchParams({ editPanel: '1' }),
});
- test('Tests visibility and display name via overrides', async ({ gotoDashboardPage, selectors, page }) => {
- const dashboardPage = await gotoDashboardPage({
- uid: DASHBOARD_UID,
- queryParams: new URLSearchParams({ editPanel: '1' }),
- });
+ await expect(
+ dashboardPage.getByGrafanaSelector(selectors.components.Panels.Panel.title('Table - Kitchen Sink'))
+ ).toBeVisible();
- await expect(
- dashboardPage.getByGrafanaSelector(selectors.components.Panels.Panel.title('Table - Kitchen Sink'))
- ).toBeVisible();
+ // disable text wrapping for this test to make it easier to click the links, the long lorem ipsum
+ // can push the links off the screen.
+ // FIXME very bad selector to get the correct "wrap text" toggle here.
+ await page
+ .locator('[id="Override 13"]')
+ .locator(`[aria-label="${selectors.components.PanelEditor.OptionsPane.fieldLabel('Wrap text')}"]`)
+ .click();
- // confirm that "State" column is hidden by default.
- expect(page.getByRole('row').nth(0)).not.toContainText('State');
+ const infoColumnIdx = await getColumnIdx(page, 'Info');
+ const pillColIdx = await getColumnIdx(page, 'Pills');
+ const dataLinkColIdx = await getColumnIdx(page, 'Data Link');
- // toggle the "State" column visibility and test that it appears before re-hiding it.
- // FIXME this selector is utterly godawful, but there's no way to give testIds or aria-labels or anything to
- // the panel editor builder. we should fix that to make e2e's easier to write for our team.
- const hideStateColumnSwitch = page.locator('[id="Override 12"]').locator('label').last();
- await hideStateColumnSwitch.click();
- expect(page.getByRole('row').nth(0)).toContainText('State');
+ // Info column has a single DataLink by default.
+ const infoCell = await getCell(page, 1, infoColumnIdx);
+ await expect(infoCell.locator('a')).toBeVisible();
+ expect(infoCell.locator('a')).toHaveAttribute('href');
+ expect(infoCell.locator('a')).not.toHaveAttribute('aria-haspopup');
- // now change the display name of the "State" column.
- // FIXME it would be good to have a better selector here too.
- const displayNameInput = page.locator('[id="Override 12"]').locator('input[value="State"]').last();
- await displayNameInput.fill('State (renamed)');
- await displayNameInput.press('Enter');
- expect(page.getByRole('row').nth(0)).toContainText('State (renamed)');
- });
+ // now, add a DataLink to the whole table
+ await addDataLink('Test link', 'https://grafana.com');
- // we test niche cases for sorting, filtering, pagination, etc. in a unit tests already.
- // we mainly want to test the happiest paths for these in e2es as well to check for integration
- // issues, but the unit tests can confirm that the internal logic works as expected much more quickly and thoroughly.
- // hashtag testing pyramid.
- test('Tests sorting by column', async ({ gotoDashboardPage, selectors, page }) => {
- const dashboardPage = await gotoDashboardPage({
- uid: DASHBOARD_UID,
- queryParams: new URLSearchParams({ editPanel: '1' }),
- });
+ // add a DataLink to the whole table, all cells will now have a single link.
+ const colCount = await page.getByRole('row').nth(1).getByRole('gridcell').count();
+ for (let colIdx = 0; colIdx < colCount; colIdx++) {
+ // - pills column currently does not support DataLinks.
+ // - we don't apply DataLinks to the DataLinks column itself, since they're rendered inside.
+ if (colIdx === pillColIdx || colIdx === dataLinkColIdx) {
+ continue;
+ }
- await expect(
- dashboardPage.getByGrafanaSelector(selectors.components.Panels.Panel.title('Table - Kitchen Sink'))
- ).toBeVisible();
+ const cell = await getCell(page, 1, colIdx);
+ await expect(cell.locator('a')).toBeVisible();
+ expect(cell.locator('a')).toHaveAttribute('href');
+ expect(cell.locator('a')).not.toHaveAttribute('aria-haspopup', 'menu');
+ }
- // click the "State" column header to sort it.
- const stateColumnHeader = await getCell(page, 0, 1);
+ const headerContainer = dashboardPage.getByGrafanaSelector(selectors.components.Panels.Panel.headerContainer);
- await stateColumnHeader.getByText('Info').click();
- await expect(stateColumnHeader).toHaveAttribute('aria-sort', 'ascending');
- expect(getCell(page, 1, 1)).resolves.toContainText('down'); // down or down fast
+ // add another data link. now we'll check that the multi-link popups work.
+ await addDataLink('Another test link', 'https://grafana.com/foo');
- await stateColumnHeader.getByText('Info').click();
- await expect(stateColumnHeader).toHaveAttribute('aria-sort', 'descending');
- expect(getCell(page, 1, 1)).resolves.toContainText('up'); // up or up fast
-
- await stateColumnHeader.getByText('Info').click();
- await expect(stateColumnHeader).not.toHaveAttribute('aria-sort');
- });
-
- test('Tests filtering within a column', async ({ gotoDashboardPage, selectors, page }) => {
- const dashboardPage = await gotoDashboardPage({
- uid: DASHBOARD_UID,
- queryParams: new URLSearchParams({ editPanel: '1' }),
- });
-
- await expect(
- dashboardPage.getByGrafanaSelector(selectors.components.Panels.Panel.title('Table - Kitchen Sink'))
- ).toBeVisible();
-
- await waitForTableLoad(page);
-
- const infoColumnIdx = await getColumnIdx(page, 'Info');
-
- const stateColumnHeader = page.getByRole('columnheader').nth(infoColumnIdx);
-
- // get the first value in the "State" column, filter it out, then check that it went away.
- const firstStateValue = (await (await getCell(page, 1, infoColumnIdx)).textContent())!;
- await stateColumnHeader
- .getByTestId(selectors.components.Panels.Visualization.TableNG.Filters.HeaderButton)
- .click();
- const filterContainer = dashboardPage.getByGrafanaSelector(
- selectors.components.Panels.Visualization.TableNG.Filters.Container
- );
-
- await expect(filterContainer).toBeVisible();
-
- // select all, then click the first value to unselect it, filtering it out.
- await filterContainer.getByTestId(selectors.components.Panels.Visualization.TableNG.Filters.SelectAll).click();
- await filterContainer.getByTitle(firstStateValue, { exact: true }).locator('label').click();
- await filterContainer.getByRole('button', { name: 'Ok' }).click();
-
- // make sure the filter container closed when we clicked "Ok".
- await expect(filterContainer).not.toBeVisible();
-
- // did it actually filter out our value?
- await expect(getCell(page, 1, infoColumnIdx)).resolves.not.toHaveText(firstStateValue);
- });
-
- test('Tests pagination, row height adjustment', async ({ gotoDashboardPage, selectors, page }) => {
- const rowRe = /([\d]+) - ([\d]+) of ([\d]+) rows/;
- const getRowStatus = async (page: Page | Locator) => {
- const text = (await page.getByText(rowRe).textContent()) ?? '';
- const match = text.match(rowRe);
- return {
- start: parseInt(match?.[1] ?? '0', 10),
- end: parseInt(match?.[2] ?? '0', 10),
- total: parseInt(match?.[3] ?? '0', 10),
- };
- };
-
- const dashboardPage = await gotoDashboardPage({
- uid: DASHBOARD_UID,
- queryParams: new URLSearchParams({ editPanel: '1' }),
- });
-
- await expect(
- dashboardPage.getByGrafanaSelector(selectors.components.Panels.Panel.title('Table - Kitchen Sink'))
- ).toBeVisible();
-
- await page
- .getByLabel(selectors.components.PanelEditor.OptionsPane.fieldLabel(`Enable pagination`), { exact: true })
- .click();
-
- // because of text wrapping, we're guaranteed to only be showing a single row when we enable pagination.
- await expect(page.getByText(/([\d]+) - ([\d]+) of ([\d]+) rows/)).toBeVisible();
-
- // disable text wrap and see the number of rows.
- await dashboardPage
- .getByGrafanaSelector(selectors.components.PanelEditor.OptionsPane.fieldLabel('Wrap text'))
- .last()
- .click();
-
- // any number of rows that is not "1" is allowed here, we don't want to police the exact number of rows that
- // are rendered since there are tons of factors which could effect this. we do want to grab this number for comparison
- // in a second, though.
- const smallRowStatus = await getRowStatus(page);
- expect(smallRowStatus.end).toBeGreaterThan(1);
- expect(page.getByRole('grid').getByRole('row')).toHaveCount(smallRowStatus.end + 1);
-
- // change cell height to Large
- await dashboardPage
- .getByGrafanaSelector(selectors.components.PanelEditor.OptionsPane.fieldLabel('Table Cell height'))
- .locator('input')
- .last()
- .click();
- const largeRowStatus = await getRowStatus(page);
- expect(largeRowStatus.end).toBeLessThan(smallRowStatus.end);
- expect(page.getByRole('grid').getByRole('row')).toHaveCount(largeRowStatus.end + 1);
-
- // click a page over with the directional nav
- await page.getByLabel('next page').click();
- const nextPageStatus = await getRowStatus(page);
- expect(nextPageStatus.start).toBe(largeRowStatus.end + 1);
- expect(nextPageStatus.end).toBe(largeRowStatus.end * 2);
- expect(nextPageStatus.total).toBe(largeRowStatus.total);
-
- // click a page number
- await page
- .getByTestId('data-testid panel content')
- .getByRole('navigation')
- .getByText('4', { exact: true })
- .click();
- const fourthPageStatus = await getRowStatus(page);
- expect(fourthPageStatus.start).toBe(largeRowStatus.end * 3 + 1);
- expect(fourthPageStatus.end).toBe(largeRowStatus.end * 4);
- expect(fourthPageStatus.total).toBe(largeRowStatus.total);
- });
-
- // TODO: skipping this test for now due to flakiness in adding DataLinks.
- test.skip('Tests DataLinks (single and multi) and actions', async ({ gotoDashboardPage, selectors, page }) => {
- const addDataLink = async (title: string, url: string) => {
- await dashboardPage
- .getByGrafanaSelector(
- selectors.components.PanelEditor.OptionsPane.fieldLabel('Data links and actions Data links')
- )
- .locator('button')
- .filter({ hasText: 'Add link' })
- .click();
-
- // DataLinks dialog has popped open - fill it in and add a global datalink.
- await expect(page.getByRole('dialog')).toBeVisible();
- await page.getByRole('dialog').locator('#link-title').fill(title);
- await page.getByRole('dialog').locator('#data-link-input [contenteditable="true"]').focus();
- await page.getByRole('dialog').locator('#data-link-input [contenteditable="true"]').fill(url);
- await page.getByRole('dialog').locator('#data-link-input [contenteditable="true"]').blur();
- await page.getByRole('dialog').locator('button[aria-disabled="false"]').filter({ hasText: 'Save' }).click();
- await expect(page.getByRole('dialog')).not.toBeVisible();
- };
-
- const dashboardPage = await gotoDashboardPage({
- uid: DASHBOARD_UID,
- queryParams: new URLSearchParams({ editPanel: '1' }),
- });
-
- await expect(
- dashboardPage.getByGrafanaSelector(selectors.components.Panels.Panel.title('Table - Kitchen Sink'))
- ).toBeVisible();
-
- // disable text wrapping for this test to make it easier to click the links, the long lorem ipsum
- // can push the links off the screen.
- await dashboardPage
- .getByGrafanaSelector(selectors.components.PanelEditor.OptionsPane.fieldLabel('Wrap text'))
- .last()
- .click();
-
- const infoColumnIdx = await getColumnIdx(page, 'Info');
- const pillColIdx = await getColumnIdx(page, 'Pills');
- const dataLinkColIdx = await getColumnIdx(page, 'Data Link');
-
- // Info column has a single DataLink by default.
- const infoCell = await getCell(page, 1, infoColumnIdx);
- await expect(infoCell.locator('a')).toBeVisible();
- expect(infoCell.locator('a')).toHaveAttribute('href');
- expect(infoCell.locator('a')).not.toHaveAttribute('aria-haspopup');
-
- // now, add a DataLink to the whole table
- await addDataLink('Test link', 'https://grafana.com');
-
- // add a DataLink to the whole table, all cells will now have a single link.
- const colCount = await page.getByRole('row').nth(1).getByRole('gridcell').count();
- for (let colIdx = 0; colIdx < colCount; colIdx++) {
- // - pills column currently does not support DataLinks.
- // - we don't apply DataLinks to the DataLinks column itself, since they're rendered inside.
- if (colIdx === pillColIdx || colIdx === dataLinkColIdx) {
- continue;
- }
-
- const cell = await getCell(page, 1, colIdx);
- await expect(cell.locator('a')).toBeVisible();
- expect(cell.locator('a')).toHaveAttribute('href');
+ // loop thru the columns, click the links, observe that the tooltip appears, and close the tooltip.
+ for (let colIdx = 0; colIdx < colCount; colIdx++) {
+ const cell = await getCell(page, 1, colIdx);
+ if (colIdx === infoColumnIdx) {
+ // the Info column should still have its single link.
expect(cell.locator('a')).not.toHaveAttribute('aria-haspopup', 'menu');
+ continue;
}
- const headerContainer = dashboardPage.getByGrafanaSelector(selectors.components.Panels.Panel.headerContainer);
-
- // add another data link. now we'll check that the multi-link popups work.
- await addDataLink('Another test link', 'https://grafana.com/foo');
-
- // loop thru the columns, click the links, observe that the tooltip appears, and close the tooltip.
- for (let colIdx = 0; colIdx < colCount; colIdx++) {
- const cell = await getCell(page, 1, colIdx);
- if (colIdx === infoColumnIdx) {
- // the Info column should still have its single link.
- expect(cell.locator('a')).not.toHaveAttribute('aria-haspopup', 'menu');
- continue;
- }
-
- // - pills column currently does not support DataLinks.
- // - we don't apply DataLinks to the DataLinks column itself, since they're rendered inside.
- if (colIdx === pillColIdx || colIdx === dataLinkColIdx) {
- continue;
- }
-
- await cell.locator('a').click({ force: true });
- await expect(page.getByTestId(selectors.components.DataLinksActionsTooltip.tooltipWrapper)).toBeVisible();
-
- await headerContainer.click(); // convenient just to click the header to close the tooltip.
- await expect(page.getByTestId(selectors.components.DataLinksActionsTooltip.tooltipWrapper)).not.toBeVisible();
+ // - pills column currently does not support DataLinks.
+ // - we don't apply DataLinks to the DataLinks column itself, since they're rendered inside.
+ if (colIdx === pillColIdx || colIdx === dataLinkColIdx) {
+ continue;
}
- // add an Action to the whole table and check that the action button is added to the tooltip.
- // TODO -- saving for another day.
+ await cell.locator('a').click({ force: true });
+ await expect(page.getByTestId(selectors.components.DataLinksActionsTooltip.tooltipWrapper)).toBeVisible();
+
+ await headerContainer.click(); // convenient just to click the header to close the tooltip.
+ await expect(page.getByTestId(selectors.components.DataLinksActionsTooltip.tooltipWrapper)).not.toBeVisible();
+ }
+
+ // add an Action to the whole table and check that the action button is added to the tooltip.
+ // TODO -- saving for another day.
+ });
+
+ test('Empty Table panel', async ({ gotoDashboardPage, selectors }) => {
+ const dashboardPage = await gotoDashboardPage({
+ uid: DASHBOARD_UID,
+ queryParams: new URLSearchParams({ editPanel: '2' }),
});
- test('Empty Table panel', async ({ gotoDashboardPage, selectors }) => {
- const dashboardPage = await gotoDashboardPage({
- uid: DASHBOARD_UID,
- queryParams: new URLSearchParams({ editPanel: '2' }),
- });
-
- await expect(
- dashboardPage.getByGrafanaSelector(selectors.components.Panels.Panel.PanelDataErrorMessage)
- ).toBeVisible();
- await expect(
- dashboardPage.getByGrafanaSelector(selectors.components.Panels.Panel.title('Table - Kitchen Sink'))
- ).not.toBeVisible();
- });
- }
-);
+ await expect(
+ dashboardPage.getByGrafanaSelector(selectors.components.Panels.Panel.PanelDataErrorMessage)
+ ).toBeVisible();
+ await expect(
+ dashboardPage.getByGrafanaSelector(selectors.components.Panels.Panel.title('Table - Kitchen Sink'))
+ ).not.toBeVisible();
+ });
+});
diff --git a/e2e-playwright/panels-suite/table-sparkline.spec.ts b/e2e-playwright/panels-suite/table-sparkline.spec.ts
index 16dc55f3df4..d2ac814e2df 100644
--- a/e2e-playwright/panels-suite/table-sparkline.spec.ts
+++ b/e2e-playwright/panels-suite/table-sparkline.spec.ts
@@ -1,29 +1,18 @@
import { test, expect } from '@grafana/plugin-e2e';
-test.use({
- viewport: { width: 1280, height: 1080 },
- featureToggles: {
- tableNextGen: true,
- },
-});
+test.use({ viewport: { width: 1280, height: 1080 }, featureToggles: { tableNextGen: true } });
-test.describe(
- 'Panels test: Table - Sparkline',
- {
- tag: ['@panels'],
- },
- () => {
- test('Tests sparkline tables are successfully rendered', async ({ gotoDashboardPage, selectors, page }) => {
- await gotoDashboardPage({
- uid: 'd6373b49-1957-4f00-9218-ee2120d3ecd9',
- queryParams: new URLSearchParams({ editPanel: '2' }),
- });
-
- await expect(page.getByRole('grid')).toBeVisible();
-
- const uplotCount = await page.locator('.uplot').count();
- const rowCount = await page.getByRole('row').count();
- expect(uplotCount).toBe(rowCount - 1);
+test.describe('Panels test: Table - Sparkline', { tag: ['@panels', '@table'] }, () => {
+ test('Tests sparkline tables are successfully rendered', async ({ gotoDashboardPage, selectors, page }) => {
+ await gotoDashboardPage({
+ uid: 'd6373b49-1957-4f00-9218-ee2120d3ecd9',
+ queryParams: new URLSearchParams({ editPanel: '2' }),
});
- }
-);
+
+ await expect(page.getByRole('grid')).toBeVisible();
+
+ const uplotCount = await page.locator('.uplot').count();
+ const rowCount = await page.getByRole('row').count();
+ expect(uplotCount).toBe(rowCount - 1);
+ });
+});
diff --git a/packages/grafana-schema/src/common/common.gen.ts b/packages/grafana-schema/src/common/common.gen.ts
index b98c83c9525..9b229c4d1e9 100644
--- a/packages/grafana-schema/src/common/common.gen.ts
+++ b/packages/grafana-schema/src/common/common.gen.ts
@@ -722,6 +722,16 @@ export enum TableCellBackgroundDisplayMode {
Gradient = 'gradient',
}
+/**
+ * Whenever we add text wrapping, we should add all text wrapping options at once
+ */
+export interface TableWrapTextOptions {
+ /**
+ * if true, wrap the text content of the cell
+ */
+ wrapText?: boolean;
+}
+
/**
* Sort by field state
*/
@@ -755,17 +765,15 @@ export const defaultTableFooterOptions: Partial = {
/**
* Auto mode table cell options
*/
-export interface TableAutoCellOptions {
+export interface TableAutoCellOptions extends TableWrapTextOptions {
type: TableCellDisplayMode.Auto;
- wrapText?: boolean;
}
/**
* Colored text cell options
*/
-export interface TableColorTextCellOptions {
+export interface TableColorTextCellOptions extends TableWrapTextOptions {
type: TableCellDisplayMode.ColorText;
- wrapText?: boolean;
}
/**
@@ -787,7 +795,7 @@ export interface TableImageCellOptions {
/**
* Show data links in the cell
*/
-export interface TableDataLinksCellOptions {
+export interface TableDataLinksCellOptions extends TableWrapTextOptions {
type: TableCellDisplayMode.DataLinks;
}
@@ -818,11 +826,14 @@ export interface TableSparklineCellOptions extends GraphFieldConfig {
/**
* Colored background cell options
*/
-export interface TableColoredBackgroundCellOptions {
+export interface TableColoredBackgroundCellOptions extends TableWrapTextOptions {
applyToRow?: boolean;
mode?: TableCellBackgroundDisplayMode;
type: TableCellDisplayMode.ColorBackground;
- wrapText?: boolean;
+}
+
+export interface TablePillCellOptions extends TableWrapTextOptions {
+ type: TableCellDisplayMode.Pill;
}
/**
@@ -841,37 +852,6 @@ export enum TableCellHeight {
*/
export type TableCellOptions = (TableAutoCellOptions | TableSparklineCellOptions | TableBarGaugeCellOptions | TableColoredBackgroundCellOptions | TableColorTextCellOptions | TableImageCellOptions | TablePillCellOptions | TableDataLinksCellOptions | TableActionsCellOptions | TableJsonViewCellOptions);
-/**
- * Field options for each field within a table (e.g 10, "The String", 64.20, etc.)
- * Generally defines alignment, filtering capabilties, display options, etc.
- */
-export interface TableFieldOptions {
- align: FieldTextAlignment;
- cellOptions: TableCellOptions;
- /**
- * This field is deprecated in favor of using cellOptions
- */
- displayMode?: TableCellDisplayMode;
- filterable?: boolean;
- hidden?: boolean; // ?? default is missing or false ??
- /**
- * Hides any header for a column, useful for columns that show some static content or buttons.
- */
- hideHeader?: boolean;
- inspect: boolean;
- minWidth?: number;
- width?: number;
- /**
- * Enables text wrapping for column headers
- */
- wrapHeaderText?: boolean;
-}
-
-export const defaultTableFieldOptions: Partial = {
- align: 'auto',
- inspect: false,
-};
-
/**
* Use UTC/GMT timezone
*/
@@ -987,10 +967,37 @@ export enum ComparisonOperation {
NEQ = 'neq',
}
-export interface TablePillCellOptions {
- type: TableCellDisplayMode.Pill;
+/**
+ * Field options for each field within a table (e.g 10, "The String", 64.20, etc.)
+ * Generally defines alignment, filtering capabilties, display options, etc.
+ */
+export interface TableFieldOptions {
+ align: FieldTextAlignment;
+ cellOptions: TableCellOptions;
+ /**
+ * This field is deprecated in favor of using cellOptions
+ */
+ displayMode?: TableCellDisplayMode;
+ filterable?: boolean;
+ hidden?: boolean; // ?? default is missing or false ??
+ /**
+ * Hides any header for a column, useful for columns that show some static content or buttons.
+ */
+ hideHeader?: boolean;
+ inspect: boolean;
+ minWidth?: number;
+ width?: number;
+ /**
+ * Enables text wrapping for column headers
+ */
+ wrapHeaderText?: boolean;
}
+export const defaultTableFieldOptions: Partial = {
+ align: 'auto',
+ inspect: false,
+};
+
/**
* A specific timezone from https://en.wikipedia.org/wiki/Tz_database
*/
diff --git a/packages/grafana-schema/src/common/table.cue b/packages/grafana-schema/src/common/table.cue
index acf4a6b7480..0a15fb926b5 100644
--- a/packages/grafana-schema/src/common/table.cue
+++ b/packages/grafana-schema/src/common/table.cue
@@ -11,6 +11,12 @@ TableCellDisplayMode: "auto" | "color-text" | "color-background" | "color-backgr
// or a gradient.
TableCellBackgroundDisplayMode: "basic" | "gradient" @cuetsy(kind="enum",memberNames="Basic|Gradient")
+// Whenever we add text wrapping, we should add all text wrapping options at once
+TableWrapTextOptions: {
+ // if true, wrap the text content of the cell
+ wrapText?: bool
+} @cuetsy(kind="interface")
+
// Sort by field state
TableSortByFieldState: {
// Sets the display name of the field to sort by
@@ -31,14 +37,12 @@ TableFooterOptions: {
// Auto mode table cell options
TableAutoCellOptions: {
type: TableCellDisplayMode & "auto"
- wrapText?: bool
-} @cuetsy(kind="interface")
+} & TableWrapTextOptions @cuetsy(kind="interface")
// Colored text cell options
TableColorTextCellOptions: {
type: TableCellDisplayMode & "color-text"
- wrapText?: bool
-} @cuetsy(kind="interface")
+} & TableWrapTextOptions @cuetsy(kind="interface")
// Json view cell options
TableJsonViewCellOptions: {
@@ -55,7 +59,7 @@ TableImageCellOptions: {
// Show data links in the cell
TableDataLinksCellOptions: {
type: TableCellDisplayMode & "data-links"
-} @cuetsy(kind="interface")
+} & TableWrapTextOptions @cuetsy(kind="interface")
// Show actions in the cell
TableActionsCellOptions: {
@@ -81,8 +85,11 @@ TableColoredBackgroundCellOptions: {
type: TableCellDisplayMode & "color-background"
mode?: TableCellBackgroundDisplayMode
applyToRow?: bool
- wrapText?: bool
-} @cuetsy(kind="interface")
+} & TableWrapTextOptions @cuetsy(kind="interface")
+
+TablePillCellOptions: {
+ type: TableCellDisplayMode & "pill"
+} & TableWrapTextOptions @cuetsy(kind="interface")
// Height of a table cell
TableCellHeight: "sm" | "md" | "lg" | "auto" @cuetsy(kind="enum")
@@ -108,7 +115,3 @@ TableFieldOptions: {
// Enables text wrapping for column headers
wrapHeaderText?: bool
} @cuetsy(kind="interface")
-
-TablePillCellOptions: {
- type: TableCellDisplayMode & "pill"
-} @cuetsy(kind="interface")
diff --git a/packages/grafana-ui/src/components/Table/TableNG/Cells/PillCell.test.tsx b/packages/grafana-ui/src/components/Table/TableNG/Cells/PillCell.test.tsx
index b7249e88b2b..55b9d3e19ba 100644
--- a/packages/grafana-ui/src/components/Table/TableNG/Cells/PillCell.test.tsx
+++ b/packages/grafana-ui/src/components/Table/TableNG/Cells/PillCell.test.tsx
@@ -1,54 +1,18 @@
import { render, RenderResult } from '@testing-library/react';
-import { DataFrame, Field, FieldType, GrafanaTheme2, MappingType, createTheme } from '@grafana/data';
-import { TableCellDisplayMode, TablePillCellOptions } from '@grafana/schema';
+import { Field, FieldType, MappingType, createTheme } from '@grafana/data';
-import { mockThemeContext } from '../../../../themes/ThemeContext';
-
-import { PillCell, getStyles } from './PillCell';
+import { PillCell } from './PillCell';
describe('PillCell', () => {
- let pillClass: string;
- let restoreThemeContext: () => void;
+ const theme = createTheme();
- beforeEach(() => {
- pillClass = getStyles(createTheme()).pill;
- restoreThemeContext = mockThemeContext(createTheme());
- });
-
- afterEach(() => {
- restoreThemeContext();
- });
-
- const mockCellOptions: TablePillCellOptions = {
- type: TableCellDisplayMode.Pill,
- };
-
- const mockField: Field = {
+ const fieldWithValues = (values: unknown[]): Field => ({
name: 'test',
type: FieldType.string,
- values: [],
+ values: values,
config: {},
- };
-
- const mockFrame: DataFrame = {
- name: 'test',
- fields: [mockField],
- length: 1,
- };
-
- const defaultProps = {
- field: mockField,
- justifyContent: 'flex-start' as const,
- cellOptions: mockCellOptions,
- rowIdx: 0,
- frame: mockFrame,
- height: 30,
- width: 100,
- theme: {} as GrafanaTheme2,
- cellInspect: false,
- showFilters: false,
- };
+ });
const ser = new XMLSerializer();
@@ -60,88 +24,84 @@ describe('PillCell', () => {
// one class for lightTextPill, darkTextPill
describe('Color by hash (classic palette)', () => {
- const props = { ...defaultProps };
-
it('single value', () => {
expectHTML(
- render(),
- `value1`
+ render(),
+ `value1`
);
});
it('empty string', () => {
- expectHTML(render(), '');
+ expectHTML(render(), '');
});
- // it('null', () => {
- // expectHTML(
- // render(),
- // 'value1'
- // );
- // });
+ it('null', () => {
+ const { container } = render();
+ expect(container).toBeEmptyDOMElement();
+ });
it('CSV values', () => {
expectHTML(
- render(),
+ render(),
`
- value1
- value2
- value3
+ value1
+ value2
+ value3
`
);
});
it('JSON array values', () => {
expectHTML(
- render(),
+ render(),
`
- value1
- value2
- value3
+ value1
+ value2
+ value3
`
);
});
-
- // TODO: handle null values?
});
describe('Color by value mappings', () => {
- const field: Field = {
- ...mockField,
- config: {
- ...mockField.config,
- mappings: [
- {
- type: MappingType.ValueToText,
- options: {
- success: { color: '#00FF00' },
- error: { color: '#FF0000' },
- warning: { color: '#FFFF00' },
- },
- },
- ],
- },
- display: (value: unknown) => ({
- text: String(value),
- color:
- value === 'success' ? '#00FF00' : value === 'error' ? '#FF0000' : value === 'warning' ? '#FFFF00' : '#FF780A',
- numeric: 0,
- }),
- };
-
- const props = {
- ...defaultProps,
- field,
- };
-
it('CSV values', () => {
+ const mockField = fieldWithValues(['success,error,warning,unknown']);
+ const field = {
+ ...mockField,
+ config: {
+ ...mockField.config,
+ mappings: [
+ {
+ type: MappingType.ValueToText,
+ options: {
+ success: { color: '#00FF00' },
+ error: { color: '#FF0000' },
+ warning: { color: '#FFFF00' },
+ },
+ },
+ ],
+ },
+ display: (value: unknown) => ({
+ text: String(value),
+ color:
+ value === 'success'
+ ? '#00FF00'
+ : value === 'error'
+ ? '#FF0000'
+ : value === 'warning'
+ ? '#FFFF00'
+ : '#FF780A',
+ numeric: 0,
+ }),
+ } satisfies Field;
+
expectHTML(
- render(),
+ render(),
`
- success
- error
- warning
- unknown
+ success
+ error
+ warning
+ unknown
`
);
});
diff --git a/packages/grafana-ui/src/components/Table/TableNG/Cells/PillCell.tsx b/packages/grafana-ui/src/components/Table/TableNG/Cells/PillCell.tsx
index c1ca23b7f69..35b759aa224 100644
--- a/packages/grafana-ui/src/components/Table/TableNG/Cells/PillCell.tsx
+++ b/packages/grafana-ui/src/components/Table/TableNG/Cells/PillCell.tsx
@@ -1,4 +1,3 @@
-import { css } from '@emotion/css';
import { useMemo } from 'react';
import {
@@ -8,11 +7,36 @@ import {
Field,
getColorByStringHash,
FALLBACK_COLOR,
+ fieldColorModeRegistry,
} from '@grafana/data';
import { FieldColorModeId } from '@grafana/schema';
-import { useStyles2, useTheme2 } from '../../../../themes/ThemeContext';
-import { TableCellRendererProps } from '../types';
+import { PillCellProps, TableCellValue } from '../types';
+
+export function PillCell({ rowIdx, field, theme }: PillCellProps) {
+ const value = field.values[rowIdx];
+ const pills: Pill[] = useMemo(() => {
+ const pillValues = inferPills(value);
+ return pillValues.length > 0 ? createPills(pillValues, field, theme) : [];
+ }, [value, field, theme]);
+
+ if (pills.length === 0) {
+ return null;
+ }
+
+ return pills.map((pill) => (
+
+ {pill.value}
+
+ ));
+}
interface Pill {
value: string;
@@ -21,6 +45,9 @@ interface Pill {
color: string;
}
+const SPLIT_RE = /\s*,\s*/;
+const TRANSPARENT = 'rgba(0,0,0,0)';
+
function createPills(pillValues: string[], field: Field, theme: GrafanaTheme2): Pill[] {
return pillValues.map((pill, index) => {
const bgColor = getPillColor(pill, field, theme);
@@ -34,38 +61,13 @@ function createPills(pillValues: string[], field: Field, theme: GrafanaTheme2):
});
}
-export function PillCell({ value, field }: TableCellRendererProps) {
- const styles = useStyles2(getStyles);
- const theme = useTheme2();
-
- const pills: Pill[] = useMemo(() => {
- const pillValues = inferPills(String(value));
- return createPills(pillValues, field, theme);
- }, [value, field, theme]);
-
- return pills.map((pill) => (
-
- {pill.value}
-
- ));
-}
-
-const SPLIT_RE = /\s*,\s*/;
-const TRANSPARENT = 'rgba(0,0,0,0)';
-
-export function inferPills(value: string): string[] {
- if (value === '') {
+export function inferPills(rawValue: TableCellValue): string[] {
+ if (rawValue === '' || rawValue == null) {
return [];
}
+ const value = String(rawValue);
+
if (value[0] === '[') {
try {
return JSON.parse(value);
@@ -77,6 +79,7 @@ export function inferPills(value: string): string[] {
return value.trim().split(SPLIT_RE);
}
+// FIXME: this does not yet support "shades of a color"
function getPillColor(value: string, field: Field, theme: GrafanaTheme2): string {
const cfg = field.config;
@@ -88,19 +91,14 @@ function getPillColor(value: string, field: Field, theme: GrafanaTheme2): string
return theme.visualization.getColorByName(cfg.color?.fixedColor ?? FALLBACK_COLOR);
}
- // TODO: instead of classicColors we need to pull colors from theme, same way as FieldColorModeId.PaletteClassicByName (see fieldColor.ts)
- return getColorByStringHash(classicColors, value);
-}
+ let colors = classicColors;
+ const configuredColor = cfg.color;
+ if (configuredColor) {
+ const mode = fieldColorModeRegistry.get(configuredColor.mode);
+ if (typeof mode?.getColors === 'function') {
+ colors = mode.getColors(theme);
+ }
+ }
-export const getStyles = (theme: GrafanaTheme2) => ({
- pill: css({
- display: 'inline-block',
- padding: theme.spacing(0.25, 0.75),
- marginInlineEnd: theme.spacing(0.5),
- marginBlock: theme.spacing(0.5),
- borderRadius: theme.shape.radius.default,
- fontSize: theme.typography.bodySmall.fontSize,
- lineHeight: theme.typography.bodySmall.lineHeight,
- whiteSpace: 'nowrap',
- }),
-});
+ return getColorByStringHash(colors, value);
+}
diff --git a/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx b/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx
index 40cd49278d3..8d6c9bb5ef8 100644
--- a/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx
+++ b/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx
@@ -367,14 +367,15 @@ export function TableNG(props: TableNGProps) {
case TableCellDisplayMode.ColorText:
case TableCellDisplayMode.DataLinks:
case TableCellDisplayMode.JSONView:
+ case TableCellDisplayMode.Pill:
cellClass = getCellStyles(
theme,
+ cellType,
textAlign,
shouldWrap,
shouldOverflow,
canBeColorized,
- isMonospace,
- cellType === TableCellDisplayMode.DataLinks
+ isMonospace
);
break;
}
@@ -821,6 +822,7 @@ const getGridStyles = (
border: 'none',
'.rdg-cell': {
+ padding: TABLE.CELL_PADDING,
'&:last-child': {
borderInlineEnd: 'none',
},
@@ -843,8 +845,6 @@ const getGridStyles = (
'.rdg-header-row, .rdg-summary-row': {
'.rdg-cell': {
zIndex: theme.zIndex.tooltip - 1,
- paddingInline: TABLE.CELL_PADDING,
- paddingBlock: TABLE.CELL_PADDING,
},
},
}),
@@ -941,35 +941,36 @@ const getHeaderCellStyles = (theme: GrafanaTheme2, justifyContent: Property.Just
const getCellStyles = (
theme: GrafanaTheme2,
+ cellType: TableCellDisplayMode,
textAlign: TextAlign,
shouldWrap: boolean,
shouldOverflow: boolean,
isColorized: boolean,
- isMonospace: boolean,
- // TODO: replace this with cellTypeStyles: TemplateStringsArray object
- isLinkCell: boolean
+ isMonospace: boolean
) =>
css({
display: 'flex',
alignItems: 'center',
textAlign,
justifyContent: getJustifyContent(textAlign),
- paddingInline: TABLE.CELL_PADDING,
minHeight: '100%',
backgroundClip: 'padding-box !important', // helps when cells have a bg color
+
...(shouldWrap && { whiteSpace: isMonospace ? 'pre' : 'pre-line' }),
...(isMonospace && { fontFamily: 'monospace' }),
- // should omit if no cell actions, and no shouldOverflow
'&:hover, &[aria-selected=true]': {
'.table-cell-actions': {
display: 'flex',
},
...(shouldOverflow && {
- whiteSpace: 'pre-line',
+ zIndex: theme.zIndex.tooltip - 2,
+ whiteSpace: isMonospace ? 'pre' : 'pre-line',
height: 'fit-content',
minWidth: 'fit-content',
- ...(isMonospace && { whiteSpace: 'pre' }),
+ ...(cellType === TableCellDisplayMode.Pill && {
+ flexWrap: 'wrap',
+ }),
}),
},
@@ -989,21 +990,39 @@ const getCellStyles = (
}),
},
- ...(isLinkCell && {
+ ...(cellType === TableCellDisplayMode.DataLinks && {
+ ...(shouldWrap && {
+ flexDirection: 'column',
+ justifyContent: 'center',
+ alignItems: getJustifyContent(textAlign),
+ }),
'> a': {
- // display: 'inline', // textWrap ? 'block' : 'inline',
+ flexWrap: 'nowrap',
+ ...(!shouldWrap && {
+ paddingInline: theme.spacing(0.5),
+ borderRight: `2px solid ${theme.colors.border.medium}`,
+ '&:first-child': {
+ paddingInlineStart: 0,
+ },
+ '&:last-child': {
+ paddingInlineEnd: 0,
+ borderRight: 'none',
+ },
+ }),
+ },
+ }),
+
+ ...(cellType === TableCellDisplayMode.Pill && {
+ display: 'inline-flex',
+ gap: theme.spacing(0.5),
+ flexWrap: shouldWrap ? 'wrap' : 'nowrap',
+ '> span': {
+ display: 'flex',
+ padding: theme.spacing(0.25, 0.75),
+ borderRadius: theme.shape.radius.default,
+ fontSize: theme.typography.bodySmall.fontSize,
+ lineHeight: theme.typography.bodySmall.lineHeight,
whiteSpace: 'nowrap',
- paddingInline: theme.spacing(1),
- borderRight: `2px solid ${theme.colors.border.medium}`,
-
- '&:first-of-type': {
- paddingInlineStart: 0,
- },
-
- '&:last-of-type': {
- borderRight: 'none',
- paddingInlineEnd: 0,
- },
},
}),
});
diff --git a/packages/grafana-ui/src/components/Table/TableNG/hooks.test.ts b/packages/grafana-ui/src/components/Table/TableNG/hooks.test.ts
index f7a1a1b858a..ce378961589 100644
--- a/packages/grafana-ui/src/components/Table/TableNG/hooks.test.ts
+++ b/packages/grafana-ui/src/components/Table/TableNG/hooks.test.ts
@@ -490,24 +490,26 @@ describe('TableNG hooks', () => {
const { fields } = setupData();
+ let modifiedFields = fields.map((field) => {
+ if (field.name === 'name') {
+ return {
+ ...field,
+ name: 'Longer name that needs wrapping',
+ config: {
+ ...field.config,
+ custom: {
+ ...field.config?.custom,
+ wrapHeaderText: true,
+ },
+ },
+ };
+ }
+ return field;
+ });
+
renderHook(() => {
return useHeaderHeight({
- fields: fields.map((field) => {
- if (field.name === 'name') {
- return {
- ...field,
- name: 'Longer name that needs wrapping',
- config: {
- ...field.config,
- custom: {
- ...field.config?.custom,
- wrapHeaderText: true,
- },
- },
- };
- }
- return field;
- }),
+ fields: modifiedFields,
columnWidths: [100, 100, 100],
enabled: true,
typographyCtx: { ...typographyCtx, wrappedCount: countFn },
@@ -516,27 +518,29 @@ describe('TableNG hooks', () => {
});
});
- expect(countFn).toHaveBeenCalledWith('Longer name that needs wrapping', 86);
+ expect(countFn).toHaveBeenCalledWith('Longer name that needs wrapping', 86, modifiedFields[0], -1);
+
+ modifiedFields = fields.map((field) => {
+ if (field.name === 'name') {
+ return {
+ ...field,
+ name: 'Longer name that needs wrapping',
+ config: {
+ ...field.config,
+ custom: {
+ ...field.config?.custom,
+ filterable: true,
+ wrapHeaderText: true,
+ },
+ },
+ };
+ }
+ return field;
+ });
renderHook(() => {
return useHeaderHeight({
- fields: fields.map((field) => {
- if (field.name === 'name') {
- return {
- ...field,
- name: 'Longer name that needs wrapping',
- config: {
- ...field.config,
- custom: {
- ...field.config?.custom,
- filterable: true,
- wrapHeaderText: true,
- },
- },
- };
- }
- return field;
- }),
+ fields: modifiedFields,
columnWidths: [100, 100, 100],
enabled: true,
typographyCtx: { ...typographyCtx, wrappedCount: countFn },
@@ -545,7 +549,7 @@ describe('TableNG hooks', () => {
});
});
- expect(countFn).toHaveBeenCalledWith('Longer name that needs wrapping', 26);
+ expect(countFn).toHaveBeenCalledWith('Longer name that needs wrapping', 26, modifiedFields[0], -1);
});
});
@@ -764,7 +768,12 @@ describe('TableNG hooks', () => {
expect(result.current(rows[0])).toEqual(expect.any(Number));
- expect(estimateLinesFn).toHaveBeenCalledWith('Annie Lennox', 100 - TABLE.CELL_PADDING * 2 - TABLE.BORDER_RIGHT);
+ expect(estimateLinesFn).toHaveBeenCalledWith(
+ 'Annie Lennox',
+ 100 - TABLE.CELL_PADDING * 2 - TABLE.BORDER_RIGHT,
+ fieldsWithWrappedText[0],
+ 0
+ );
});
});
});
diff --git a/packages/grafana-ui/src/components/Table/TableNG/hooks.ts b/packages/grafana-ui/src/components/Table/TableNG/hooks.ts
index 70a790d9133..5c349bdd1ab 100644
--- a/packages/grafana-ui/src/components/Table/TableNG/hooks.ts
+++ b/packages/grafana-ui/src/components/Table/TableNG/hooks.ts
@@ -3,7 +3,7 @@ import { Column, DataGridHandle, DataGridProps, SortColumn } from 'react-data-gr
import { Field, fieldReducers, FieldType, formattedValueToString, reduceField } from '@grafana/data';
-import { TableColumnResizeActionCallback } from '../types';
+import { TableCellDisplayMode, TableColumnResizeActionCallback } from '../types';
import { TABLE } from './constants';
import { FilterType, TableFooterCalc, TableRow, TableSortByFieldState, TableSummaryRow, TypographyCtx } from './types';
@@ -15,6 +15,7 @@ import {
getRowHeight,
buildHeaderLineCounters,
buildRowLineCounters,
+ getCellOptions,
} from './utils';
// Helper function to get displayed value
@@ -437,7 +438,15 @@ export function useRowHeight({
defaultHeight,
lineCounters,
TABLE.LINE_HEIGHT,
- TABLE.CELL_PADDING * 2
+ (field, numLines) => {
+ // Pill cells have vertical padding between each row
+ if (getCellOptions(field).type === TableCellDisplayMode.Pill) {
+ return TABLE.CELL_PADDING * (numLines - 1) + TABLE.CELL_PADDING * 2;
+ }
+
+ // default vertical padding for cells
+ return TABLE.CELL_PADDING * 2;
+ }
);
}
return result;
diff --git a/packages/grafana-ui/src/components/Table/TableNG/types.ts b/packages/grafana-ui/src/components/Table/TableNG/types.ts
index 1d736974189..d28bab7b668 100644
--- a/packages/grafana-ui/src/components/Table/TableNG/types.ts
+++ b/packages/grafana-ui/src/components/Table/TableNG/types.ts
@@ -248,6 +248,12 @@ export interface ActionCellProps {
getActions: GetActionsFunctionLocal;
}
+export interface PillCellProps {
+ theme: GrafanaTheme2;
+ field: Field;
+ rowIdx: number;
+}
+
// Comparator for sorting table values
export type Comparator = (a: TableCellValue, b: TableCellValue) => number;
@@ -264,13 +270,14 @@ export interface ScrollPosition {
export interface TypographyCtx {
ctx: CanvasRenderingContext2D;
- font: string;
+ fontFamily: string;
+ letterSpacing: number;
avgCharWidth: number;
estimateLines: LineCounter;
wrappedCount: LineCounter;
}
-export type LineCounter = (value: unknown, width: number) => number;
+export type LineCounter = (value: unknown, width: number, field: Field, rowIdx: number) => number;
export interface LineCounterEntry {
/**
* given a values and the available width, returns the line count for that value
diff --git a/packages/grafana-ui/src/components/Table/TableNG/utils.test.ts b/packages/grafana-ui/src/components/Table/TableNG/utils.test.ts
index e86de2a7009..4dd20cac215 100644
--- a/packages/grafana-ui/src/components/Table/TableNG/utils.test.ts
+++ b/packages/grafana-ui/src/components/Table/TableNG/utils.test.ts
@@ -5,6 +5,7 @@ import {
createTheme,
DataFrame,
DataFrameWithValue,
+ DataLink,
DisplayValue,
Field,
FieldType,
@@ -40,23 +41,17 @@ import {
createTypographyContext,
applySort,
SINGLE_LINE_ESTIMATE_THRESHOLD,
+ wrapUwrapCount,
+ getDataLinksCounter,
+ getPillLineCounter,
} from './utils';
describe('TableNG utils', () => {
describe('alignment', () => {
it.each(['left', 'center', 'right'] as const)('should return "%s" when configured', (align) => {
- expect(
- getAlignment({
- name: 'Value',
- type: FieldType.string,
- values: [],
- config: {
- custom: {
- align,
- },
- },
- })
- ).toBe(align);
+ expect(getAlignment({ name: 'Value', type: FieldType.string, values: [], config: { custom: { align } } })).toBe(
+ align
+ );
});
it.each([
@@ -65,16 +60,7 @@ describe('TableNG utils', () => {
{ type: FieldType.boolean, align: 'left' },
{ type: FieldType.time, align: 'left' },
])('should return "$align" for field type $type by default', ({ type, align }) => {
- expect(
- getAlignment({
- name: 'Test',
- type,
- values: [],
- config: {
- custom: {},
- },
- })
- ).toBe(align);
+ expect(getAlignment({ name: 'Test', type, values: [], config: { custom: {} } })).toBe(align);
});
it.each([
@@ -91,17 +77,7 @@ describe('TableNG utils', () => {
name: 'Test',
type: FieldType.number,
values: [],
- config: {
- custom: {
- ...(cellType !== undefined
- ? {
- cellOptions: {
- type: cellType,
- },
- }
- : {}),
- },
- },
+ config: { custom: { ...(cellType !== undefined ? { cellOptions: { type: cellType } } : {}) } },
})
).toBe(align);
});
@@ -122,34 +98,17 @@ describe('TableNG utils', () => {
colors: {
isDark: true,
mode: 'dark',
- primary: {
- text: '#FFFFFF',
- main: '#FF0000',
- },
- background: {
- canvas: '#000000',
- primary: '#111111',
- },
- text: {
- primary: '#FFFFFF',
- },
- action: {
- hover: '#FF0000',
- },
+ primary: { text: '#FFFFFF', main: '#FF0000' },
+ background: { canvas: '#000000', primary: '#111111' },
+ text: { primary: '#FFFFFF' },
+ action: { hover: '#FF0000' },
},
} as unknown as GrafanaTheme2;
it('should handle color background mode', () => {
- const field = {
- type: TableCellDisplayMode.ColorBackground as const,
- mode: TableCellBackgroundDisplayMode.Basic,
- };
+ const field = { type: TableCellDisplayMode.ColorBackground as const, mode: TableCellBackgroundDisplayMode.Basic };
- const displayValue = {
- text: '100',
- numeric: 100,
- color: '#ff0000',
- };
+ const displayValue = { text: '100', numeric: 100, color: '#ff0000' };
const colors = getCellColors(theme, field, displayValue);
expect(colors.bgColor).toBe('rgb(255, 0, 0)');
@@ -162,11 +121,7 @@ describe('TableNG utils', () => {
mode: TableCellBackgroundDisplayMode.Gradient,
};
- const displayValue = {
- text: '100',
- numeric: 100,
- color: '#ff0000',
- };
+ const displayValue = { text: '100', numeric: 100, color: '#ff0000' };
const colors = getCellColors(theme, field, displayValue);
expect(colors.bgColor).toBe('linear-gradient(120deg, rgb(255, 54, 36), #ff0000)');
@@ -185,12 +140,7 @@ describe('TableNG utils', () => {
const records = frameToRecords(frame);
expect(records).toHaveLength(2);
- expect(records[0]).toEqual({
- __depth: 0,
- __index: 0,
- time: 1,
- value: 10,
- });
+ expect(records[0]).toEqual({ __depth: 0, __index: 0, time: 1, value: 10 });
});
});
@@ -203,36 +153,22 @@ describe('TableNG utils', () => {
config: {},
values: [1, 22, 333, 4444],
// No state property initially
- display: (value: unknown) => ({
- text: String(value),
- numeric: Number(value),
- }),
+ display: (value: unknown) => ({ text: String(value), numeric: Number(value) }),
};
// Create a display value
- const displayValue: DisplayValue = {
- text: '1',
- numeric: 1,
- };
+ const displayValue: DisplayValue = { text: '1', numeric: 1 };
// Call getAlignmentFactor with the first row
const result = getAlignmentFactor(field, displayValue, 0);
// Verify the result has the text property
- expect(result).toEqual(
- expect.objectContaining({
- text: '1',
- })
- );
+ expect(result).toEqual(expect.objectContaining({ text: '1' }));
// Verify that field.state was created and contains the alignment factor
expect(field.state).toBeDefined();
expect(field.state?.alignmentFactors).toBeDefined();
- expect(field.state?.alignmentFactors).toEqual(
- expect.objectContaining({
- text: '1',
- })
- );
+ expect(field.state?.alignmentFactors).toEqual(expect.objectContaining({ text: '1' }));
});
it('should update alignment factor when a longer value is found', () => {
@@ -242,39 +178,21 @@ describe('TableNG utils', () => {
type: FieldType.number,
config: {},
values: [1, 22, 333, 4444],
- state: {
- alignmentFactors: {
- text: '1',
- },
- },
- display: (value: unknown) => ({
- text: String(value),
- numeric: Number(value),
- }),
+ state: { alignmentFactors: { text: '1' } },
+ display: (value: unknown) => ({ text: String(value), numeric: Number(value) }),
};
// Create a display value that is longer than the existing alignment factor
- const displayValue: DisplayValue = {
- text: '4444',
- numeric: 4444,
- };
+ const displayValue: DisplayValue = { text: '4444', numeric: 4444 };
// Call getAlignmentFactor
const result = getAlignmentFactor(field, displayValue, 3);
// Verify the result is updated to the longer value
- expect(result).toEqual(
- expect.objectContaining({
- text: '4444',
- })
- );
+ expect(result).toEqual(expect.objectContaining({ text: '4444' }));
// Verify that field.state.alignmentFactors was updated
- expect(field.state?.alignmentFactors).toEqual(
- expect.objectContaining({
- text: '4444',
- })
- );
+ expect(field.state?.alignmentFactors).toEqual(expect.objectContaining({ text: '4444' }));
});
it('should not update alignment factor when a shorter value is found', () => {
@@ -284,39 +202,21 @@ describe('TableNG utils', () => {
type: FieldType.number,
config: {},
values: [1, 22, 333, 4444],
- state: {
- alignmentFactors: {
- text: '4444',
- },
- },
- display: (value: unknown) => ({
- text: String(value),
- numeric: Number(value),
- }),
+ state: { alignmentFactors: { text: '4444' } },
+ display: (value: unknown) => ({ text: String(value), numeric: Number(value) }),
};
// Create a display value that is shorter than the existing alignment factor
- const displayValue: DisplayValue = {
- text: '1',
- numeric: 1,
- };
+ const displayValue: DisplayValue = { text: '1', numeric: 1 };
// Call getAlignmentFactor
const result = getAlignmentFactor(field, displayValue, 0);
// Verify the result is still the longer value
- expect(result).toEqual(
- expect.objectContaining({
- text: '4444',
- })
- );
+ expect(result).toEqual(expect.objectContaining({ text: '4444' }));
// Verify that field.state.alignmentFactors was not changed
- expect(field.state?.alignmentFactors).toEqual(
- expect.objectContaining({
- text: '4444',
- })
- );
+ expect(field.state?.alignmentFactors).toEqual(expect.objectContaining({ text: '4444' }));
});
it('should add alignment factor to existing field state', () => {
@@ -334,38 +234,24 @@ describe('TableNG utils', () => {
// Or if noValue is a valid property:
// noValue: true
},
- display: (value: unknown) => ({
- text: String(value),
- numeric: Number(value),
- }),
+ display: (value: unknown) => ({ text: String(value), numeric: Number(value) }),
};
// Create a display value
- const displayValue: DisplayValue = {
- text: '1',
- numeric: 1,
- };
+ const displayValue: DisplayValue = { text: '1', numeric: 1 };
// Call getAlignmentFactor with the first row
const result = getAlignmentFactor(field, displayValue, 0);
// Verify the result has the text property
- expect(result).toEqual(
- expect.objectContaining({
- text: '1',
- })
- );
+ expect(result).toEqual(expect.objectContaining({ text: '1' }));
// Verify that field.state was preserved and alignment factor was added
expect(field.state).toBeDefined();
// Check for the valid property we used
expect(field.state?.calcs).toBeDefined();
expect(field.state?.alignmentFactors).toBeDefined();
- expect(field.state?.alignmentFactors).toEqual(
- expect.objectContaining({
- text: '1',
- })
- );
+ expect(field.state?.alignmentFactors).toEqual(expect.objectContaining({ text: '1' }));
});
it.todo('alignmentFactor.text = displayValue.text;');
@@ -398,11 +284,7 @@ describe('TableNG utils', () => {
];
const result = getColumnTypes(fields);
- expect(result).toEqual({
- name: FieldType.string,
- age: FieldType.number,
- active: FieldType.boolean,
- });
+ expect(result).toEqual({ name: FieldType.string, age: FieldType.number, active: FieldType.boolean });
});
it('should recursively build column types when nested fields are present', () => {
@@ -448,20 +330,13 @@ describe('TableNG utils', () => {
const frame: DataFrame = {
fields: [
{ type: FieldType.string, name: 'stringCol', config: {}, values: [] },
- {
- type: FieldType.nestedFrames,
- name: 'nestedCol',
- config: {},
- values: [],
- },
+ { type: FieldType.nestedFrames, name: 'nestedCol', config: {}, values: [] },
],
length: 0,
name: 'test',
};
- expect(getColumnTypes(frame.fields)).toEqual({
- stringCol: FieldType.string,
- });
+ expect(getColumnTypes(frame.fields)).toEqual({ stringCol: FieldType.string });
});
});
@@ -557,18 +432,12 @@ describe('TableNG utils', () => {
describe('migrateTableDisplayModeToCellOptions', () => {
it('should migrate basic to gauge mode', () => {
const result = migrateTableDisplayModeToCellOptions(TableCellDisplayMode.BasicGauge);
- expect(result).toEqual({
- type: TableCellDisplayMode.Gauge,
- mode: BarGaugeDisplayMode.Basic,
- });
+ expect(result).toEqual({ type: TableCellDisplayMode.Gauge, mode: BarGaugeDisplayMode.Basic });
});
it('should migrate gradient-gauge to gauge mode with gradient', () => {
const result = migrateTableDisplayModeToCellOptions(TableCellDisplayMode.GradientGauge);
- expect(result).toEqual({
- type: TableCellDisplayMode.Gauge,
- mode: BarGaugeDisplayMode.Gradient,
- });
+ expect(result).toEqual({ type: TableCellDisplayMode.Gauge, mode: BarGaugeDisplayMode.Gradient });
});
it('should migrate color-background to color background with gradient', () => {
@@ -581,20 +450,13 @@ describe('TableNG utils', () => {
it('should handle other display modes', () => {
const result = migrateTableDisplayModeToCellOptions(TableCellDisplayMode.ColorText);
- expect(result).toEqual({
- type: TableCellDisplayMode.ColorText,
- });
+ expect(result).toEqual({ type: TableCellDisplayMode.ColorText });
});
});
describe('getCellOptions', () => {
it('should return default options when no custom config is provided', () => {
- const field: Field = {
- name: 'test',
- type: FieldType.string,
- config: {},
- values: [],
- };
+ const field: Field = { name: 'test', type: FieldType.string, config: {}, values: [] };
const options = getCellOptions(field);
@@ -607,35 +469,21 @@ describe('TableNG utils', () => {
name: 'test',
type: FieldType.string,
config: {
- custom: {
- cellOptions: {
- type: TableCellDisplayMode.ColorText,
- inspectEnabled: false,
- wrapText: true,
- },
- },
+ custom: { cellOptions: { type: TableCellDisplayMode.ColorText, inspectEnabled: false, wrapText: true } },
},
values: [],
};
const options = getCellOptions(field);
- expect(options).toEqual({
- type: TableCellDisplayMode.ColorText,
- inspectEnabled: false,
- wrapText: true,
- });
+ expect(options).toEqual({ type: TableCellDisplayMode.ColorText, inspectEnabled: false, wrapText: true });
});
it('should handle legacy displayMode property', () => {
const field: Field = {
name: 'test',
type: FieldType.string,
- config: {
- custom: {
- displayMode: 'color-background',
- },
- },
+ config: { custom: { displayMode: 'color-background' } },
values: [],
};
@@ -649,14 +497,7 @@ describe('TableNG utils', () => {
const field: Field = {
name: 'test',
type: FieldType.string,
- config: {
- custom: {
- displayMode: 'color-background',
- cellOptions: {
- type: TableCellDisplayMode.ColorText,
- },
- },
- },
+ config: { custom: { displayMode: 'color-background', cellOptions: { type: TableCellDisplayMode.ColorText } } },
values: [],
};
@@ -689,13 +530,7 @@ describe('TableNG utils', () => {
const field: Field = {
name: 'test',
type: FieldType.string,
- config: {
- custom: {
- cellOptions: {
- type: TableCellDisplayMode.JSONView,
- },
- },
- },
+ config: { custom: { cellOptions: { type: TableCellDisplayMode.JSONView } } },
values: [],
};
@@ -707,12 +542,7 @@ describe('TableNG utils', () => {
describe('getCellLinks', () => {
it('should return undefined when field has no getLinks function', () => {
- const field: Field = {
- name: 'test',
- type: FieldType.string,
- config: {},
- values: ['value'],
- };
+ const field: Field = { name: 'test', type: FieldType.string, config: {}, values: ['value'] };
const links = getCellLinks(field, 0);
expect(links).toEqual(undefined);
@@ -987,36 +817,112 @@ describe('TableNG utils', () => {
// actually executed the JS correctly. If you called `count` with a sensible value and width,
// it wouldn't give you a very reasonable answer in Jest's DOM environment for some reason.
it('creates the context using uwrap', () => {
+ const field: Field = { name: 'test', type: FieldType.string, config: {}, values: ['foo', 'bar', 'baz'] };
const ctx = createTypographyContext(14, 'sans-serif', 0.15);
+
expect(ctx).toEqual(
expect.objectContaining({
- font: '14px sans-serif',
ctx: expect.any(CanvasRenderingContext2D),
+ fontFamily: 'sans-serif',
+ letterSpacing: 0.15,
wrappedCount: expect.any(Function),
estimateLines: expect.any(Function),
avgCharWidth: expect.any(Number),
})
);
- expect(ctx.wrappedCount('the quick brown fox jumps over the lazy dog', 100)).toEqual(expect.any(Number));
- expect(ctx.estimateLines('the quick brown fox jumps over the lazy dog', 100)).toEqual(expect.any(Number));
+ expect(ctx.wrappedCount('the quick brown fox jumps over the lazy dog', 100, field, 0)).toEqual(
+ expect.any(Number)
+ );
+ expect(ctx.estimateLines('the quick brown fox jumps over the lazy dog', 100, field, 0)).toEqual(
+ expect.any(Number)
+ );
+ });
+ });
+
+ describe('wrapUwrapCount', () => {
+ const field: Field = { name: 'test', type: FieldType.string, config: {}, values: ['foo', 'bar', 'baz'] };
+
+ it('wraps the uwrap count function', () => {
+ const wrappedCount = wrapUwrapCount(jest.fn(() => 2));
+ expect(wrappedCount('test string', 100, field, 0)).toBe(2);
+ });
+
+ it('returns 1 for null or undefined values', () => {
+ const wrappedCount = wrapUwrapCount(jest.fn(() => 2));
+ expect(wrappedCount(null, 100, field, 0)).toBe(1);
+ expect(wrappedCount(undefined, 100, field, 0)).toBe(1);
});
});
describe('getTextLineEstimator', () => {
const counter = getTextLineEstimator(10);
+ const field: Field = { name: 'test', type: FieldType.string, config: {}, values: ['foo', 'bar', 'baz'] };
it('returns -1 if there are no strings or dashes within the string', () => {
- expect(counter('asdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdf', 5)).toBe(-1);
+ expect(counter('asdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdf', 5, field, 0)).toBe(-1);
});
it('calculates an approximate rendered height for the text based on the width and avgCharWidth', () => {
- expect(counter('asdfas dfasdfasdf asdfasdfasdfa sdfasdfasdfasdf 23', 200)).toBe(2.5);
+ expect(counter('asdfas dfasdfasdf asdfasdfasdfa sdfasdfasdfasdf 23', 200, field, 0)).toBe(2.5);
+ });
+ });
+
+ describe('getDataLinksCounter', () => {
+ it('counts number of valid links using getCellLinks', () => {
+ const field: Field = {
+ name: 'test',
+ type: FieldType.string,
+ config: {
+ links: [
+ { title: 'Link 1', url: 'http://example.com/1' },
+ { title: 'Invalid Link' } as DataLink, // No href or onClick
+ {
+ title: 'Link w',
+ url: 'asdf',
+ onClick: jest.fn(() => {}),
+ },
+ ],
+ },
+ values: ['value1'],
+ };
+
+ const counter = getDataLinksCounter();
+ expect(counter('my value', 100, field, 0)).toBe(2);
+ });
+ });
+
+ describe('getPillLineCounter', () => {
+ it('counts up the number of lines using the pill measuring method', () => {
+ const counter = getPillLineCounter(jest.fn((str) => str.length * 5));
+ expect(counter('tag1,tag2', 100, {} as Field, 0)).toBe(1);
+ expect(counter('tag1,tag2,tag3,tag4,tag5,tag6', 100, {} as Field, 0)).toBe(3);
+ });
+
+ it('returns 0 if value is null', () => {
+ const counter = getPillLineCounter(jest.fn((str) => str.length * 5));
+ expect(counter(null, 100, {} as Field, 0)).toBe(0);
+ });
+
+ it('returns 0 if no pills are inferred', () => {
+ const counter = getPillLineCounter(jest.fn((str) => str.length * 5));
+ expect(counter('', 100, {} as Field, 0)).toBe(0);
+ });
+
+ it('caches the width measurement for the same value', () => {
+ const widthMeasurement = jest.fn((str) => str.length * 5);
+ const counter = getPillLineCounter(widthMeasurement);
+ counter('tag1,tag2,tag3,tag4,tag5,tag6', 100, {} as Field, 0);
+ counter('tag1,tag2', 100, {} as Field, 0);
+ counter('tag2', 200, {} as Field, 0);
+ counter('tag2,tag3,tag2,tag4,tag4,tag2,tag5', 300, {} as Field, 0);
+ expect(widthMeasurement).toHaveBeenCalledTimes(6); // Should only call for unique values
});
});
describe('buildHeaderLineCounters', () => {
const ctx = {
- font: '14px sans-serif',
+ fontFamily: 'sans-serif',
+ letterSpacing: 0.15,
ctx: {} as CanvasRenderingContext2D,
count: jest.fn(() => 2),
avgCharWidth: 7,
@@ -1057,15 +963,15 @@ describe('TableNG utils', () => {
describe('buildRowLineCounters', () => {
const ctx = {
- font: '14px sans-serif',
+ fontFamily: 'sans-serif',
+ letterSpacing: 0.15,
ctx: {} as CanvasRenderingContext2D,
- count: jest.fn(() => 2),
wrappedCount: jest.fn(() => 2),
estimateLines: jest.fn(() => 2),
avgCharWidth: 7,
};
- it('returns an array of line counters for each column', () => {
+ it('sets up text line counters for each text column if wrapping is on', () => {
const fields: Field[] = [
{ name: 'Name', type: FieldType.string, values: [], config: { custom: { cellOptions: { wrapText: true } } } },
{
@@ -1095,6 +1001,42 @@ describe('TableNG utils', () => {
expect(counters![0].fieldIdxs).toEqual([1]);
});
+ it('sets up line counting for pills if present and wrapping is on', () => {
+ const fields: Field[] = [
+ {
+ name: 'Tags',
+ type: FieldType.string,
+ values: ['tag1,tag2', 'tag3', '["tag4","tag5","tag6"]'],
+ config: { custom: { cellOptions: { type: TableCellDisplayMode.Pill, wrapText: true } } },
+ },
+ ];
+ const counters = buildRowLineCounters(fields, ctx);
+ expect(counters![0].estimate).toEqual(expect.any(Function));
+ expect(counters![0].estimate!('tag1,tag2', 100, fields[0], 0)).toEqual(expect.any(Number));
+ expect(counters![0].counter).toEqual(expect.any(Function));
+ expect(counters![0].counter('tag1,tag2', 100, fields[0], 0)).toEqual(expect.any(Number));
+ expect(counters![0].fieldIdxs).toEqual([0]);
+ });
+
+ it('sets up line counting for datalinks if present and wrapping is on', () => {
+ const fields: Field[] = [
+ {
+ name: 'Links',
+ type: FieldType.string,
+ values: ['http://example.com/1', 'http://example.com/2'],
+ config: { custom: { cellOptions: { type: TableCellDisplayMode.DataLinks, wrapText: true } } },
+ getLinks: jest.fn((): LinkModel[] => [
+ { title: 'Link 1', href: 'http://example.com/1', target: '_blank', origin: { datasourceUid: 'test' } },
+ { title: 'Link 2', href: 'http://example.com/2', target: '_self', origin: { datasourceUid: 'test' } },
+ ]),
+ },
+ ];
+ const counters = buildRowLineCounters(fields, ctx);
+ expect(counters![0].counter).toEqual(expect.any(Function));
+ expect(counters![0].counter('http://example.com/1', 100, fields[0], 0)).toEqual(expect.any(Number));
+ expect(counters![0].fieldIdxs).toEqual([0]);
+ });
+
it('does not enable text counting for non-string fields', () => {
const fields: Field[] = [
{ name: 'Name', type: FieldType.string, values: [], config: { custom: {} } },
@@ -1162,15 +1104,15 @@ describe('TableNG utils', () => {
it('should take colWidths into account when calculating max wrap cell', () => {
getRowHeight(fields, 3, [50, 60], 36, counters, 20, 10);
- expect(counters[0].counter).toHaveBeenCalledWith('longer one here', 50);
- expect(counters[1].counter).toHaveBeenCalledWith(123456, 60);
+ expect(counters[0].counter).toHaveBeenCalledWith('longer one here', 50, fields[0], 3);
+ expect(counters[1].counter).toHaveBeenCalledWith(123456, 60, fields[1], 3);
});
// this is used to calc wrapped header height
it('should use the display name if the rowIdx is -1', () => {
getRowHeight(fields, -1, [50, 60], 36, counters, 20, 10);
- expect(counters[0].counter).toHaveBeenCalledWith('Name', 50);
- expect(counters[1].counter).toHaveBeenCalledWith('Age', 60);
+ expect(counters[0].counter).toHaveBeenCalledWith('Name', 50, fields[0], -1);
+ expect(counters[1].counter).toHaveBeenCalledWith('Age', 60, fields[1], -1);
});
it('should ignore columns which do not have line counters', () => {
@@ -1230,18 +1172,8 @@ describe('TableNG utils', () => {
expect(
computeColWidths(
[
- {
- name: 'A',
- type: FieldType.string,
- values: [],
- config: { custom: { width: 100 } },
- },
- {
- name: 'B',
- type: FieldType.string,
- values: [],
- config: { custom: { width: 200 } },
- },
+ { name: 'A', type: FieldType.string, values: [], config: { custom: { width: 100 } } },
+ { name: 'B', type: FieldType.string, values: [], config: { custom: { width: 200 } } },
],
500
)
@@ -1252,18 +1184,8 @@ describe('TableNG utils', () => {
expect(
computeColWidths(
[
- {
- name: 'A',
- type: FieldType.string,
- values: [],
- config: {},
- },
- {
- name: 'B',
- type: FieldType.string,
- values: [],
- config: { custom: { width: 200 } },
- },
+ { name: 'A', type: FieldType.string, values: [], config: {} },
+ { name: 'B', type: FieldType.string, values: [], config: { custom: { width: 200 } } },
],
500
)
@@ -1274,18 +1196,8 @@ describe('TableNG utils', () => {
expect(
computeColWidths(
[
- {
- name: 'A',
- type: FieldType.string,
- values: [],
- config: { custom: { minWidth: 100 } },
- },
- {
- name: 'B',
- type: FieldType.string,
- values: [],
- config: { custom: { minWidth: 100 } },
- },
+ { name: 'A', type: FieldType.string, values: [], config: { custom: { minWidth: 100 } } },
+ { name: 'B', type: FieldType.string, values: [], config: { custom: { minWidth: 100 } } },
],
100
)
@@ -1296,18 +1208,8 @@ describe('TableNG utils', () => {
expect(
computeColWidths(
[
- {
- name: 'A',
- type: FieldType.string,
- values: [],
- config: {},
- },
- {
- name: 'B',
- type: FieldType.string,
- values: [],
- config: {},
- },
+ { name: 'A', type: FieldType.string, values: [], config: {} },
+ { name: 'B', type: FieldType.string, values: [], config: {} },
],
// we have two columns but have set the table to the width of one default column.
COLUMN.DEFAULT_WIDTH
@@ -1332,28 +1234,14 @@ describe('TableNG utils', () => {
],
});
- const sortColumns: SortColumn[] = [
- {
- columnKey: 'time',
- direction: 'ASC',
- },
- ];
+ const sortColumns: SortColumn[] = [{ columnKey: 'time', direction: 'ASC' }];
const records = applySort(frameToRecords(frame), frame.fields, sortColumns);
expect(records).toMatchObject([
- {
- time: 1,
- value: 20,
- },
- {
- time: 1,
- value: 10,
- },
- {
- time: 2,
- value: 30,
- },
+ { time: 1, value: 20 },
+ { time: 1, value: 10 },
+ { time: 2, value: 30 },
]);
});
});
diff --git a/packages/grafana-ui/src/components/Table/TableNG/utils.ts b/packages/grafana-ui/src/components/Table/TableNG/utils.ts
index a24a361bfda..96e6c71c018 100644
--- a/packages/grafana-ui/src/components/Table/TableNG/utils.ts
+++ b/packages/grafana-ui/src/components/Table/TableNG/utils.ts
@@ -25,6 +25,7 @@ import {
import { getTextColorForAlphaBackground } from '../../../utils/colors';
import { TableCellOptions } from '../types';
+import { inferPills } from './Cells/PillCell';
import { COLUMN, TABLE } from './constants';
import {
CellColors,
@@ -92,15 +93,19 @@ export function createTypographyContext(fontSize: number, fontFamily: string, le
ctx.letterSpacing = `${letterSpacing}px`;
ctx.font = font;
+ // 1/6 of the characters in this string are capitalized. Since the avgCharWidth is used for estimation, it's
+ // better that the estimation over-estimates the width than if it underestimates it, so we're a little on the
+ // aggressive side here and could even go more aggressive if we get complaints in the future.
const txt =
- "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s.";
+ "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s. 1234567890 ALL CAPS TO HELP WITH MEASUREMENT.";
const txtWidth = ctx.measureText(txt).width;
const avgCharWidth = txtWidth / txt.length + letterSpacing;
const { count } = varPreLine(ctx);
return {
ctx,
- font,
+ fontFamily,
+ letterSpacing,
avgCharWidth,
estimateLines: getTextLineEstimator(avgCharWidth),
wrappedCount: wrapUwrapCount(count),
@@ -108,7 +113,7 @@ export function createTypographyContext(fontSize: number, fontFamily: string, le
}
/**
- * @internal
+ * @internal wraps the uwrap count function to ensure that it is given a string.
*/
export function wrapUwrapCount(count: Count): LineCounter {
return (value, width) => {
@@ -141,6 +146,71 @@ export function getTextLineEstimator(avgCharWidth: number): LineCounter {
};
}
+/**
+ * @internal
+ */
+export function getDataLinksCounter(): LineCounter {
+ const linksCountCache: Record = {};
+
+ // when we render links, we need to filter out the invalid links. since the call to `getLinks` is expensive,
+ // we'll cache the result and reuse it for every row in the table. this cache is cleared when line counts are
+ // rebuilt anytime from the `useRowHeight` hook, and that includes adding and removing data links.
+ return (_value, _width, field) => {
+ const cacheKey = getDisplayName(field);
+ if (linksCountCache[cacheKey] === undefined) {
+ let count = 0;
+ for (const l of field.config?.links ?? []) {
+ if (l.onClick || l.url) {
+ count += 1;
+ }
+ }
+ linksCountCache[cacheKey] = count;
+ }
+
+ return linksCountCache[cacheKey];
+ };
+}
+
+const PILLS_FONT_SIZE = 12;
+const PILLS_SPACING = 12; // 6px horizontal padding on each side
+const PILLS_GAP = 4; // gap between pills
+
+export function getPillLineCounter(measureWidth: (value: string) => number): LineCounter {
+ const widthCache: Record = {};
+
+ return (value, width) => {
+ if (value == null) {
+ return 0;
+ }
+
+ const pillValues = inferPills(String(value));
+ if (pillValues.length === 0) {
+ return 0;
+ }
+
+ let lines = 0;
+ let currentLineUse = width;
+
+ for (const pillValue of pillValues) {
+ let rawWidth = widthCache[pillValue];
+ if (rawWidth === undefined) {
+ rawWidth = measureWidth(pillValue);
+ widthCache[pillValue] = rawWidth;
+ }
+ const pillWidth = rawWidth + PILLS_SPACING;
+
+ if (currentLineUse + pillWidth + PILLS_GAP > width) {
+ lines++;
+ currentLineUse = pillWidth;
+ } else {
+ currentLineUse += pillWidth + PILLS_GAP;
+ }
+ }
+
+ return lines;
+ };
+}
+
/**
* @internal return a text line counter for every field which has wrapHeaderText enabled.
*/
@@ -175,12 +245,33 @@ export function buildRowLineCounters(fields: Field[], typographyCtx: TypographyC
const field = fields[fieldIdx];
if (shouldTextWrap(field)) {
wrappedFields++;
- // TODO: Pills, DataLinks, and JSON will have custom line counters here.
- // for string fields, we really want to find the longest field ahead of time to reduce the number of calls to `count`.
- // calling `count` is going to get a perfectly accurate line count, but it is expensive, so we'd rather estimate the line
- // count and call the counter only for the field which will take up the most space based on its
- if (field.type === FieldType.string) {
+ const cellType = getCellOptions(field).type;
+ if (cellType === TableCellDisplayMode.DataLinks) {
+ result.dataLinksCounter = result.dataLinksCounter ?? {
+ counter: getDataLinksCounter(),
+ fieldIdxs: [],
+ };
+ result.dataLinksCounter.fieldIdxs.push(fieldIdx);
+ } else if (cellType === TableCellDisplayMode.Pill) {
+ if (!result.pillCounter) {
+ const pillTypographyCtx = createTypographyContext(
+ PILLS_FONT_SIZE,
+ typographyCtx.fontFamily,
+ typographyCtx.letterSpacing
+ );
+
+ result.pillCounter = {
+ estimate: getPillLineCounter((value) => value.length * pillTypographyCtx.avgCharWidth),
+ counter: getPillLineCounter((value) => pillTypographyCtx.ctx.measureText(value).width),
+ fieldIdxs: [],
+ };
+ }
+ result.pillCounter.fieldIdxs.push(fieldIdx);
+ }
+
+ // for string fields, we estimate the length of a line using `avgCharWidth` to limit expensive calls `count`.
+ else if (field.type === FieldType.string) {
result.textCounter = result.textCounter ?? {
counter: typographyCtx.wrappedCount,
estimate: typographyCtx.estimateLines,
@@ -215,7 +306,9 @@ export function getRowHeight(
defaultHeight: number,
lineCounters?: LineCounterEntry[],
lineHeight = TABLE.LINE_HEIGHT,
- verticalPadding = 0
+ // when this is a function, the field which was measured as the maximum size will be returned, as well as the
+ // calculated number of lines, so that the consumer can use it in case the vertical padding value differs field-by-field.
+ verticalPadding: number | ((field: Field, numLines: number) => number) = TABLE.CELL_PADDING
): number {
if (!lineCounters?.length) {
return defaultHeight;
@@ -224,6 +317,7 @@ export function getRowHeight(
let maxLines = -1;
let maxValue = '';
let maxWidth = 0;
+ let maxField: Field | undefined;
let preciseCounter: LineCounter | undefined;
for (const { estimate, counter, fieldIdxs } of lineCounters) {
@@ -239,11 +333,12 @@ export function getRowHeight(
const cellValueRaw = rowIdx === -1 ? getDisplayName(field) : field.values[rowIdx];
if (cellValueRaw != null) {
const colWidth = columnWidths[fieldIdx];
- const approxLines = count(cellValueRaw, colWidth);
+ const approxLines = count(cellValueRaw, colWidth, field, rowIdx);
if (approxLines > maxLines) {
maxLines = approxLines;
maxValue = cellValueRaw;
maxWidth = colWidth;
+ maxField = field;
preciseCounter = isEstimating ? counter : undefined;
}
}
@@ -252,18 +347,23 @@ export function getRowHeight(
// if the value is -1 or the estimate for the max cell was less than the SINGLE_LINE_ESTIMATE_THRESHOLD, we trust
// that the estimator correctly identified that no text wrapping is needed for this row, skipping the preciseCounter.
- if (maxLines < SINGLE_LINE_ESTIMATE_THRESHOLD) {
+ if (maxField === undefined || maxLines < SINGLE_LINE_ESTIMATE_THRESHOLD) {
return defaultHeight;
}
// if we finished this row height loop with an estimate, we need to call
// the `preciseCounter` method to get the exact line count.
if (preciseCounter !== undefined) {
- maxLines = preciseCounter(maxValue, maxWidth);
+ maxLines = preciseCounter(maxValue, maxWidth, maxField, rowIdx);
}
- // we want a round number of lines for rendering
- const totalHeight = Math.ceil(maxLines) * lineHeight + verticalPadding;
+ // round up to the nearest line before doing math
+ maxLines = Math.ceil(maxLines);
+
+ // adjust for vertical padding and line height, and clamp to a minimum default height
+ const verticalPaddingValue =
+ typeof verticalPadding === 'function' ? verticalPadding(maxField, maxLines) : verticalPadding;
+ const totalHeight = maxLines * lineHeight + verticalPaddingValue;
return Math.max(totalHeight, defaultHeight);
}
@@ -276,9 +376,7 @@ export function shouldTextOverflow(field: Field): boolean {
const eligibleCellType =
// Tech debt: Technically image cells are of type string, which is misleading (kinda?)
// so we need to ensurefield.type === FieldType.string we don't apply overflow hover states for type image
- (field.type === FieldType.string &&
- cellOptions.type !== TableCellDisplayMode.Image &&
- cellOptions.type !== TableCellDisplayMode.Pill) ||
+ (field.type === FieldType.string && cellOptions.type !== TableCellDisplayMode.Image) ||
// regardless of the underlying cell type, data links cells have text overflow.
cellOptions.type === TableCellDisplayMode.DataLinks;
diff --git a/public/app/plugins/panel/table/cells/AutoCellOptionsEditor.tsx b/public/app/plugins/panel/table/cells/AutoCellOptionsEditor.tsx
index 7b108fbc155..e97d481e0b3 100644
--- a/public/app/plugins/panel/table/cells/AutoCellOptionsEditor.tsx
+++ b/public/app/plugins/panel/table/cells/AutoCellOptionsEditor.tsx
@@ -8,8 +8,6 @@ export const AutoCellOptionsEditor = ({
cellOptions,
onChange,
}: TableCellEditorProps) => {
- // Handle row coloring changes
-
const onWrapTextChange = () => {
cellOptions.wrapText = !cellOptions.wrapText;
onChange(cellOptions);
diff --git a/public/app/plugins/panel/table/cells/ColorBackgroundCellOptionsEditor.tsx b/public/app/plugins/panel/table/cells/ColorBackgroundCellOptionsEditor.tsx
index afd6353a43e..33c3761493d 100644
--- a/public/app/plugins/panel/table/cells/ColorBackgroundCellOptionsEditor.tsx
+++ b/public/app/plugins/panel/table/cells/ColorBackgroundCellOptionsEditor.tsx
@@ -21,13 +21,11 @@ export const ColorBackgroundCellOptionsEditor = ({
onChange(cellOptions);
};
- // Handle row coloring changes
const onColorRowChange = () => {
cellOptions.applyToRow = !cellOptions.applyToRow;
onChange(cellOptions);
};
- // Handle row coloring changes
const onWrapTextChange = () => {
cellOptions.wrapText = !cellOptions.wrapText;
onChange(cellOptions);
diff --git a/public/app/plugins/panel/table/table-new/TableCellOptionEditor.tsx b/public/app/plugins/panel/table/table-new/TableCellOptionEditor.tsx
index 77e2acf0808..92cbbf0f264 100644
--- a/public/app/plugins/panel/table/table-new/TableCellOptionEditor.tsx
+++ b/public/app/plugins/panel/table/table-new/TableCellOptionEditor.tsx
@@ -4,14 +4,14 @@ import { useState } from 'react';
import { GrafanaTheme2 } from '@grafana/data';
import { t } from '@grafana/i18n';
-import { TableCellOptions } from '@grafana/schema';
+import { TableCellOptions, TableWrapTextOptions } from '@grafana/schema';
import { Combobox, ComboboxOption, Field, TableCellDisplayMode, useStyles2 } from '@grafana/ui';
-import { AutoCellOptionsEditor } from './cells/AutoCellOptionsEditor';
import { BarGaugeCellOptionsEditor } from './cells/BarGaugeCellOptionsEditor';
import { ColorBackgroundCellOptionsEditor } from './cells/ColorBackgroundCellOptionsEditor';
import { ImageCellOptionsEditor } from './cells/ImageCellOptionsEditor';
import { SparklineCellOptionsEditor } from './cells/SparklineCellOptionsEditor';
+import { TextWrapOptionsEditor } from './cells/TextWrapOptionsEditor';
// The props that any cell type editor are expected
// to handle. In this case the generic type should
@@ -26,6 +26,19 @@ interface Props {
onChange: (v: TableCellOptions) => void;
}
+const TEXT_WRAP_CELL_TYPES = new Set([
+ TableCellDisplayMode.Auto,
+ TableCellDisplayMode.Sparkline,
+ TableCellDisplayMode.ColorText,
+ TableCellDisplayMode.ColorBackground,
+ TableCellDisplayMode.DataLinks,
+ TableCellDisplayMode.Pill,
+]);
+
+function isTextWrapCellType(value: TableCellOptions): value is TableCellOptions & TableWrapTextOptions {
+ return TEXT_WRAP_CELL_TYPES.has(value.type);
+}
+
export const TableCellOptionEditor = ({ value, onChange }: Props) => {
const cellType = value.type;
const styles = useStyles2(getStyles);
@@ -79,9 +92,7 @@ export const TableCellOptionEditor = ({ value, onChange }: Props) => {
- {(cellType === TableCellDisplayMode.Auto || cellType === TableCellDisplayMode.ColorText) && (
-
- )}
+ {isTextWrapCellType(value) && }
{cellType === TableCellDisplayMode.Gauge && (
)}
diff --git a/public/app/plugins/panel/table/table-new/cells/AutoCellOptionsEditor.tsx b/public/app/plugins/panel/table/table-new/cells/AutoCellOptionsEditor.tsx
deleted file mode 100644
index 5aae9d65b7a..00000000000
--- a/public/app/plugins/panel/table/table-new/cells/AutoCellOptionsEditor.tsx
+++ /dev/null
@@ -1,27 +0,0 @@
-import { selectors } from '@grafana/e2e-selectors';
-import { t } from '@grafana/i18n';
-import { TableAutoCellOptions, TableColoredBackgroundCellOptions, TableColorTextCellOptions } from '@grafana/schema';
-import { Field, Switch } from '@grafana/ui';
-
-import { TableCellEditorProps } from '../TableCellOptionEditor';
-
-export const AutoCellOptionsEditor = ({
- cellOptions,
- onChange,
-}: TableCellEditorProps) => {
- // Handle row coloring changes
- const onWrapTextChange = () => {
- cellOptions.wrapText = !cellOptions.wrapText;
- onChange(cellOptions);
- };
-
- return (
-
-
-
- );
-};
diff --git a/public/app/plugins/panel/table/table-new/cells/ColorBackgroundCellOptionsEditor.tsx b/public/app/plugins/panel/table/table-new/cells/ColorBackgroundCellOptionsEditor.tsx
index b7d5d2ab3dc..33d85539ec4 100644
--- a/public/app/plugins/panel/table/table-new/cells/ColorBackgroundCellOptionsEditor.tsx
+++ b/public/app/plugins/panel/table/table-new/cells/ColorBackgroundCellOptionsEditor.tsx
@@ -6,7 +6,7 @@ import { Field, RadioButtonGroup, Switch } from '@grafana/ui';
import { TableCellEditorProps } from '../TableCellOptionEditor';
-import { AutoCellOptionsEditor } from './AutoCellOptionsEditor';
+import { TextWrapOptionsEditor } from './TextWrapOptionsEditor';
const colorBackgroundOpts: Array> = [
{ value: TableCellBackgroundDisplayMode.Basic, label: 'Basic' },
@@ -21,7 +21,6 @@ export const ColorBackgroundCellOptionsEditor = ({
cellOptions.mode = v;
onChange(cellOptions);
};
- // Handle row coloring changes
const onColorRowChange = () => {
cellOptions.applyToRow = !cellOptions.applyToRow;
onChange(cellOptions);
@@ -54,7 +53,7 @@ export const ColorBackgroundCellOptionsEditor = ({
/>
- {
cellOptions.wrapText = updatedCellOptions.wrapText;
diff --git a/public/app/plugins/panel/table/table-new/cells/TextWrapOptionsEditor.tsx b/public/app/plugins/panel/table/table-new/cells/TextWrapOptionsEditor.tsx
new file mode 100644
index 00000000000..74ddaa092ea
--- /dev/null
+++ b/public/app/plugins/panel/table/table-new/cells/TextWrapOptionsEditor.tsx
@@ -0,0 +1,29 @@
+import { selectors } from '@grafana/e2e-selectors';
+import { t } from '@grafana/i18n';
+import { TableCellOptions, TableWrapTextOptions } from '@grafana/schema';
+import { Field, Switch } from '@grafana/ui';
+
+import { TableCellEditorProps } from '../TableCellOptionEditor';
+
+export const TextWrapOptionsEditor = ({
+ cellOptions,
+ onChange,
+}: TableCellEditorProps) => {
+ // Handle row coloring changes
+ const onWrapTextChange = () => {
+ cellOptions.wrapText = !cellOptions.wrapText;
+ onChange(cellOptions);
+ };
+
+ return (
+ <>
+
+
+
+ >
+ );
+};
diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json
index 339cb81d0d1..927b0c58839 100644
--- a/public/locales/en-US/grafana.json
+++ b/public/locales/en-US/grafana.json
@@ -12582,7 +12582,10 @@
"name-show-table-header": "Show table header",
"name-wrap-header-text": "Wrap header text",
"placeholder-column-width": "auto",
- "placeholder-fields": "All Numeric Fields"
+ "placeholder-fields": "All Numeric Fields",
+ "text-wrap-options": {
+ "label-wrap-text": "Wrap text"
+ }
},
"table-new": {
"category-cell-options": "Cell options",
From 2f0190d775062b1f1e33a8b6323f5fad78ddacb0 Mon Sep 17 00:00:00 2001
From: William Wernert
Date: Fri, 1 Aug 2025 12:54:13 -0400
Subject: [PATCH 34/89] Alerting: Add store level pagination of rules (#108633)
---
pkg/services/ngalert/api/persist.go | 1 +
.../ngalert/api/prometheus/api_prometheus.go | 66 ++----
pkg/services/ngalert/models/alert_rule.go | 56 +++++
pkg/services/ngalert/store/alert_rule.go | 180 ++++++++++++++++
pkg/services/ngalert/store/alert_rule_test.go | 197 +++++++++++++++++-
pkg/services/ngalert/tests/fakes/rules.go | 87 ++++++++
6 files changed, 536 insertions(+), 51 deletions(-)
diff --git a/pkg/services/ngalert/api/persist.go b/pkg/services/ngalert/api/persist.go
index 2150edbe34c..5a60c2e0637 100644
--- a/pkg/services/ngalert/api/persist.go
+++ b/pkg/services/ngalert/api/persist.go
@@ -23,6 +23,7 @@ type RuleStore interface {
GetAlertRuleByUID(ctx context.Context, query *ngmodels.GetAlertRuleByUIDQuery) (*ngmodels.AlertRule, error)
GetAlertRulesGroupByRuleUID(ctx context.Context, query *ngmodels.GetAlertRulesGroupByRuleUIDQuery) ([]*ngmodels.AlertRule, error)
ListAlertRules(ctx context.Context, query *ngmodels.ListAlertRulesQuery) (ngmodels.RulesGroup, error)
+ ListAlertRulesByGroup(ctx context.Context, query *ngmodels.ListAlertRulesByGroupQuery) (ngmodels.RulesGroup, string, error)
ListDeletedRules(ctx context.Context, orgID int64) ([]*ngmodels.AlertRule, error)
// InsertAlertRules will insert all alert rules passed into the function
diff --git a/pkg/services/ngalert/api/prometheus/api_prometheus.go b/pkg/services/ngalert/api/prometheus/api_prometheus.go
index 2e753a6db1d..c223bce9aac 100644
--- a/pkg/services/ngalert/api/prometheus/api_prometheus.go
+++ b/pkg/services/ngalert/api/prometheus/api_prometheus.go
@@ -2,7 +2,6 @@ package api
import (
"context"
- "encoding/base64"
"encoding/json"
"errors"
"fmt"
@@ -30,7 +29,7 @@ import (
type RuleStoreReader interface {
GetUserVisibleNamespaces(context.Context, int64, identity.Requester) (map[string]*folder.Folder, error)
- ListAlertRules(ctx context.Context, query *ngmodels.ListAlertRulesQuery) (ngmodels.RulesGroup, error)
+ ListAlertRulesStore
}
type RuleGroupAccessControlService interface {
@@ -241,7 +240,7 @@ type RuleGroupStatusesOptions struct {
}
type ListAlertRulesStore interface {
- ListAlertRules(ctx context.Context, query *ngmodels.ListAlertRulesQuery) (ngmodels.RulesGroup, error)
+ ListAlertRulesByGroup(ctx context.Context, query *ngmodels.ListAlertRulesByGroupQuery) (ngmodels.RulesGroup, string, error)
}
func (srv PrometheusSrv) RouteGetRuleStatuses(c *contextmodel.ReqContext) response.Response {
@@ -476,15 +475,24 @@ func PrepareRuleGroupStatuses(log log.Logger, store ListAlertRulesStore, opts Ru
receiverName := opts.Query.Get("receiver_name")
- alertRuleQuery := ngmodels.ListAlertRulesQuery{
- OrgID: opts.OrgID,
- NamespaceUIDs: namespaceUIDs,
- DashboardUID: dashboardUID,
- PanelID: panelID,
- RuleGroups: ruleGroups,
- ReceiverName: receiverName,
+ maxGroups := getInt64WithDefault(opts.Query, "group_limit", -1)
+ nextToken := opts.Query.Get("group_next_token")
+
+ if maxGroups == 0 {
+ return ruleResponse
}
- ruleList, err := store.ListAlertRules(opts.Ctx, &alertRuleQuery)
+
+ byGroupQuery := ngmodels.ListAlertRulesByGroupQuery{
+ OrgID: opts.OrgID,
+ GroupLimit: maxGroups,
+ GroupContinueToken: nextToken,
+ NamespaceUIDs: namespaceUIDs,
+ DashboardUID: dashboardUID,
+ PanelID: panelID,
+ RuleGroups: ruleGroups,
+ ReceiverName: receiverName,
+ }
+ ruleList, continueToken, err := store.ListAlertRulesByGroup(opts.Ctx, &byGroupQuery)
if err != nil {
ruleResponse.Status = "error"
ruleResponse.Error = fmt.Sprintf("failure getting rules: %s", err.Error())
@@ -498,31 +506,9 @@ func PrepareRuleGroupStatuses(log log.Logger, store ListAlertRulesStore, opts Ru
ruleNamesSet[rn] = struct{}{}
}
- maxGroups := getInt64WithDefault(opts.Query, "group_limit", -1)
- nextToken := opts.Query.Get("group_next_token")
- if nextToken != "" {
- if _, err := base64.URLEncoding.DecodeString(nextToken); err != nil {
- nextToken = ""
- }
- }
-
groupedRules := getGroupedRules(log, ruleList, ruleNamesSet, opts.AllowedNamespaces)
rulesTotals := make(map[string]int64, len(groupedRules))
- var newToken string
- foundToken := false
for _, rg := range groupedRules {
- if nextToken != "" && !foundToken {
- if !tokenGreaterThanOrEqual(getRuleGroupNextToken(rg.Folder, rg.GroupKey.RuleGroup), nextToken) {
- continue
- }
- foundToken = true
- }
-
- if maxGroups > -1 && len(ruleResponse.Data.RuleGroups) == int(maxGroups) {
- newToken = getRuleGroupNextToken(rg.Folder, rg.GroupKey.RuleGroup)
- break
- }
-
ruleGroup, totals := toRuleGroup(log, rg.GroupKey, rg.Folder, rg.Rules, provenanceRecords, limitAlertsPerRule, stateFilterSet, matchers, labelOptions, ruleStatusMutator, alertStateMutator)
ruleGroup.Totals = totals
for k, v := range totals {
@@ -546,7 +532,7 @@ func PrepareRuleGroupStatuses(log log.Logger, store ListAlertRulesStore, opts Ru
}
}
- ruleResponse.Data.NextToken = newToken
+ ruleResponse.Data.NextToken = continueToken
// Only return Totals if there is no pagination
if maxGroups == -1 {
@@ -556,18 +542,6 @@ func PrepareRuleGroupStatuses(log log.Logger, store ListAlertRulesStore, opts Ru
return ruleResponse
}
-func getRuleGroupNextToken(namespace, group string) string {
- return base64.URLEncoding.EncodeToString([]byte(namespace + "/" + group))
-}
-
-// Returns true if tokenA >= tokenB
-func tokenGreaterThanOrEqual(tokenA string, tokenB string) bool {
- decodedTokenA, _ := base64.URLEncoding.DecodeString(tokenA)
- decodedTokenB, _ := base64.URLEncoding.DecodeString(tokenB)
-
- return string(decodedTokenA) >= string(decodedTokenB)
-}
-
type ruleGroup struct {
Folder string
GroupKey ngmodels.AlertRuleGroupKey
diff --git a/pkg/services/ngalert/models/alert_rule.go b/pkg/services/ngalert/models/alert_rule.go
index b4a4a94eaba..b1f415f929d 100644
--- a/pkg/services/ngalert/models/alert_rule.go
+++ b/pkg/services/ngalert/models/alert_rule.go
@@ -2,6 +2,7 @@ package models
import (
"context"
+ "encoding/base64"
"encoding/json"
"errors"
"fmt"
@@ -848,6 +849,61 @@ type GetAlertRulesGroupByRuleUIDQuery struct {
OrgID int64
}
+type RuleTypeFilter int
+
+const (
+ RuleTypeFilterAll RuleTypeFilter = iota
+ RuleTypeFilterAlerting
+ RuleTypeFilterRecording
+)
+
+type ListAlertRulesByGroupQuery struct {
+ OrgID int64
+ RuleUIDs []string
+ NamespaceUIDs []string
+ ExcludeOrgs []int64
+ RuleGroups []string
+
+ // DashboardUID and PanelID are optional and allow filtering rules
+ // to return just those for a dashboard and panel.
+ DashboardUID string
+ PanelID int64
+
+ ReceiverName string
+ TimeIntervalName string
+
+ HasPrometheusRuleDefinition *bool
+
+ RuleType RuleTypeFilter
+
+ GroupLimit int64 // Number of groups to fetch
+ GroupContinueToken string // Token for per-group pagination
+}
+
+type GroupCursor struct {
+ NamespaceUID string `json:"n"`
+ RuleGroup string `json:"g"`
+}
+
+func EncodeGroupCursor(c GroupCursor) string {
+ data, _ := json.Marshal(c)
+ return base64.URLEncoding.EncodeToString(data)
+}
+
+func DecodeGroupCursor(token string) (GroupCursor, error) {
+ var c GroupCursor
+ data, err := base64.URLEncoding.DecodeString(token)
+ if err != nil {
+ return c, fmt.Errorf("failed to decode group token: %w", err)
+ }
+
+ if err := json.Unmarshal(data, &c); err != nil {
+ return c, fmt.Errorf("failed to unmarshal group cursor: %w", err)
+ }
+
+ return c, nil
+}
+
// ListAlertRulesQuery is the query for listing alert rules
type ListAlertRulesQuery struct {
OrgID int64
diff --git a/pkg/services/ngalert/store/alert_rule.go b/pkg/services/ngalert/store/alert_rule.go
index c535bbe4379..ab8676c754d 100644
--- a/pkg/services/ngalert/store/alert_rule.go
+++ b/pkg/services/ngalert/store/alert_rule.go
@@ -582,6 +582,186 @@ func (st DBstore) CountInFolders(ctx context.Context, orgID int64, folderUIDs []
return count, err
}
+func (st DBstore) ListAlertRulesByGroup(ctx context.Context, query *ngmodels.ListAlertRulesByGroupQuery) (result ngmodels.RulesGroup, nextToken string, err error) {
+ err = st.SQLStore.WithDbSession(ctx, func(sess *db.Session) error {
+ q := sess.Table("alert_rule")
+
+ if query.OrgID >= 0 {
+ q = q.Where("org_id = ?", query.OrgID)
+ }
+
+ if query.DashboardUID != "" {
+ q = q.Where("dashboard_uid = ?", query.DashboardUID)
+ if query.PanelID != 0 {
+ q = q.Where("panel_id = ?", query.PanelID)
+ }
+ }
+
+ if len(query.NamespaceUIDs) > 0 {
+ args, in := getINSubQueryArgs(query.NamespaceUIDs)
+ q = q.Where(fmt.Sprintf("namespace_uid IN (%s)", strings.Join(in, ",")), args...)
+ }
+
+ if len(query.RuleUIDs) > 0 {
+ args, in := getINSubQueryArgs(query.RuleUIDs)
+ q = q.Where(fmt.Sprintf("uid IN (%s)", strings.Join(in, ",")), args...)
+ }
+
+ var groupsMap map[string]struct{}
+ if len(query.RuleGroups) > 0 {
+ groupsMap = make(map[string]struct{})
+ args, in := getINSubQueryArgs(query.RuleGroups)
+ q = q.Where(fmt.Sprintf("rule_group IN (%s)", strings.Join(in, ",")), args...)
+ for _, group := range query.RuleGroups {
+ groupsMap[group] = struct{}{}
+ }
+ }
+
+ if query.ReceiverName != "" {
+ q, err = st.filterByContentInNotificationSettings(query.ReceiverName, q)
+ if err != nil {
+ return err
+ }
+ }
+
+ if query.TimeIntervalName != "" {
+ q, err = st.filterByContentInNotificationSettings(query.TimeIntervalName, q)
+ if err != nil {
+ return err
+ }
+ }
+
+ if query.HasPrometheusRuleDefinition != nil {
+ q, err = st.filterWithPrometheusRuleDefinition(*query.HasPrometheusRuleDefinition, q)
+ if err != nil {
+ return err
+ }
+ }
+
+ switch query.RuleType {
+ case ngmodels.RuleTypeFilterAlerting:
+ q = q.Where("record = ''")
+ case ngmodels.RuleTypeFilterRecording:
+ q = q.Where("record != ''")
+ case ngmodels.RuleTypeFilterAll:
+ // no additional filter
+ default:
+ return fmt.Errorf("unknown rule type filter %q", query.RuleType)
+ }
+
+ // Order by group first, then by rule index within group
+ q = q.Asc("namespace_uid", "rule_group", "rule_group_idx", "id")
+
+ var cursor ngmodels.GroupCursor
+ if query.GroupContinueToken != "" {
+ // only set the cursor if it's valid, otherwise we'll start from the beginning
+ if cur, err := ngmodels.DecodeGroupCursor(query.GroupContinueToken); err == nil {
+ cursor = cur
+ }
+ }
+
+ // Build group cursor condition
+ if cursor.NamespaceUID != "" {
+ q = buildGroupCursorCondition(q, cursor)
+ }
+
+ // No arbitrary fetch limit - let the loop control pagination
+ alertRules := make([]*ngmodels.AlertRule, 0)
+ rule := new(alertRule)
+ rows, err := q.Rows(rule)
+ if err != nil {
+ return err
+ }
+ defer func() {
+ _ = rows.Close()
+ }()
+
+ // Process rules and implement per-group pagination
+ var groupsFetched int64
+ for rows.Next() {
+ rule := new(alertRule)
+ err = rows.Scan(rule)
+ if err != nil {
+ st.Logger.Error("Invalid rule found in DB store, ignoring it", "func", "ListAlertRulesByGroup", "error", err)
+ continue
+ }
+
+ converted, err := alertRuleToModelsAlertRule(*rule, st.Logger)
+ if err != nil {
+ st.Logger.Error("Invalid rule found in DB store, cannot convert, ignoring it", "func", "ListAlertRulesByGroup", "error", err)
+ continue
+ }
+
+ // Check if we've moved to a new group
+ key := ngmodels.GroupCursor{
+ NamespaceUID: converted.NamespaceUID,
+ RuleGroup: converted.RuleGroup,
+ }
+ if key != cursor {
+ // Check if we've reached the group limit
+ if query.GroupLimit > 0 && groupsFetched == query.GroupLimit {
+ // Generate next token for the next group
+ nextToken = ngmodels.EncodeGroupCursor(cursor)
+ break
+ }
+
+ // Reset for new group
+ cursor = key
+ groupsFetched++
+ }
+
+ // Apply post-query filters
+ if !shouldIncludeRule(&converted, query, groupsMap) {
+ continue
+ }
+
+ alertRules = append(alertRules, &converted)
+ }
+
+ result = alertRules
+ return nil
+ })
+ return result, nextToken, err
+}
+
+func buildGroupCursorCondition(sess *xorm.Session, c ngmodels.GroupCursor) *xorm.Session {
+ return sess.Where("(namespace_uid > ?)", c.NamespaceUID).
+ Or("(namespace_uid = ? AND rule_group > ?)", c.NamespaceUID, c.RuleGroup)
+}
+
+func shouldIncludeRule(rule *ngmodels.AlertRule, query *ngmodels.ListAlertRulesByGroupQuery, groupsMap map[string]struct{}) bool {
+ if query.ReceiverName != "" {
+ if !slices.ContainsFunc(rule.NotificationSettings, func(settings ngmodels.NotificationSettings) bool {
+ return settings.Receiver == query.ReceiverName
+ }) {
+ return false
+ }
+ }
+
+ if query.TimeIntervalName != "" {
+ if !slices.ContainsFunc(rule.NotificationSettings, func(settings ngmodels.NotificationSettings) bool {
+ return slices.Contains(settings.MuteTimeIntervals, query.TimeIntervalName) ||
+ slices.Contains(settings.ActiveTimeIntervals, query.TimeIntervalName)
+ }) {
+ return false
+ }
+ }
+
+ if query.HasPrometheusRuleDefinition != nil {
+ if *query.HasPrometheusRuleDefinition != rule.HasPrometheusRuleDefinition() {
+ return false
+ }
+ }
+
+ if groupsMap != nil {
+ if _, ok := groupsMap[rule.RuleGroup]; !ok {
+ return false
+ }
+ }
+
+ return true
+}
+
// ListAlertRules is a handler for retrieving alert rules of specific organisation.
func (st DBstore) ListAlertRules(ctx context.Context, query *ngmodels.ListAlertRulesQuery) (result ngmodels.RulesGroup, err error) {
err = st.SQLStore.WithDbSession(ctx, func(sess *db.Session) error {
diff --git a/pkg/services/ngalert/store/alert_rule_test.go b/pkg/services/ngalert/store/alert_rule_test.go
index 74f51fd305a..a512b5949b4 100644
--- a/pkg/services/ngalert/store/alert_rule_test.go
+++ b/pkg/services/ngalert/store/alert_rule_test.go
@@ -1596,14 +1596,14 @@ func TestIntegrationGetRuleVersions(t *testing.T) {
// createAlertRule creates an alert rule in the database and returns it.
// If a generator is not specified, uniqueness of primary key is not guaranteed.
-func createRule(t *testing.T, store *DBstore, generator *models.AlertRuleGenerator) *models.AlertRule {
- t.Helper()
+func createRule(tb testing.TB, store *DBstore, generator *models.AlertRuleGenerator) *models.AlertRule {
+ tb.Helper()
if generator == nil {
generator = models.RuleGen.With(models.RuleMuts.WithIntervalMatching(store.Cfg.BaseInterval))
}
rule := generator.GenerateRef()
converted, err := alertRuleFromModelsAlertRule(*rule)
- require.NoError(t, err)
+ require.NoError(tb, err)
err = store.SQLStore.WithDbSession(context.Background(), func(sess *db.Session) error {
converted.ID = 0
_, err := sess.Table(alertRule{}).InsertOne(&converted)
@@ -1622,12 +1622,12 @@ func createRule(t *testing.T, store *DBstore, generator *models.AlertRuleGenerat
rule = &r
return err
})
- require.NoError(t, err)
+ require.NoError(tb, err)
return rule
}
-func setupFolderService(t *testing.T, sqlStore db.DB, cfg *setting.Cfg, features featuremgmt.FeatureToggles) folder.Service {
+func setupFolderService(t testing.TB, sqlStore db.DB, cfg *setting.Cfg, features featuremgmt.FeatureToggles) folder.Service {
tracer := tracing.InitializeTracerForTest()
inProcBus := bus.ProvideBus(tracer)
folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore)
@@ -1735,6 +1735,169 @@ func TestIntegration_AlertRuleVersionsCleanup(t *testing.T) {
})
}
+func TestIntegration_ListAlertRulesByGroup(t *testing.T) {
+ if testing.Short() {
+ t.Skip("skipping integration test")
+ }
+
+ sqlStore := db.InitTestDB(t)
+ cfg := setting.NewCfg()
+ cfg.UnifiedAlerting = setting.UnifiedAlertingSettings{
+ BaseInterval: time.Duration(rand.Int64N(100)+1) * time.Second,
+ }
+ folderService := setupFolderService(t, sqlStore, cfg, featuremgmt.WithFeatures())
+ bus := &fakeBus{}
+ orgID := int64(1)
+ ruleGen := models.RuleGen.With(
+ models.RuleMuts.WithIntervalMatching(cfg.UnifiedAlerting.BaseInterval),
+ models.RuleMuts.WithOrgID(orgID),
+ )
+ store := createTestStore(sqlStore, folderService, &logtest.Fake{}, cfg.UnifiedAlerting, bus)
+
+ // set test params
+ numFolders := 10
+ numRules := 50
+ rulesPerGroup := 5
+ totalGroups := numRules / rulesPerGroup // 10
+
+ // create rules with different group names
+ rules, _ := createManyRules(t,
+ store,
+ ruleGen,
+ numFolders,
+ numRules,
+ rulesPerGroup,
+ )
+ // sort rules by folder, then group, then group index
+ slices.SortStableFunc(rules, func(a, b *models.AlertRule) int {
+ if a.NamespaceUID != b.NamespaceUID {
+ return strings.Compare(a.NamespaceUID, b.NamespaceUID)
+ }
+ if a.RuleGroup != b.RuleGroup {
+ return strings.Compare(a.RuleGroup, b.RuleGroup)
+ }
+ return a.RuleGroupIndex - b.RuleGroupIndex
+ })
+
+ t.Run("should return all rules when no limit passed", func(t *testing.T) {
+ result, continueToken, err := store.ListAlertRulesByGroup(context.Background(), &models.ListAlertRulesByGroupQuery{
+ OrgID: orgID,
+ })
+ require.NoError(t, err)
+ require.Len(t, result, 50, "should return all rules when no limit is set")
+ require.Empty(t, continueToken, "continue token should be empty when no limit is set")
+ })
+
+ t.Run("should return paginated results when group limit is set", func(t *testing.T) {
+ // random number from 1 to totalGroups - 1 (to ensure we always receive less than totalGroups)
+ groupLimit := rand.Int64N(int64(totalGroups)-1) + 1
+ result, continueToken, err := store.ListAlertRulesByGroup(context.Background(), &models.ListAlertRulesByGroupQuery{
+ OrgID: orgID,
+ GroupLimit: groupLimit,
+ })
+ require.NoError(t, err)
+ expectedRuleCount := groupLimit * int64(rulesPerGroup)
+ require.Len(t, result, int(expectedRuleCount), fmt.Sprintf("should return %d rules when group limit is set", expectedRuleCount))
+ require.NotEmpty(t, continueToken, "continue token should not be empty when limit is set")
+ })
+
+ t.Run("pagination should all for continuation", func(t *testing.T) {
+ groupLimit := int64(2) // fixed group limit for this test
+ result, continueToken, err := store.ListAlertRulesByGroup(context.Background(), &models.ListAlertRulesByGroupQuery{
+ OrgID: orgID,
+ GroupLimit: groupLimit,
+ })
+ require.NoError(t, err)
+ require.Len(t, result, int(groupLimit*int64(rulesPerGroup)), "should return rules for the first two groups")
+ require.NotEmpty(t, continueToken, "continue token should not be empty")
+
+ for i, rule := range result {
+ expected := rules[i].RuleGroup
+ actual := rule.RuleGroup
+ require.Equal(t, expected, actual, "rules should be ordered by group name")
+ }
+
+ resultRules := make([]*models.AlertRule, 0, len(result))
+ resultRules = append(resultRules, result...)
+
+ // Continue from previous, fetching the rest of the rules
+ result, continueToken, err = store.ListAlertRulesByGroup(context.Background(), &models.ListAlertRulesByGroupQuery{
+ OrgID: orgID,
+ GroupContinueToken: continueToken,
+ })
+ require.NoError(t, err)
+ resultRules = append(resultRules, result...)
+ require.Len(t, resultRules, numRules, "should return all rules when continuing from the last token")
+ require.Empty(t, continueToken, "continue token should be empty when all rules are fetched")
+ for i, rule := range resultRules {
+ expected := rules[i].RuleGroup
+ actual := rule.RuleGroup
+ require.Equal(t, expected, actual, "rules should be ordered by group name")
+ }
+ })
+}
+
+func Benchmark_ListAlertRules(b *testing.B) {
+ orgID := int64(1)
+ ruleGen := models.RuleGen
+
+ // init
+ sqlStore := db.InitTestDB(b)
+ cfg := setting.NewCfg()
+ cfg.UnifiedAlerting = setting.UnifiedAlertingSettings{
+ BaseInterval: time.Duration(rand.Int64N(100)) * time.Second,
+ }
+ folderService := setupFolderService(b, sqlStore, cfg, featuremgmt.WithFeatures())
+ bus := &fakeBus{}
+ store := createTestStore(sqlStore, folderService, &logtest.Fake{}, cfg.UnifiedAlerting, bus)
+
+ ruleGen = ruleGen.With(
+ ruleGen.WithIntervalMatching(cfg.UnifiedAlerting.BaseInterval),
+ ruleGen.WithOrgID(orgID),
+ )
+
+ // define benchmark parameters
+ numFolders := 5
+ numRules := 10000
+ rulesPerGroup := 100
+ assert.Greater(b, numRules, rulesPerGroup, "n must be greater than rulesPerGroup")
+ assert.Equal(b, 0, numRules%rulesPerGroup, "n % rulesPerGroup must be zero to create equal groups")
+
+ // create rules and folders (5 folders, each with n/rulesPerGroup rules)
+ _, _ = createManyRules(b,
+ store,
+ ruleGen,
+ numFolders, // number of folders
+ numRules, // total number of rules
+ rulesPerGroup, // rules per group
+ )
+
+ b.Run(fmt.Sprintf("list %d rules unpaginated", numRules), func(b *testing.B) {
+ for b.Loop() {
+ _, err := store.ListAlertRules(context.Background(), &models.ListAlertRulesQuery{
+ OrgID: orgID,
+ })
+ if err != nil {
+ b.Fatal(err)
+ }
+ }
+ })
+
+ for _, groupLimit := range []int{1, 2, 5, 10, 50, 100} {
+ b.Run(fmt.Sprintf("list %d groups paginated", groupLimit), func(b *testing.B) {
+ for b.Loop() {
+ _, _, err := store.ListAlertRulesByGroup(context.Background(), &models.ListAlertRulesByGroupQuery{
+ OrgID: orgID,
+ GroupLimit: int64(groupLimit),
+ })
+ if err != nil {
+ b.Fatal(err)
+ }
+ }
+ })
+ }
+}
+
func TestIntegration_ListAlertRules(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test")
@@ -1975,3 +2138,27 @@ func (f *fakeBus) Publish(ctx context.Context, msg bus.Msg) error {
return nil
}
+
+func createManyRules(tb testing.TB, store *DBstore, ruleGen *models.AlertRuleGenerator, numFolders, numRules, rulesPerGroup int) ([]*models.AlertRule, []string) {
+ tb.Helper()
+
+ require.Greater(tb, numRules, 0, "numRules must be greater than 0")
+ require.Greater(tb, numFolders, 0, "numFolders must be greater than 0")
+ require.Greater(tb, numRules, rulesPerGroup, "numRules must be greater than rulesPerGroup")
+ require.Greater(tb, rulesPerGroup, 0, "rulesPerGroup must be greater than 0")
+ require.Equal(tb, numRules%rulesPerGroup, 0, "numRules % rulesPerGroup must be zero to create equal groups")
+
+ rules := make([]*models.AlertRule, 0, numRules)
+ namespaceUIDs := make([]string, numFolders)
+ for i := range namespaceUIDs {
+ namespaceUIDs[i] = fmt.Sprintf("ns-%d", i)
+ }
+ for i := 0; i < numRules; i++ {
+ gen := ruleGen.With(
+ ruleGen.WithNamespaceUID(namespaceUIDs[i%numFolders]),
+ ruleGen.WithGroupName(fmt.Sprintf("group_%d", i%(numRules/rulesPerGroup))),
+ )
+ rules = append(rules, createRule(tb, store, gen))
+ }
+ return rules, namespaceUIDs
+}
diff --git a/pkg/services/ngalert/tests/fakes/rules.go b/pkg/services/ngalert/tests/fakes/rules.go
index 046522f3214..c17c70bf6c9 100644
--- a/pkg/services/ngalert/tests/fakes/rules.go
+++ b/pkg/services/ngalert/tests/fakes/rules.go
@@ -4,6 +4,7 @@ import (
"context"
"math/rand"
"slices"
+ "strings"
"sync"
"testing"
"time"
@@ -185,6 +186,88 @@ func (f *RuleStore) GetAlertRulesGroupByRuleUID(_ context.Context, q *models.Get
return ruleList, nil
}
+func (f *RuleStore) ListAlertRulesByGroup(_ context.Context, q *models.ListAlertRulesByGroupQuery) (models.RulesGroup, string, error) {
+ f.mtx.Lock()
+ defer f.mtx.Unlock()
+ f.RecordedOps = append(f.RecordedOps, *q)
+
+ if err := f.Hook(*q); err != nil {
+ return nil, "", err
+ }
+
+ query := &models.ListAlertRulesQuery{
+ OrgID: q.OrgID,
+ NamespaceUIDs: q.NamespaceUIDs,
+ DashboardUID: q.DashboardUID,
+ PanelID: q.PanelID,
+ RuleGroups: q.RuleGroups,
+ RuleUIDs: q.RuleUIDs,
+ ReceiverName: q.ReceiverName,
+ HasPrometheusRuleDefinition: q.HasPrometheusRuleDefinition,
+ }
+
+ ruleList, err := f.listAlertRules(query)
+ if err != nil {
+ return nil, "", err
+ }
+
+ // < group limit logic >
+
+ // sort rules to ensure order is consistent, pagination depends on this
+ slices.SortFunc(ruleList, func(a, b *models.AlertRule) int {
+ nsCmp := strings.Compare(a.NamespaceUID, b.NamespaceUID)
+ if nsCmp != 0 {
+ return nsCmp
+ }
+ rgCmp := strings.Compare(a.RuleGroup, b.RuleGroup)
+ if rgCmp != 0 {
+ return rgCmp
+ }
+ return models.RulesGroupComparer(a, b)
+ })
+
+ var nextToken string
+ var cursor models.GroupCursor
+ if q.GroupContinueToken != "" {
+ if cur, err := models.DecodeGroupCursor(q.GroupContinueToken); err == nil {
+ cursor = cur
+ }
+ }
+
+ if q.GroupLimit < 0 {
+ return ruleList, "", nil
+ }
+
+ outputRules := make([]*models.AlertRule, 0, len(ruleList))
+ var groupsFetched int64
+ initialCursor := cursor
+ for _, r := range ruleList {
+ // skip rules before the initial cursor
+ if initialCursor.NamespaceUID != "" &&
+ (strings.Compare(r.NamespaceUID, initialCursor.NamespaceUID) < 0 ||
+ (strings.Compare(r.NamespaceUID, initialCursor.NamespaceUID) == 0 && strings.Compare(r.RuleGroup, initialCursor.RuleGroup) <= 0)) {
+ continue
+ }
+
+ key := models.GroupCursor{
+ NamespaceUID: r.NamespaceUID,
+ RuleGroup: r.RuleGroup,
+ }
+ if key != cursor {
+ if q.GroupLimit > 0 && groupsFetched == q.GroupLimit {
+ nextToken = models.EncodeGroupCursor(cursor)
+ break
+ }
+ cursor = key
+ groupsFetched++
+ }
+
+ outputRules = append(outputRules, r)
+ }
+
+ return outputRules, nextToken, nil
+}
+
func (f *RuleStore) ListAlertRules(_ context.Context, q *models.ListAlertRulesQuery) (models.RulesGroup, error) {
f.mtx.Lock()
defer f.mtx.Unlock()
@@ -194,6 +277,10 @@ func (f *RuleStore) ListAlertRules(_ context.Context, q *models.ListAlertRulesQu
return nil, err
}
+ return f.listAlertRules(q)
+}
+
+func (f *RuleStore) listAlertRules(q *models.ListAlertRulesQuery) (models.RulesGroup, error) {
hasDashboard := func(r *models.AlertRule, dashboardUID string, panelID int64) bool {
if dashboardUID != "" {
if r.DashboardUID == nil || *r.DashboardUID != dashboardUID {
From eb3a457c6dc95ed1ceba0d9ea48eada5d6a5fed7 Mon Sep 17 00:00:00 2001
From: Alexander Akhmetov
Date: Fri, 1 Aug 2025 21:15:21 +0200
Subject: [PATCH 35/89] Alerting: Support JSON responses in the Prometheus
conversion API (#109070)
---
.../ngalert/api/api_convert_prometheus.go | 36 +++-
.../api/api_convert_prometheus_test.go | 185 +++++++++++++++---
2 files changed, 188 insertions(+), 33 deletions(-)
diff --git a/pkg/services/ngalert/api/api_convert_prometheus.go b/pkg/services/ngalert/api/api_convert_prometheus.go
index 345569be3c8..97afcf561d1 100644
--- a/pkg/services/ngalert/api/api_convert_prometheus.go
+++ b/pkg/services/ngalert/api/api_convert_prometheus.go
@@ -5,6 +5,7 @@ import (
"encoding/json"
"errors"
"fmt"
+ "mime"
"net/http"
"path/filepath"
"strconv"
@@ -155,7 +156,7 @@ func NewConvertPrometheusSrv(
// RouteConvertPrometheusGetRules returns all Grafana-managed alert rules in all namespaces (folders)
// that were imported from a Prometheus-compatible source.
-// It responds with a YAML containing a mapping of folders to arrays of Prometheus rule groups.
+// It responds with JSON or YAML containing a mapping of folders to arrays of Prometheus rule groups.
func (srv *ConvertPrometheusSrv) RouteConvertPrometheusGetRules(c *contextmodel.ReqContext) response.Response {
logger := srv.logger.FromContext(c.Req.Context())
@@ -166,7 +167,7 @@ func (srv *ConvertPrometheusSrv) RouteConvertPrometheusGetRules(c *contextmodel.
if len(folders) == 0 || errors.Is(err, dashboards.ErrFolderNotFound) {
// If there is no such folder or no children, return empty response
// because mimirtool expects 200 OK response in this case.
- return response.YAML(http.StatusOK, map[string][]apimodels.PrometheusRuleGroup{})
+ return convertPrometheusResponse(c, http.StatusOK, map[string][]apimodels.PrometheusRuleGroup{})
}
if err != nil {
logger.Error("Failed to get folders", "error", err)
@@ -193,7 +194,7 @@ func (srv *ConvertPrometheusSrv) RouteConvertPrometheusGetRules(c *contextmodel.
return errorToResponse(err)
}
- return response.YAML(http.StatusOK, namespaces)
+ return convertPrometheusResponse(c, http.StatusOK, namespaces)
}
// RouteConvertPrometheusDeleteNamespace deletes all rule groups that were imported from a Prometheus-compatible source
@@ -256,7 +257,7 @@ func (srv *ConvertPrometheusSrv) RouteConvertPrometheusDeleteRuleGroup(c *contex
}
// RouteConvertPrometheusGetNamespace returns the Grafana-managed alert rules for a specified namespace (folder).
-// It responds with a YAML containing a mapping of a single folder to an array of Prometheus rule groups.
+// It responds with JSON or YAML containing a mapping of a single folder to an array of Prometheus rule groups.
func (srv *ConvertPrometheusSrv) RouteConvertPrometheusGetNamespace(c *contextmodel.ReqContext, namespaceTitle string) response.Response {
logger := srv.logger.FromContext(c.Req.Context())
@@ -286,11 +287,11 @@ func (srv *ConvertPrometheusSrv) RouteConvertPrometheusGetNamespace(c *contextmo
return errorToResponse(err)
}
- return response.YAML(http.StatusOK, ns)
+ return convertPrometheusResponse(c, http.StatusOK, ns)
}
// RouteConvertPrometheusGetRuleGroup retrieves a single rule group for a given namespace (folder)
-// in Prometheus-compatible YAML format if it was imported from a Prometheus-compatible source.
+// in Prometheus-compatible JSON or YAML format if it was imported from a Prometheus-compatible source.
func (srv *ConvertPrometheusSrv) RouteConvertPrometheusGetRuleGroup(c *contextmodel.ReqContext, namespaceTitle string, group string) response.Response {
logger := srv.logger.FromContext(c.Req.Context())
@@ -332,7 +333,7 @@ func (srv *ConvertPrometheusSrv) RouteConvertPrometheusGetRuleGroup(c *contextmo
return errorToResponse(err)
}
- return response.YAML(http.StatusOK, promGroup)
+ return convertPrometheusResponse(c, http.StatusOK, promGroup)
}
// RouteConvertPrometheusPostRuleGroup converts a Prometheus rule group into a Grafana rule group
@@ -608,7 +609,7 @@ func (srv *ConvertPrometheusSrv) RouteConvertPrometheusGetAlertmanagerConfig(c *
TemplateFiles: extraCfg.TemplateFiles,
}
- resp := response.YAML(http.StatusOK, respBody)
+ resp := convertPrometheusResponse(c, http.StatusOK, respBody)
resp.SetHeader(configIdentifierHeader, extraCfg.Identifier)
resp.SetHeader(mergeMatchersHeader, formatMergeMatchers(extraCfg.MergeMatchers))
@@ -808,3 +809,22 @@ func parseConfigIdentifierHeader(c *contextmodel.ReqContext) string {
}
return identifier
}
+
+// convertPrometheusResponse returns a JSON or YAML response based on the Accept header.
+// Default is YAML for backward compatibility with mimirtool.
+func convertPrometheusResponse(c *contextmodel.ReqContext, status int, body interface{}) *response.NormalResponse {
+ acceptHeader := c.Req.Header.Get("Accept")
+
+ for _, accept := range strings.Split(acceptHeader, ",") {
+ mediaType, _, err := mime.ParseMediaType(accept)
+ if err != nil {
+ continue
+ }
+
+ if mediaType == "application/json" {
+ return response.JSON(status, body)
+ }
+ }
+
+ return response.YAML(status, body)
+}
diff --git a/pkg/services/ngalert/api/api_convert_prometheus_test.go b/pkg/services/ngalert/api/api_convert_prometheus_test.go
index ea3a9a16a13..ca7833361c2 100644
--- a/pkg/services/ngalert/api/api_convert_prometheus_test.go
+++ b/pkg/services/ngalert/api/api_convert_prometheus_test.go
@@ -6,6 +6,7 @@ import (
"errors"
"net/http"
"net/http/httptest"
+ "strings"
"testing"
"time"
@@ -597,17 +598,34 @@ func TestRouteConvertPrometheusGetRuleGroup(t *testing.T) {
GenerateRef()
ruleStore.PutRule(context.Background(), ruleInOtherFolder)
- getResp := srv.RouteConvertPrometheusGetRuleGroup(rc, fldr.Title, groupKey.RuleGroup)
- require.Equal(t, http.StatusOK, getResp.Status())
+ t.Run("YAML response", func(t *testing.T) {
+ getResp := srv.RouteConvertPrometheusGetRuleGroup(rc, fldr.Title, groupKey.RuleGroup)
+ require.Equal(t, http.StatusOK, getResp.Status())
- var respGroup apimodels.PrometheusRuleGroup
- err := yaml.Unmarshal(getResp.Body(), &respGroup)
- require.NoError(t, err)
+ var respGroup apimodels.PrometheusRuleGroup
+ err := yaml.Unmarshal(getResp.Body(), &respGroup)
+ require.NoError(t, err)
- require.Equal(t, groupKey.RuleGroup, respGroup.Name)
- require.Equal(t, prommodel.Duration(time.Duration(rule.IntervalSeconds)*time.Second), respGroup.Interval)
- require.Len(t, respGroup.Rules, 1)
- require.Equal(t, promRule.Alert, respGroup.Rules[0].Alert)
+ require.Equal(t, groupKey.RuleGroup, respGroup.Name)
+ require.Equal(t, prommodel.Duration(time.Duration(rule.IntervalSeconds)*time.Second), respGroup.Interval)
+ require.Len(t, respGroup.Rules, 1)
+ require.Equal(t, promRule.Alert, respGroup.Rules[0].Alert)
+ })
+
+ t.Run("JSON response", func(t *testing.T) {
+ rc.Req.Header.Set("Accept", "application/json")
+ getResp := srv.RouteConvertPrometheusGetRuleGroup(rc, fldr.Title, groupKey.RuleGroup)
+ require.Equal(t, http.StatusOK, getResp.Status())
+
+ var jsonGroup apimodels.PrometheusRuleGroup
+ err := json.Unmarshal(getResp.Body(), &jsonGroup)
+ require.NoError(t, err)
+
+ require.Equal(t, groupKey.RuleGroup, jsonGroup.Name)
+ require.Equal(t, prommodel.Duration(time.Duration(rule.IntervalSeconds)*time.Second), jsonGroup.Interval)
+ require.Len(t, jsonGroup.Rules, 1)
+ require.Equal(t, promRule.Alert, jsonGroup.Rules[0].Alert)
+ })
})
}
@@ -690,16 +708,32 @@ func TestRouteConvertPrometheusGetNamespace(t *testing.T) {
ruleStore.PutRule(context.Background(), rule)
}
- response := srv.RouteConvertPrometheusGetNamespace(rc, fldr.Title)
- require.Equal(t, http.StatusOK, response.Status())
+ t.Run("YAML response", func(t *testing.T) {
+ response := srv.RouteConvertPrometheusGetNamespace(rc, fldr.Title)
+ require.Equal(t, http.StatusOK, response.Status())
- var respNamespaces map[string][]apimodels.PrometheusRuleGroup
- err := yaml.Unmarshal(response.Body(), &respNamespaces)
- require.NoError(t, err)
+ var respNamespaces map[string][]apimodels.PrometheusRuleGroup
+ err := yaml.Unmarshal(response.Body(), &respNamespaces)
+ require.NoError(t, err)
- require.Len(t, respNamespaces, 1)
- require.Contains(t, respNamespaces, fldr.Title)
- require.ElementsMatch(t, respNamespaces[fldr.Title], []apimodels.PrometheusRuleGroup{promGroup1, promGroup2})
+ require.Len(t, respNamespaces, 1)
+ require.Contains(t, respNamespaces, fldr.Title)
+ require.ElementsMatch(t, respNamespaces[fldr.Title], []apimodels.PrometheusRuleGroup{promGroup1, promGroup2})
+ })
+
+ t.Run("JSON response", func(t *testing.T) {
+ rc.Req.Header.Set("Accept", "application/json")
+ response := srv.RouteConvertPrometheusGetNamespace(rc, fldr.Title)
+ require.Equal(t, http.StatusOK, response.Status())
+
+ var jsonNamespaces map[string][]apimodels.PrometheusRuleGroup
+ err := json.Unmarshal(response.Body(), &jsonNamespaces)
+ require.NoError(t, err)
+
+ require.Len(t, jsonNamespaces, 1)
+ require.Contains(t, jsonNamespaces, fldr.Title)
+ require.ElementsMatch(t, jsonNamespaces[fldr.Title], []apimodels.PrometheusRuleGroup{promGroup1, promGroup2})
+ })
})
}
@@ -821,19 +855,120 @@ func TestRouteConvertPrometheusGetRules(t *testing.T) {
ruleStore.PutRule(context.Background(), rule)
}
- response := srv.RouteConvertPrometheusGetRules(rc)
- require.Equal(t, http.StatusOK, response.Status())
+ t.Run("YAML response", func(t *testing.T) {
+ response := srv.RouteConvertPrometheusGetRules(rc)
+ require.Equal(t, http.StatusOK, response.Status())
- var respNamespaces map[string][]apimodels.PrometheusRuleGroup
- err := yaml.Unmarshal(response.Body(), &respNamespaces)
- require.NoError(t, err)
+ var respNamespaces map[string][]apimodels.PrometheusRuleGroup
+ err := yaml.Unmarshal(response.Body(), &respNamespaces)
+ require.NoError(t, err)
- require.Len(t, respNamespaces, 1)
- require.Contains(t, respNamespaces, fldr.Title)
- require.ElementsMatch(t, respNamespaces[fldr.Title], []apimodels.PrometheusRuleGroup{promGroup1, promGroup2})
+ require.Len(t, respNamespaces, 1)
+ require.Contains(t, respNamespaces, fldr.Title)
+ require.ElementsMatch(t, respNamespaces[fldr.Title], []apimodels.PrometheusRuleGroup{promGroup1, promGroup2})
+ })
+
+ t.Run("JSON response", func(t *testing.T) {
+ rc.Req.Header.Set("Accept", "application/json")
+ response := srv.RouteConvertPrometheusGetRules(rc)
+ require.Equal(t, http.StatusOK, response.Status())
+
+ var jsonNamespaces map[string][]apimodels.PrometheusRuleGroup
+ err := json.Unmarshal(response.Body(), &jsonNamespaces)
+ require.NoError(t, err)
+
+ require.Len(t, jsonNamespaces, 1)
+ require.Contains(t, jsonNamespaces, fldr.Title)
+ require.ElementsMatch(t, jsonNamespaces[fldr.Title], []apimodels.PrometheusRuleGroup{promGroup1, promGroup2})
+ })
})
}
+func TestConvertPrometheusResponse(t *testing.T) {
+ testData := map[string][]apimodels.PrometheusRuleGroup{
+ "test": {
+ {
+ Name: "test-group",
+ Rules: []apimodels.PrometheusRule{
+ {
+ Alert: "TestAlert",
+ Expr: "up == 0",
+ },
+ },
+ },
+ },
+ }
+
+ testCases := []struct {
+ name string
+ acceptHeader string
+ expectedType string
+ checkResponse func(t *testing.T, body []byte)
+ }{
+ {
+ name: "by default returns YAML",
+ expectedType: "text/yaml",
+ checkResponse: func(t *testing.T, body []byte) {
+ require.True(t, strings.Contains(string(body), "test-group"))
+ require.True(t, strings.Contains(string(body), "TestAlert"))
+ var result map[string][]apimodels.PrometheusRuleGroup
+ err := yaml.Unmarshal(body, &result)
+ require.NoError(t, err)
+ },
+ },
+ {
+ name: "with application/json Accept header returns JSON",
+ acceptHeader: "application/json",
+ expectedType: "application/json",
+ checkResponse: func(t *testing.T, body []byte) {
+ require.True(t, strings.Contains(string(body), "test-group"))
+ require.True(t, strings.Contains(string(body), "TestAlert"))
+ var result map[string][]apimodels.PrometheusRuleGroup
+ err := json.Unmarshal(body, &result)
+ require.NoError(t, err)
+ },
+ },
+ {
+ name: "with application/yaml accept header returns YAML",
+ acceptHeader: "application/yaml",
+ expectedType: "text/yaml",
+ checkResponse: func(t *testing.T, body []byte) {
+ require.True(t, strings.Contains(string(body), "test-group"))
+ require.True(t, strings.Contains(string(body), "TestAlert"))
+ var result map[string][]apimodels.PrometheusRuleGroup
+ err := yaml.Unmarshal(body, &result)
+ require.NoError(t, err)
+ },
+ },
+ {
+ name: "with a header with both json and yaml returns JSON",
+ acceptHeader: "application/yaml, application/json",
+ expectedType: "application/json",
+ checkResponse: func(t *testing.T, body []byte) {
+ require.True(t, strings.Contains(string(body), "test-group"))
+ require.True(t, strings.Contains(string(body), "TestAlert"))
+ var result map[string][]apimodels.PrometheusRuleGroup
+ err := json.Unmarshal(body, &result)
+ require.NoError(t, err)
+ },
+ },
+ }
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ rc := createRequestCtx()
+ if tc.acceptHeader != "" {
+ rc.Req.Header.Set("Accept", tc.acceptHeader)
+ }
+
+ response := convertPrometheusResponse(rc, http.StatusOK, testData)
+
+ require.Equal(t, http.StatusOK, response.Status())
+ tc.checkResponse(t, response.Body())
+ })
+ }
+}
+
func TestRouteConvertPrometheusDeleteNamespace(t *testing.T) {
t.Run("for non-existent folder should return 404", func(t *testing.T) {
srv, _, _ := createConvertPrometheusSrv(t)
From b36a8e84ccb35b8ec3770b5a03c606b82453df2a Mon Sep 17 00:00:00 2001
From: Alexander Akhmetov
Date: Fri, 1 Aug 2025 21:15:33 +0200
Subject: [PATCH 36/89] Alerting: Document "Get rule group" Prometheus
conversion API endpoint (#109075)
---
docs/sources/alerting/alerting-rules/alerting-migration.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/docs/sources/alerting/alerting-rules/alerting-migration.md b/docs/sources/alerting/alerting-rules/alerting-migration.md
index 328ca0eefb7..08a301c24c3 100644
--- a/docs/sources/alerting/alerting-rules/alerting-migration.md
+++ b/docs/sources/alerting/alerting-rules/alerting-migration.md
@@ -287,6 +287,7 @@ The `GET` and `DELETE` endpoints work only with provisioned and imported alert r
| -------- | ------------------------------------------------------------ | --------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
| GET | `/convert/prometheus/config/v1/rules` | Get all imported rule groups across all namespaces. | [List rule groups](/docs/mimir/latest/references/http-api/#list-rule-groups) |
| GET | `/convert/prometheus/config/v1/rules/:namespaceTitle` | Get imported rule groups in a specific namespace. | [Get rule groups by namespace](/docs/mimir/latest/references/http-api/#get-rule-groups-by-namespace) |
+| GET | `/convert/prometheus/config/v1/rules/:namespaceTitle/:group` | Get imported rule group in a specific namespace. | [Get rule group](/docs/mimir/latest/references/http-api/#get-rule-group) |
| DELETE | `/convert/prometheus/config/v1/rules/:namespaceTitle` | Delete all imported alert rules in a namespace. | [Delete namespace](/docs/mimir/latest/references/http-api/#delete-namespace) |
| DELETE | `/convert/prometheus/config/v1/rules/:namespaceTitle/:group` | Delete a specific imported rule group. | [Delete rule group](/docs/mimir/latest/references/http-api/#delete-rule-group) |
From 172a69da751f20253e6584d810c883effa6de293 Mon Sep 17 00:00:00 2001
From: Stephanie Hingtgen
Date: Fri, 1 Aug 2025 14:29:42 -0500
Subject: [PATCH 37/89] Feature toggle: Cleanup old ones (#109072)
---
e2e/dashboards-search-suite/mode0.ini | 4 ----
e2e/dashboards-search-suite/mode1.ini | 4 ----
e2e/dashboards-search-suite/mode2-legacy-search-api.ini | 4 ----
e2e/dashboards-search-suite/mode2.ini | 4 ----
e2e/dashboards-search-suite/mode3.ini | 4 ----
e2e/dashboards-search-suite/mode4.ini | 4 ----
e2e/dashboards-search-suite/mode5.ini | 4 ----
pkg/services/featuremgmt/toggles-gitlog.csv | 4 ----
pkg/storage/unified/README.md | 4 ----
9 files changed, 36 deletions(-)
diff --git a/e2e/dashboards-search-suite/mode0.ini b/e2e/dashboards-search-suite/mode0.ini
index 1e0c6c55b0a..e8f0a768c77 100644
--- a/e2e/dashboards-search-suite/mode0.ini
+++ b/e2e/dashboards-search-suite/mode0.ini
@@ -1,14 +1,10 @@
[server]
[feature_toggles]
-kubernetesFolders = true
unifiedStorageSearch = true
unifiedStorageSearchUI = true
grafanaAPIServerWithExperimentalAPIs = true
-kubernetesDashboardsAPI = true
-kubernetesCliDashboards = true
unifiedStorageSearchSprinkles = true
-kubernetesFoldersServiceV2 = true
unifiedStorageSearchPermissionFiltering = true
[unified_storage.folders.folder.grafana.app]
diff --git a/e2e/dashboards-search-suite/mode1.ini b/e2e/dashboards-search-suite/mode1.ini
index 56e16cb4cff..5beecc31f65 100644
--- a/e2e/dashboards-search-suite/mode1.ini
+++ b/e2e/dashboards-search-suite/mode1.ini
@@ -1,14 +1,10 @@
[server]
[feature_toggles]
-kubernetesFolders = true
unifiedStorageSearch = true
unifiedStorageSearchUI = true
grafanaAPIServerWithExperimentalAPIs = true
-kubernetesDashboardsAPI = true
-kubernetesCliDashboards = true
unifiedStorageSearchSprinkles = true
-kubernetesFoldersServiceV2 = true
unifiedStorageSearchPermissionFiltering = true
[unified_storage.folders.folder.grafana.app]
diff --git a/e2e/dashboards-search-suite/mode2-legacy-search-api.ini b/e2e/dashboards-search-suite/mode2-legacy-search-api.ini
index 4b671c8b78b..45b16de4034 100644
--- a/e2e/dashboards-search-suite/mode2-legacy-search-api.ini
+++ b/e2e/dashboards-search-suite/mode2-legacy-search-api.ini
@@ -1,14 +1,10 @@
[server]
[feature_toggles]
-kubernetesFolders = true
unifiedStorageSearch = true
unifiedStorageSearchUI = false
grafanaAPIServerWithExperimentalAPIs = true
-kubernetesDashboardsAPI = true
-kubernetesCliDashboards = true
unifiedStorageSearchSprinkles = true
-kubernetesFoldersServiceV2 = true
unifiedStorageSearchPermissionFiltering = true
[unified_storage.folders.folder.grafana.app]
diff --git a/e2e/dashboards-search-suite/mode2.ini b/e2e/dashboards-search-suite/mode2.ini
index af96c552def..55538ee82e3 100644
--- a/e2e/dashboards-search-suite/mode2.ini
+++ b/e2e/dashboards-search-suite/mode2.ini
@@ -1,14 +1,10 @@
[server]
[feature_toggles]
-kubernetesFolders = true
unifiedStorageSearch = true
unifiedStorageSearchUI = true
grafanaAPIServerWithExperimentalAPIs = true
-kubernetesDashboardsAPI = true
-kubernetesCliDashboards = true
unifiedStorageSearchSprinkles = true
-kubernetesFoldersServiceV2 = true
unifiedStorageSearchPermissionFiltering = true
[unified_storage.folders.folder.grafana.app]
diff --git a/e2e/dashboards-search-suite/mode3.ini b/e2e/dashboards-search-suite/mode3.ini
index a4a56f9d80c..5955dfcd72a 100644
--- a/e2e/dashboards-search-suite/mode3.ini
+++ b/e2e/dashboards-search-suite/mode3.ini
@@ -1,14 +1,10 @@
[server]
[feature_toggles]
-kubernetesFolders = true
unifiedStorageSearch = true
unifiedStorageSearchUI = true
grafanaAPIServerWithExperimentalAPIs = true
-kubernetesDashboardsAPI = true
-kubernetesCliDashboards = true
unifiedStorageSearchSprinkles = true
-kubernetesFoldersServiceV2 = true
unifiedStorageSearchPermissionFiltering = true
[unified_storage.folders.folder.grafana.app]
diff --git a/e2e/dashboards-search-suite/mode4.ini b/e2e/dashboards-search-suite/mode4.ini
index dd774cb2f14..e0f6f6d892d 100644
--- a/e2e/dashboards-search-suite/mode4.ini
+++ b/e2e/dashboards-search-suite/mode4.ini
@@ -1,14 +1,10 @@
[server]
[feature_toggles]
-kubernetesFolders = true
unifiedStorageSearch = true
unifiedStorageSearchUI = true
grafanaAPIServerWithExperimentalAPIs = true
-kubernetesDashboardsAPI = true
-kubernetesCliDashboards = true
unifiedStorageSearchSprinkles = true
-kubernetesFoldersServiceV2 = true
unifiedStorageSearchPermissionFiltering = true
[unified_storage.folders.folder.grafana.app]
diff --git a/e2e/dashboards-search-suite/mode5.ini b/e2e/dashboards-search-suite/mode5.ini
index 83437bda185..8722cb091eb 100644
--- a/e2e/dashboards-search-suite/mode5.ini
+++ b/e2e/dashboards-search-suite/mode5.ini
@@ -1,14 +1,10 @@
[server]
[feature_toggles]
-kubernetesFolders = true
unifiedStorageSearch = true
unifiedStorageSearchUI = true
grafanaAPIServerWithExperimentalAPIs = true
-kubernetesDashboardsAPI = true
-kubernetesCliDashboards = true
unifiedStorageSearchSprinkles = true
-kubernetesFoldersServiceV2 = true
unifiedStorageSearchPermissionFiltering = true
[unified_storage.folders.folder.grafana.app]
diff --git a/pkg/services/featuremgmt/toggles-gitlog.csv b/pkg/services/featuremgmt/toggles-gitlog.csv
index 4a0ff55bc6e..3024aa121f9 100644
--- a/pkg/services/featuremgmt/toggles-gitlog.csv
+++ b/pkg/services/featuremgmt/toggles-gitlog.csv
@@ -360,7 +360,6 @@ appPlatformAccessTokens,2024-09-05T16:18:44Z,2024-10-14T10:47:18Z,d5ebaa0ef92ede
appSidecar,2024-09-09T12:45:05Z,2025-04-10T20:04:12Z,5e2ac24890906e5070323d87730dd78a4f885963,Andrej Ocenas
vizActions,2024-09-09T14:11:55Z,2025-02-26T23:15:01Z,af48d3db1eb2d8681843f5997e50fea5e5ea3096,Adela Almasan
groupAttributeSync,2024-09-09T15:29:43Z,,6ded6a8872204a818b3795dc733cc5fe5db066a0,Aaron Godin
-kubernetesFolders,2024-09-10T09:22:08Z,2025-01-23T14:25:03Z,b12a29a1dac8b9aec4a99be08e1665939cb27dc5,Arati R.
alertingFilterV2,2024-09-11T11:29:26Z,,90ee52e8d9c14237f8a57b622c0def7512e657cd,Gilles De Mey
improvedExternalSessionHandling,2024-09-17T10:54:39Z,,41cd0f51800d4849345fc0980ca4173967fc8e9e,Misi
datasourceAPIServers,2024-09-19T08:28:27Z,,f21a5987a22bcdb596d6a258d2960e4151348b63,Ryan McKinley
@@ -374,7 +373,6 @@ grafanaAPIServerTestingWithExperimentalAPIs,2024-10-03T10:11:40Z,2025-01-23T14:2
pluginsSriChecks,2024-10-04T12:55:09Z,,0db65d229e36b78802c1e8bd0713ac44e7a7cdc7,Giuseppe Guerra
onPremToCloudMigrationsAlerts,2024-10-07T10:53:24Z,2024-12-17T11:56:18Z,712314e8324fd86ec33ecb840f868eb1f1cac154,Matheus Macabu
appPlatformGrpcClientAuth,2024-10-14T10:47:18Z,,a69ee676babc7644da41781efc5ae2c301f06de6,Claudiu Dragalina-Paraipan
-kubernetesDashboardsAPI,2024-10-15T19:30:05Z,2024-12-10T18:35:36Z,644a16048f034f6bc79883678f1f1a4b48233483,Stephanie Hingtgen
unifiedStorageBigObjectsSupport,2024-10-17T10:18:29Z,,3457f219be1c8bce99f713d7a907ee339ef38229,Ryan McKinley
timeRangeProvider,2024-10-22T10:52:33Z,,3bf3290340a7842bb1d83647343234b2e9e83b18,Andrej Ocenas
dashboardNewLayouts,2024-10-23T08:55:45Z,,b700de81224caacd327fafb6ce0dfda8b38d39c6,Torkel Ödegaard
@@ -407,7 +405,6 @@ feedbackButton,2024-12-02T17:08:15Z,,8a1b89a5ebb847f6b29e92dcea796f00c30431d6,Mi
elasticsearchCrossClusterSearch,2024-12-12T22:20:04Z,,b3a12f486eba69e20dd7ff3a3d4dd065ede7a99f,Isabella Siu
unifiedHistory,2024-12-13T10:41:18Z,,aac62c89dae1092836a91d1b6ae6bd7127fe676a,Laura Fernández
lokiLabelNamesQueryApi,2024-12-13T14:31:41Z,,5ac7443fcec0db412d3333044a82c2c26b5aece7,Sven Grossmann
-kubernetesCliDashboards,2024-12-13T22:55:43Z,2025-02-18T23:11:26Z,8f6e9f8ed0a5024a510cc337c9f1e6972bfb23d4,Stephanie Hingtgen
useV2DashboardsAPI,2024-12-17T21:17:09Z,2025-03-12T17:43:32Z,070f0e4457c5967102ef157197073dc2662f6fb8,Dominik Prokop
investigationsBackend,2024-12-18T08:31:03Z,,f46c07aba7b6faccd2ecafc83051d1410cacc867,Jackson Coelho
unifiedStorageSearchSprinkles,2024-12-18T17:00:54Z,,4837585cab0fd84184a8c6f5d6891f442a2b95f1,owensmallwood
@@ -421,7 +418,6 @@ improvedExternalSessionHandlingSAML,2025-01-09T17:02:49Z,,c52ec21c75ab72c2f7d282
teamHttpHeadersMimir,2025-01-13T10:42:47Z,,04acbcdef23f673bd6bbfdbbece29c9769ce155a,Eric Leijonmarck
ABTestFeatureToggleA,2025-01-13T21:13:13Z,2025-05-27T19:18:23Z,009d7f42b3d09b3a6be1f00f07314e2b25af7ebc,Nathan Marrs
ABTestFeatureToggleB,2025-01-13T21:13:13Z,2025-05-27T19:18:23Z,009d7f42b3d09b3a6be1f00f07314e2b25af7ebc,Nathan Marrs
-kubernetesFoldersServiceV2,2025-01-13T21:15:35Z,2025-02-18T23:11:26Z,766d645d827f5e6e0872ae30e5fe23226ae85785,maicon
queryLibraryDashboards,2025-01-14T11:01:15Z,2025-02-14T16:39:22Z,740cd22fe51a3543c182857f31fc97fd42263306,Ashley Harrison
elasticsearchImprovedParsing,2025-01-15T17:05:54Z,,bab55a4cb84f2ba57838f96a492ab9aa7f307957,Adam Yeats
grafanaAdvisor,2025-01-20T10:08:00Z,,c1364d6be6f552203ba786f17a89664304b89247,Andres Martinez Gotor
diff --git a/pkg/storage/unified/README.md b/pkg/storage/unified/README.md
index dd53fa622b0..608f51c062d 100644
--- a/pkg/storage/unified/README.md
+++ b/pkg/storage/unified/README.md
@@ -367,8 +367,6 @@ signing_keys_url = http://localhost:3011/api/signing-keys/keys
mode = "on-prem"
[feature_toggles]
-kubernetesDashboardsAPI = true
-kubernetesFolders = true
unifiedStorage = true
unifiedStorageSearch = true
@@ -415,8 +413,6 @@ http_port = 3011
http_addr = "127.0.0.2"
[feature_toggles]
-kubernetesDashboardsAPI = true
-kubernetesFolders = true
unifiedStorageSearchUI = true
[unified_storage.dashboards.dashboard.grafana.app]
From 9f4773c9a5a35f7452e3df92006ac050fd397052 Mon Sep 17 00:00:00 2001
From: Stephanie Hingtgen
Date: Fri, 1 Aug 2025 14:35:18 -0500
Subject: [PATCH 38/89] Provisioning: Extract to apps submodule (#109074)
---
.github/CODEOWNERS | 2 +-
Dockerfile | 1 +
apps/provisioning/Makefile | 26 +
apps/provisioning/go.mod | 78 +
apps/provisioning/go.sum | 195 ++
apps/provisioning/kinds/cue.mod/module.cue | 4 +
apps/provisioning/kinds/manifest.cue | 9 +
.../kinds/provisioning/v0alpha1/constants.go | 18 +
.../v0alpha1/repository_codec_gen.go | 28 +
.../v0alpha1/repository_metadata_gen.go | 31 +
.../v0alpha1/repository_object_gen.go | 319 +++
.../v0alpha1/repository_schema_gen.go | 34 +
.../v0alpha1/repository_spec_gen.go | 169 ++
.../v0alpha1/repository_status_gen.go | 137 ++
.../provisioning/v0alpha1/zz_openapi_gen.go | 879 ++++++++
.../kinds/provisioning_manifest.go | 83 +
apps/provisioning/kinds/repository.cue | 171 ++
.../apis/provisioning/v0alpha1/classic.go | 0
.../pkg}/apis/provisioning/v0alpha1/doc.go | 2 +-
.../pkg}/apis/provisioning/v0alpha1/jobs.go | 0
.../apis/provisioning/v0alpha1/register.go | 0
.../apis/provisioning/v0alpha1/settings.go | 0
.../pkg}/apis/provisioning/v0alpha1/types.go | 0
.../apis/provisioning/v0alpha1/types_test.go | 2 +-
.../v0alpha1/zz_generated.deepcopy.go | 0
.../v0alpha1/zz_generated.defaults.go | 0
.../v0alpha1/zz_generated.openapi.go | 218 +-
...enerated.openapi_violation_exceptions.list | 26 +
.../applyconfiguration/internal/internal.go | 48 +
.../v0alpha1/bitbucketrepositoryconfig.go | 0
.../v0alpha1/githubrepositoryconfig.go | 0
.../v0alpha1/gitlabrepositoryconfig.go | 0
.../v0alpha1/gitrepositoryconfig.go | 0
.../provisioning/v0alpha1/healthstatus.go | 0
.../v0alpha1/localrepositoryconfig.go | 0
.../provisioning/v0alpha1/repository.go | 0
.../provisioning/v0alpha1/repositoryspec.go | 2 +-
.../provisioning/v0alpha1/repositorystatus.go | 0
.../provisioning/v0alpha1/resourcecount.go | 0
.../provisioning/v0alpha1/syncoptions.go | 2 +-
.../provisioning/v0alpha1/syncstatus.go | 2 +-
.../provisioning/v0alpha1/webhookstatus.go | 0
.../pkg/generated/applyconfiguration/utils.go | 54 +
.../clientset/versioned/clientset.go | 106 +
.../versioned/fake/clientset_generated.go | 117 +
.../generated/clientset/versioned/fake/doc.go | 6 +
.../clientset/versioned/fake/register.go | 42 +
.../clientset/versioned/scheme/doc.go | 6 +
.../clientset/versioned/scheme/register.go | 42 +
.../typed/provisioning/v0alpha1/doc.go | 0
.../typed/provisioning/v0alpha1/fake/doc.go | 0
.../v0alpha1/fake/fake_provisioning_client.go | 2 +-
.../v0alpha1/fake/fake_repository.go | 6 +-
.../v0alpha1/generated_expansion.go | 0
.../v0alpha1/provisioning_client.go | 4 +-
.../typed/provisioning/v0alpha1/repository.go | 6 +-
.../informers/externalversions/factory.go | 248 +++
.../informers/externalversions/generic.go | 48 +
.../internalinterfaces/factory_interfaces.go | 26 +
.../provisioning/interface.go | 4 +-
.../provisioning/v0alpha1/interface.go | 2 +-
.../provisioning/v0alpha1/repository.go | 8 +-
.../v0alpha1/expansion_generated.go | 0
.../provisioning/v0alpha1/repository.go | 2 +-
go.work | 1 +
hack/update-codegen.sh | 1 +
...enerated.openapi_violation_exceptions.list | 26 -
pkg/generated/applyconfiguration/utils.go | 44 +-
.../clientset/versioned/clientset.go | 15 +-
.../versioned/fake/clientset_generated.go | 7 -
.../clientset/versioned/fake/register.go | 2 -
.../clientset/versioned/scheme/register.go | 2 -
.../informers/externalversions/factory.go | 6 -
.../informers/externalversions/generic.go | 11 +-
.../provisioning/controller/finalizers.go | 2 +-
.../provisioning/controller/repository.go | 10 +-
.../apis/provisioning/controller/status.go | 4 +-
.../provisioning/controller/status_test.go | 4 +-
pkg/registry/apis/provisioning/extra.go | 2 +-
pkg/registry/apis/provisioning/files.go | 2 +-
pkg/registry/apis/provisioning/history.go | 2 +-
pkg/registry/apis/provisioning/jobs.go | 2 +-
.../apis/provisioning/jobs/delete/worker.go | 2 +-
.../provisioning/jobs/delete/worker_test.go | 2 +-
pkg/registry/apis/provisioning/jobs/driver.go | 2 +-
.../apis/provisioning/jobs/export/all.go | 2 +-
.../apis/provisioning/jobs/export/folders.go | 2 +-
.../provisioning/jobs/export/folders_test.go | 2 +-
.../jobs/export/mock_export_fn.go | 2 +-
.../provisioning/jobs/export/resources.go | 2 +-
.../jobs/export/resources_test.go | 2 +-
.../apis/provisioning/jobs/export/worker.go | 2 +-
.../provisioning/jobs/export/worker_test.go | 2 +-
.../apis/provisioning/jobs/history.go | 2 +-
.../apis/provisioning/jobs/history_mock.go | 2 +-
.../jobs/job_progress_recorder_mock.go | 2 +-
.../apis/provisioning/jobs/migrate/legacy.go | 2 +-
.../jobs/migrate/legacy_resources.go | 2 +-
.../jobs/migrate/legacy_resources_test.go | 2 +-
.../provisioning/jobs/migrate/legacy_test.go | 2 +-
.../migrate/mock_legacy_resources_migrator.go | 2 +-
.../jobs/migrate/mock_migrator.go | 2 +-
.../jobs/migrate/unifiedstorage.go | 2 +-
.../jobs/migrate/unifiedstorage_test.go | 2 +-
.../apis/provisioning/jobs/migrate/worker.go | 2 +-
.../provisioning/jobs/migrate/worker_test.go | 2 +-
.../apis/provisioning/jobs/move/worker.go | 2 +-
.../provisioning/jobs/move/worker_test.go | 2 +-
.../apis/provisioning/jobs/persistentstore.go | 2 +-
.../apis/provisioning/jobs/progress.go | 2 +-
.../provisioning/jobs/progress_fn_mock.go | 2 +-
pkg/registry/apis/provisioning/jobs/queue.go | 2 +-
.../apis/provisioning/jobs/queue_mock.go | 2 +-
.../apis/provisioning/jobs/store_mock.go | 2 +-
.../apis/provisioning/jobs/sync/changes.go | 2 +-
.../provisioning/jobs/sync/changes_test.go | 2 +-
.../apis/provisioning/jobs/sync/full_test.go | 2 +-
.../jobs/sync/repository_patch_fn_mock.go | 2 +-
.../apis/provisioning/jobs/sync/sync.go | 2 +-
.../apis/provisioning/jobs/sync/sync_test.go | 2 +-
.../provisioning/jobs/sync/syncer_mock.go | 2 +-
.../apis/provisioning/jobs/sync/worker.go | 2 +-
.../provisioning/jobs/sync/worker_test.go | 2 +-
.../apis/provisioning/jobs/worker_mock.go | 2 +-
pkg/registry/apis/provisioning/list.go | 2 +-
pkg/registry/apis/provisioning/refs.go | 2 +-
pkg/registry/apis/provisioning/register.go | 12 +-
.../repository/config_repository_mock.go | 2 +-
.../repository/git/git_repository_mock.go | 2 +-
.../provisioning/repository/git/mutator.go | 2 +-
.../repository/git/mutator_test.go | 2 +-
.../provisioning/repository/git/repository.go | 2 +-
.../repository/git/repository_test.go | 2 +-
.../repository/git/staged_test.go | 2 +-
.../github/github_repository_mock.go | 2 +-
.../provisioning/repository/github/mutator.go | 2 +-
.../repository/github/mutator_test.go | 2 +-
.../repository/github/repository.go | 2 +-
.../repository/github/repository_test.go | 2 +-
.../provisioning/repository/local/local.go | 2 +-
.../repository/local/local_test.go | 2 +-
.../provisioning/repository/reader_mock.go | 2 +-
.../provisioning/repository/repository.go | 2 +-
.../repository/repository_mock.go | 2 +-
.../repository/staged_repository_mock.go | 2 +-
.../apis/provisioning/repository/test.go | 2 +-
.../apis/provisioning/repository/test_test.go | 2 +-
.../provisioning/repository/versioned_mock.go | 2 +-
.../apis/provisioning/repository/workflows.go | 2 +-
.../provisioning/repository/workflows_test.go | 2 +-
.../apis/provisioning/resources/dualwriter.go | 2 +-
.../apis/provisioning/resources/fileformat.go | 2 +-
.../provisioning/resources/fileformat_test.go | 2 +-
.../apis/provisioning/resources/id.go | 2 +-
.../apis/provisioning/resources/object.go | 2 +-
.../apis/provisioning/resources/parser.go | 2 +-
.../provisioning/resources/parser_test.go | 2 +-
.../apis/provisioning/resources/repository.go | 2 +-
.../resources/repository_resources_mock.go | 2 +-
.../resources/resource_lister_mock.go | 2 +-
.../apis/provisioning/resources/tree.go | 2 +-
pkg/registry/apis/provisioning/routes.go | 2 +-
.../apis/provisioning/secrets/repository.go | 2 +-
.../secrets/repository_secrets_mock.go | 2 +-
.../provisioning/secrets/repository_test.go | 2 +-
pkg/registry/apis/provisioning/test.go | 4 +-
pkg/registry/apis/provisioning/types.go | 4 +-
pkg/registry/apis/provisioning/usage/usage.go | 2 +-
.../apis/provisioning/webhooks/mutator.go | 2 +-
.../provisioning/webhooks/mutator_test.go | 2 +-
.../webhooks/pullrequest/changes.go | 2 +-
.../webhooks/pullrequest/changes_test.go | 2 +-
.../webhooks/pullrequest/comment_test.go | 2 +-
.../webhooks/pullrequest/mock_evaluator.go | 2 +-
.../pullrequest/mock_pullrequest_repo.go | 2 +-
.../webhooks/pullrequest/render.go | 2 +-
.../webhooks/pullrequest/render_mock.go | 2 +-
.../webhooks/pullrequest/render_test.go | 2 +-
.../webhooks/pullrequest/worker.go | 2 +-
.../webhooks/pullrequest/worker_test.go | 2 +-
.../apis/provisioning/webhooks/register.go | 2 +-
.../apis/provisioning/webhooks/render.go | 2 +-
.../apis/provisioning/webhooks/repository.go | 2 +-
.../provisioning/webhooks/repository_test.go | 2 +-
.../apis/provisioning/webhooks/webhook.go | 2 +-
pkg/services/authn/clients/provisioning.go | 2 +-
.../authn/clients/provisioning_test.go | 2 +-
pkg/services/live/features/watch.go | 2 +-
.../provisioning.grafana.app-v0alpha1.json | 1950 +++++++++++++----
pkg/tests/apis/provisioning/helper_test.go | 2 +-
.../apis/provisioning/provisioning_test.go | 2 +-
pkg/tests/apis/provisioning/secrets_test.go | 2 +-
192 files changed, 4774 insertions(+), 766 deletions(-)
create mode 100644 apps/provisioning/Makefile
create mode 100644 apps/provisioning/go.mod
create mode 100644 apps/provisioning/go.sum
create mode 100644 apps/provisioning/kinds/cue.mod/module.cue
create mode 100644 apps/provisioning/kinds/manifest.cue
create mode 100644 apps/provisioning/kinds/provisioning/v0alpha1/constants.go
create mode 100644 apps/provisioning/kinds/provisioning/v0alpha1/repository_codec_gen.go
create mode 100644 apps/provisioning/kinds/provisioning/v0alpha1/repository_metadata_gen.go
create mode 100644 apps/provisioning/kinds/provisioning/v0alpha1/repository_object_gen.go
create mode 100644 apps/provisioning/kinds/provisioning/v0alpha1/repository_schema_gen.go
create mode 100644 apps/provisioning/kinds/provisioning/v0alpha1/repository_spec_gen.go
create mode 100644 apps/provisioning/kinds/provisioning/v0alpha1/repository_status_gen.go
create mode 100644 apps/provisioning/kinds/provisioning/v0alpha1/zz_openapi_gen.go
create mode 100644 apps/provisioning/kinds/provisioning_manifest.go
create mode 100644 apps/provisioning/kinds/repository.cue
rename {pkg => apps/provisioning/pkg}/apis/provisioning/v0alpha1/classic.go (100%)
rename {pkg => apps/provisioning/pkg}/apis/provisioning/v0alpha1/doc.go (54%)
rename {pkg => apps/provisioning/pkg}/apis/provisioning/v0alpha1/jobs.go (100%)
rename {pkg => apps/provisioning/pkg}/apis/provisioning/v0alpha1/register.go (100%)
rename {pkg => apps/provisioning/pkg}/apis/provisioning/v0alpha1/settings.go (100%)
rename {pkg => apps/provisioning/pkg}/apis/provisioning/v0alpha1/types.go (100%)
rename {pkg => apps/provisioning/pkg}/apis/provisioning/v0alpha1/types_test.go (91%)
rename {pkg => apps/provisioning/pkg}/apis/provisioning/v0alpha1/zz_generated.deepcopy.go (100%)
rename {pkg => apps/provisioning/pkg}/apis/provisioning/v0alpha1/zz_generated.defaults.go (100%)
rename {pkg => apps/provisioning/pkg}/apis/provisioning/v0alpha1/zz_generated.openapi.go (82%)
create mode 100644 apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.openapi_violation_exceptions.list
create mode 100644 apps/provisioning/pkg/generated/applyconfiguration/internal/internal.go
rename {pkg => apps/provisioning/pkg}/generated/applyconfiguration/provisioning/v0alpha1/bitbucketrepositoryconfig.go (100%)
rename {pkg => apps/provisioning/pkg}/generated/applyconfiguration/provisioning/v0alpha1/githubrepositoryconfig.go (100%)
rename {pkg => apps/provisioning/pkg}/generated/applyconfiguration/provisioning/v0alpha1/gitlabrepositoryconfig.go (100%)
rename {pkg => apps/provisioning/pkg}/generated/applyconfiguration/provisioning/v0alpha1/gitrepositoryconfig.go (100%)
rename {pkg => apps/provisioning/pkg}/generated/applyconfiguration/provisioning/v0alpha1/healthstatus.go (100%)
rename {pkg => apps/provisioning/pkg}/generated/applyconfiguration/provisioning/v0alpha1/localrepositoryconfig.go (100%)
rename {pkg => apps/provisioning/pkg}/generated/applyconfiguration/provisioning/v0alpha1/repository.go (100%)
rename {pkg => apps/provisioning/pkg}/generated/applyconfiguration/provisioning/v0alpha1/repositoryspec.go (98%)
rename {pkg => apps/provisioning/pkg}/generated/applyconfiguration/provisioning/v0alpha1/repositorystatus.go (100%)
rename {pkg => apps/provisioning/pkg}/generated/applyconfiguration/provisioning/v0alpha1/resourcecount.go (100%)
rename {pkg => apps/provisioning/pkg}/generated/applyconfiguration/provisioning/v0alpha1/syncoptions.go (95%)
rename {pkg => apps/provisioning/pkg}/generated/applyconfiguration/provisioning/v0alpha1/syncstatus.go (97%)
rename {pkg => apps/provisioning/pkg}/generated/applyconfiguration/provisioning/v0alpha1/webhookstatus.go (100%)
create mode 100644 apps/provisioning/pkg/generated/applyconfiguration/utils.go
create mode 100644 apps/provisioning/pkg/generated/clientset/versioned/clientset.go
create mode 100644 apps/provisioning/pkg/generated/clientset/versioned/fake/clientset_generated.go
create mode 100644 apps/provisioning/pkg/generated/clientset/versioned/fake/doc.go
create mode 100644 apps/provisioning/pkg/generated/clientset/versioned/fake/register.go
create mode 100644 apps/provisioning/pkg/generated/clientset/versioned/scheme/doc.go
create mode 100644 apps/provisioning/pkg/generated/clientset/versioned/scheme/register.go
rename {pkg => apps/provisioning/pkg}/generated/clientset/versioned/typed/provisioning/v0alpha1/doc.go (100%)
rename {pkg => apps/provisioning/pkg}/generated/clientset/versioned/typed/provisioning/v0alpha1/fake/doc.go (100%)
rename {pkg => apps/provisioning/pkg}/generated/clientset/versioned/typed/provisioning/v0alpha1/fake/fake_provisioning_client.go (83%)
rename {pkg => apps/provisioning/pkg}/generated/clientset/versioned/typed/provisioning/v0alpha1/fake/fake_repository.go (79%)
rename {pkg => apps/provisioning/pkg}/generated/clientset/versioned/typed/provisioning/v0alpha1/generated_expansion.go (100%)
rename {pkg => apps/provisioning/pkg}/generated/clientset/versioned/typed/provisioning/v0alpha1/provisioning_client.go (92%)
rename {pkg => apps/provisioning/pkg}/generated/clientset/versioned/typed/provisioning/v0alpha1/repository.go (91%)
create mode 100644 apps/provisioning/pkg/generated/informers/externalversions/factory.go
create mode 100644 apps/provisioning/pkg/generated/informers/externalversions/generic.go
create mode 100644 apps/provisioning/pkg/generated/informers/externalversions/internalinterfaces/factory_interfaces.go
rename {pkg => apps/provisioning/pkg}/generated/informers/externalversions/provisioning/interface.go (78%)
rename {pkg => apps/provisioning/pkg}/generated/informers/externalversions/provisioning/v0alpha1/interface.go (88%)
rename {pkg => apps/provisioning/pkg}/generated/informers/externalversions/provisioning/v0alpha1/repository.go (88%)
rename {pkg => apps/provisioning/pkg}/generated/listers/provisioning/v0alpha1/expansion_generated.go (100%)
rename {pkg => apps/provisioning/pkg}/generated/listers/provisioning/v0alpha1/repository.go (95%)
delete mode 100644 pkg/apis/provisioning/v0alpha1/zz_generated.openapi_violation_exceptions.list
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index f26cfd876aa..749fa4f3058 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -73,7 +73,7 @@
/.air.toml @macabu
# Git Sync / App Platform Provisioning
-/pkg/apis/provisioning @grafana/grafana-git-ui-sync-team
+/apps/provisioning/ @grafana/grafana-git-ui-sync-team
/public/app/features/provisioning @grafana/grafana-git-ui-sync-team
/pkg/registry/apis/provisioning @grafana/grafana-git-ui-sync-team
/pkg/tests/apis/provisioning @grafana/grafana-git-ui-sync-team
diff --git a/Dockerfile b/Dockerfile
index ddc832d65c9..7300e989823 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -83,6 +83,7 @@ COPY pkg/storage/unified/apistore pkg/storage/unified/apistore
COPY pkg/semconv pkg/semconv
COPY pkg/aggregator pkg/aggregator
COPY apps/playlist apps/playlist
+COPY apps/provisioning apps/provisioning
COPY apps/secret apps/secret
COPY apps/investigations apps/investigations
COPY apps/advisor apps/advisor
diff --git a/apps/provisioning/Makefile b/apps/provisioning/Makefile
new file mode 100644
index 00000000000..c3d7e748603
--- /dev/null
+++ b/apps/provisioning/Makefile
@@ -0,0 +1,26 @@
+include ../sdk.mk
+
+.PHONY: generate
+generate: install-app-sdk update-app-sdk
+ @$(APP_SDK_BIN) generate -g ./kinds --grouping=group --postprocess --defencoding=none --useoldmanifestkinds
+
+.PHONY: build
+build: generate
+ go build -o bin/provisioning ./cmd/operator
+
+# .PHONY: build/operator
+# build/operator: build
+# docker build -t provisioning:latest -f cmd/operator/Dockerfile .
+
+.PHONY: clean
+clean:
+ rm -rf bin/
+ rm -rf pkg/generated/
+
+.PHONY: test
+test: generate
+ go test ./...
+
+.PHONY: run
+run: build
+ ./bin/provisioning
\ No newline at end of file
diff --git a/apps/provisioning/go.mod b/apps/provisioning/go.mod
new file mode 100644
index 00000000000..ee2ff39fb13
--- /dev/null
+++ b/apps/provisioning/go.mod
@@ -0,0 +1,78 @@
+module github.com/grafana/grafana/apps/provisioning
+
+go 1.24.5
+
+require (
+ github.com/grafana/grafana-app-sdk v0.40.2
+ github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250801162753-7e4796893956
+ k8s.io/apimachinery v0.33.3
+ k8s.io/client-go v0.33.3
+ k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff
+ sigs.k8s.io/structured-merge-diff/v4 v4.7.0
+)
+
+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.12.1 // indirect
+ github.com/fxamacker/cbor/v2 v2.7.0 // indirect
+ github.com/getkin/kin-openapi v0.132.0 // indirect
+ github.com/go-logr/logr v1.4.3 // indirect
+ github.com/go-openapi/jsonpointer v0.21.0 // indirect
+ github.com/go-openapi/jsonreference v0.21.0 // indirect
+ github.com/go-openapi/swag v0.23.0 // indirect
+ github.com/go-test/deep v1.1.1 // indirect
+ github.com/gogo/protobuf v1.3.2 // indirect
+ github.com/google/gnostic-models v0.6.9 // 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.40.1 // 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.2 // 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/onsi/ginkgo/v2 v2.22.2 // indirect
+ github.com/onsi/gomega v1.36.2 // 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.22.0 // indirect
+ github.com/prometheus/client_model v0.6.2 // indirect
+ github.com/prometheus/common v0.65.0 // indirect
+ github.com/prometheus/procfs v0.16.1 // indirect
+ github.com/stretchr/objx v0.5.2 // indirect
+ github.com/stretchr/testify v1.10.0 // indirect
+ github.com/x448/float16 v0.8.4 // indirect
+ go.opentelemetry.io/otel v1.37.0 // indirect
+ go.opentelemetry.io/otel/trace v1.37.0 // indirect
+ go.yaml.in/yaml/v2 v2.4.2 // indirect
+ golang.org/x/net v0.42.0 // indirect
+ golang.org/x/oauth2 v0.30.0 // indirect
+ golang.org/x/sys v0.34.0 // indirect
+ golang.org/x/term v0.33.0 // indirect
+ golang.org/x/text v0.27.0 // indirect
+ golang.org/x/time v0.11.0 // indirect
+ google.golang.org/protobuf v1.36.6 // indirect
+ gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
+ gopkg.in/inf.v0 v0.9.1 // indirect
+ gopkg.in/yaml.v3 v3.0.1 // indirect
+ k8s.io/api v0.33.3 // indirect
+ k8s.io/klog/v2 v2.130.1 // indirect
+ k8s.io/utils v0.0.0-20241210054802-24370beab758 // indirect
+ sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
+ sigs.k8s.io/randfill v1.0.0 // indirect
+ sigs.k8s.io/yaml v1.5.0 // indirect
+)
+
+// transitive dependencies that need replaced
+// TODO: stop depending on grafana core
+replace github.com/grafana/grafana => ../..
diff --git a/apps/provisioning/go.sum b/apps/provisioning/go.sum
new file mode 100644
index 00000000000..3b7ee516782
--- /dev/null
+++ b/apps/provisioning/go.sum
@@ -0,0 +1,195 @@
+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=
+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.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtzpL63nKAU=
+github.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
+github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
+github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
+github.com/getkin/kin-openapi v0.132.0 h1:3ISeLMsQzcb5v26yeJrBcdTCEQTag36ZjaGk7MIRUwk=
+github.com/getkin/kin-openapi v0.132.0/go.mod h1:3OlG51PCYNsPByuiMB0t4fjnNlIDnaEDsjiKUV8nL58=
+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.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
+github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
+github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
+github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
+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-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/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.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw=
+github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw=
+github.com/google/go-cmp v0.5.9/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/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/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.40.2 h1:j2ftFuqhX+exYUipfEjeWDs3i7oiJkweTF8gFLL7wWU=
+github.com/grafana/grafana-app-sdk v0.40.2/go.mod h1:BbNXPNki3mtbkWxYqJsyA1Cj9AShSyaY33z8WkyfVv0=
+github.com/grafana/grafana-app-sdk/logging v0.40.1 h1:ru+GqbaQk6jthA5l2Yo1WI/JbNXKNQmLiqNrxz7HGP4=
+github.com/grafana/grafana-app-sdk/logging v0.40.1/go.mod h1:otUD9XpJD7A5sCLb8mcs9hIXGdeV6lnhzVwe747g4RU=
+github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250801162753-7e4796893956 h1:FzReg7qT3G+11ZsFFbtguMdx+w1w76bJCOOH1fWfDKs=
+github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250801162753-7e4796893956/go.mod h1:RRvSjHH12/PnQaXraMO65jUhVu8n59mzvhfIMBETnV4=
+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=
+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 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
+github.com/modern-go/reflect2 v1.0.2/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/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/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.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
+github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
+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.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE=
+github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
+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.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M=
+github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+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.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
+github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+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/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.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
+go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
+go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
+go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
+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.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
+go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
+go.yaml.in/yaml/v3 v3.0.3 h1:bXOww4E/J3f66rav3pX3m8w6jDE4knZjGOw8b5Y6iNE=
+go.yaml.in/yaml/v3 v3.0.3/go.mod h1:tBHosrYAkRZjRAOREWbDnBXUf08JOwYq++0QNwQiWzI=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+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/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/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-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
+golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
+golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
+golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
+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.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
+golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
+golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg=
+golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0=
+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.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
+golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
+golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
+golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
+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=
+golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0=
+golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw=
+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=
+google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
+google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
+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.33.3 h1:SRd5t//hhkI1buzxb288fy2xvjubstenEKL9K51KBI8=
+k8s.io/api v0.33.3/go.mod h1:01Y/iLUjNBM3TAvypct7DIj0M0NIZc+PzAHCIo0CYGE=
+k8s.io/apimachinery v0.33.3 h1:4ZSrmNa0c/ZpZJhAgRdcsFcZOw1PQU1bALVQ0B3I5LA=
+k8s.io/apimachinery v0.33.3/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM=
+k8s.io/client-go v0.33.3 h1:M5AfDnKfYmVJif92ngN532gFqakcGi6RvaOF16efrpA=
+k8s.io/client-go v0.33.3/go.mod h1:luqKBQggEf3shbxHY4uVENAxrDISLOarxpTKMiUuujg=
+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-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4=
+k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8=
+k8s.io/utils v0.0.0-20241210054802-24370beab758 h1:sdbE21q2nlQtFh65saZY+rRM6x6aJJI8IUa1AmH/qa0=
+k8s.io/utils v0.0.0-20241210054802-24370beab758/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
+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 v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
+sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
+sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
+sigs.k8s.io/structured-merge-diff/v4 v4.7.0 h1:qPeWmscJcXP0snki5IYF79Z8xrl8ETFxgMd7wez1XkI=
+sigs.k8s.io/structured-merge-diff/v4 v4.7.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps=
+sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
+sigs.k8s.io/yaml v1.5.0 h1:M10b2U7aEUY6hRtU870n2VTPgR5RZiL/I6Lcc2F4NUQ=
+sigs.k8s.io/yaml v1.5.0/go.mod h1:wZs27Rbxoai4C0f8/9urLZtZtF3avA3gKvGyPdDqTO4=
diff --git a/apps/provisioning/kinds/cue.mod/module.cue b/apps/provisioning/kinds/cue.mod/module.cue
new file mode 100644
index 00000000000..20a2c78795d
--- /dev/null
+++ b/apps/provisioning/kinds/cue.mod/module.cue
@@ -0,0 +1,4 @@
+module: "github.com/grafana/grafana/apps/provisioning"
+language: {
+ version: "v0.9.0"
+}
\ No newline at end of file
diff --git a/apps/provisioning/kinds/manifest.cue b/apps/provisioning/kinds/manifest.cue
new file mode 100644
index 00000000000..40ffa64d922
--- /dev/null
+++ b/apps/provisioning/kinds/manifest.cue
@@ -0,0 +1,9 @@
+package repository
+
+manifest: {
+ appName: "provisioning"
+ groupOverride: "provisioning.grafana.app"
+ kinds: [
+ repository,
+ ]
+}
\ No newline at end of file
diff --git a/apps/provisioning/kinds/provisioning/v0alpha1/constants.go b/apps/provisioning/kinds/provisioning/v0alpha1/constants.go
new file mode 100644
index 00000000000..c326607a228
--- /dev/null
+++ b/apps/provisioning/kinds/provisioning/v0alpha1/constants.go
@@ -0,0 +1,18 @@
+package v0alpha1
+
+import "k8s.io/apimachinery/pkg/runtime/schema"
+
+const (
+ // APIGroup is the API group used by all kinds in this package
+ APIGroup = "provisioning.grafana.app"
+ // APIVersion is the API version used by all kinds in this package
+ APIVersion = "v0alpha1"
+)
+
+var (
+ // GroupVersion is a schema.GroupVersion consisting of the Group and Version constants for this package
+ GroupVersion = schema.GroupVersion{
+ Group: APIGroup,
+ Version: APIVersion,
+ }
+)
diff --git a/apps/provisioning/kinds/provisioning/v0alpha1/repository_codec_gen.go b/apps/provisioning/kinds/provisioning/v0alpha1/repository_codec_gen.go
new file mode 100644
index 00000000000..6202f5c6172
--- /dev/null
+++ b/apps/provisioning/kinds/provisioning/v0alpha1/repository_codec_gen.go
@@ -0,0 +1,28 @@
+//
+// Code generated by grafana-app-sdk. DO NOT EDIT.
+//
+
+package v0alpha1
+
+import (
+ "encoding/json"
+ "io"
+
+ "github.com/grafana/grafana-app-sdk/resource"
+)
+
+// RepositoryJSONCodec is an implementation of resource.Codec for kubernetes JSON encoding
+type RepositoryJSONCodec struct{}
+
+// Read reads JSON-encoded bytes from `reader` and unmarshals them into `into`
+func (*RepositoryJSONCodec) Read(reader io.Reader, into resource.Object) error {
+ return json.NewDecoder(reader).Decode(into)
+}
+
+// Write writes JSON-encoded bytes into `writer` marshaled from `from`
+func (*RepositoryJSONCodec) Write(writer io.Writer, from resource.Object) error {
+ return json.NewEncoder(writer).Encode(from)
+}
+
+// Interface compliance checks
+var _ resource.Codec = &RepositoryJSONCodec{}
diff --git a/apps/provisioning/kinds/provisioning/v0alpha1/repository_metadata_gen.go b/apps/provisioning/kinds/provisioning/v0alpha1/repository_metadata_gen.go
new file mode 100644
index 00000000000..162a34fd13c
--- /dev/null
+++ b/apps/provisioning/kinds/provisioning/v0alpha1/repository_metadata_gen.go
@@ -0,0 +1,31 @@
+// Code generated - EDITING IS FUTILE. DO NOT EDIT.
+
+package v0alpha1
+
+import (
+ time "time"
+)
+
+// metadata contains embedded CommonMetadata and can be extended with custom string fields
+// TODO: use CommonMetadata instead of redefining here; currently needs to be defined here
+// without external reference as using the CommonMetadata reference breaks thema codegen.
+type RepositoryMetadata struct {
+ UpdateTimestamp time.Time `json:"updateTimestamp"`
+ CreatedBy string `json:"createdBy"`
+ Uid string `json:"uid"`
+ CreationTimestamp time.Time `json:"creationTimestamp"`
+ DeletionTimestamp *time.Time `json:"deletionTimestamp,omitempty"`
+ Finalizers []string `json:"finalizers"`
+ ResourceVersion string `json:"resourceVersion"`
+ Generation int64 `json:"generation"`
+ UpdatedBy string `json:"updatedBy"`
+ Labels map[string]string `json:"labels"`
+}
+
+// NewRepositoryMetadata creates a new RepositoryMetadata object.
+func NewRepositoryMetadata() *RepositoryMetadata {
+ return &RepositoryMetadata{
+ Finalizers: []string{},
+ Labels: map[string]string{},
+ }
+}
diff --git a/apps/provisioning/kinds/provisioning/v0alpha1/repository_object_gen.go b/apps/provisioning/kinds/provisioning/v0alpha1/repository_object_gen.go
new file mode 100644
index 00000000000..4154ffefb36
--- /dev/null
+++ b/apps/provisioning/kinds/provisioning/v0alpha1/repository_object_gen.go
@@ -0,0 +1,319 @@
+//
+// Code generated by grafana-app-sdk. DO NOT EDIT.
+//
+
+package v0alpha1
+
+import (
+ "fmt"
+ "github.com/grafana/grafana-app-sdk/resource"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/runtime"
+ "k8s.io/apimachinery/pkg/runtime/schema"
+ "k8s.io/apimachinery/pkg/types"
+ "time"
+)
+
+// +k8s:openapi-gen=true
+type Repository struct {
+ metav1.TypeMeta `json:",inline" yaml:",inline"`
+ metav1.ObjectMeta `json:"metadata" yaml:"metadata"`
+
+ // Spec is the spec of the Repository
+ Spec RepositorySpec `json:"spec" yaml:"spec"`
+
+ Status RepositoryStatus `json:"status" yaml:"status"`
+}
+
+func (o *Repository) GetSpec() any {
+ return o.Spec
+}
+
+func (o *Repository) SetSpec(spec any) error {
+ cast, ok := spec.(RepositorySpec)
+ if !ok {
+ return fmt.Errorf("cannot set spec type %#v, not of type Spec", spec)
+ }
+ o.Spec = cast
+ return nil
+}
+
+func (o *Repository) GetSubresources() map[string]any {
+ return map[string]any{
+ "status": o.Status,
+ }
+}
+
+func (o *Repository) GetSubresource(name string) (any, bool) {
+ switch name {
+ case "status":
+ return o.Status, true
+ default:
+ return nil, false
+ }
+}
+
+func (o *Repository) SetSubresource(name string, value any) error {
+ switch name {
+ case "status":
+ cast, ok := value.(RepositoryStatus)
+ if !ok {
+ return fmt.Errorf("cannot set status type %#v, not of type RepositoryStatus", value)
+ }
+ o.Status = cast
+ return nil
+ default:
+ return fmt.Errorf("subresource '%s' does not exist", name)
+ }
+}
+
+func (o *Repository) GetStaticMetadata() resource.StaticMetadata {
+ gvk := o.GroupVersionKind()
+ return resource.StaticMetadata{
+ Name: o.ObjectMeta.Name,
+ Namespace: o.ObjectMeta.Namespace,
+ Group: gvk.Group,
+ Version: gvk.Version,
+ Kind: gvk.Kind,
+ }
+}
+
+func (o *Repository) SetStaticMetadata(metadata resource.StaticMetadata) {
+ o.Name = metadata.Name
+ o.Namespace = metadata.Namespace
+ o.SetGroupVersionKind(schema.GroupVersionKind{
+ Group: metadata.Group,
+ Version: metadata.Version,
+ Kind: metadata.Kind,
+ })
+}
+
+func (o *Repository) GetCommonMetadata() resource.CommonMetadata {
+ dt := o.DeletionTimestamp
+ var deletionTimestamp *time.Time
+ if dt != nil {
+ deletionTimestamp = &dt.Time
+ }
+ // Legacy ExtraFields support
+ extraFields := make(map[string]any)
+ if o.Annotations != nil {
+ extraFields["annotations"] = o.Annotations
+ }
+ if o.ManagedFields != nil {
+ extraFields["managedFields"] = o.ManagedFields
+ }
+ if o.OwnerReferences != nil {
+ extraFields["ownerReferences"] = o.OwnerReferences
+ }
+ return resource.CommonMetadata{
+ UID: string(o.UID),
+ ResourceVersion: o.ResourceVersion,
+ Generation: o.Generation,
+ Labels: o.Labels,
+ CreationTimestamp: o.CreationTimestamp.Time,
+ DeletionTimestamp: deletionTimestamp,
+ Finalizers: o.Finalizers,
+ UpdateTimestamp: o.GetUpdateTimestamp(),
+ CreatedBy: o.GetCreatedBy(),
+ UpdatedBy: o.GetUpdatedBy(),
+ ExtraFields: extraFields,
+ }
+}
+
+func (o *Repository) SetCommonMetadata(metadata resource.CommonMetadata) {
+ o.UID = types.UID(metadata.UID)
+ o.ResourceVersion = metadata.ResourceVersion
+ o.Generation = metadata.Generation
+ o.Labels = metadata.Labels
+ o.CreationTimestamp = metav1.NewTime(metadata.CreationTimestamp)
+ if metadata.DeletionTimestamp != nil {
+ dt := metav1.NewTime(*metadata.DeletionTimestamp)
+ o.DeletionTimestamp = &dt
+ } else {
+ o.DeletionTimestamp = nil
+ }
+ o.Finalizers = metadata.Finalizers
+ if o.Annotations == nil {
+ o.Annotations = make(map[string]string)
+ }
+ if !metadata.UpdateTimestamp.IsZero() {
+ o.SetUpdateTimestamp(metadata.UpdateTimestamp)
+ }
+ if metadata.CreatedBy != "" {
+ o.SetCreatedBy(metadata.CreatedBy)
+ }
+ if metadata.UpdatedBy != "" {
+ o.SetUpdatedBy(metadata.UpdatedBy)
+ }
+ // Legacy support for setting Annotations, ManagedFields, and OwnerReferences via ExtraFields
+ if metadata.ExtraFields != nil {
+ if annotations, ok := metadata.ExtraFields["annotations"]; ok {
+ if cast, ok := annotations.(map[string]string); ok {
+ o.Annotations = cast
+ }
+ }
+ if managedFields, ok := metadata.ExtraFields["managedFields"]; ok {
+ if cast, ok := managedFields.([]metav1.ManagedFieldsEntry); ok {
+ o.ManagedFields = cast
+ }
+ }
+ if ownerReferences, ok := metadata.ExtraFields["ownerReferences"]; ok {
+ if cast, ok := ownerReferences.([]metav1.OwnerReference); ok {
+ o.OwnerReferences = cast
+ }
+ }
+ }
+}
+
+func (o *Repository) GetCreatedBy() string {
+ if o.ObjectMeta.Annotations == nil {
+ o.ObjectMeta.Annotations = make(map[string]string)
+ }
+
+ return o.ObjectMeta.Annotations["grafana.com/createdBy"]
+}
+
+func (o *Repository) SetCreatedBy(createdBy string) {
+ if o.ObjectMeta.Annotations == nil {
+ o.ObjectMeta.Annotations = make(map[string]string)
+ }
+
+ o.ObjectMeta.Annotations["grafana.com/createdBy"] = createdBy
+}
+
+func (o *Repository) GetUpdateTimestamp() time.Time {
+ if o.ObjectMeta.Annotations == nil {
+ o.ObjectMeta.Annotations = make(map[string]string)
+ }
+
+ parsed, _ := time.Parse(time.RFC3339, o.ObjectMeta.Annotations["grafana.com/updateTimestamp"])
+ return parsed
+}
+
+func (o *Repository) SetUpdateTimestamp(updateTimestamp time.Time) {
+ if o.ObjectMeta.Annotations == nil {
+ o.ObjectMeta.Annotations = make(map[string]string)
+ }
+
+ o.ObjectMeta.Annotations["grafana.com/updateTimestamp"] = updateTimestamp.Format(time.RFC3339)
+}
+
+func (o *Repository) GetUpdatedBy() string {
+ if o.ObjectMeta.Annotations == nil {
+ o.ObjectMeta.Annotations = make(map[string]string)
+ }
+
+ return o.ObjectMeta.Annotations["grafana.com/updatedBy"]
+}
+
+func (o *Repository) SetUpdatedBy(updatedBy string) {
+ if o.ObjectMeta.Annotations == nil {
+ o.ObjectMeta.Annotations = make(map[string]string)
+ }
+
+ o.ObjectMeta.Annotations["grafana.com/updatedBy"] = updatedBy
+}
+
+func (o *Repository) Copy() resource.Object {
+ return resource.CopyObject(o)
+}
+
+func (o *Repository) DeepCopyObject() runtime.Object {
+ return o.Copy()
+}
+
+func (o *Repository) DeepCopy() *Repository {
+ cpy := &Repository{}
+ o.DeepCopyInto(cpy)
+ return cpy
+}
+
+func (o *Repository) DeepCopyInto(dst *Repository) {
+ dst.TypeMeta.APIVersion = o.TypeMeta.APIVersion
+ dst.TypeMeta.Kind = o.TypeMeta.Kind
+ o.ObjectMeta.DeepCopyInto(&dst.ObjectMeta)
+ o.Spec.DeepCopyInto(&dst.Spec)
+ o.Status.DeepCopyInto(&dst.Status)
+}
+
+// Interface compliance compile-time check
+var _ resource.Object = &Repository{}
+
+// +k8s:openapi-gen=true
+type RepositoryList struct {
+ metav1.TypeMeta `json:",inline" yaml:",inline"`
+ metav1.ListMeta `json:"metadata" yaml:"metadata"`
+ Items []Repository `json:"items" yaml:"items"`
+}
+
+func (o *RepositoryList) DeepCopyObject() runtime.Object {
+ return o.Copy()
+}
+
+func (o *RepositoryList) Copy() resource.ListObject {
+ cpy := &RepositoryList{
+ TypeMeta: o.TypeMeta,
+ Items: make([]Repository, len(o.Items)),
+ }
+ o.ListMeta.DeepCopyInto(&cpy.ListMeta)
+ for i := 0; i < len(o.Items); i++ {
+ if item, ok := o.Items[i].Copy().(*Repository); ok {
+ cpy.Items[i] = *item
+ }
+ }
+ return cpy
+}
+
+func (o *RepositoryList) GetItems() []resource.Object {
+ items := make([]resource.Object, len(o.Items))
+ for i := 0; i < len(o.Items); i++ {
+ items[i] = &o.Items[i]
+ }
+ return items
+}
+
+func (o *RepositoryList) SetItems(items []resource.Object) {
+ o.Items = make([]Repository, len(items))
+ for i := 0; i < len(items); i++ {
+ o.Items[i] = *items[i].(*Repository)
+ }
+}
+
+func (o *RepositoryList) DeepCopy() *RepositoryList {
+ cpy := &RepositoryList{}
+ o.DeepCopyInto(cpy)
+ return cpy
+}
+
+func (o *RepositoryList) DeepCopyInto(dst *RepositoryList) {
+ resource.CopyObjectInto(dst, o)
+}
+
+// Interface compliance compile-time check
+var _ resource.ListObject = &RepositoryList{}
+
+// Copy methods for all subresource types
+
+// DeepCopy creates a full deep copy of Spec
+func (s *RepositorySpec) DeepCopy() *RepositorySpec {
+ cpy := &RepositorySpec{}
+ s.DeepCopyInto(cpy)
+ return cpy
+}
+
+// DeepCopyInto deep copies Spec into another Spec object
+func (s *RepositorySpec) DeepCopyInto(dst *RepositorySpec) {
+ resource.CopyObjectInto(dst, s)
+}
+
+// DeepCopy creates a full deep copy of RepositoryStatus
+func (s *RepositoryStatus) DeepCopy() *RepositoryStatus {
+ cpy := &RepositoryStatus{}
+ s.DeepCopyInto(cpy)
+ return cpy
+}
+
+// DeepCopyInto deep copies RepositoryStatus into another RepositoryStatus object
+func (s *RepositoryStatus) DeepCopyInto(dst *RepositoryStatus) {
+ resource.CopyObjectInto(dst, s)
+}
diff --git a/apps/provisioning/kinds/provisioning/v0alpha1/repository_schema_gen.go b/apps/provisioning/kinds/provisioning/v0alpha1/repository_schema_gen.go
new file mode 100644
index 00000000000..e8f3a525d75
--- /dev/null
+++ b/apps/provisioning/kinds/provisioning/v0alpha1/repository_schema_gen.go
@@ -0,0 +1,34 @@
+//
+// Code generated by grafana-app-sdk. DO NOT EDIT.
+//
+
+package v0alpha1
+
+import (
+ "github.com/grafana/grafana-app-sdk/resource"
+)
+
+// schema is unexported to prevent accidental overwrites
+var (
+ schemaRepository = resource.NewSimpleSchema("provisioning.grafana.app", "v0alpha1", &Repository{}, &RepositoryList{}, resource.WithKind("Repository"),
+ resource.WithPlural("repositories"), resource.WithScope(resource.NamespacedScope))
+ kindRepository = resource.Kind{
+ Schema: schemaRepository,
+ Codecs: map[resource.KindEncoding]resource.Codec{
+ resource.KindEncodingJSON: &RepositoryJSONCodec{},
+ },
+ }
+)
+
+// Kind returns a resource.Kind for this Schema with a JSON codec
+func RepositoryKind() resource.Kind {
+ return kindRepository
+}
+
+// Schema returns a resource.SimpleSchema representation of Repository
+func RepositorySchema() *resource.SimpleSchema {
+ return schemaRepository
+}
+
+// Interface compliance checks
+var _ resource.Schema = kindRepository
diff --git a/apps/provisioning/kinds/provisioning/v0alpha1/repository_spec_gen.go b/apps/provisioning/kinds/provisioning/v0alpha1/repository_spec_gen.go
new file mode 100644
index 00000000000..54eb7ba4eda
--- /dev/null
+++ b/apps/provisioning/kinds/provisioning/v0alpha1/repository_spec_gen.go
@@ -0,0 +1,169 @@
+// Code generated - EDITING IS FUTILE. DO NOT EDIT.
+
+package v0alpha1
+
+// +k8s:openapi-gen=true
+type RepositorySyncOptions struct {
+ // Enabled must be saved as true before any sync job will run
+ Enabled bool `json:"enabled"`
+ // Where values should be saved
+ Target RepositorySyncOptionsTarget `json:"target"`
+ // When non-zero, the sync will run periodically
+ IntervalSeconds *int64 `json:"intervalSeconds,omitempty"`
+}
+
+// NewRepositorySyncOptions creates a new RepositorySyncOptions object.
+func NewRepositorySyncOptions() *RepositorySyncOptions {
+ return &RepositorySyncOptions{}
+}
+
+// +k8s:openapi-gen=true
+type RepositoryLocalRepositoryConfig struct {
+ // Path to the local repository
+ Path string `json:"path"`
+}
+
+// NewRepositoryLocalRepositoryConfig creates a new RepositoryLocalRepositoryConfig object.
+func NewRepositoryLocalRepositoryConfig() *RepositoryLocalRepositoryConfig {
+ return &RepositoryLocalRepositoryConfig{}
+}
+
+// +k8s:openapi-gen=true
+type RepositoryGitHubRepositoryConfig struct {
+ // The repository URL (e.g. `https://github.com/example/test`).
+ Url *string `json:"url,omitempty"`
+ // The branch to use in the repository.
+ Branch string `json:"branch"`
+ // Token for accessing the repository. If set, it will be encrypted into encryptedToken, then set to an empty string again.
+ Token *string `json:"token,omitempty"`
+ // Token for accessing the repository, but encrypted. This is not possible to read back to a user decrypted.
+ EncryptedToken []string `json:"encryptedToken,omitempty"`
+ // Whether we should show dashboard previews for pull requests.
+ // By default, this is false (i.e. we will not create previews).
+ GenerateDashboardPreviews *bool `json:"generateDashboardPreviews,omitempty"`
+ // Path is the subdirectory for the Grafana data. If specified, Grafana will ignore anything that is outside this directory in the repository.
+ Path *string `json:"path,omitempty"`
+}
+
+// NewRepositoryGitHubRepositoryConfig creates a new RepositoryGitHubRepositoryConfig object.
+func NewRepositoryGitHubRepositoryConfig() *RepositoryGitHubRepositoryConfig {
+ return &RepositoryGitHubRepositoryConfig{}
+}
+
+// +k8s:openapi-gen=true
+type RepositoryGitRepositoryConfig struct {
+ // The repository URL (e.g. `https://github.com/example/test.git`).
+ Url *string `json:"url,omitempty"`
+ // The branch to use in the repository.
+ Branch string `json:"branch"`
+ // TokenUser is the user that will be used to access the repository if it's a personal access token.
+ TokenUser *string `json:"tokenUser,omitempty"`
+ // Token for accessing the repository. If set, it will be encrypted into encryptedToken, then set to an empty string again.
+ Token *string `json:"token,omitempty"`
+ // Token for accessing the repository, but encrypted. This is not possible to read back to a user decrypted.
+ EncryptedToken []string `json:"encryptedToken,omitempty"`
+ // Path is the subdirectory for the Grafana data. If specified, Grafana will ignore anything that is outside this directory in the repository.
+ Path *string `json:"path,omitempty"`
+}
+
+// NewRepositoryGitRepositoryConfig creates a new RepositoryGitRepositoryConfig object.
+func NewRepositoryGitRepositoryConfig() *RepositoryGitRepositoryConfig {
+ return &RepositoryGitRepositoryConfig{}
+}
+
+// +k8s:openapi-gen=true
+type RepositoryBitbucketRepositoryConfig struct {
+ // The repository URL (e.g. `https://bitbucket.org/example/test`).
+ Url *string `json:"url,omitempty"`
+ // The branch to use in the repository.
+ Branch string `json:"branch"`
+ // TokenUser is the user that will be used to access the repository if it's a personal access token.
+ TokenUser *string `json:"tokenUser,omitempty"`
+ // Token for accessing the repository. If set, it will be encrypted into encryptedToken, then set to an empty string again.
+ Token *string `json:"token,omitempty"`
+ // Token for accessing the repository, but encrypted. This is not possible to read back to a user decrypted.
+ EncryptedToken []string `json:"encryptedToken,omitempty"`
+ // Path is the subdirectory for the Grafana data. If specified, Grafana will ignore anything that is outside this directory in the repository.
+ Path *string `json:"path,omitempty"`
+}
+
+// NewRepositoryBitbucketRepositoryConfig creates a new RepositoryBitbucketRepositoryConfig object.
+func NewRepositoryBitbucketRepositoryConfig() *RepositoryBitbucketRepositoryConfig {
+ return &RepositoryBitbucketRepositoryConfig{}
+}
+
+// +k8s:openapi-gen=true
+type RepositoryGitLabRepositoryConfig struct {
+ // The repository URL (e.g. `https://gitlab.com/example/test`).
+ Url *string `json:"url,omitempty"`
+ // The branch to use in the repository.
+ Branch string `json:"branch"`
+ // Token for accessing the repository. If set, it will be encrypted into encryptedToken, then set to an empty string again.
+ Token *string `json:"token,omitempty"`
+ // Token for accessing the repository, but encrypted. This is not possible to read back to a user decrypted.
+ EncryptedToken []string `json:"encryptedToken,omitempty"`
+ // Path is the subdirectory for the Grafana data. If specified, Grafana will ignore anything that is outside this directory in the repository.
+ Path *string `json:"path,omitempty"`
+}
+
+// NewRepositoryGitLabRepositoryConfig creates a new RepositoryGitLabRepositoryConfig object.
+func NewRepositoryGitLabRepositoryConfig() *RepositoryGitLabRepositoryConfig {
+ return &RepositoryGitLabRepositoryConfig{}
+}
+
+// +k8s:openapi-gen=true
+type RepositorySpec struct {
+ // The repository display name (shown in the UI)
+ Title string `json:"title"`
+ // Repository description
+ Description *string `json:"description,omitempty"`
+ // UI driven Workflow that allow changes to the contends of the repository.
+ // The order is relevant for defining the precedence of the workflows.
+ // When empty, the repository does not support any edits (eg, readonly)
+ Workflows []string `json:"workflows,omitempty"`
+ // Sync settings -- how values are pulled from the repository into grafana
+ Sync RepositorySyncOptions `json:"sync"`
+ // The repository type. When selected oneOf the values below should be non-nil
+ Type RepositorySpecType `json:"type"`
+ // The repository on the local file system.
+ // Mutually exclusive with local | github.
+ Local *RepositoryLocalRepositoryConfig `json:"local,omitempty"`
+ // The repository on GitHub.
+ // Mutually exclusive with local | github | git.
+ Github *RepositoryGitHubRepositoryConfig `json:"github,omitempty"`
+ // The repository on Git.
+ // Mutually exclusive with local | github | git.
+ Git *RepositoryGitRepositoryConfig `json:"git,omitempty"`
+ // The repository on Bitbucket.
+ // Mutually exclusive with local | github | git.
+ Bitbucket *RepositoryBitbucketRepositoryConfig `json:"bitbucket,omitempty"`
+ // The repository on GitLab.
+ // Mutually exclusive with local | github | git.
+ Gitlab *RepositoryGitLabRepositoryConfig `json:"gitlab,omitempty"`
+}
+
+// NewRepositorySpec creates a new RepositorySpec object.
+func NewRepositorySpec() *RepositorySpec {
+ return &RepositorySpec{
+ Sync: *NewRepositorySyncOptions(),
+ }
+}
+
+// +k8s:openapi-gen=true
+type RepositorySyncOptionsTarget string
+
+const (
+ RepositorySyncOptionsTargetUnified RepositorySyncOptionsTarget = "unified"
+ RepositorySyncOptionsTargetLegacy RepositorySyncOptionsTarget = "legacy"
+)
+
+// +k8s:openapi-gen=true
+type RepositorySpecType string
+
+const (
+ RepositorySpecTypeLocal RepositorySpecType = "local"
+ RepositorySpecTypeGithub RepositorySpecType = "github"
+ RepositorySpecTypeGit RepositorySpecType = "git"
+ RepositorySpecTypeBitbucket RepositorySpecType = "bitbucket"
+ RepositorySpecTypeGitlab RepositorySpecType = "gitlab"
+)
diff --git a/apps/provisioning/kinds/provisioning/v0alpha1/repository_status_gen.go b/apps/provisioning/kinds/provisioning/v0alpha1/repository_status_gen.go
new file mode 100644
index 00000000000..c2512cc2851
--- /dev/null
+++ b/apps/provisioning/kinds/provisioning/v0alpha1/repository_status_gen.go
@@ -0,0 +1,137 @@
+// Code generated - EDITING IS FUTILE. DO NOT EDIT.
+
+package v0alpha1
+
+// +k8s:openapi-gen=true
+type RepositoryHealthStatus struct {
+ // When not healthy, requests will not be executed
+ Healthy bool `json:"healthy"`
+ // When the health was checked last time
+ Checked *int64 `json:"checked,omitempty"`
+ // Summary messages (can be shown to users)
+ // Will only be populated when not healthy
+ Message []string `json:"message,omitempty"`
+}
+
+// NewRepositoryHealthStatus creates a new RepositoryHealthStatus object.
+func NewRepositoryHealthStatus() *RepositoryHealthStatus {
+ return &RepositoryHealthStatus{}
+}
+
+// +k8s:openapi-gen=true
+type RepositorySyncStatus struct {
+ // pending, running, success, error
+ State RepositorySyncStatusState `json:"state"`
+ // The ID for the job that ran this sync
+ Job *string `json:"job,omitempty"`
+ // When the sync job started
+ Started *int64 `json:"started,omitempty"`
+ // When the sync job finished
+ Finished *int64 `json:"finished,omitempty"`
+ // When the next sync check is scheduled
+ Scheduled *int64 `json:"scheduled,omitempty"`
+ // Summary messages (will be shown to users)
+ Message []string `json:"message"`
+ // The repository ref when the last successful sync ran
+ LastRef *string `json:"lastRef,omitempty"`
+ // Incremental synchronization for versioned repositories
+ Incremental *bool `json:"incremental,omitempty"`
+}
+
+// NewRepositorySyncStatus creates a new RepositorySyncStatus object.
+func NewRepositorySyncStatus() *RepositorySyncStatus {
+ return &RepositorySyncStatus{
+ Message: []string{},
+ }
+}
+
+// +k8s:openapi-gen=true
+type RepositoryResourceCount struct {
+ Group string `json:"group"`
+ Resource string `json:"resource"`
+ Count int64 `json:"count"`
+}
+
+// NewRepositoryResourceCount creates a new RepositoryResourceCount object.
+func NewRepositoryResourceCount() *RepositoryResourceCount {
+ return &RepositoryResourceCount{}
+}
+
+// +k8s:openapi-gen=true
+type RepositorystatusOperatorState struct {
+ // lastEvaluation is the ResourceVersion last evaluated
+ LastEvaluation string `json:"lastEvaluation"`
+ // state describes the state of the lastEvaluation.
+ // It is limited to three possible states for machine evaluation.
+ State RepositoryStatusOperatorStateState `json:"state"`
+ // descriptiveState is an optional more descriptive state field which has no requirements on format
+ DescriptiveState *string `json:"descriptiveState,omitempty"`
+ // details contains any extra information that is operator-specific
+ Details map[string]interface{} `json:"details,omitempty"`
+}
+
+// NewRepositorystatusOperatorState creates a new RepositorystatusOperatorState object.
+func NewRepositorystatusOperatorState() *RepositorystatusOperatorState {
+ return &RepositorystatusOperatorState{}
+}
+
+// +k8s:openapi-gen=true
+type RepositoryWebhookStatus struct {
+ Id *int64 `json:"id,omitempty"`
+ Url *string `json:"url,omitempty"`
+ Secret *string `json:"secret,omitempty"`
+ EncryptedSecret []string `json:"encryptedSecret,omitempty"`
+ SubscribedEvents []string `json:"subscribedEvents,omitempty"`
+ LastEvent *int64 `json:"lastEvent,omitempty"`
+}
+
+// NewRepositoryWebhookStatus creates a new RepositoryWebhookStatus object.
+func NewRepositoryWebhookStatus() *RepositoryWebhookStatus {
+ return &RepositoryWebhookStatus{}
+}
+
+// +k8s:openapi-gen=true
+type RepositoryStatus struct {
+ // The generation of the spec last time reconciliation ran
+ ObservedGeneration *int64 `json:"observedGeneration,omitempty"`
+ // This will get updated with the current health status (and updated periodically)
+ Health RepositoryHealthStatus `json:"health"`
+ // Sync information with the last sync information
+ Sync RepositorySyncStatus `json:"sync"`
+ // The object count when sync last ran
+ Stats []RepositoryResourceCount `json:"stats,omitempty"`
+ // operatorStates is a map of operator ID to operator state evaluations.
+ // Any operator which consumes this kind SHOULD add its state evaluation information to this field.
+ OperatorStates map[string]RepositorystatusOperatorState `json:"operatorStates,omitempty"`
+ // Webhook Information (if applicable)
+ Webhook *RepositoryWebhookStatus `json:"webhook,omitempty"`
+ // additionalFields is reserved for future use
+ AdditionalFields map[string]interface{} `json:"additionalFields,omitempty"`
+}
+
+// NewRepositoryStatus creates a new RepositoryStatus object.
+func NewRepositoryStatus() *RepositoryStatus {
+ return &RepositoryStatus{
+ Health: *NewRepositoryHealthStatus(),
+ Sync: *NewRepositorySyncStatus(),
+ }
+}
+
+// +k8s:openapi-gen=true
+type RepositorySyncStatusState string
+
+const (
+ RepositorySyncStatusStatePending RepositorySyncStatusState = "pending"
+ RepositorySyncStatusStateRunning RepositorySyncStatusState = "running"
+ RepositorySyncStatusStateSuccess RepositorySyncStatusState = "success"
+ RepositorySyncStatusStateError RepositorySyncStatusState = "error"
+)
+
+// +k8s:openapi-gen=true
+type RepositoryStatusOperatorStateState string
+
+const (
+ RepositoryStatusOperatorStateStateSuccess RepositoryStatusOperatorStateState = "success"
+ RepositoryStatusOperatorStateStateInProgress RepositoryStatusOperatorStateState = "in_progress"
+ RepositoryStatusOperatorStateStateFailed RepositoryStatusOperatorStateState = "failed"
+)
diff --git a/apps/provisioning/kinds/provisioning/v0alpha1/zz_openapi_gen.go b/apps/provisioning/kinds/provisioning/v0alpha1/zz_openapi_gen.go
new file mode 100644
index 00000000000..583d9f07f60
--- /dev/null
+++ b/apps/provisioning/kinds/provisioning/v0alpha1/zz_openapi_gen.go
@@ -0,0 +1,879 @@
+//go:build !ignore_autogenerated
+// +build !ignore_autogenerated
+
+// Code generated by grafana-app-sdk. DO NOT EDIT.
+
+package v0alpha1
+
+import (
+ common "k8s.io/kube-openapi/pkg/common"
+ spec "k8s.io/kube-openapi/pkg/validation/spec"
+)
+
+func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition {
+ return map[string]common.OpenAPIDefinition{
+ "github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.Repository": schema_provisioning_kinds_provisioning_v0alpha1_Repository(ref),
+ "github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.RepositoryBitbucketRepositoryConfig": schema_provisioning_kinds_provisioning_v0alpha1_RepositoryBitbucketRepositoryConfig(ref),
+ "github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.RepositoryGitHubRepositoryConfig": schema_provisioning_kinds_provisioning_v0alpha1_RepositoryGitHubRepositoryConfig(ref),
+ "github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.RepositoryGitLabRepositoryConfig": schema_provisioning_kinds_provisioning_v0alpha1_RepositoryGitLabRepositoryConfig(ref),
+ "github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.RepositoryGitRepositoryConfig": schema_provisioning_kinds_provisioning_v0alpha1_RepositoryGitRepositoryConfig(ref),
+ "github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.RepositoryHealthStatus": schema_provisioning_kinds_provisioning_v0alpha1_RepositoryHealthStatus(ref),
+ "github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.RepositoryList": schema_provisioning_kinds_provisioning_v0alpha1_RepositoryList(ref),
+ "github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.RepositoryLocalRepositoryConfig": schema_provisioning_kinds_provisioning_v0alpha1_RepositoryLocalRepositoryConfig(ref),
+ "github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.RepositoryResourceCount": schema_provisioning_kinds_provisioning_v0alpha1_RepositoryResourceCount(ref),
+ "github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.RepositorySpec": schema_provisioning_kinds_provisioning_v0alpha1_RepositorySpec(ref),
+ "github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.RepositoryStatus": schema_provisioning_kinds_provisioning_v0alpha1_RepositoryStatus(ref),
+ "github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.RepositorySyncOptions": schema_provisioning_kinds_provisioning_v0alpha1_RepositorySyncOptions(ref),
+ "github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.RepositorySyncStatus": schema_provisioning_kinds_provisioning_v0alpha1_RepositorySyncStatus(ref),
+ "github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.RepositoryWebhookStatus": schema_provisioning_kinds_provisioning_v0alpha1_RepositoryWebhookStatus(ref),
+ "github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.RepositorystatusOperatorState": schema_provisioning_kinds_provisioning_v0alpha1_RepositorystatusOperatorState(ref),
+ }
+}
+
+func schema_provisioning_kinds_provisioning_v0alpha1_Repository(ref common.ReferenceCallback) common.OpenAPIDefinition {
+ return common.OpenAPIDefinition{
+ Schema: spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Type: []string{"object"},
+ Properties: map[string]spec.Schema{
+ "kind": {
+ SchemaProps: spec.SchemaProps{
+ Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "apiVersion": {
+ SchemaProps: spec.SchemaProps{
+ Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "metadata": {
+ SchemaProps: spec.SchemaProps{
+ Default: map[string]interface{}{},
+ Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"),
+ },
+ },
+ "spec": {
+ SchemaProps: spec.SchemaProps{
+ Description: "Spec is the spec of the Repository",
+ Default: map[string]interface{}{},
+ Ref: ref("github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.RepositorySpec"),
+ },
+ },
+ "status": {
+ SchemaProps: spec.SchemaProps{
+ Default: map[string]interface{}{},
+ Ref: ref("github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.RepositoryStatus"),
+ },
+ },
+ },
+ Required: []string{"metadata", "spec", "status"},
+ },
+ },
+ Dependencies: []string{
+ "github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.RepositorySpec", "github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.RepositoryStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"},
+ }
+}
+
+func schema_provisioning_kinds_provisioning_v0alpha1_RepositoryBitbucketRepositoryConfig(ref common.ReferenceCallback) common.OpenAPIDefinition {
+ return common.OpenAPIDefinition{
+ Schema: spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Type: []string{"object"},
+ Properties: map[string]spec.Schema{
+ "url": {
+ SchemaProps: spec.SchemaProps{
+ Description: "The repository URL (e.g. `https://bitbucket.org/example/test`).",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "branch": {
+ SchemaProps: spec.SchemaProps{
+ Description: "The branch to use in the repository.",
+ Default: "",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "tokenUser": {
+ SchemaProps: spec.SchemaProps{
+ Description: "TokenUser is the user that will be used to access the repository if it's a personal access token.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "token": {
+ SchemaProps: spec.SchemaProps{
+ Description: "Token for accessing the repository. If set, it will be encrypted into encryptedToken, then set to an empty string again.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "encryptedToken": {
+ SchemaProps: spec.SchemaProps{
+ Description: "Token for accessing the repository, but encrypted. This is not possible to read back to a user decrypted.",
+ Type: []string{"array"},
+ Items: &spec.SchemaOrArray{
+ Schema: &spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Default: "",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ },
+ },
+ },
+ "path": {
+ SchemaProps: spec.SchemaProps{
+ Description: "Path is the subdirectory for the Grafana data. If specified, Grafana will ignore anything that is outside this directory in the repository.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ },
+ Required: []string{"branch"},
+ },
+ },
+ }
+}
+
+func schema_provisioning_kinds_provisioning_v0alpha1_RepositoryGitHubRepositoryConfig(ref common.ReferenceCallback) common.OpenAPIDefinition {
+ return common.OpenAPIDefinition{
+ Schema: spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Type: []string{"object"},
+ Properties: map[string]spec.Schema{
+ "url": {
+ SchemaProps: spec.SchemaProps{
+ Description: "The repository URL (e.g. `https://github.com/example/test`).",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "branch": {
+ SchemaProps: spec.SchemaProps{
+ Description: "The branch to use in the repository.",
+ Default: "",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "token": {
+ SchemaProps: spec.SchemaProps{
+ Description: "Token for accessing the repository. If set, it will be encrypted into encryptedToken, then set to an empty string again.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "encryptedToken": {
+ SchemaProps: spec.SchemaProps{
+ Description: "Token for accessing the repository, but encrypted. This is not possible to read back to a user decrypted.",
+ Type: []string{"array"},
+ Items: &spec.SchemaOrArray{
+ Schema: &spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Default: "",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ },
+ },
+ },
+ "generateDashboardPreviews": {
+ SchemaProps: spec.SchemaProps{
+ Description: "Whether we should show dashboard previews for pull requests. By default, this is false (i.e. we will not create previews).",
+ Type: []string{"boolean"},
+ Format: "",
+ },
+ },
+ "path": {
+ SchemaProps: spec.SchemaProps{
+ Description: "Path is the subdirectory for the Grafana data. If specified, Grafana will ignore anything that is outside this directory in the repository.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ },
+ Required: []string{"branch"},
+ },
+ },
+ }
+}
+
+func schema_provisioning_kinds_provisioning_v0alpha1_RepositoryGitLabRepositoryConfig(ref common.ReferenceCallback) common.OpenAPIDefinition {
+ return common.OpenAPIDefinition{
+ Schema: spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Type: []string{"object"},
+ Properties: map[string]spec.Schema{
+ "url": {
+ SchemaProps: spec.SchemaProps{
+ Description: "The repository URL (e.g. `https://gitlab.com/example/test`).",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "branch": {
+ SchemaProps: spec.SchemaProps{
+ Description: "The branch to use in the repository.",
+ Default: "",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "token": {
+ SchemaProps: spec.SchemaProps{
+ Description: "Token for accessing the repository. If set, it will be encrypted into encryptedToken, then set to an empty string again.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "encryptedToken": {
+ SchemaProps: spec.SchemaProps{
+ Description: "Token for accessing the repository, but encrypted. This is not possible to read back to a user decrypted.",
+ Type: []string{"array"},
+ Items: &spec.SchemaOrArray{
+ Schema: &spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Default: "",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ },
+ },
+ },
+ "path": {
+ SchemaProps: spec.SchemaProps{
+ Description: "Path is the subdirectory for the Grafana data. If specified, Grafana will ignore anything that is outside this directory in the repository.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ },
+ Required: []string{"branch"},
+ },
+ },
+ }
+}
+
+func schema_provisioning_kinds_provisioning_v0alpha1_RepositoryGitRepositoryConfig(ref common.ReferenceCallback) common.OpenAPIDefinition {
+ return common.OpenAPIDefinition{
+ Schema: spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Type: []string{"object"},
+ Properties: map[string]spec.Schema{
+ "url": {
+ SchemaProps: spec.SchemaProps{
+ Description: "The repository URL (e.g. `https://github.com/example/test.git`).",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "branch": {
+ SchemaProps: spec.SchemaProps{
+ Description: "The branch to use in the repository.",
+ Default: "",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "tokenUser": {
+ SchemaProps: spec.SchemaProps{
+ Description: "TokenUser is the user that will be used to access the repository if it's a personal access token.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "token": {
+ SchemaProps: spec.SchemaProps{
+ Description: "Token for accessing the repository. If set, it will be encrypted into encryptedToken, then set to an empty string again.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "encryptedToken": {
+ SchemaProps: spec.SchemaProps{
+ Description: "Token for accessing the repository, but encrypted. This is not possible to read back to a user decrypted.",
+ Type: []string{"array"},
+ Items: &spec.SchemaOrArray{
+ Schema: &spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Default: "",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ },
+ },
+ },
+ "path": {
+ SchemaProps: spec.SchemaProps{
+ Description: "Path is the subdirectory for the Grafana data. If specified, Grafana will ignore anything that is outside this directory in the repository.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ },
+ Required: []string{"branch"},
+ },
+ },
+ }
+}
+
+func schema_provisioning_kinds_provisioning_v0alpha1_RepositoryHealthStatus(ref common.ReferenceCallback) common.OpenAPIDefinition {
+ return common.OpenAPIDefinition{
+ Schema: spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Type: []string{"object"},
+ Properties: map[string]spec.Schema{
+ "healthy": {
+ SchemaProps: spec.SchemaProps{
+ Description: "When not healthy, requests will not be executed",
+ Default: false,
+ Type: []string{"boolean"},
+ Format: "",
+ },
+ },
+ "checked": {
+ SchemaProps: spec.SchemaProps{
+ Description: "When the health was checked last time",
+ Type: []string{"integer"},
+ Format: "int64",
+ },
+ },
+ "message": {
+ SchemaProps: spec.SchemaProps{
+ Description: "Summary messages (can be shown to users) Will only be populated when not healthy",
+ Type: []string{"array"},
+ Items: &spec.SchemaOrArray{
+ Schema: &spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Default: "",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ },
+ },
+ },
+ },
+ Required: []string{"healthy"},
+ },
+ },
+ }
+}
+
+func schema_provisioning_kinds_provisioning_v0alpha1_RepositoryList(ref common.ReferenceCallback) common.OpenAPIDefinition {
+ return common.OpenAPIDefinition{
+ Schema: spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Type: []string{"object"},
+ Properties: map[string]spec.Schema{
+ "kind": {
+ SchemaProps: spec.SchemaProps{
+ Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "apiVersion": {
+ SchemaProps: spec.SchemaProps{
+ Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "metadata": {
+ SchemaProps: spec.SchemaProps{
+ Default: map[string]interface{}{},
+ Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"),
+ },
+ },
+ "items": {
+ SchemaProps: spec.SchemaProps{
+ Type: []string{"array"},
+ Items: &spec.SchemaOrArray{
+ Schema: &spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Default: map[string]interface{}{},
+ Ref: ref("github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.Repository"),
+ },
+ },
+ },
+ },
+ },
+ },
+ Required: []string{"metadata", "items"},
+ },
+ },
+ Dependencies: []string{
+ "github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.Repository", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"},
+ }
+}
+
+func schema_provisioning_kinds_provisioning_v0alpha1_RepositoryLocalRepositoryConfig(ref common.ReferenceCallback) common.OpenAPIDefinition {
+ return common.OpenAPIDefinition{
+ Schema: spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Type: []string{"object"},
+ Properties: map[string]spec.Schema{
+ "path": {
+ SchemaProps: spec.SchemaProps{
+ Description: "Path to the local repository",
+ Default: "",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ },
+ Required: []string{"path"},
+ },
+ },
+ }
+}
+
+func schema_provisioning_kinds_provisioning_v0alpha1_RepositoryResourceCount(ref common.ReferenceCallback) common.OpenAPIDefinition {
+ return common.OpenAPIDefinition{
+ Schema: spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Type: []string{"object"},
+ Properties: map[string]spec.Schema{
+ "group": {
+ SchemaProps: spec.SchemaProps{
+ Default: "",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "resource": {
+ SchemaProps: spec.SchemaProps{
+ Default: "",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "count": {
+ SchemaProps: spec.SchemaProps{
+ Default: 0,
+ Type: []string{"integer"},
+ Format: "int64",
+ },
+ },
+ },
+ Required: []string{"group", "resource", "count"},
+ },
+ },
+ }
+}
+
+func schema_provisioning_kinds_provisioning_v0alpha1_RepositorySpec(ref common.ReferenceCallback) common.OpenAPIDefinition {
+ return common.OpenAPIDefinition{
+ Schema: spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Type: []string{"object"},
+ Properties: map[string]spec.Schema{
+ "title": {
+ SchemaProps: spec.SchemaProps{
+ Description: "The repository display name (shown in the UI)",
+ Default: "",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "description": {
+ SchemaProps: spec.SchemaProps{
+ Description: "Repository description",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "workflows": {
+ SchemaProps: spec.SchemaProps{
+ Description: "UI driven Workflow that allow changes to the contends of the repository. The order is relevant for defining the precedence of the workflows. When empty, the repository does not support any edits (eg, readonly)",
+ Type: []string{"array"},
+ Items: &spec.SchemaOrArray{
+ Schema: &spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Default: "",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ },
+ },
+ },
+ "sync": {
+ SchemaProps: spec.SchemaProps{
+ Description: "Sync settings -- how values are pulled from the repository into grafana",
+ Default: map[string]interface{}{},
+ Ref: ref("github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.RepositorySyncOptions"),
+ },
+ },
+ "type": {
+ SchemaProps: spec.SchemaProps{
+ Description: "The repository type. When selected oneOf the values below should be non-nil",
+ Default: "",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "local": {
+ SchemaProps: spec.SchemaProps{
+ Description: "The repository on the local file system. Mutually exclusive with local | github.",
+ Ref: ref("github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.RepositoryLocalRepositoryConfig"),
+ },
+ },
+ "github": {
+ SchemaProps: spec.SchemaProps{
+ Description: "The repository on GitHub. Mutually exclusive with local | github | git.",
+ Ref: ref("github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.RepositoryGitHubRepositoryConfig"),
+ },
+ },
+ "git": {
+ SchemaProps: spec.SchemaProps{
+ Description: "The repository on Git. Mutually exclusive with local | github | git.",
+ Ref: ref("github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.RepositoryGitRepositoryConfig"),
+ },
+ },
+ "bitbucket": {
+ SchemaProps: spec.SchemaProps{
+ Description: "The repository on Bitbucket. Mutually exclusive with local | github | git.",
+ Ref: ref("github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.RepositoryBitbucketRepositoryConfig"),
+ },
+ },
+ "gitlab": {
+ SchemaProps: spec.SchemaProps{
+ Description: "The repository on GitLab. Mutually exclusive with local | github | git.",
+ Ref: ref("github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.RepositoryGitLabRepositoryConfig"),
+ },
+ },
+ },
+ Required: []string{"title", "sync", "type"},
+ },
+ },
+ Dependencies: []string{
+ "github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.RepositoryBitbucketRepositoryConfig", "github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.RepositoryGitHubRepositoryConfig", "github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.RepositoryGitLabRepositoryConfig", "github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.RepositoryGitRepositoryConfig", "github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.RepositoryLocalRepositoryConfig", "github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.RepositorySyncOptions"},
+ }
+}
+
+func schema_provisioning_kinds_provisioning_v0alpha1_RepositoryStatus(ref common.ReferenceCallback) common.OpenAPIDefinition {
+ return common.OpenAPIDefinition{
+ Schema: spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Type: []string{"object"},
+ Properties: map[string]spec.Schema{
+ "observedGeneration": {
+ SchemaProps: spec.SchemaProps{
+ Description: "The generation of the spec last time reconciliation ran",
+ Type: []string{"integer"},
+ Format: "int64",
+ },
+ },
+ "health": {
+ SchemaProps: spec.SchemaProps{
+ Description: "This will get updated with the current health status (and updated periodically)",
+ Default: map[string]interface{}{},
+ Ref: ref("github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.RepositoryHealthStatus"),
+ },
+ },
+ "sync": {
+ SchemaProps: spec.SchemaProps{
+ Description: "Sync information with the last sync information",
+ Default: map[string]interface{}{},
+ Ref: ref("github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.RepositorySyncStatus"),
+ },
+ },
+ "stats": {
+ SchemaProps: spec.SchemaProps{
+ Description: "The object count when sync last ran",
+ Type: []string{"array"},
+ Items: &spec.SchemaOrArray{
+ Schema: &spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Default: map[string]interface{}{},
+ Ref: ref("github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.RepositoryResourceCount"),
+ },
+ },
+ },
+ },
+ },
+ "operatorStates": {
+ SchemaProps: spec.SchemaProps{
+ Description: "operatorStates is a map of operator ID to operator state evaluations. Any operator which consumes this kind SHOULD add its state evaluation information to this field.",
+ Type: []string{"object"},
+ AdditionalProperties: &spec.SchemaOrBool{
+ Allows: true,
+ Schema: &spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Default: map[string]interface{}{},
+ Ref: ref("github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.RepositorystatusOperatorState"),
+ },
+ },
+ },
+ },
+ },
+ "webhook": {
+ SchemaProps: spec.SchemaProps{
+ Description: "Webhook Information (if applicable)",
+ Ref: ref("github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.RepositoryWebhookStatus"),
+ },
+ },
+ "additionalFields": {
+ SchemaProps: spec.SchemaProps{
+ Description: "additionalFields is reserved for future use",
+ Type: []string{"object"},
+ AdditionalProperties: &spec.SchemaOrBool{
+ Allows: true,
+ Schema: &spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Type: []string{"object"},
+ Format: "",
+ },
+ },
+ },
+ },
+ },
+ },
+ Required: []string{"health", "sync"},
+ },
+ },
+ Dependencies: []string{
+ "github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.RepositoryHealthStatus", "github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.RepositoryResourceCount", "github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.RepositorySyncStatus", "github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.RepositoryWebhookStatus", "github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1.RepositorystatusOperatorState"},
+ }
+}
+
+func schema_provisioning_kinds_provisioning_v0alpha1_RepositorySyncOptions(ref common.ReferenceCallback) common.OpenAPIDefinition {
+ return common.OpenAPIDefinition{
+ Schema: spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Type: []string{"object"},
+ Properties: map[string]spec.Schema{
+ "enabled": {
+ SchemaProps: spec.SchemaProps{
+ Description: "Enabled must be saved as true before any sync job will run",
+ Default: false,
+ Type: []string{"boolean"},
+ Format: "",
+ },
+ },
+ "target": {
+ SchemaProps: spec.SchemaProps{
+ Description: "Where values should be saved",
+ Default: "",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "intervalSeconds": {
+ SchemaProps: spec.SchemaProps{
+ Description: "When non-zero, the sync will run periodically",
+ Type: []string{"integer"},
+ Format: "int64",
+ },
+ },
+ },
+ Required: []string{"enabled", "target"},
+ },
+ },
+ }
+}
+
+func schema_provisioning_kinds_provisioning_v0alpha1_RepositorySyncStatus(ref common.ReferenceCallback) common.OpenAPIDefinition {
+ return common.OpenAPIDefinition{
+ Schema: spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Type: []string{"object"},
+ Properties: map[string]spec.Schema{
+ "state": {
+ SchemaProps: spec.SchemaProps{
+ Description: "pending, running, success, error",
+ Default: "",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "job": {
+ SchemaProps: spec.SchemaProps{
+ Description: "The ID for the job that ran this sync",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "started": {
+ SchemaProps: spec.SchemaProps{
+ Description: "When the sync job started",
+ Type: []string{"integer"},
+ Format: "int64",
+ },
+ },
+ "finished": {
+ SchemaProps: spec.SchemaProps{
+ Description: "When the sync job finished",
+ Type: []string{"integer"},
+ Format: "int64",
+ },
+ },
+ "scheduled": {
+ SchemaProps: spec.SchemaProps{
+ Description: "When the next sync check is scheduled",
+ Type: []string{"integer"},
+ Format: "int64",
+ },
+ },
+ "message": {
+ SchemaProps: spec.SchemaProps{
+ Description: "Summary messages (will be shown to users)",
+ Type: []string{"array"},
+ Items: &spec.SchemaOrArray{
+ Schema: &spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Default: "",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ },
+ },
+ },
+ "lastRef": {
+ SchemaProps: spec.SchemaProps{
+ Description: "The repository ref when the last successful sync ran",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "incremental": {
+ SchemaProps: spec.SchemaProps{
+ Description: "Incremental synchronization for versioned repositories",
+ Type: []string{"boolean"},
+ Format: "",
+ },
+ },
+ },
+ Required: []string{"state", "message"},
+ },
+ },
+ }
+}
+
+func schema_provisioning_kinds_provisioning_v0alpha1_RepositoryWebhookStatus(ref common.ReferenceCallback) common.OpenAPIDefinition {
+ return common.OpenAPIDefinition{
+ Schema: spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Type: []string{"object"},
+ Properties: map[string]spec.Schema{
+ "id": {
+ SchemaProps: spec.SchemaProps{
+ Type: []string{"integer"},
+ Format: "int64",
+ },
+ },
+ "url": {
+ SchemaProps: spec.SchemaProps{
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "secret": {
+ SchemaProps: spec.SchemaProps{
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "encryptedSecret": {
+ SchemaProps: spec.SchemaProps{
+ Type: []string{"array"},
+ Items: &spec.SchemaOrArray{
+ Schema: &spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Default: "",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ },
+ },
+ },
+ "subscribedEvents": {
+ SchemaProps: spec.SchemaProps{
+ Type: []string{"array"},
+ Items: &spec.SchemaOrArray{
+ Schema: &spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Default: "",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ },
+ },
+ },
+ "lastEvent": {
+ SchemaProps: spec.SchemaProps{
+ Type: []string{"integer"},
+ Format: "int64",
+ },
+ },
+ },
+ },
+ },
+ }
+}
+
+func schema_provisioning_kinds_provisioning_v0alpha1_RepositorystatusOperatorState(ref common.ReferenceCallback) common.OpenAPIDefinition {
+ return common.OpenAPIDefinition{
+ Schema: spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Type: []string{"object"},
+ Properties: map[string]spec.Schema{
+ "lastEvaluation": {
+ SchemaProps: spec.SchemaProps{
+ Description: "lastEvaluation is the ResourceVersion last evaluated",
+ Default: "",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "state": {
+ SchemaProps: spec.SchemaProps{
+ Description: "state describes the state of the lastEvaluation. It is limited to three possible states for machine evaluation.",
+ Default: "",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "descriptiveState": {
+ SchemaProps: spec.SchemaProps{
+ Description: "descriptiveState is an optional more descriptive state field which has no requirements on format",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "details": {
+ SchemaProps: spec.SchemaProps{
+ Description: "details contains any extra information that is operator-specific",
+ Type: []string{"object"},
+ AdditionalProperties: &spec.SchemaOrBool{
+ Allows: true,
+ Schema: &spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Type: []string{"object"},
+ Format: "",
+ },
+ },
+ },
+ },
+ },
+ },
+ Required: []string{"lastEvaluation", "state"},
+ },
+ },
+ }
+}
diff --git a/apps/provisioning/kinds/provisioning_manifest.go b/apps/provisioning/kinds/provisioning_manifest.go
new file mode 100644
index 00000000000..677a17d8b55
--- /dev/null
+++ b/apps/provisioning/kinds/provisioning_manifest.go
@@ -0,0 +1,83 @@
+//
+// This file is generated by grafana-app-sdk
+// DO NOT EDIT
+//
+
+package kinds
+
+import (
+ "encoding/json"
+ "fmt"
+ "strings"
+
+ "github.com/grafana/grafana-app-sdk/app"
+ "github.com/grafana/grafana-app-sdk/resource"
+
+ v0alpha1 "github.com/grafana/grafana/apps/provisioning/kinds/provisioning/v0alpha1"
+)
+
+var (
+ rawSchemaRepositoryv0alpha1 = []byte(`{"spec":{"properties":{"bitbucket":{"description":"The repository on Bitbucket.\nMutually exclusive with local | github | git.","properties":{"branch":{"description":"The branch to use in the repository.","type":"string"},"encryptedToken":{"description":"Token for accessing the repository, but encrypted. This is not possible to read back to a user decrypted.","items":{"type":"string"},"type":"array"},"path":{"description":"Path is the subdirectory for the Grafana data. If specified, Grafana will ignore anything that is outside this directory in the repository.","type":"string"},"token":{"description":"Token for accessing the repository. If set, it will be encrypted into encryptedToken, then set to an empty string again.","type":"string"},"tokenUser":{"description":"TokenUser is the user that will be used to access the repository if it's a personal access token.","type":"string"},"url":{"description":"The repository URL (e.g. ` + "`" + `https://bitbucket.org/example/test` + "`" + `).","type":"string"}},"required":["branch"],"type":"object"},"description":{"description":"Repository description","type":"string"},"git":{"description":"The repository on Git.\nMutually exclusive with local | github | git.","properties":{"branch":{"description":"The branch to use in the repository.","type":"string"},"encryptedToken":{"description":"Token for accessing the repository, but encrypted. This is not possible to read back to a user decrypted.","items":{"type":"string"},"type":"array"},"path":{"description":"Path is the subdirectory for the Grafana data. If specified, Grafana will ignore anything that is outside this directory in the repository.","type":"string"},"token":{"description":"Token for accessing the repository. If set, it will be encrypted into encryptedToken, then set to an empty string again.","type":"string"},"tokenUser":{"description":"TokenUser is the user that will be used to access the repository if it's a personal access token.","type":"string"},"url":{"description":"The repository URL (e.g. ` + "`" + `https://github.com/example/test.git` + "`" + `).","type":"string"}},"required":["branch"],"type":"object"},"github":{"description":"The repository on GitHub.\nMutually exclusive with local | github | git.","properties":{"branch":{"description":"The branch to use in the repository.","type":"string"},"encryptedToken":{"description":"Token for accessing the repository, but encrypted. This is not possible to read back to a user decrypted.","items":{"type":"string"},"type":"array"},"generateDashboardPreviews":{"description":"Whether we should show dashboard previews for pull requests.\nBy default, this is false (i.e. we will not create previews).","type":"boolean"},"path":{"description":"Path is the subdirectory for the Grafana data. If specified, Grafana will ignore anything that is outside this directory in the repository.","type":"string"},"token":{"description":"Token for accessing the repository. If set, it will be encrypted into encryptedToken, then set to an empty string again.","type":"string"},"url":{"description":"The repository URL (e.g. ` + "`" + `https://github.com/example/test` + "`" + `).","type":"string"}},"required":["branch"],"type":"object"},"gitlab":{"description":"The repository on GitLab.\nMutually exclusive with local | github | git.","properties":{"branch":{"description":"The branch to use in the repository.","type":"string"},"encryptedToken":{"description":"Token for accessing the repository, but encrypted. This is not possible to read back to a user decrypted.","items":{"type":"string"},"type":"array"},"path":{"description":"Path is the subdirectory for the Grafana data. If specified, Grafana will ignore anything that is outside this directory in the repository.","type":"string"},"token":{"description":"Token for accessing the repository. If set, it will be encrypted into encryptedToken, then set to an empty string again.","type":"string"},"url":{"description":"The repository URL (e.g. ` + "`" + `https://gitlab.com/example/test` + "`" + `).","type":"string"}},"required":["branch"],"type":"object"},"local":{"description":"The repository on the local file system.\nMutually exclusive with local | github.","properties":{"path":{"description":"Path to the local repository","type":"string"}},"required":["path"],"type":"object"},"sync":{"description":"Sync settings -- how values are pulled from the repository into grafana","properties":{"enabled":{"description":"Enabled must be saved as true before any sync job will run","type":"boolean"},"intervalSeconds":{"description":"When non-zero, the sync will run periodically","type":"integer"},"target":{"description":"Where values should be saved","enum":["unified","legacy"],"type":"string"}},"required":["enabled","target"],"type":"object"},"title":{"description":"The repository display name (shown in the UI)","type":"string"},"type":{"description":"The repository type. When selected oneOf the values below should be non-nil","enum":["local","github","git","bitbucket","gitlab"],"type":"string"},"workflows":{"description":"UI driven Workflow that allow changes to the contends of the repository.\nThe order is relevant for defining the precedence of the workflows.\nWhen empty, the repository does not support any edits (eg, readonly)","items":{"type":"string"},"type":"array"}},"required":["title","sync","type"],"type":"object"},"status":{"properties":{"additionalFields":{"description":"additionalFields is reserved for future use","type":"object","x-kubernetes-preserve-unknown-fields":true},"health":{"description":"This will get updated with the current health status (and updated periodically)","properties":{"checked":{"description":"When the health was checked last time","type":"integer"},"healthy":{"description":"When not healthy, requests will not be executed","type":"boolean"},"message":{"description":"Summary messages (can be shown to users)\nWill only be populated when not healthy","items":{"type":"string"},"type":"array"}},"required":["healthy"],"type":"object"},"observedGeneration":{"description":"The generation of the spec last time reconciliation ran","type":"integer"},"operatorStates":{"additionalProperties":{"properties":{"descriptiveState":{"description":"descriptiveState is an optional more descriptive state field which has no requirements on format","type":"string"},"details":{"description":"details contains any extra information that is operator-specific","type":"object","x-kubernetes-preserve-unknown-fields":true},"lastEvaluation":{"description":"lastEvaluation is the ResourceVersion last evaluated","type":"string"},"state":{"description":"state describes the state of the lastEvaluation.\nIt is limited to three possible states for machine evaluation.","enum":["success","in_progress","failed"],"type":"string"}},"required":["lastEvaluation","state"],"type":"object"},"description":"operatorStates is a map of operator ID to operator state evaluations.\nAny operator which consumes this kind SHOULD add its state evaluation information to this field.","type":"object"},"stats":{"description":"The object count when sync last ran","items":{"properties":{"count":{"type":"integer"},"group":{"type":"string"},"resource":{"type":"string"}},"required":["group","resource","count"],"type":"object"},"type":"array"},"sync":{"description":"Sync information with the last sync information","properties":{"finished":{"description":"When the sync job finished","type":"integer"},"incremental":{"description":"Incremental synchronization for versioned repositories","type":"boolean"},"job":{"description":"The ID for the job that ran this sync","type":"string"},"lastRef":{"description":"The repository ref when the last successful sync ran","type":"string"},"message":{"description":"Summary messages (will be shown to users)","items":{"type":"string"},"type":"array"},"scheduled":{"description":"When the next sync check is scheduled","type":"integer"},"started":{"description":"When the sync job started","type":"integer"},"state":{"description":"pending, running, success, error","enum":["pending","running","success","error"],"type":"string"}},"required":["state","message"],"type":"object"},"webhook":{"description":"Webhook Information (if applicable)","properties":{"encryptedSecret":{"items":{"type":"string"},"type":"array"},"id":{"type":"integer"},"lastEvent":{"type":"integer"},"secret":{"type":"string"},"subscribedEvents":{"items":{"type":"string"},"type":"array"},"url":{"type":"string"}},"type":"object"}},"required":["health","sync"],"type":"object"}}`)
+ versionSchemaRepositoryv0alpha1 app.VersionSchema
+ _ = json.Unmarshal(rawSchemaRepositoryv0alpha1, &versionSchemaRepositoryv0alpha1)
+)
+
+var appManifestData = app.ManifestData{
+ AppName: "provisioning",
+ Group: "provisioning.grafana.app",
+ Versions: []app.ManifestVersion{
+ {
+ Name: "v0alpha1",
+ Served: true,
+ Kinds: []app.ManifestVersionKind{
+ {
+ Kind: "Repository",
+ Plural: "Repositories",
+ Scope: "Namespaced",
+ Conversion: false,
+ Admission: &app.AdmissionCapabilities{
+ Validation: &app.ValidationCapability{
+ Operations: []app.AdmissionOperation{
+ app.AdmissionOperationCreate,
+ app.AdmissionOperationUpdate,
+ },
+ },
+ },
+ Schema: &versionSchemaRepositoryv0alpha1,
+ },
+ },
+ },
+ },
+}
+
+func LocalManifest() app.Manifest {
+ return app.NewEmbeddedManifest(appManifestData)
+}
+
+func RemoteManifest() app.Manifest {
+ return app.NewAPIServerManifest("provisioning")
+}
+
+var kindVersionToGoType = map[string]resource.Kind{
+ "Repository/v0alpha1": v0alpha1.RepositoryKind(),
+}
+
+// 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.
+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
+}
diff --git a/apps/provisioning/kinds/repository.cue b/apps/provisioning/kinds/repository.cue
new file mode 100644
index 00000000000..ba36b4b962f
--- /dev/null
+++ b/apps/provisioning/kinds/repository.cue
@@ -0,0 +1,171 @@
+package repository
+
+repository: {
+ kind: "Repository"
+ pluralName: "Repositories"
+ current: "v0alpha1"
+ validation: {
+ operations: [
+ "CREATE",
+ "UPDATE",
+ ]
+ }
+ versions: {
+ "v0alpha1": {
+ codegen: {
+ ts: {enabled: false}
+ go: {enabled: true}
+ }
+ schema: {
+ #LocalRepositoryConfig: {
+ // Path to the local repository
+ path: string
+ }
+ #GitHubRepositoryConfig: {
+ // The repository URL (e.g. `https://github.com/example/test`).
+ url?: string
+ // The branch to use in the repository.
+ branch: string
+ // Token for accessing the repository. If set, it will be encrypted into encryptedToken, then set to an empty string again.
+ token?: string
+ // Token for accessing the repository, but encrypted. This is not possible to read back to a user decrypted.
+ encryptedToken?: [...string]
+ // Whether we should show dashboard previews for pull requests.
+ // By default, this is false (i.e. we will not create previews).
+ generateDashboardPreviews?: bool
+ // Path is the subdirectory for the Grafana data. If specified, Grafana will ignore anything that is outside this directory in the repository.
+ path?: string
+ }
+ #GitRepositoryConfig: {
+ // The repository URL (e.g. `https://github.com/example/test.git`).
+ url?: string
+ // The branch to use in the repository.
+ branch: string
+ // TokenUser is the user that will be used to access the repository if it's a personal access token.
+ tokenUser?: string
+ // Token for accessing the repository. If set, it will be encrypted into encryptedToken, then set to an empty string again.
+ token?: string
+ // Token for accessing the repository, but encrypted. This is not possible to read back to a user decrypted.
+ encryptedToken?: [...string]
+ // Path is the subdirectory for the Grafana data. If specified, Grafana will ignore anything that is outside this directory in the repository.
+ path?: string
+ }
+ #BitbucketRepositoryConfig: {
+ // The repository URL (e.g. `https://bitbucket.org/example/test`).
+ url?: string
+ // The branch to use in the repository.
+ branch: string
+ // TokenUser is the user that will be used to access the repository if it's a personal access token.
+ tokenUser?: string
+ // Token for accessing the repository. If set, it will be encrypted into encryptedToken, then set to an empty string again.
+ token?: string
+ // Token for accessing the repository, but encrypted. This is not possible to read back to a user decrypted.
+ encryptedToken?: [...string]
+ // Path is the subdirectory for the Grafana data. If specified, Grafana will ignore anything that is outside this directory in the repository.
+ path?: string
+ }
+ #GitLabRepositoryConfig: {
+ // The repository URL (e.g. `https://gitlab.com/example/test`).
+ url?: string
+ // The branch to use in the repository.
+ branch: string
+ // Token for accessing the repository. If set, it will be encrypted into encryptedToken, then set to an empty string again.
+ token?: string
+ // Token for accessing the repository, but encrypted. This is not possible to read back to a user decrypted.
+ encryptedToken?: [...string]
+ // Path is the subdirectory for the Grafana data. If specified, Grafana will ignore anything that is outside this directory in the repository.
+ path?: string
+ }
+ #SyncOptions: {
+ // Enabled must be saved as true before any sync job will run
+ enabled: bool
+ // Where values should be saved
+ target: "unified" | "legacy"
+ // When non-zero, the sync will run periodically
+ intervalSeconds?: int
+ }
+ #HealthStatus: {
+ // When not healthy, requests will not be executed
+ healthy: bool
+ // When the health was checked last time
+ checked?: int
+ // Summary messages (can be shown to users)
+ // Will only be populated when not healthy
+ message?: [...string]
+ }
+ #SyncStatus: {
+ // pending, running, success, error
+ state: "pending" | "running" | "success" | "error"
+ // The ID for the job that ran this sync
+ job?: string
+ // When the sync job started
+ started?: int
+ // When the sync job finished
+ finished?: int
+ // When the next sync check is scheduled
+ scheduled?: int
+ // Summary messages (will be shown to users)
+ message: [...string]
+ // The repository ref when the last successful sync ran
+ lastRef?: string
+ // Incremental synchronization for versioned repositories
+ incremental?: bool
+ }
+ #ResourceCount: {
+ group: string
+ resource: string
+ count: int
+ }
+ #WebhookStatus: {
+ id?: int
+ url?: string
+ secret?: string
+ encryptedSecret?: [...string]
+ subscribedEvents?: [...string]
+ lastEvent?: int
+ }
+ spec: {
+ // The repository display name (shown in the UI)
+ title: string
+ // Repository description
+ description?: string
+ // UI driven Workflow that allow changes to the contends of the repository.
+ // The order is relevant for defining the precedence of the workflows.
+ // When empty, the repository does not support any edits (eg, readonly)
+ workflows?: [...string]
+ // Sync settings -- how values are pulled from the repository into grafana
+ sync: #SyncOptions
+ // The repository type. When selected oneOf the values below should be non-nil
+ type: "local" | "github" | "git" | "bitbucket" | "gitlab"
+ // The repository on the local file system.
+ // Mutually exclusive with local | github.
+ local?: #LocalRepositoryConfig
+ // The repository on GitHub.
+ // Mutually exclusive with local | github | git.
+ github?: #GitHubRepositoryConfig
+ // The repository on Git.
+ // Mutually exclusive with local | github | git.
+ git?: #GitRepositoryConfig
+ // The repository on Bitbucket.
+ // Mutually exclusive with local | github | git.
+ bitbucket?: #BitbucketRepositoryConfig
+ // The repository on GitLab.
+ // Mutually exclusive with local | github | git.
+ gitlab?: #GitLabRepositoryConfig
+ }
+ status: {
+ // The generation of the spec last time reconciliation ran
+ observedGeneration?: int
+ // This will get updated with the current health status (and updated periodically)
+ health: #HealthStatus
+ // Sync information with the last sync information
+ sync: #SyncStatus
+ // The object count when sync last ran
+ stats?: [...#ResourceCount]
+ // Webhook Information (if applicable)
+ webhook?: #WebhookStatus
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/pkg/apis/provisioning/v0alpha1/classic.go b/apps/provisioning/pkg/apis/provisioning/v0alpha1/classic.go
similarity index 100%
rename from pkg/apis/provisioning/v0alpha1/classic.go
rename to apps/provisioning/pkg/apis/provisioning/v0alpha1/classic.go
diff --git a/pkg/apis/provisioning/v0alpha1/doc.go b/apps/provisioning/pkg/apis/provisioning/v0alpha1/doc.go
similarity index 54%
rename from pkg/apis/provisioning/v0alpha1/doc.go
rename to apps/provisioning/pkg/apis/provisioning/v0alpha1/doc.go
index f24a3d11d11..4499de75b13 100644
--- a/pkg/apis/provisioning/v0alpha1/doc.go
+++ b/apps/provisioning/pkg/apis/provisioning/v0alpha1/doc.go
@@ -3,4 +3,4 @@
// +k8s:defaulter-gen=TypeMeta
// +groupName=provisioning.grafana.app
-package v0alpha1 // import "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+package v0alpha1 // import "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
diff --git a/pkg/apis/provisioning/v0alpha1/jobs.go b/apps/provisioning/pkg/apis/provisioning/v0alpha1/jobs.go
similarity index 100%
rename from pkg/apis/provisioning/v0alpha1/jobs.go
rename to apps/provisioning/pkg/apis/provisioning/v0alpha1/jobs.go
diff --git a/pkg/apis/provisioning/v0alpha1/register.go b/apps/provisioning/pkg/apis/provisioning/v0alpha1/register.go
similarity index 100%
rename from pkg/apis/provisioning/v0alpha1/register.go
rename to apps/provisioning/pkg/apis/provisioning/v0alpha1/register.go
diff --git a/pkg/apis/provisioning/v0alpha1/settings.go b/apps/provisioning/pkg/apis/provisioning/v0alpha1/settings.go
similarity index 100%
rename from pkg/apis/provisioning/v0alpha1/settings.go
rename to apps/provisioning/pkg/apis/provisioning/v0alpha1/settings.go
diff --git a/pkg/apis/provisioning/v0alpha1/types.go b/apps/provisioning/pkg/apis/provisioning/v0alpha1/types.go
similarity index 100%
rename from pkg/apis/provisioning/v0alpha1/types.go
rename to apps/provisioning/pkg/apis/provisioning/v0alpha1/types.go
diff --git a/pkg/apis/provisioning/v0alpha1/types_test.go b/apps/provisioning/pkg/apis/provisioning/v0alpha1/types_test.go
similarity index 91%
rename from pkg/apis/provisioning/v0alpha1/types_test.go
rename to apps/provisioning/pkg/apis/provisioning/v0alpha1/types_test.go
index ef8643e2a1e..82421b7d611 100644
--- a/pkg/apis/provisioning/v0alpha1/types_test.go
+++ b/apps/provisioning/pkg/apis/provisioning/v0alpha1/types_test.go
@@ -3,7 +3,7 @@ package v0alpha1_test
import (
"testing"
- "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
)
func TestRepositoryType_IsGit(t *testing.T) {
diff --git a/pkg/apis/provisioning/v0alpha1/zz_generated.deepcopy.go b/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.deepcopy.go
similarity index 100%
rename from pkg/apis/provisioning/v0alpha1/zz_generated.deepcopy.go
rename to apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.deepcopy.go
diff --git a/pkg/apis/provisioning/v0alpha1/zz_generated.defaults.go b/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.defaults.go
similarity index 100%
rename from pkg/apis/provisioning/v0alpha1/zz_generated.defaults.go
rename to apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.defaults.go
diff --git a/pkg/apis/provisioning/v0alpha1/zz_generated.openapi.go b/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.openapi.go
similarity index 82%
rename from pkg/apis/provisioning/v0alpha1/zz_generated.openapi.go
rename to apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.openapi.go
index e6e9e4e9ab5..19911653735 100644
--- a/pkg/apis/provisioning/v0alpha1/zz_generated.openapi.go
+++ b/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.openapi.go
@@ -14,53 +14,53 @@ import (
func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition {
return map[string]common.OpenAPIDefinition{
- "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.Author": schema_pkg_apis_provisioning_v0alpha1_Author(ref),
- "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.BitbucketRepositoryConfig": schema_pkg_apis_provisioning_v0alpha1_BitbucketRepositoryConfig(ref),
- "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.DeleteJobOptions": schema_pkg_apis_provisioning_v0alpha1_DeleteJobOptions(ref),
- "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.ErrorDetails": schema_pkg_apis_provisioning_v0alpha1_ErrorDetails(ref),
- "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.ExportJobOptions": schema_pkg_apis_provisioning_v0alpha1_ExportJobOptions(ref),
- "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.FileItem": schema_pkg_apis_provisioning_v0alpha1_FileItem(ref),
- "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.FileList": schema_pkg_apis_provisioning_v0alpha1_FileList(ref),
- "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.GitHubRepositoryConfig": schema_pkg_apis_provisioning_v0alpha1_GitHubRepositoryConfig(ref),
- "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.GitLabRepositoryConfig": schema_pkg_apis_provisioning_v0alpha1_GitLabRepositoryConfig(ref),
- "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.GitRepositoryConfig": schema_pkg_apis_provisioning_v0alpha1_GitRepositoryConfig(ref),
- "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.HealthStatus": schema_pkg_apis_provisioning_v0alpha1_HealthStatus(ref),
- "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.HistoryItem": schema_pkg_apis_provisioning_v0alpha1_HistoryItem(ref),
- "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.HistoryList": schema_pkg_apis_provisioning_v0alpha1_HistoryList(ref),
- "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.Job": schema_pkg_apis_provisioning_v0alpha1_Job(ref),
- "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.JobList": schema_pkg_apis_provisioning_v0alpha1_JobList(ref),
- "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.JobResourceSummary": schema_pkg_apis_provisioning_v0alpha1_JobResourceSummary(ref),
- "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.JobSpec": schema_pkg_apis_provisioning_v0alpha1_JobSpec(ref),
- "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.JobStatus": schema_pkg_apis_provisioning_v0alpha1_JobStatus(ref),
- "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.LocalRepositoryConfig": schema_pkg_apis_provisioning_v0alpha1_LocalRepositoryConfig(ref),
- "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.ManagerStats": schema_pkg_apis_provisioning_v0alpha1_ManagerStats(ref),
- "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.MigrateJobOptions": schema_pkg_apis_provisioning_v0alpha1_MigrateJobOptions(ref),
- "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.MoveJobOptions": schema_pkg_apis_provisioning_v0alpha1_MoveJobOptions(ref),
- "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.PullRequestJobOptions": schema_pkg_apis_provisioning_v0alpha1_PullRequestJobOptions(ref),
- "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.RefItem": schema_pkg_apis_provisioning_v0alpha1_RefItem(ref),
- "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.RefList": schema_pkg_apis_provisioning_v0alpha1_RefList(ref),
- "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.Repository": schema_pkg_apis_provisioning_v0alpha1_Repository(ref),
- "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.RepositoryList": schema_pkg_apis_provisioning_v0alpha1_RepositoryList(ref),
- "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.RepositorySpec": schema_pkg_apis_provisioning_v0alpha1_RepositorySpec(ref),
- "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.RepositoryStatus": schema_pkg_apis_provisioning_v0alpha1_RepositoryStatus(ref),
- "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.RepositoryView": schema_pkg_apis_provisioning_v0alpha1_RepositoryView(ref),
- "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.RepositoryViewList": schema_pkg_apis_provisioning_v0alpha1_RepositoryViewList(ref),
- "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.ResourceCount": schema_pkg_apis_provisioning_v0alpha1_ResourceCount(ref),
- "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.ResourceList": schema_pkg_apis_provisioning_v0alpha1_ResourceList(ref),
- "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.ResourceListItem": schema_pkg_apis_provisioning_v0alpha1_ResourceListItem(ref),
- "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.ResourceObjects": schema_pkg_apis_provisioning_v0alpha1_ResourceObjects(ref),
- "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.ResourceRef": schema_pkg_apis_provisioning_v0alpha1_ResourceRef(ref),
- "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.ResourceRepositoryInfo": schema_pkg_apis_provisioning_v0alpha1_ResourceRepositoryInfo(ref),
- "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.ResourceStats": schema_pkg_apis_provisioning_v0alpha1_ResourceStats(ref),
- "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.ResourceType": schema_pkg_apis_provisioning_v0alpha1_ResourceType(ref),
- "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.ResourceURLs": schema_pkg_apis_provisioning_v0alpha1_ResourceURLs(ref),
- "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.ResourceWrapper": schema_pkg_apis_provisioning_v0alpha1_ResourceWrapper(ref),
- "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.SyncJobOptions": schema_pkg_apis_provisioning_v0alpha1_SyncJobOptions(ref),
- "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.SyncOptions": schema_pkg_apis_provisioning_v0alpha1_SyncOptions(ref),
- "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.SyncStatus": schema_pkg_apis_provisioning_v0alpha1_SyncStatus(ref),
- "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.TestResults": schema_pkg_apis_provisioning_v0alpha1_TestResults(ref),
- "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.WebhookResponse": schema_pkg_apis_provisioning_v0alpha1_WebhookResponse(ref),
- "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.WebhookStatus": schema_pkg_apis_provisioning_v0alpha1_WebhookStatus(ref),
+ "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.Author": schema_pkg_apis_provisioning_v0alpha1_Author(ref),
+ "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.BitbucketRepositoryConfig": schema_pkg_apis_provisioning_v0alpha1_BitbucketRepositoryConfig(ref),
+ "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.DeleteJobOptions": schema_pkg_apis_provisioning_v0alpha1_DeleteJobOptions(ref),
+ "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ErrorDetails": schema_pkg_apis_provisioning_v0alpha1_ErrorDetails(ref),
+ "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ExportJobOptions": schema_pkg_apis_provisioning_v0alpha1_ExportJobOptions(ref),
+ "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.FileItem": schema_pkg_apis_provisioning_v0alpha1_FileItem(ref),
+ "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.FileList": schema_pkg_apis_provisioning_v0alpha1_FileList(ref),
+ "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.GitHubRepositoryConfig": schema_pkg_apis_provisioning_v0alpha1_GitHubRepositoryConfig(ref),
+ "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.GitLabRepositoryConfig": schema_pkg_apis_provisioning_v0alpha1_GitLabRepositoryConfig(ref),
+ "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.GitRepositoryConfig": schema_pkg_apis_provisioning_v0alpha1_GitRepositoryConfig(ref),
+ "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.HealthStatus": schema_pkg_apis_provisioning_v0alpha1_HealthStatus(ref),
+ "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.HistoryItem": schema_pkg_apis_provisioning_v0alpha1_HistoryItem(ref),
+ "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.HistoryList": schema_pkg_apis_provisioning_v0alpha1_HistoryList(ref),
+ "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.Job": schema_pkg_apis_provisioning_v0alpha1_Job(ref),
+ "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.JobList": schema_pkg_apis_provisioning_v0alpha1_JobList(ref),
+ "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.JobResourceSummary": schema_pkg_apis_provisioning_v0alpha1_JobResourceSummary(ref),
+ "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.JobSpec": schema_pkg_apis_provisioning_v0alpha1_JobSpec(ref),
+ "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.JobStatus": schema_pkg_apis_provisioning_v0alpha1_JobStatus(ref),
+ "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.LocalRepositoryConfig": schema_pkg_apis_provisioning_v0alpha1_LocalRepositoryConfig(ref),
+ "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ManagerStats": schema_pkg_apis_provisioning_v0alpha1_ManagerStats(ref),
+ "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.MigrateJobOptions": schema_pkg_apis_provisioning_v0alpha1_MigrateJobOptions(ref),
+ "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.MoveJobOptions": schema_pkg_apis_provisioning_v0alpha1_MoveJobOptions(ref),
+ "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.PullRequestJobOptions": schema_pkg_apis_provisioning_v0alpha1_PullRequestJobOptions(ref),
+ "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.RefItem": schema_pkg_apis_provisioning_v0alpha1_RefItem(ref),
+ "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.RefList": schema_pkg_apis_provisioning_v0alpha1_RefList(ref),
+ "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.Repository": schema_pkg_apis_provisioning_v0alpha1_Repository(ref),
+ "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.RepositoryList": schema_pkg_apis_provisioning_v0alpha1_RepositoryList(ref),
+ "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.RepositorySpec": schema_pkg_apis_provisioning_v0alpha1_RepositorySpec(ref),
+ "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.RepositoryStatus": schema_pkg_apis_provisioning_v0alpha1_RepositoryStatus(ref),
+ "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.RepositoryView": schema_pkg_apis_provisioning_v0alpha1_RepositoryView(ref),
+ "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.RepositoryViewList": schema_pkg_apis_provisioning_v0alpha1_RepositoryViewList(ref),
+ "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ResourceCount": schema_pkg_apis_provisioning_v0alpha1_ResourceCount(ref),
+ "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ResourceList": schema_pkg_apis_provisioning_v0alpha1_ResourceList(ref),
+ "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ResourceListItem": schema_pkg_apis_provisioning_v0alpha1_ResourceListItem(ref),
+ "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ResourceObjects": schema_pkg_apis_provisioning_v0alpha1_ResourceObjects(ref),
+ "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ResourceRef": schema_pkg_apis_provisioning_v0alpha1_ResourceRef(ref),
+ "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ResourceRepositoryInfo": schema_pkg_apis_provisioning_v0alpha1_ResourceRepositoryInfo(ref),
+ "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ResourceStats": schema_pkg_apis_provisioning_v0alpha1_ResourceStats(ref),
+ "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ResourceType": schema_pkg_apis_provisioning_v0alpha1_ResourceType(ref),
+ "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ResourceURLs": schema_pkg_apis_provisioning_v0alpha1_ResourceURLs(ref),
+ "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ResourceWrapper": schema_pkg_apis_provisioning_v0alpha1_ResourceWrapper(ref),
+ "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.SyncJobOptions": schema_pkg_apis_provisioning_v0alpha1_SyncJobOptions(ref),
+ "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.SyncOptions": schema_pkg_apis_provisioning_v0alpha1_SyncOptions(ref),
+ "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.SyncStatus": schema_pkg_apis_provisioning_v0alpha1_SyncStatus(ref),
+ "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.TestResults": schema_pkg_apis_provisioning_v0alpha1_TestResults(ref),
+ "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.WebhookResponse": schema_pkg_apis_provisioning_v0alpha1_WebhookResponse(ref),
+ "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.WebhookStatus": schema_pkg_apis_provisioning_v0alpha1_WebhookStatus(ref),
}
}
@@ -194,7 +194,7 @@ func schema_pkg_apis_provisioning_v0alpha1_DeleteJobOptions(ref common.Reference
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
- Ref: ref("github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.ResourceRef"),
+ Ref: ref("github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ResourceRef"),
},
},
},
@@ -204,7 +204,7 @@ func schema_pkg_apis_provisioning_v0alpha1_DeleteJobOptions(ref common.Reference
},
},
Dependencies: []string{
- "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.ResourceRef"},
+ "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ResourceRef"},
}
}
@@ -363,7 +363,7 @@ func schema_pkg_apis_provisioning_v0alpha1_FileList(ref common.ReferenceCallback
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
- Ref: ref("github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.FileItem"),
+ Ref: ref("github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.FileItem"),
},
},
},
@@ -374,7 +374,7 @@ func schema_pkg_apis_provisioning_v0alpha1_FileList(ref common.ReferenceCallback
},
},
Dependencies: []string{
- "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.FileItem", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"},
+ "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.FileItem", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"},
}
}
@@ -634,7 +634,7 @@ func schema_pkg_apis_provisioning_v0alpha1_HistoryItem(ref common.ReferenceCallb
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
- Ref: ref("github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.Author"),
+ Ref: ref("github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.Author"),
},
},
},
@@ -652,7 +652,7 @@ func schema_pkg_apis_provisioning_v0alpha1_HistoryItem(ref common.ReferenceCallb
},
},
Dependencies: []string{
- "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.Author"},
+ "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.Author"},
}
}
@@ -695,7 +695,7 @@ func schema_pkg_apis_provisioning_v0alpha1_HistoryList(ref common.ReferenceCallb
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
- Ref: ref("github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.HistoryItem"),
+ Ref: ref("github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.HistoryItem"),
},
},
},
@@ -706,7 +706,7 @@ func schema_pkg_apis_provisioning_v0alpha1_HistoryList(ref common.ReferenceCallb
},
},
Dependencies: []string{
- "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.HistoryItem", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"},
+ "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.HistoryItem", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"},
}
}
@@ -740,20 +740,20 @@ func schema_pkg_apis_provisioning_v0alpha1_Job(ref common.ReferenceCallback) com
"spec": {
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
- Ref: ref("github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.JobSpec"),
+ Ref: ref("github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.JobSpec"),
},
},
"status": {
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
- Ref: ref("github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.JobStatus"),
+ Ref: ref("github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.JobStatus"),
},
},
},
},
},
Dependencies: []string{
- "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.JobSpec", "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.JobStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"},
+ "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.JobSpec", "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.JobStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"},
}
}
@@ -790,7 +790,7 @@ func schema_pkg_apis_provisioning_v0alpha1_JobList(ref common.ReferenceCallback)
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
- Ref: ref("github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.Job"),
+ Ref: ref("github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.Job"),
},
},
},
@@ -801,7 +801,7 @@ func schema_pkg_apis_provisioning_v0alpha1_JobList(ref common.ReferenceCallback)
},
},
Dependencies: []string{
- "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.Job", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"},
+ "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.Job", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"},
}
}
@@ -912,44 +912,44 @@ func schema_pkg_apis_provisioning_v0alpha1_JobSpec(ref common.ReferenceCallback)
"pr": {
SchemaProps: spec.SchemaProps{
Description: "Pull request options",
- Ref: ref("github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.PullRequestJobOptions"),
+ Ref: ref("github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.PullRequestJobOptions"),
},
},
"push": {
SchemaProps: spec.SchemaProps{
Description: "Required when the action is `push`",
- Ref: ref("github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.ExportJobOptions"),
+ Ref: ref("github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ExportJobOptions"),
},
},
"pull": {
SchemaProps: spec.SchemaProps{
Description: "Required when the action is `pull`",
- Ref: ref("github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.SyncJobOptions"),
+ Ref: ref("github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.SyncJobOptions"),
},
},
"migrate": {
SchemaProps: spec.SchemaProps{
Description: "Required when the action is `migrate`",
- Ref: ref("github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.MigrateJobOptions"),
+ Ref: ref("github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.MigrateJobOptions"),
},
},
"delete": {
SchemaProps: spec.SchemaProps{
Description: "Delete when the action is `delete`",
- Ref: ref("github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.DeleteJobOptions"),
+ Ref: ref("github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.DeleteJobOptions"),
},
},
"move": {
SchemaProps: spec.SchemaProps{
Description: "Move when the action is `move`",
- Ref: ref("github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.MoveJobOptions"),
+ Ref: ref("github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.MoveJobOptions"),
},
},
},
},
},
Dependencies: []string{
- "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.DeleteJobOptions", "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.ExportJobOptions", "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.MigrateJobOptions", "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.MoveJobOptions", "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.PullRequestJobOptions", "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.SyncJobOptions"},
+ "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.DeleteJobOptions", "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ExportJobOptions", "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.MigrateJobOptions", "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.MoveJobOptions", "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.PullRequestJobOptions", "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.SyncJobOptions"},
}
}
@@ -1014,7 +1014,7 @@ func schema_pkg_apis_provisioning_v0alpha1_JobStatus(ref common.ReferenceCallbac
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
- Ref: ref("github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.JobResourceSummary"),
+ Ref: ref("github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.JobResourceSummary"),
},
},
},
@@ -1024,7 +1024,7 @@ func schema_pkg_apis_provisioning_v0alpha1_JobStatus(ref common.ReferenceCallbac
},
},
Dependencies: []string{
- "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.JobResourceSummary"},
+ "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.JobResourceSummary"},
}
}
@@ -1074,7 +1074,7 @@ func schema_pkg_apis_provisioning_v0alpha1_ManagerStats(ref common.ReferenceCall
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
- Ref: ref("github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.ResourceCount"),
+ Ref: ref("github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ResourceCount"),
},
},
},
@@ -1085,7 +1085,7 @@ func schema_pkg_apis_provisioning_v0alpha1_ManagerStats(ref common.ReferenceCall
},
},
Dependencies: []string{
- "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.ResourceCount"},
+ "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ResourceCount"},
}
}
@@ -1158,7 +1158,7 @@ func schema_pkg_apis_provisioning_v0alpha1_MoveJobOptions(ref common.ReferenceCa
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
- Ref: ref("github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.ResourceRef"),
+ Ref: ref("github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ResourceRef"),
},
},
},
@@ -1168,7 +1168,7 @@ func schema_pkg_apis_provisioning_v0alpha1_MoveJobOptions(ref common.ReferenceCa
},
},
Dependencies: []string{
- "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.ResourceRef"},
+ "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ResourceRef"},
}
}
@@ -1285,7 +1285,7 @@ func schema_pkg_apis_provisioning_v0alpha1_RefList(ref common.ReferenceCallback)
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
- Ref: ref("github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.RefItem"),
+ Ref: ref("github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.RefItem"),
},
},
},
@@ -1296,7 +1296,7 @@ func schema_pkg_apis_provisioning_v0alpha1_RefList(ref common.ReferenceCallback)
},
},
Dependencies: []string{
- "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.RefItem", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"},
+ "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.RefItem", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"},
}
}
@@ -1330,20 +1330,20 @@ func schema_pkg_apis_provisioning_v0alpha1_Repository(ref common.ReferenceCallba
"spec": {
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
- Ref: ref("github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.RepositorySpec"),
+ Ref: ref("github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.RepositorySpec"),
},
},
"status": {
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
- Ref: ref("github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.RepositoryStatus"),
+ Ref: ref("github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.RepositoryStatus"),
},
},
},
},
},
Dependencies: []string{
- "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.RepositorySpec", "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.RepositoryStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"},
+ "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.RepositorySpec", "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.RepositoryStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"},
}
}
@@ -1385,7 +1385,7 @@ func schema_pkg_apis_provisioning_v0alpha1_RepositoryList(ref common.ReferenceCa
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
- Ref: ref("github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.Repository"),
+ Ref: ref("github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.Repository"),
},
},
},
@@ -1396,7 +1396,7 @@ func schema_pkg_apis_provisioning_v0alpha1_RepositoryList(ref common.ReferenceCa
},
},
Dependencies: []string{
- "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.Repository", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"},
+ "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.Repository", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"},
}
}
@@ -1441,7 +1441,7 @@ func schema_pkg_apis_provisioning_v0alpha1_RepositorySpec(ref common.ReferenceCa
SchemaProps: spec.SchemaProps{
Description: "Sync settings -- how values are pulled from the repository into grafana",
Default: map[string]interface{}{},
- Ref: ref("github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.SyncOptions"),
+ Ref: ref("github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.SyncOptions"),
},
},
"type": {
@@ -1456,31 +1456,31 @@ func schema_pkg_apis_provisioning_v0alpha1_RepositorySpec(ref common.ReferenceCa
"local": {
SchemaProps: spec.SchemaProps{
Description: "The repository on the local file system. Mutually exclusive with local | github.",
- Ref: ref("github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.LocalRepositoryConfig"),
+ Ref: ref("github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.LocalRepositoryConfig"),
},
},
"github": {
SchemaProps: spec.SchemaProps{
Description: "The repository on GitHub. Mutually exclusive with local | github | git.",
- Ref: ref("github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.GitHubRepositoryConfig"),
+ Ref: ref("github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.GitHubRepositoryConfig"),
},
},
"git": {
SchemaProps: spec.SchemaProps{
Description: "The repository on Git. Mutually exclusive with local | github | git.",
- Ref: ref("github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.GitRepositoryConfig"),
+ Ref: ref("github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.GitRepositoryConfig"),
},
},
"bitbucket": {
SchemaProps: spec.SchemaProps{
Description: "The repository on Bitbucket. Mutually exclusive with local | github | git.",
- Ref: ref("github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.BitbucketRepositoryConfig"),
+ Ref: ref("github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.BitbucketRepositoryConfig"),
},
},
"gitlab": {
SchemaProps: spec.SchemaProps{
Description: "The repository on GitLab. Mutually exclusive with local | github | git.",
- Ref: ref("github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.GitLabRepositoryConfig"),
+ Ref: ref("github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.GitLabRepositoryConfig"),
},
},
},
@@ -1488,7 +1488,7 @@ func schema_pkg_apis_provisioning_v0alpha1_RepositorySpec(ref common.ReferenceCa
},
},
Dependencies: []string{
- "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.BitbucketRepositoryConfig", "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.GitHubRepositoryConfig", "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.GitLabRepositoryConfig", "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.GitRepositoryConfig", "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.LocalRepositoryConfig", "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.SyncOptions"},
+ "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.BitbucketRepositoryConfig", "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.GitHubRepositoryConfig", "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.GitLabRepositoryConfig", "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.GitRepositoryConfig", "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.LocalRepositoryConfig", "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.SyncOptions"},
}
}
@@ -1511,14 +1511,14 @@ func schema_pkg_apis_provisioning_v0alpha1_RepositoryStatus(ref common.Reference
SchemaProps: spec.SchemaProps{
Description: "This will get updated with the current health status (and updated periodically)",
Default: map[string]interface{}{},
- Ref: ref("github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.HealthStatus"),
+ Ref: ref("github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.HealthStatus"),
},
},
"sync": {
SchemaProps: spec.SchemaProps{
Description: "Sync information with the last sync information",
Default: map[string]interface{}{},
- Ref: ref("github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.SyncStatus"),
+ Ref: ref("github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.SyncStatus"),
},
},
"stats": {
@@ -1534,7 +1534,7 @@ func schema_pkg_apis_provisioning_v0alpha1_RepositoryStatus(ref common.Reference
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
- Ref: ref("github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.ResourceCount"),
+ Ref: ref("github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ResourceCount"),
},
},
},
@@ -1543,7 +1543,7 @@ func schema_pkg_apis_provisioning_v0alpha1_RepositoryStatus(ref common.Reference
"webhook": {
SchemaProps: spec.SchemaProps{
Description: "Webhook Information (if applicable)",
- Ref: ref("github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.WebhookStatus"),
+ Ref: ref("github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.WebhookStatus"),
},
},
},
@@ -1551,7 +1551,7 @@ func schema_pkg_apis_provisioning_v0alpha1_RepositoryStatus(ref common.Reference
},
},
Dependencies: []string{
- "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.HealthStatus", "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.ResourceCount", "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.SyncStatus", "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.WebhookStatus"},
+ "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.HealthStatus", "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ResourceCount", "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.SyncStatus", "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.WebhookStatus"},
}
}
@@ -1681,7 +1681,7 @@ func schema_pkg_apis_provisioning_v0alpha1_RepositoryViewList(ref common.Referen
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
- Ref: ref("github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.RepositoryView"),
+ Ref: ref("github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.RepositoryView"),
},
},
},
@@ -1692,7 +1692,7 @@ func schema_pkg_apis_provisioning_v0alpha1_RepositoryViewList(ref common.Referen
},
},
Dependencies: []string{
- "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.RepositoryView"},
+ "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.RepositoryView"},
}
}
@@ -1769,7 +1769,7 @@ func schema_pkg_apis_provisioning_v0alpha1_ResourceList(ref common.ReferenceCall
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
- Ref: ref("github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.ResourceListItem"),
+ Ref: ref("github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ResourceListItem"),
},
},
},
@@ -1780,7 +1780,7 @@ func schema_pkg_apis_provisioning_v0alpha1_ResourceList(ref common.ReferenceCall
},
},
Dependencies: []string{
- "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.ResourceListItem", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"},
+ "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ResourceListItem", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"},
}
}
@@ -1861,7 +1861,7 @@ func schema_pkg_apis_provisioning_v0alpha1_ResourceObjects(ref common.ReferenceC
SchemaProps: spec.SchemaProps{
Description: "The identified type for this object",
Default: map[string]interface{}{},
- Ref: ref("github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.ResourceType"),
+ Ref: ref("github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ResourceType"),
},
},
"file": {
@@ -1901,7 +1901,7 @@ func schema_pkg_apis_provisioning_v0alpha1_ResourceObjects(ref common.ReferenceC
},
},
Dependencies: []string{
- "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1.Unstructured", "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.ResourceType"},
+ "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ResourceType", "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1.Unstructured"},
}
}
@@ -2024,7 +2024,7 @@ func schema_pkg_apis_provisioning_v0alpha1_ResourceStats(ref common.ReferenceCal
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
- Ref: ref("github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.ResourceCount"),
+ Ref: ref("github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ResourceCount"),
},
},
},
@@ -2043,7 +2043,7 @@ func schema_pkg_apis_provisioning_v0alpha1_ResourceStats(ref common.ReferenceCal
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
- Ref: ref("github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.ManagerStats"),
+ Ref: ref("github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ManagerStats"),
},
},
},
@@ -2053,7 +2053,7 @@ func schema_pkg_apis_provisioning_v0alpha1_ResourceStats(ref common.ReferenceCal
},
},
Dependencies: []string{
- "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.ManagerStats", "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.ResourceCount", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"},
+ "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ManagerStats", "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ResourceCount", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"},
}
}
@@ -2187,13 +2187,13 @@ func schema_pkg_apis_provisioning_v0alpha1_ResourceWrapper(ref common.ReferenceC
SchemaProps: spec.SchemaProps{
Description: "Basic repository info",
Default: map[string]interface{}{},
- Ref: ref("github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.ResourceRepositoryInfo"),
+ Ref: ref("github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ResourceRepositoryInfo"),
},
},
"urls": {
SchemaProps: spec.SchemaProps{
Description: "Typed links for this file (only supported by external systems, github etc)",
- Ref: ref("github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.ResourceURLs"),
+ Ref: ref("github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ResourceURLs"),
},
},
"timestamp": {
@@ -2206,7 +2206,7 @@ func schema_pkg_apis_provisioning_v0alpha1_ResourceWrapper(ref common.ReferenceC
SchemaProps: spec.SchemaProps{
Description: "Different flavors of the same object",
Default: map[string]interface{}{},
- Ref: ref("github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.ResourceObjects"),
+ Ref: ref("github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ResourceObjects"),
},
},
"errors": {
@@ -2234,7 +2234,7 @@ func schema_pkg_apis_provisioning_v0alpha1_ResourceWrapper(ref common.ReferenceC
},
},
Dependencies: []string{
- "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.ResourceObjects", "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.ResourceRepositoryInfo", "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.ResourceURLs", "k8s.io/apimachinery/pkg/apis/meta/v1.Time"},
+ "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ResourceObjects", "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ResourceRepositoryInfo", "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ResourceURLs", "k8s.io/apimachinery/pkg/apis/meta/v1.Time"},
}
}
@@ -2425,7 +2425,7 @@ func schema_pkg_apis_provisioning_v0alpha1_TestResults(ref common.ReferenceCallb
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
- Ref: ref("github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.ErrorDetails"),
+ Ref: ref("github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ErrorDetails"),
},
},
},
@@ -2436,7 +2436,7 @@ func schema_pkg_apis_provisioning_v0alpha1_TestResults(ref common.ReferenceCallb
},
},
Dependencies: []string{
- "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.ErrorDetails"},
+ "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ErrorDetails"},
}
}
@@ -2477,14 +2477,14 @@ func schema_pkg_apis_provisioning_v0alpha1_WebhookResponse(ref common.ReferenceC
"job": {
SchemaProps: spec.SchemaProps{
Description: "Jobs to be processed When the response is 202 (Accepted) the queued jobs will be returned",
- Ref: ref("github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.JobSpec"),
+ Ref: ref("github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.JobSpec"),
},
},
},
},
},
Dependencies: []string{
- "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.JobSpec"},
+ "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.JobSpec"},
}
}
diff --git a/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.openapi_violation_exceptions.list b/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.openapi_violation_exceptions.list
new file mode 100644
index 00000000000..a92f95837a2
--- /dev/null
+++ b/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.openapi_violation_exceptions.list
@@ -0,0 +1,26 @@
+API rule violation: list_type_missing,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,DeleteJobOptions,Paths
+API rule violation: list_type_missing,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,DeleteJobOptions,Resources
+API rule violation: list_type_missing,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,FileList,Items
+API rule violation: list_type_missing,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,HistoryList,Items
+API rule violation: list_type_missing,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,JobResourceSummary,Errors
+API rule violation: list_type_missing,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,JobStatus,Errors
+API rule violation: list_type_missing,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,JobStatus,Summary
+API rule violation: list_type_missing,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,ManagerStats,Stats
+API rule violation: list_type_missing,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,MoveJobOptions,Paths
+API rule violation: list_type_missing,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,MoveJobOptions,Resources
+API rule violation: list_type_missing,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,RefList,Items
+API rule violation: list_type_missing,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,RepositoryList,Items
+API rule violation: list_type_missing,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,RepositorySpec,Workflows
+API rule violation: list_type_missing,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,RepositoryView,Workflows
+API rule violation: list_type_missing,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,RepositoryViewList,AvailableRepositoryTypes
+API rule violation: list_type_missing,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,RepositoryViewList,Items
+API rule violation: list_type_missing,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,ResourceList,Items
+API rule violation: list_type_missing,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,TestResults,Errors
+API rule violation: list_type_missing,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,WebhookStatus,SubscribedEvents
+API rule violation: names_match,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,JobSpec,PullRequest
+API rule violation: names_match,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,ManagerStats,Identity
+API rule violation: names_match,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,RepositorySpec,GitHub
+API rule violation: names_match,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,RepositorySpec,GitLab
+API rule violation: names_match,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,ResourceWrapper,URLs
+API rule violation: names_match,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,SyncStatus,JobID
+API rule violation: names_match,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,WebhookResponse,Message
diff --git a/apps/provisioning/pkg/generated/applyconfiguration/internal/internal.go b/apps/provisioning/pkg/generated/applyconfiguration/internal/internal.go
new file mode 100644
index 00000000000..ddd9f734c85
--- /dev/null
+++ b/apps/provisioning/pkg/generated/applyconfiguration/internal/internal.go
@@ -0,0 +1,48 @@
+// SPDX-License-Identifier: AGPL-3.0-only
+
+// Code generated by applyconfiguration-gen. DO NOT EDIT.
+
+package internal
+
+import (
+ fmt "fmt"
+ sync "sync"
+
+ typed "sigs.k8s.io/structured-merge-diff/v4/typed"
+)
+
+func Parser() *typed.Parser {
+ parserOnce.Do(func() {
+ var err error
+ parser, err = typed.NewParser(schemaYAML)
+ if err != nil {
+ panic(fmt.Sprintf("Failed to parse schema: %v", err))
+ }
+ })
+ return parser
+}
+
+var parserOnce sync.Once
+var parser *typed.Parser
+var schemaYAML = typed.YAMLObject(`types:
+- name: __untyped_atomic_
+ scalar: untyped
+ list:
+ elementType:
+ namedType: __untyped_atomic_
+ elementRelationship: atomic
+ map:
+ elementType:
+ namedType: __untyped_atomic_
+ elementRelationship: atomic
+- name: __untyped_deduced_
+ scalar: untyped
+ list:
+ elementType:
+ namedType: __untyped_atomic_
+ elementRelationship: atomic
+ map:
+ elementType:
+ namedType: __untyped_deduced_
+ elementRelationship: separable
+`)
diff --git a/pkg/generated/applyconfiguration/provisioning/v0alpha1/bitbucketrepositoryconfig.go b/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/bitbucketrepositoryconfig.go
similarity index 100%
rename from pkg/generated/applyconfiguration/provisioning/v0alpha1/bitbucketrepositoryconfig.go
rename to apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/bitbucketrepositoryconfig.go
diff --git a/pkg/generated/applyconfiguration/provisioning/v0alpha1/githubrepositoryconfig.go b/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/githubrepositoryconfig.go
similarity index 100%
rename from pkg/generated/applyconfiguration/provisioning/v0alpha1/githubrepositoryconfig.go
rename to apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/githubrepositoryconfig.go
diff --git a/pkg/generated/applyconfiguration/provisioning/v0alpha1/gitlabrepositoryconfig.go b/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/gitlabrepositoryconfig.go
similarity index 100%
rename from pkg/generated/applyconfiguration/provisioning/v0alpha1/gitlabrepositoryconfig.go
rename to apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/gitlabrepositoryconfig.go
diff --git a/pkg/generated/applyconfiguration/provisioning/v0alpha1/gitrepositoryconfig.go b/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/gitrepositoryconfig.go
similarity index 100%
rename from pkg/generated/applyconfiguration/provisioning/v0alpha1/gitrepositoryconfig.go
rename to apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/gitrepositoryconfig.go
diff --git a/pkg/generated/applyconfiguration/provisioning/v0alpha1/healthstatus.go b/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/healthstatus.go
similarity index 100%
rename from pkg/generated/applyconfiguration/provisioning/v0alpha1/healthstatus.go
rename to apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/healthstatus.go
diff --git a/pkg/generated/applyconfiguration/provisioning/v0alpha1/localrepositoryconfig.go b/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/localrepositoryconfig.go
similarity index 100%
rename from pkg/generated/applyconfiguration/provisioning/v0alpha1/localrepositoryconfig.go
rename to apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/localrepositoryconfig.go
diff --git a/pkg/generated/applyconfiguration/provisioning/v0alpha1/repository.go b/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/repository.go
similarity index 100%
rename from pkg/generated/applyconfiguration/provisioning/v0alpha1/repository.go
rename to apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/repository.go
diff --git a/pkg/generated/applyconfiguration/provisioning/v0alpha1/repositoryspec.go b/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/repositoryspec.go
similarity index 98%
rename from pkg/generated/applyconfiguration/provisioning/v0alpha1/repositoryspec.go
rename to apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/repositoryspec.go
index 13d9e45d582..6fff6f2de42 100644
--- a/pkg/generated/applyconfiguration/provisioning/v0alpha1/repositoryspec.go
+++ b/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/repositoryspec.go
@@ -5,7 +5,7 @@
package v0alpha1
import (
- provisioningv0alpha1 "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ provisioningv0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
)
// RepositorySpecApplyConfiguration represents a declarative configuration of the RepositorySpec type for use
diff --git a/pkg/generated/applyconfiguration/provisioning/v0alpha1/repositorystatus.go b/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/repositorystatus.go
similarity index 100%
rename from pkg/generated/applyconfiguration/provisioning/v0alpha1/repositorystatus.go
rename to apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/repositorystatus.go
diff --git a/pkg/generated/applyconfiguration/provisioning/v0alpha1/resourcecount.go b/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/resourcecount.go
similarity index 100%
rename from pkg/generated/applyconfiguration/provisioning/v0alpha1/resourcecount.go
rename to apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/resourcecount.go
diff --git a/pkg/generated/applyconfiguration/provisioning/v0alpha1/syncoptions.go b/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/syncoptions.go
similarity index 95%
rename from pkg/generated/applyconfiguration/provisioning/v0alpha1/syncoptions.go
rename to apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/syncoptions.go
index 837b071930f..05e7396e536 100644
--- a/pkg/generated/applyconfiguration/provisioning/v0alpha1/syncoptions.go
+++ b/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/syncoptions.go
@@ -5,7 +5,7 @@
package v0alpha1
import (
- provisioningv0alpha1 "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ provisioningv0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
)
// SyncOptionsApplyConfiguration represents a declarative configuration of the SyncOptions type for use
diff --git a/pkg/generated/applyconfiguration/provisioning/v0alpha1/syncstatus.go b/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/syncstatus.go
similarity index 97%
rename from pkg/generated/applyconfiguration/provisioning/v0alpha1/syncstatus.go
rename to apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/syncstatus.go
index 6b8f5abbac1..408e452f9a1 100644
--- a/pkg/generated/applyconfiguration/provisioning/v0alpha1/syncstatus.go
+++ b/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/syncstatus.go
@@ -5,7 +5,7 @@
package v0alpha1
import (
- provisioningv0alpha1 "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ provisioningv0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
)
// SyncStatusApplyConfiguration represents a declarative configuration of the SyncStatus type for use
diff --git a/pkg/generated/applyconfiguration/provisioning/v0alpha1/webhookstatus.go b/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/webhookstatus.go
similarity index 100%
rename from pkg/generated/applyconfiguration/provisioning/v0alpha1/webhookstatus.go
rename to apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/webhookstatus.go
diff --git a/apps/provisioning/pkg/generated/applyconfiguration/utils.go b/apps/provisioning/pkg/generated/applyconfiguration/utils.go
new file mode 100644
index 00000000000..29392ef2f2a
--- /dev/null
+++ b/apps/provisioning/pkg/generated/applyconfiguration/utils.go
@@ -0,0 +1,54 @@
+// SPDX-License-Identifier: AGPL-3.0-only
+
+// Code generated by applyconfiguration-gen. DO NOT EDIT.
+
+package applyconfiguration
+
+import (
+ v0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
+ internal "github.com/grafana/grafana/apps/provisioning/pkg/generated/applyconfiguration/internal"
+ provisioningv0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1"
+ runtime "k8s.io/apimachinery/pkg/runtime"
+ schema "k8s.io/apimachinery/pkg/runtime/schema"
+ testing "k8s.io/client-go/testing"
+)
+
+// ForKind returns an apply configuration type for the given GroupVersionKind, or nil if no
+// apply configuration type exists for the given GroupVersionKind.
+func ForKind(kind schema.GroupVersionKind) interface{} {
+ switch kind {
+ // Group=provisioning.grafana.app, Version=v0alpha1
+ case v0alpha1.SchemeGroupVersion.WithKind("BitbucketRepositoryConfig"):
+ return &provisioningv0alpha1.BitbucketRepositoryConfigApplyConfiguration{}
+ case v0alpha1.SchemeGroupVersion.WithKind("GitHubRepositoryConfig"):
+ return &provisioningv0alpha1.GitHubRepositoryConfigApplyConfiguration{}
+ case v0alpha1.SchemeGroupVersion.WithKind("GitLabRepositoryConfig"):
+ return &provisioningv0alpha1.GitLabRepositoryConfigApplyConfiguration{}
+ case v0alpha1.SchemeGroupVersion.WithKind("GitRepositoryConfig"):
+ return &provisioningv0alpha1.GitRepositoryConfigApplyConfiguration{}
+ case v0alpha1.SchemeGroupVersion.WithKind("HealthStatus"):
+ return &provisioningv0alpha1.HealthStatusApplyConfiguration{}
+ case v0alpha1.SchemeGroupVersion.WithKind("LocalRepositoryConfig"):
+ return &provisioningv0alpha1.LocalRepositoryConfigApplyConfiguration{}
+ case v0alpha1.SchemeGroupVersion.WithKind("Repository"):
+ return &provisioningv0alpha1.RepositoryApplyConfiguration{}
+ case v0alpha1.SchemeGroupVersion.WithKind("RepositorySpec"):
+ return &provisioningv0alpha1.RepositorySpecApplyConfiguration{}
+ case v0alpha1.SchemeGroupVersion.WithKind("RepositoryStatus"):
+ return &provisioningv0alpha1.RepositoryStatusApplyConfiguration{}
+ case v0alpha1.SchemeGroupVersion.WithKind("ResourceCount"):
+ return &provisioningv0alpha1.ResourceCountApplyConfiguration{}
+ case v0alpha1.SchemeGroupVersion.WithKind("SyncOptions"):
+ return &provisioningv0alpha1.SyncOptionsApplyConfiguration{}
+ case v0alpha1.SchemeGroupVersion.WithKind("SyncStatus"):
+ return &provisioningv0alpha1.SyncStatusApplyConfiguration{}
+ case v0alpha1.SchemeGroupVersion.WithKind("WebhookStatus"):
+ return &provisioningv0alpha1.WebhookStatusApplyConfiguration{}
+
+ }
+ return nil
+}
+
+func NewTypeConverter(scheme *runtime.Scheme) *testing.TypeConverter {
+ return &testing.TypeConverter{Scheme: scheme, TypeResolver: internal.Parser()}
+}
diff --git a/apps/provisioning/pkg/generated/clientset/versioned/clientset.go b/apps/provisioning/pkg/generated/clientset/versioned/clientset.go
new file mode 100644
index 00000000000..f38fb19e56e
--- /dev/null
+++ b/apps/provisioning/pkg/generated/clientset/versioned/clientset.go
@@ -0,0 +1,106 @@
+// SPDX-License-Identifier: AGPL-3.0-only
+
+// Code generated by client-gen. DO NOT EDIT.
+
+package versioned
+
+import (
+ fmt "fmt"
+ http "net/http"
+
+ provisioningv0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1"
+ discovery "k8s.io/client-go/discovery"
+ rest "k8s.io/client-go/rest"
+ flowcontrol "k8s.io/client-go/util/flowcontrol"
+)
+
+type Interface interface {
+ Discovery() discovery.DiscoveryInterface
+ ProvisioningV0alpha1() provisioningv0alpha1.ProvisioningV0alpha1Interface
+}
+
+// Clientset contains the clients for groups.
+type Clientset struct {
+ *discovery.DiscoveryClient
+ provisioningV0alpha1 *provisioningv0alpha1.ProvisioningV0alpha1Client
+}
+
+// ProvisioningV0alpha1 retrieves the ProvisioningV0alpha1Client
+func (c *Clientset) ProvisioningV0alpha1() provisioningv0alpha1.ProvisioningV0alpha1Interface {
+ return c.provisioningV0alpha1
+}
+
+// Discovery retrieves the DiscoveryClient
+func (c *Clientset) Discovery() discovery.DiscoveryInterface {
+ if c == nil {
+ return nil
+ }
+ return c.DiscoveryClient
+}
+
+// NewForConfig creates a new Clientset for the given config.
+// If config's RateLimiter is not set and QPS and Burst are acceptable,
+// NewForConfig will generate a rate-limiter in configShallowCopy.
+// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient),
+// where httpClient was generated with rest.HTTPClientFor(c).
+func NewForConfig(c *rest.Config) (*Clientset, error) {
+ configShallowCopy := *c
+
+ if configShallowCopy.UserAgent == "" {
+ configShallowCopy.UserAgent = rest.DefaultKubernetesUserAgent()
+ }
+
+ // share the transport between all clients
+ httpClient, err := rest.HTTPClientFor(&configShallowCopy)
+ if err != nil {
+ return nil, err
+ }
+
+ return NewForConfigAndClient(&configShallowCopy, httpClient)
+}
+
+// NewForConfigAndClient creates a new Clientset for the given config and http client.
+// Note the http client provided takes precedence over the configured transport values.
+// If config's RateLimiter is not set and QPS and Burst are acceptable,
+// NewForConfigAndClient will generate a rate-limiter in configShallowCopy.
+func NewForConfigAndClient(c *rest.Config, httpClient *http.Client) (*Clientset, error) {
+ configShallowCopy := *c
+ if configShallowCopy.RateLimiter == nil && configShallowCopy.QPS > 0 {
+ if configShallowCopy.Burst <= 0 {
+ return nil, fmt.Errorf("burst is required to be greater than 0 when RateLimiter is not set and QPS is set to greater than 0")
+ }
+ configShallowCopy.RateLimiter = flowcontrol.NewTokenBucketRateLimiter(configShallowCopy.QPS, configShallowCopy.Burst)
+ }
+
+ var cs Clientset
+ var err error
+ cs.provisioningV0alpha1, err = provisioningv0alpha1.NewForConfigAndClient(&configShallowCopy, httpClient)
+ if err != nil {
+ return nil, err
+ }
+
+ cs.DiscoveryClient, err = discovery.NewDiscoveryClientForConfigAndClient(&configShallowCopy, httpClient)
+ if err != nil {
+ return nil, err
+ }
+ return &cs, nil
+}
+
+// NewForConfigOrDie creates a new Clientset for the given config and
+// panics if there is an error in the config.
+func NewForConfigOrDie(c *rest.Config) *Clientset {
+ cs, err := NewForConfig(c)
+ if err != nil {
+ panic(err)
+ }
+ return cs
+}
+
+// New creates a new Clientset for the given RESTClient.
+func New(c rest.Interface) *Clientset {
+ var cs Clientset
+ cs.provisioningV0alpha1 = provisioningv0alpha1.New(c)
+
+ cs.DiscoveryClient = discovery.NewDiscoveryClient(c)
+ return &cs
+}
diff --git a/apps/provisioning/pkg/generated/clientset/versioned/fake/clientset_generated.go b/apps/provisioning/pkg/generated/clientset/versioned/fake/clientset_generated.go
new file mode 100644
index 00000000000..bcb742b634e
--- /dev/null
+++ b/apps/provisioning/pkg/generated/clientset/versioned/fake/clientset_generated.go
@@ -0,0 +1,117 @@
+// SPDX-License-Identifier: AGPL-3.0-only
+
+// Code generated by client-gen. DO NOT EDIT.
+
+package fake
+
+import (
+ applyconfiguration "github.com/grafana/grafana/apps/provisioning/pkg/generated/applyconfiguration"
+ clientset "github.com/grafana/grafana/apps/provisioning/pkg/generated/clientset/versioned"
+ provisioningv0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1"
+ fakeprovisioningv0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1/fake"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/runtime"
+ "k8s.io/apimachinery/pkg/watch"
+ "k8s.io/client-go/discovery"
+ fakediscovery "k8s.io/client-go/discovery/fake"
+ "k8s.io/client-go/testing"
+)
+
+// NewSimpleClientset returns a clientset that will respond with the provided objects.
+// It's backed by a very simple object tracker that processes creates, updates and deletions as-is,
+// without applying any field management, validations and/or defaults. It shouldn't be considered a replacement
+// for a real clientset and is mostly useful in simple unit tests.
+//
+// DEPRECATED: NewClientset replaces this with support for field management, which significantly improves
+// server side apply testing. NewClientset is only available when apply configurations are generated (e.g.
+// via --with-applyconfig).
+func NewSimpleClientset(objects ...runtime.Object) *Clientset {
+ o := testing.NewObjectTracker(scheme, codecs.UniversalDecoder())
+ for _, obj := range objects {
+ if err := o.Add(obj); err != nil {
+ panic(err)
+ }
+ }
+
+ cs := &Clientset{tracker: o}
+ cs.discovery = &fakediscovery.FakeDiscovery{Fake: &cs.Fake}
+ cs.AddReactor("*", "*", testing.ObjectReaction(o))
+ cs.AddWatchReactor("*", func(action testing.Action) (handled bool, ret watch.Interface, err error) {
+ var opts metav1.ListOptions
+ if watchActcion, ok := action.(testing.WatchActionImpl); ok {
+ opts = watchActcion.ListOptions
+ }
+ gvr := action.GetResource()
+ ns := action.GetNamespace()
+ watch, err := o.Watch(gvr, ns, opts)
+ if err != nil {
+ return false, nil, err
+ }
+ return true, watch, nil
+ })
+
+ return cs
+}
+
+// Clientset implements clientset.Interface. Meant to be embedded into a
+// struct to get a default implementation. This makes faking out just the method
+// you want to test easier.
+type Clientset struct {
+ testing.Fake
+ discovery *fakediscovery.FakeDiscovery
+ tracker testing.ObjectTracker
+}
+
+func (c *Clientset) Discovery() discovery.DiscoveryInterface {
+ return c.discovery
+}
+
+func (c *Clientset) Tracker() testing.ObjectTracker {
+ return c.tracker
+}
+
+// NewClientset returns a clientset that will respond with the provided objects.
+// It's backed by a very simple object tracker that processes creates, updates and deletions as-is,
+// without applying any validations and/or defaults. It shouldn't be considered a replacement
+// for a real clientset and is mostly useful in simple unit tests.
+func NewClientset(objects ...runtime.Object) *Clientset {
+ o := testing.NewFieldManagedObjectTracker(
+ scheme,
+ codecs.UniversalDecoder(),
+ applyconfiguration.NewTypeConverter(scheme),
+ )
+ for _, obj := range objects {
+ if err := o.Add(obj); err != nil {
+ panic(err)
+ }
+ }
+
+ cs := &Clientset{tracker: o}
+ cs.discovery = &fakediscovery.FakeDiscovery{Fake: &cs.Fake}
+ cs.AddReactor("*", "*", testing.ObjectReaction(o))
+ cs.AddWatchReactor("*", func(action testing.Action) (handled bool, ret watch.Interface, err error) {
+ var opts metav1.ListOptions
+ if watchActcion, ok := action.(testing.WatchActionImpl); ok {
+ opts = watchActcion.ListOptions
+ }
+ gvr := action.GetResource()
+ ns := action.GetNamespace()
+ watch, err := o.Watch(gvr, ns, opts)
+ if err != nil {
+ return false, nil, err
+ }
+ return true, watch, nil
+ })
+
+ return cs
+}
+
+var (
+ _ clientset.Interface = &Clientset{}
+ _ testing.FakeClient = &Clientset{}
+)
+
+// ProvisioningV0alpha1 retrieves the ProvisioningV0alpha1Client
+func (c *Clientset) ProvisioningV0alpha1() provisioningv0alpha1.ProvisioningV0alpha1Interface {
+ return &fakeprovisioningv0alpha1.FakeProvisioningV0alpha1{Fake: &c.Fake}
+}
diff --git a/apps/provisioning/pkg/generated/clientset/versioned/fake/doc.go b/apps/provisioning/pkg/generated/clientset/versioned/fake/doc.go
new file mode 100644
index 00000000000..bc6b017db1b
--- /dev/null
+++ b/apps/provisioning/pkg/generated/clientset/versioned/fake/doc.go
@@ -0,0 +1,6 @@
+// SPDX-License-Identifier: AGPL-3.0-only
+
+// Code generated by client-gen. DO NOT EDIT.
+
+// This package has the automatically generated fake clientset.
+package fake
diff --git a/apps/provisioning/pkg/generated/clientset/versioned/fake/register.go b/apps/provisioning/pkg/generated/clientset/versioned/fake/register.go
new file mode 100644
index 00000000000..840c9e7813b
--- /dev/null
+++ b/apps/provisioning/pkg/generated/clientset/versioned/fake/register.go
@@ -0,0 +1,42 @@
+// SPDX-License-Identifier: AGPL-3.0-only
+
+// Code generated by client-gen. DO NOT EDIT.
+
+package fake
+
+import (
+ provisioningv0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
+ v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ runtime "k8s.io/apimachinery/pkg/runtime"
+ schema "k8s.io/apimachinery/pkg/runtime/schema"
+ serializer "k8s.io/apimachinery/pkg/runtime/serializer"
+ utilruntime "k8s.io/apimachinery/pkg/util/runtime"
+)
+
+var scheme = runtime.NewScheme()
+var codecs = serializer.NewCodecFactory(scheme)
+
+var localSchemeBuilder = runtime.SchemeBuilder{
+ provisioningv0alpha1.AddToScheme,
+}
+
+// AddToScheme adds all types of this clientset into the given scheme. This allows composition
+// of clientsets, like in:
+//
+// import (
+// "k8s.io/client-go/kubernetes"
+// clientsetscheme "k8s.io/client-go/kubernetes/scheme"
+// aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme"
+// )
+//
+// kclientset, _ := kubernetes.NewForConfig(c)
+// _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme)
+//
+// After this, RawExtensions in Kubernetes types will serialize kube-aggregator types
+// correctly.
+var AddToScheme = localSchemeBuilder.AddToScheme
+
+func init() {
+ v1.AddToGroupVersion(scheme, schema.GroupVersion{Version: "v1"})
+ utilruntime.Must(AddToScheme(scheme))
+}
diff --git a/apps/provisioning/pkg/generated/clientset/versioned/scheme/doc.go b/apps/provisioning/pkg/generated/clientset/versioned/scheme/doc.go
new file mode 100644
index 00000000000..b69e1aef906
--- /dev/null
+++ b/apps/provisioning/pkg/generated/clientset/versioned/scheme/doc.go
@@ -0,0 +1,6 @@
+// SPDX-License-Identifier: AGPL-3.0-only
+
+// Code generated by client-gen. DO NOT EDIT.
+
+// This package contains the scheme of the automatically generated clientset.
+package scheme
diff --git a/apps/provisioning/pkg/generated/clientset/versioned/scheme/register.go b/apps/provisioning/pkg/generated/clientset/versioned/scheme/register.go
new file mode 100644
index 00000000000..133c9b3f74f
--- /dev/null
+++ b/apps/provisioning/pkg/generated/clientset/versioned/scheme/register.go
@@ -0,0 +1,42 @@
+// SPDX-License-Identifier: AGPL-3.0-only
+
+// Code generated by client-gen. DO NOT EDIT.
+
+package scheme
+
+import (
+ provisioningv0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
+ v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ runtime "k8s.io/apimachinery/pkg/runtime"
+ schema "k8s.io/apimachinery/pkg/runtime/schema"
+ serializer "k8s.io/apimachinery/pkg/runtime/serializer"
+ utilruntime "k8s.io/apimachinery/pkg/util/runtime"
+)
+
+var Scheme = runtime.NewScheme()
+var Codecs = serializer.NewCodecFactory(Scheme)
+var ParameterCodec = runtime.NewParameterCodec(Scheme)
+var localSchemeBuilder = runtime.SchemeBuilder{
+ provisioningv0alpha1.AddToScheme,
+}
+
+// AddToScheme adds all types of this clientset into the given scheme. This allows composition
+// of clientsets, like in:
+//
+// import (
+// "k8s.io/client-go/kubernetes"
+// clientsetscheme "k8s.io/client-go/kubernetes/scheme"
+// aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme"
+// )
+//
+// kclientset, _ := kubernetes.NewForConfig(c)
+// _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme)
+//
+// After this, RawExtensions in Kubernetes types will serialize kube-aggregator types
+// correctly.
+var AddToScheme = localSchemeBuilder.AddToScheme
+
+func init() {
+ v1.AddToGroupVersion(Scheme, schema.GroupVersion{Version: "v1"})
+ utilruntime.Must(AddToScheme(Scheme))
+}
diff --git a/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1/doc.go b/apps/provisioning/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1/doc.go
similarity index 100%
rename from pkg/generated/clientset/versioned/typed/provisioning/v0alpha1/doc.go
rename to apps/provisioning/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1/doc.go
diff --git a/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1/fake/doc.go b/apps/provisioning/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1/fake/doc.go
similarity index 100%
rename from pkg/generated/clientset/versioned/typed/provisioning/v0alpha1/fake/doc.go
rename to apps/provisioning/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1/fake/doc.go
diff --git a/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1/fake/fake_provisioning_client.go b/apps/provisioning/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1/fake/fake_provisioning_client.go
similarity index 83%
rename from pkg/generated/clientset/versioned/typed/provisioning/v0alpha1/fake/fake_provisioning_client.go
rename to apps/provisioning/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1/fake/fake_provisioning_client.go
index eb97df6de39..19a9c803579 100644
--- a/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1/fake/fake_provisioning_client.go
+++ b/apps/provisioning/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1/fake/fake_provisioning_client.go
@@ -5,7 +5,7 @@
package fake
import (
- v0alpha1 "github.com/grafana/grafana/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1"
+ v0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1"
rest "k8s.io/client-go/rest"
testing "k8s.io/client-go/testing"
)
diff --git a/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1/fake/fake_repository.go b/apps/provisioning/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1/fake/fake_repository.go
similarity index 79%
rename from pkg/generated/clientset/versioned/typed/provisioning/v0alpha1/fake/fake_repository.go
rename to apps/provisioning/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1/fake/fake_repository.go
index 3991d3c4ea8..15d0d1bf749 100644
--- a/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1/fake/fake_repository.go
+++ b/apps/provisioning/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1/fake/fake_repository.go
@@ -5,9 +5,9 @@
package fake
import (
- v0alpha1 "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
- provisioningv0alpha1 "github.com/grafana/grafana/pkg/generated/applyconfiguration/provisioning/v0alpha1"
- typedprovisioningv0alpha1 "github.com/grafana/grafana/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1"
+ v0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
+ provisioningv0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1"
+ typedprovisioningv0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1"
gentype "k8s.io/client-go/gentype"
)
diff --git a/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1/generated_expansion.go b/apps/provisioning/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1/generated_expansion.go
similarity index 100%
rename from pkg/generated/clientset/versioned/typed/provisioning/v0alpha1/generated_expansion.go
rename to apps/provisioning/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1/generated_expansion.go
diff --git a/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1/provisioning_client.go b/apps/provisioning/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1/provisioning_client.go
similarity index 92%
rename from pkg/generated/clientset/versioned/typed/provisioning/v0alpha1/provisioning_client.go
rename to apps/provisioning/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1/provisioning_client.go
index 27363d30e99..75998b83300 100644
--- a/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1/provisioning_client.go
+++ b/apps/provisioning/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1/provisioning_client.go
@@ -7,8 +7,8 @@ package v0alpha1
import (
http "net/http"
- provisioningv0alpha1 "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
- scheme "github.com/grafana/grafana/pkg/generated/clientset/versioned/scheme"
+ provisioningv0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
+ scheme "github.com/grafana/grafana/apps/provisioning/pkg/generated/clientset/versioned/scheme"
rest "k8s.io/client-go/rest"
)
diff --git a/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1/repository.go b/apps/provisioning/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1/repository.go
similarity index 91%
rename from pkg/generated/clientset/versioned/typed/provisioning/v0alpha1/repository.go
rename to apps/provisioning/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1/repository.go
index 9425083857b..98e7b47f40b 100644
--- a/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1/repository.go
+++ b/apps/provisioning/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1/repository.go
@@ -7,9 +7,9 @@ package v0alpha1
import (
context "context"
- provisioningv0alpha1 "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
- applyconfigurationprovisioningv0alpha1 "github.com/grafana/grafana/pkg/generated/applyconfiguration/provisioning/v0alpha1"
- scheme "github.com/grafana/grafana/pkg/generated/clientset/versioned/scheme"
+ provisioningv0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
+ applyconfigurationprovisioningv0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1"
+ scheme "github.com/grafana/grafana/apps/provisioning/pkg/generated/clientset/versioned/scheme"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
types "k8s.io/apimachinery/pkg/types"
watch "k8s.io/apimachinery/pkg/watch"
diff --git a/apps/provisioning/pkg/generated/informers/externalversions/factory.go b/apps/provisioning/pkg/generated/informers/externalversions/factory.go
new file mode 100644
index 00000000000..fb8443331f2
--- /dev/null
+++ b/apps/provisioning/pkg/generated/informers/externalversions/factory.go
@@ -0,0 +1,248 @@
+// SPDX-License-Identifier: AGPL-3.0-only
+
+// Code generated by informer-gen. DO NOT EDIT.
+
+package externalversions
+
+import (
+ reflect "reflect"
+ sync "sync"
+ time "time"
+
+ versioned "github.com/grafana/grafana/apps/provisioning/pkg/generated/clientset/versioned"
+ internalinterfaces "github.com/grafana/grafana/apps/provisioning/pkg/generated/informers/externalversions/internalinterfaces"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/generated/informers/externalversions/provisioning"
+ v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ runtime "k8s.io/apimachinery/pkg/runtime"
+ schema "k8s.io/apimachinery/pkg/runtime/schema"
+ cache "k8s.io/client-go/tools/cache"
+)
+
+// SharedInformerOption defines the functional option type for SharedInformerFactory.
+type SharedInformerOption func(*sharedInformerFactory) *sharedInformerFactory
+
+type sharedInformerFactory struct {
+ client versioned.Interface
+ namespace string
+ tweakListOptions internalinterfaces.TweakListOptionsFunc
+ lock sync.Mutex
+ defaultResync time.Duration
+ customResync map[reflect.Type]time.Duration
+ transform cache.TransformFunc
+
+ informers map[reflect.Type]cache.SharedIndexInformer
+ // startedInformers is used for tracking which informers have been started.
+ // This allows Start() to be called multiple times safely.
+ startedInformers map[reflect.Type]bool
+ // wg tracks how many goroutines were started.
+ wg sync.WaitGroup
+ // shuttingDown is true when Shutdown has been called. It may still be running
+ // because it needs to wait for goroutines.
+ shuttingDown bool
+}
+
+// WithCustomResyncConfig sets a custom resync period for the specified informer types.
+func WithCustomResyncConfig(resyncConfig map[v1.Object]time.Duration) SharedInformerOption {
+ return func(factory *sharedInformerFactory) *sharedInformerFactory {
+ for k, v := range resyncConfig {
+ factory.customResync[reflect.TypeOf(k)] = v
+ }
+ return factory
+ }
+}
+
+// WithTweakListOptions sets a custom filter on all listers of the configured SharedInformerFactory.
+func WithTweakListOptions(tweakListOptions internalinterfaces.TweakListOptionsFunc) SharedInformerOption {
+ return func(factory *sharedInformerFactory) *sharedInformerFactory {
+ factory.tweakListOptions = tweakListOptions
+ return factory
+ }
+}
+
+// WithNamespace limits the SharedInformerFactory to the specified namespace.
+func WithNamespace(namespace string) SharedInformerOption {
+ return func(factory *sharedInformerFactory) *sharedInformerFactory {
+ factory.namespace = namespace
+ return factory
+ }
+}
+
+// WithTransform sets a transform on all informers.
+func WithTransform(transform cache.TransformFunc) SharedInformerOption {
+ return func(factory *sharedInformerFactory) *sharedInformerFactory {
+ factory.transform = transform
+ return factory
+ }
+}
+
+// NewSharedInformerFactory constructs a new instance of sharedInformerFactory for all namespaces.
+func NewSharedInformerFactory(client versioned.Interface, defaultResync time.Duration) SharedInformerFactory {
+ return NewSharedInformerFactoryWithOptions(client, defaultResync)
+}
+
+// NewFilteredSharedInformerFactory constructs a new instance of sharedInformerFactory.
+// Listers obtained via this SharedInformerFactory will be subject to the same filters
+// as specified here.
+// Deprecated: Please use NewSharedInformerFactoryWithOptions instead
+func NewFilteredSharedInformerFactory(client versioned.Interface, defaultResync time.Duration, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) SharedInformerFactory {
+ return NewSharedInformerFactoryWithOptions(client, defaultResync, WithNamespace(namespace), WithTweakListOptions(tweakListOptions))
+}
+
+// NewSharedInformerFactoryWithOptions constructs a new instance of a SharedInformerFactory with additional options.
+func NewSharedInformerFactoryWithOptions(client versioned.Interface, defaultResync time.Duration, options ...SharedInformerOption) SharedInformerFactory {
+ factory := &sharedInformerFactory{
+ client: client,
+ namespace: v1.NamespaceAll,
+ defaultResync: defaultResync,
+ informers: make(map[reflect.Type]cache.SharedIndexInformer),
+ startedInformers: make(map[reflect.Type]bool),
+ customResync: make(map[reflect.Type]time.Duration),
+ }
+
+ // Apply all options
+ for _, opt := range options {
+ factory = opt(factory)
+ }
+
+ return factory
+}
+
+func (f *sharedInformerFactory) Start(stopCh <-chan struct{}) {
+ f.lock.Lock()
+ defer f.lock.Unlock()
+
+ if f.shuttingDown {
+ return
+ }
+
+ for informerType, informer := range f.informers {
+ if !f.startedInformers[informerType] {
+ f.wg.Add(1)
+ // We need a new variable in each loop iteration,
+ // otherwise the goroutine would use the loop variable
+ // and that keeps changing.
+ informer := informer
+ go func() {
+ defer f.wg.Done()
+ informer.Run(stopCh)
+ }()
+ f.startedInformers[informerType] = true
+ }
+ }
+}
+
+func (f *sharedInformerFactory) Shutdown() {
+ f.lock.Lock()
+ f.shuttingDown = true
+ f.lock.Unlock()
+
+ // Will return immediately if there is nothing to wait for.
+ f.wg.Wait()
+}
+
+func (f *sharedInformerFactory) WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool {
+ informers := func() map[reflect.Type]cache.SharedIndexInformer {
+ f.lock.Lock()
+ defer f.lock.Unlock()
+
+ informers := map[reflect.Type]cache.SharedIndexInformer{}
+ for informerType, informer := range f.informers {
+ if f.startedInformers[informerType] {
+ informers[informerType] = informer
+ }
+ }
+ return informers
+ }()
+
+ res := map[reflect.Type]bool{}
+ for informType, informer := range informers {
+ res[informType] = cache.WaitForCacheSync(stopCh, informer.HasSynced)
+ }
+ return res
+}
+
+// InformerFor returns the SharedIndexInformer for obj using an internal
+// client.
+func (f *sharedInformerFactory) InformerFor(obj runtime.Object, newFunc internalinterfaces.NewInformerFunc) cache.SharedIndexInformer {
+ f.lock.Lock()
+ defer f.lock.Unlock()
+
+ informerType := reflect.TypeOf(obj)
+ informer, exists := f.informers[informerType]
+ if exists {
+ return informer
+ }
+
+ resyncPeriod, exists := f.customResync[informerType]
+ if !exists {
+ resyncPeriod = f.defaultResync
+ }
+
+ informer = newFunc(f.client, resyncPeriod)
+ informer.SetTransform(f.transform)
+ f.informers[informerType] = informer
+
+ return informer
+}
+
+// SharedInformerFactory provides shared informers for resources in all known
+// API group versions.
+//
+// It is typically used like this:
+//
+// ctx, cancel := context.Background()
+// defer cancel()
+// factory := NewSharedInformerFactory(client, resyncPeriod)
+// defer factory.WaitForStop() // Returns immediately if nothing was started.
+// genericInformer := factory.ForResource(resource)
+// typedInformer := factory.SomeAPIGroup().V1().SomeType()
+// factory.Start(ctx.Done()) // Start processing these informers.
+// synced := factory.WaitForCacheSync(ctx.Done())
+// for v, ok := range synced {
+// if !ok {
+// fmt.Fprintf(os.Stderr, "caches failed to sync: %v", v)
+// return
+// }
+// }
+//
+// // Creating informers can also be created after Start, but then
+// // Start must be called again:
+// anotherGenericInformer := factory.ForResource(resource)
+// factory.Start(ctx.Done())
+type SharedInformerFactory interface {
+ internalinterfaces.SharedInformerFactory
+
+ // Start initializes all requested informers. They are handled in goroutines
+ // which run until the stop channel gets closed.
+ // Warning: Start does not block. When run in a go-routine, it will race with a later WaitForCacheSync.
+ Start(stopCh <-chan struct{})
+
+ // Shutdown marks a factory as shutting down. At that point no new
+ // informers can be started anymore and Start will return without
+ // doing anything.
+ //
+ // In addition, Shutdown blocks until all goroutines have terminated. For that
+ // to happen, the close channel(s) that they were started with must be closed,
+ // either before Shutdown gets called or while it is waiting.
+ //
+ // Shutdown may be called multiple times, even concurrently. All such calls will
+ // block until all goroutines have terminated.
+ Shutdown()
+
+ // WaitForCacheSync blocks until all started informers' caches were synced
+ // or the stop channel gets closed.
+ WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool
+
+ // ForResource gives generic access to a shared informer of the matching type.
+ ForResource(resource schema.GroupVersionResource) (GenericInformer, error)
+
+ // InformerFor returns the SharedIndexInformer for obj using an internal
+ // client.
+ InformerFor(obj runtime.Object, newFunc internalinterfaces.NewInformerFunc) cache.SharedIndexInformer
+
+ Provisioning() provisioning.Interface
+}
+
+func (f *sharedInformerFactory) Provisioning() provisioning.Interface {
+ return provisioning.New(f, f.namespace, f.tweakListOptions)
+}
diff --git a/apps/provisioning/pkg/generated/informers/externalversions/generic.go b/apps/provisioning/pkg/generated/informers/externalversions/generic.go
new file mode 100644
index 00000000000..d4b7d049e54
--- /dev/null
+++ b/apps/provisioning/pkg/generated/informers/externalversions/generic.go
@@ -0,0 +1,48 @@
+// SPDX-License-Identifier: AGPL-3.0-only
+
+// Code generated by informer-gen. DO NOT EDIT.
+
+package externalversions
+
+import (
+ fmt "fmt"
+
+ v0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
+ schema "k8s.io/apimachinery/pkg/runtime/schema"
+ cache "k8s.io/client-go/tools/cache"
+)
+
+// GenericInformer is type of SharedIndexInformer which will locate and delegate to other
+// sharedInformers based on type
+type GenericInformer interface {
+ Informer() cache.SharedIndexInformer
+ Lister() cache.GenericLister
+}
+
+type genericInformer struct {
+ informer cache.SharedIndexInformer
+ resource schema.GroupResource
+}
+
+// Informer returns the SharedIndexInformer.
+func (f *genericInformer) Informer() cache.SharedIndexInformer {
+ return f.informer
+}
+
+// Lister returns the GenericLister.
+func (f *genericInformer) Lister() cache.GenericLister {
+ return cache.NewGenericLister(f.Informer().GetIndexer(), f.resource)
+}
+
+// ForResource gives generic access to a shared informer of the matching type
+// TODO extend this to unknown resources with a client pool
+func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource) (GenericInformer, error) {
+ switch resource {
+ // Group=provisioning.grafana.app, Version=v0alpha1
+ case v0alpha1.SchemeGroupVersion.WithResource("repositories"):
+ return &genericInformer{resource: resource.GroupResource(), informer: f.Provisioning().V0alpha1().Repositories().Informer()}, nil
+
+ }
+
+ return nil, fmt.Errorf("no informer found for %v", resource)
+}
diff --git a/apps/provisioning/pkg/generated/informers/externalversions/internalinterfaces/factory_interfaces.go b/apps/provisioning/pkg/generated/informers/externalversions/internalinterfaces/factory_interfaces.go
new file mode 100644
index 00000000000..06fe98fc993
--- /dev/null
+++ b/apps/provisioning/pkg/generated/informers/externalversions/internalinterfaces/factory_interfaces.go
@@ -0,0 +1,26 @@
+// SPDX-License-Identifier: AGPL-3.0-only
+
+// Code generated by informer-gen. DO NOT EDIT.
+
+package internalinterfaces
+
+import (
+ time "time"
+
+ versioned "github.com/grafana/grafana/apps/provisioning/pkg/generated/clientset/versioned"
+ v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ runtime "k8s.io/apimachinery/pkg/runtime"
+ cache "k8s.io/client-go/tools/cache"
+)
+
+// NewInformerFunc takes versioned.Interface and time.Duration to return a SharedIndexInformer.
+type NewInformerFunc func(versioned.Interface, time.Duration) cache.SharedIndexInformer
+
+// SharedInformerFactory a small interface to allow for adding an informer without an import cycle
+type SharedInformerFactory interface {
+ Start(stopCh <-chan struct{})
+ InformerFor(obj runtime.Object, newFunc NewInformerFunc) cache.SharedIndexInformer
+}
+
+// TweakListOptionsFunc is a function that transforms a v1.ListOptions.
+type TweakListOptionsFunc func(*v1.ListOptions)
diff --git a/pkg/generated/informers/externalversions/provisioning/interface.go b/apps/provisioning/pkg/generated/informers/externalversions/provisioning/interface.go
similarity index 78%
rename from pkg/generated/informers/externalversions/provisioning/interface.go
rename to apps/provisioning/pkg/generated/informers/externalversions/provisioning/interface.go
index 851568050e0..7961fd26712 100644
--- a/pkg/generated/informers/externalversions/provisioning/interface.go
+++ b/apps/provisioning/pkg/generated/informers/externalversions/provisioning/interface.go
@@ -5,8 +5,8 @@
package provisioning
import (
- internalinterfaces "github.com/grafana/grafana/pkg/generated/informers/externalversions/internalinterfaces"
- v0alpha1 "github.com/grafana/grafana/pkg/generated/informers/externalversions/provisioning/v0alpha1"
+ internalinterfaces "github.com/grafana/grafana/apps/provisioning/pkg/generated/informers/externalversions/internalinterfaces"
+ v0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/generated/informers/externalversions/provisioning/v0alpha1"
)
// Interface provides access to each of this group's versions.
diff --git a/pkg/generated/informers/externalversions/provisioning/v0alpha1/interface.go b/apps/provisioning/pkg/generated/informers/externalversions/provisioning/v0alpha1/interface.go
similarity index 88%
rename from pkg/generated/informers/externalversions/provisioning/v0alpha1/interface.go
rename to apps/provisioning/pkg/generated/informers/externalversions/provisioning/v0alpha1/interface.go
index 51060296a02..b358790c61e 100644
--- a/pkg/generated/informers/externalversions/provisioning/v0alpha1/interface.go
+++ b/apps/provisioning/pkg/generated/informers/externalversions/provisioning/v0alpha1/interface.go
@@ -5,7 +5,7 @@
package v0alpha1
import (
- internalinterfaces "github.com/grafana/grafana/pkg/generated/informers/externalversions/internalinterfaces"
+ internalinterfaces "github.com/grafana/grafana/apps/provisioning/pkg/generated/informers/externalversions/internalinterfaces"
)
// Interface provides access to all the informers in this group version.
diff --git a/pkg/generated/informers/externalversions/provisioning/v0alpha1/repository.go b/apps/provisioning/pkg/generated/informers/externalversions/provisioning/v0alpha1/repository.go
similarity index 88%
rename from pkg/generated/informers/externalversions/provisioning/v0alpha1/repository.go
rename to apps/provisioning/pkg/generated/informers/externalversions/provisioning/v0alpha1/repository.go
index 58df4bad73d..025ce07d20f 100644
--- a/pkg/generated/informers/externalversions/provisioning/v0alpha1/repository.go
+++ b/apps/provisioning/pkg/generated/informers/externalversions/provisioning/v0alpha1/repository.go
@@ -8,10 +8,10 @@ import (
context "context"
time "time"
- apisprovisioningv0alpha1 "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
- versioned "github.com/grafana/grafana/pkg/generated/clientset/versioned"
- internalinterfaces "github.com/grafana/grafana/pkg/generated/informers/externalversions/internalinterfaces"
- provisioningv0alpha1 "github.com/grafana/grafana/pkg/generated/listers/provisioning/v0alpha1"
+ apisprovisioningv0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
+ versioned "github.com/grafana/grafana/apps/provisioning/pkg/generated/clientset/versioned"
+ internalinterfaces "github.com/grafana/grafana/apps/provisioning/pkg/generated/informers/externalversions/internalinterfaces"
+ provisioningv0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/generated/listers/provisioning/v0alpha1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
watch "k8s.io/apimachinery/pkg/watch"
diff --git a/pkg/generated/listers/provisioning/v0alpha1/expansion_generated.go b/apps/provisioning/pkg/generated/listers/provisioning/v0alpha1/expansion_generated.go
similarity index 100%
rename from pkg/generated/listers/provisioning/v0alpha1/expansion_generated.go
rename to apps/provisioning/pkg/generated/listers/provisioning/v0alpha1/expansion_generated.go
diff --git a/pkg/generated/listers/provisioning/v0alpha1/repository.go b/apps/provisioning/pkg/generated/listers/provisioning/v0alpha1/repository.go
similarity index 95%
rename from pkg/generated/listers/provisioning/v0alpha1/repository.go
rename to apps/provisioning/pkg/generated/listers/provisioning/v0alpha1/repository.go
index 969cb034e88..4196743bc1b 100644
--- a/pkg/generated/listers/provisioning/v0alpha1/repository.go
+++ b/apps/provisioning/pkg/generated/listers/provisioning/v0alpha1/repository.go
@@ -5,7 +5,7 @@
package v0alpha1
import (
- provisioningv0alpha1 "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ provisioningv0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
labels "k8s.io/apimachinery/pkg/labels"
listers "k8s.io/client-go/listers"
cache "k8s.io/client-go/tools/cache"
diff --git a/go.work b/go.work
index 2735423d372..a3bb9e88c39 100644
--- a/go.work
+++ b/go.work
@@ -12,6 +12,7 @@ use (
./apps/iam
./apps/investigations
./apps/playlist
+ ./apps/provisioning
./apps/secret
./pkg/aggregator
./pkg/apimachinery
diff --git a/hack/update-codegen.sh b/hack/update-codegen.sh
index aec836f5e3e..2e432ac72e4 100755
--- a/hack/update-codegen.sh
+++ b/hack/update-codegen.sh
@@ -87,6 +87,7 @@ grafana::codegen:run pkg
grafana::codegen:run pkg/apimachinery
grafana::codegen:run pkg/aggregator
grafana::codegen:run apps/dashboard/pkg
+grafana::codegen:run apps/provisioning/pkg
grafana::codegen:run apps/folder/pkg
if [ -d "pkg/extensions/apis" ]; then
diff --git a/pkg/apis/provisioning/v0alpha1/zz_generated.openapi_violation_exceptions.list b/pkg/apis/provisioning/v0alpha1/zz_generated.openapi_violation_exceptions.list
deleted file mode 100644
index 49a2170ce4e..00000000000
--- a/pkg/apis/provisioning/v0alpha1/zz_generated.openapi_violation_exceptions.list
+++ /dev/null
@@ -1,26 +0,0 @@
-API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,DeleteJobOptions,Paths
-API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,DeleteJobOptions,Resources
-API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,FileList,Items
-API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,HistoryList,Items
-API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,JobResourceSummary,Errors
-API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,JobStatus,Errors
-API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,JobStatus,Summary
-API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,ManagerStats,Stats
-API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,MoveJobOptions,Paths
-API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,MoveJobOptions,Resources
-API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,RefList,Items
-API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,RepositoryList,Items
-API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,RepositorySpec,Workflows
-API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,RepositoryView,Workflows
-API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,RepositoryViewList,AvailableRepositoryTypes
-API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,RepositoryViewList,Items
-API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,ResourceList,Items
-API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,TestResults,Errors
-API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,WebhookStatus,SubscribedEvents
-API rule violation: names_match,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,JobSpec,PullRequest
-API rule violation: names_match,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,ManagerStats,Identity
-API rule violation: names_match,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,RepositorySpec,GitHub
-API rule violation: names_match,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,RepositorySpec,GitLab
-API rule violation: names_match,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,ResourceWrapper,URLs
-API rule violation: names_match,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,SyncStatus,JobID
-API rule violation: names_match,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,WebhookResponse,Message
diff --git a/pkg/generated/applyconfiguration/utils.go b/pkg/generated/applyconfiguration/utils.go
index a512116f25c..d792c0be680 100644
--- a/pkg/generated/applyconfiguration/utils.go
+++ b/pkg/generated/applyconfiguration/utils.go
@@ -5,11 +5,9 @@
package applyconfiguration
import (
- v0alpha1 "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
- servicev0alpha1 "github.com/grafana/grafana/pkg/apis/service/v0alpha1"
+ v0alpha1 "github.com/grafana/grafana/pkg/apis/service/v0alpha1"
internal "github.com/grafana/grafana/pkg/generated/applyconfiguration/internal"
- provisioningv0alpha1 "github.com/grafana/grafana/pkg/generated/applyconfiguration/provisioning/v0alpha1"
- applyconfigurationservicev0alpha1 "github.com/grafana/grafana/pkg/generated/applyconfiguration/service/v0alpha1"
+ servicev0alpha1 "github.com/grafana/grafana/pkg/generated/applyconfiguration/service/v0alpha1"
runtime "k8s.io/apimachinery/pkg/runtime"
schema "k8s.io/apimachinery/pkg/runtime/schema"
testing "k8s.io/client-go/testing"
@@ -19,39 +17,11 @@ import (
// apply configuration type exists for the given GroupVersionKind.
func ForKind(kind schema.GroupVersionKind) interface{} {
switch kind {
- // Group=provisioning.grafana.app, Version=v0alpha1
- case v0alpha1.SchemeGroupVersion.WithKind("BitbucketRepositoryConfig"):
- return &provisioningv0alpha1.BitbucketRepositoryConfigApplyConfiguration{}
- case v0alpha1.SchemeGroupVersion.WithKind("GitHubRepositoryConfig"):
- return &provisioningv0alpha1.GitHubRepositoryConfigApplyConfiguration{}
- case v0alpha1.SchemeGroupVersion.WithKind("GitLabRepositoryConfig"):
- return &provisioningv0alpha1.GitLabRepositoryConfigApplyConfiguration{}
- case v0alpha1.SchemeGroupVersion.WithKind("GitRepositoryConfig"):
- return &provisioningv0alpha1.GitRepositoryConfigApplyConfiguration{}
- case v0alpha1.SchemeGroupVersion.WithKind("HealthStatus"):
- return &provisioningv0alpha1.HealthStatusApplyConfiguration{}
- case v0alpha1.SchemeGroupVersion.WithKind("LocalRepositoryConfig"):
- return &provisioningv0alpha1.LocalRepositoryConfigApplyConfiguration{}
- case v0alpha1.SchemeGroupVersion.WithKind("Repository"):
- return &provisioningv0alpha1.RepositoryApplyConfiguration{}
- case v0alpha1.SchemeGroupVersion.WithKind("RepositorySpec"):
- return &provisioningv0alpha1.RepositorySpecApplyConfiguration{}
- case v0alpha1.SchemeGroupVersion.WithKind("RepositoryStatus"):
- return &provisioningv0alpha1.RepositoryStatusApplyConfiguration{}
- case v0alpha1.SchemeGroupVersion.WithKind("ResourceCount"):
- return &provisioningv0alpha1.ResourceCountApplyConfiguration{}
- case v0alpha1.SchemeGroupVersion.WithKind("SyncOptions"):
- return &provisioningv0alpha1.SyncOptionsApplyConfiguration{}
- case v0alpha1.SchemeGroupVersion.WithKind("SyncStatus"):
- return &provisioningv0alpha1.SyncStatusApplyConfiguration{}
- case v0alpha1.SchemeGroupVersion.WithKind("WebhookStatus"):
- return &provisioningv0alpha1.WebhookStatusApplyConfiguration{}
-
- // Group=service.grafana.app, Version=v0alpha1
- case servicev0alpha1.SchemeGroupVersion.WithKind("ExternalName"):
- return &applyconfigurationservicev0alpha1.ExternalNameApplyConfiguration{}
- case servicev0alpha1.SchemeGroupVersion.WithKind("ExternalNameSpec"):
- return &applyconfigurationservicev0alpha1.ExternalNameSpecApplyConfiguration{}
+ // Group=service.grafana.app, Version=v0alpha1
+ case v0alpha1.SchemeGroupVersion.WithKind("ExternalName"):
+ return &servicev0alpha1.ExternalNameApplyConfiguration{}
+ case v0alpha1.SchemeGroupVersion.WithKind("ExternalNameSpec"):
+ return &servicev0alpha1.ExternalNameSpecApplyConfiguration{}
}
return nil
diff --git a/pkg/generated/clientset/versioned/clientset.go b/pkg/generated/clientset/versioned/clientset.go
index ad40ccbcfed..972fed70d47 100644
--- a/pkg/generated/clientset/versioned/clientset.go
+++ b/pkg/generated/clientset/versioned/clientset.go
@@ -8,7 +8,6 @@ import (
fmt "fmt"
http "net/http"
- provisioningv0alpha1 "github.com/grafana/grafana/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1"
servicev0alpha1 "github.com/grafana/grafana/pkg/generated/clientset/versioned/typed/service/v0alpha1"
discovery "k8s.io/client-go/discovery"
rest "k8s.io/client-go/rest"
@@ -17,20 +16,13 @@ import (
type Interface interface {
Discovery() discovery.DiscoveryInterface
- ProvisioningV0alpha1() provisioningv0alpha1.ProvisioningV0alpha1Interface
ServiceV0alpha1() servicev0alpha1.ServiceV0alpha1Interface
}
// Clientset contains the clients for groups.
type Clientset struct {
*discovery.DiscoveryClient
- provisioningV0alpha1 *provisioningv0alpha1.ProvisioningV0alpha1Client
- serviceV0alpha1 *servicev0alpha1.ServiceV0alpha1Client
-}
-
-// ProvisioningV0alpha1 retrieves the ProvisioningV0alpha1Client
-func (c *Clientset) ProvisioningV0alpha1() provisioningv0alpha1.ProvisioningV0alpha1Interface {
- return c.provisioningV0alpha1
+ serviceV0alpha1 *servicev0alpha1.ServiceV0alpha1Client
}
// ServiceV0alpha1 retrieves the ServiceV0alpha1Client
@@ -82,10 +74,6 @@ func NewForConfigAndClient(c *rest.Config, httpClient *http.Client) (*Clientset,
var cs Clientset
var err error
- cs.provisioningV0alpha1, err = provisioningv0alpha1.NewForConfigAndClient(&configShallowCopy, httpClient)
- if err != nil {
- return nil, err
- }
cs.serviceV0alpha1, err = servicev0alpha1.NewForConfigAndClient(&configShallowCopy, httpClient)
if err != nil {
return nil, err
@@ -111,7 +99,6 @@ func NewForConfigOrDie(c *rest.Config) *Clientset {
// New creates a new Clientset for the given RESTClient.
func New(c rest.Interface) *Clientset {
var cs Clientset
- cs.provisioningV0alpha1 = provisioningv0alpha1.New(c)
cs.serviceV0alpha1 = servicev0alpha1.New(c)
cs.DiscoveryClient = discovery.NewDiscoveryClient(c)
diff --git a/pkg/generated/clientset/versioned/fake/clientset_generated.go b/pkg/generated/clientset/versioned/fake/clientset_generated.go
index a68ce76d23c..7b3cf0c1dad 100644
--- a/pkg/generated/clientset/versioned/fake/clientset_generated.go
+++ b/pkg/generated/clientset/versioned/fake/clientset_generated.go
@@ -7,8 +7,6 @@ package fake
import (
applyconfiguration "github.com/grafana/grafana/pkg/generated/applyconfiguration"
clientset "github.com/grafana/grafana/pkg/generated/clientset/versioned"
- provisioningv0alpha1 "github.com/grafana/grafana/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1"
- fakeprovisioningv0alpha1 "github.com/grafana/grafana/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1/fake"
servicev0alpha1 "github.com/grafana/grafana/pkg/generated/clientset/versioned/typed/service/v0alpha1"
fakeservicev0alpha1 "github.com/grafana/grafana/pkg/generated/clientset/versioned/typed/service/v0alpha1/fake"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -113,11 +111,6 @@ var (
_ testing.FakeClient = &Clientset{}
)
-// ProvisioningV0alpha1 retrieves the ProvisioningV0alpha1Client
-func (c *Clientset) ProvisioningV0alpha1() provisioningv0alpha1.ProvisioningV0alpha1Interface {
- return &fakeprovisioningv0alpha1.FakeProvisioningV0alpha1{Fake: &c.Fake}
-}
-
// ServiceV0alpha1 retrieves the ServiceV0alpha1Client
func (c *Clientset) ServiceV0alpha1() servicev0alpha1.ServiceV0alpha1Interface {
return &fakeservicev0alpha1.FakeServiceV0alpha1{Fake: &c.Fake}
diff --git a/pkg/generated/clientset/versioned/fake/register.go b/pkg/generated/clientset/versioned/fake/register.go
index d64a8572697..bbf12658e76 100644
--- a/pkg/generated/clientset/versioned/fake/register.go
+++ b/pkg/generated/clientset/versioned/fake/register.go
@@ -5,7 +5,6 @@
package fake
import (
- provisioningv0alpha1 "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
servicev0alpha1 "github.com/grafana/grafana/pkg/apis/service/v0alpha1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
@@ -18,7 +17,6 @@ var scheme = runtime.NewScheme()
var codecs = serializer.NewCodecFactory(scheme)
var localSchemeBuilder = runtime.SchemeBuilder{
- provisioningv0alpha1.AddToScheme,
servicev0alpha1.AddToScheme,
}
diff --git a/pkg/generated/clientset/versioned/scheme/register.go b/pkg/generated/clientset/versioned/scheme/register.go
index 0aa0fb1be43..dd6e9619160 100644
--- a/pkg/generated/clientset/versioned/scheme/register.go
+++ b/pkg/generated/clientset/versioned/scheme/register.go
@@ -5,7 +5,6 @@
package scheme
import (
- provisioningv0alpha1 "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
servicev0alpha1 "github.com/grafana/grafana/pkg/apis/service/v0alpha1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
@@ -18,7 +17,6 @@ var Scheme = runtime.NewScheme()
var Codecs = serializer.NewCodecFactory(Scheme)
var ParameterCodec = runtime.NewParameterCodec(Scheme)
var localSchemeBuilder = runtime.SchemeBuilder{
- provisioningv0alpha1.AddToScheme,
servicev0alpha1.AddToScheme,
}
diff --git a/pkg/generated/informers/externalversions/factory.go b/pkg/generated/informers/externalversions/factory.go
index 86ef036dae3..93a498c5d2a 100644
--- a/pkg/generated/informers/externalversions/factory.go
+++ b/pkg/generated/informers/externalversions/factory.go
@@ -11,7 +11,6 @@ import (
versioned "github.com/grafana/grafana/pkg/generated/clientset/versioned"
internalinterfaces "github.com/grafana/grafana/pkg/generated/informers/externalversions/internalinterfaces"
- provisioning "github.com/grafana/grafana/pkg/generated/informers/externalversions/provisioning"
service "github.com/grafana/grafana/pkg/generated/informers/externalversions/service"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
@@ -241,14 +240,9 @@ type SharedInformerFactory interface {
// client.
InformerFor(obj runtime.Object, newFunc internalinterfaces.NewInformerFunc) cache.SharedIndexInformer
- Provisioning() provisioning.Interface
Service() service.Interface
}
-func (f *sharedInformerFactory) Provisioning() provisioning.Interface {
- return provisioning.New(f, f.namespace, f.tweakListOptions)
-}
-
func (f *sharedInformerFactory) Service() service.Interface {
return service.New(f, f.namespace, f.tweakListOptions)
}
diff --git a/pkg/generated/informers/externalversions/generic.go b/pkg/generated/informers/externalversions/generic.go
index 676e6b91f8d..9731bd8cbf2 100644
--- a/pkg/generated/informers/externalversions/generic.go
+++ b/pkg/generated/informers/externalversions/generic.go
@@ -7,8 +7,7 @@ package externalversions
import (
fmt "fmt"
- v0alpha1 "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
- servicev0alpha1 "github.com/grafana/grafana/pkg/apis/service/v0alpha1"
+ v0alpha1 "github.com/grafana/grafana/pkg/apis/service/v0alpha1"
schema "k8s.io/apimachinery/pkg/runtime/schema"
cache "k8s.io/client-go/tools/cache"
)
@@ -39,12 +38,8 @@ func (f *genericInformer) Lister() cache.GenericLister {
// TODO extend this to unknown resources with a client pool
func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource) (GenericInformer, error) {
switch resource {
- // Group=provisioning.grafana.app, Version=v0alpha1
- case v0alpha1.SchemeGroupVersion.WithResource("repositories"):
- return &genericInformer{resource: resource.GroupResource(), informer: f.Provisioning().V0alpha1().Repositories().Informer()}, nil
-
- // Group=service.grafana.app, Version=v0alpha1
- case servicev0alpha1.SchemeGroupVersion.WithResource("externalnames"):
+ // Group=service.grafana.app, Version=v0alpha1
+ case v0alpha1.SchemeGroupVersion.WithResource("externalnames"):
return &genericInformer{resource: resource.GroupResource(), informer: f.Service().V0alpha1().ExternalNames().Informer()}, nil
}
diff --git a/pkg/registry/apis/provisioning/controller/finalizers.go b/pkg/registry/apis/provisioning/controller/finalizers.go
index 554baa86ca9..b30e670a80f 100644
--- a/pkg/registry/apis/provisioning/controller/finalizers.go
+++ b/pkg/registry/apis/provisioning/controller/finalizers.go
@@ -12,8 +12,8 @@ import (
"github.com/grafana/grafana-app-sdk/logging"
folders "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/apimachinery/utils"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/resources"
)
diff --git a/pkg/registry/apis/provisioning/controller/repository.go b/pkg/registry/apis/provisioning/controller/repository.go
index 4b673907e0f..ef5578b1455 100644
--- a/pkg/registry/apis/provisioning/controller/repository.go
+++ b/pkg/registry/apis/provisioning/controller/repository.go
@@ -17,11 +17,11 @@ import (
"k8s.io/client-go/util/workqueue"
"github.com/grafana/grafana-app-sdk/logging"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
+ client "github.com/grafana/grafana/apps/provisioning/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1"
+ informer "github.com/grafana/grafana/apps/provisioning/pkg/generated/informers/externalversions/provisioning/v0alpha1"
+ listers "github.com/grafana/grafana/apps/provisioning/pkg/generated/listers/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/apimachinery/identity"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
- client "github.com/grafana/grafana/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1"
- informer "github.com/grafana/grafana/pkg/generated/informers/externalversions/provisioning/v0alpha1"
- listers "github.com/grafana/grafana/pkg/generated/listers/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/jobs"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/resources"
@@ -239,7 +239,7 @@ func (rc *RepositoryController) handleDelete(ctx context.Context, obj *provision
Patch(ctx, obj.Name, types.JSONPatchType, []byte(`[
{ "op": "remove", "path": "/metadata/finalizers" }
]`), v1.PatchOptions{
- FieldManager: "repository-controller",
+ FieldManager: "provisioning-controller",
})
return err // delete will be called again
}
diff --git a/pkg/registry/apis/provisioning/controller/status.go b/pkg/registry/apis/provisioning/controller/status.go
index 1cbedf57c9b..40ed29624c1 100644
--- a/pkg/registry/apis/provisioning/controller/status.go
+++ b/pkg/registry/apis/provisioning/controller/status.go
@@ -5,8 +5,8 @@ import (
"encoding/json"
"fmt"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
- client "github.com/grafana/grafana/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
+ client "github.com/grafana/grafana/apps/provisioning/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
)
diff --git a/pkg/registry/apis/provisioning/controller/status_test.go b/pkg/registry/apis/provisioning/controller/status_test.go
index 56354c2c165..77e7f737ea5 100644
--- a/pkg/registry/apis/provisioning/controller/status_test.go
+++ b/pkg/registry/apis/provisioning/controller/status_test.go
@@ -6,8 +6,8 @@ import (
"fmt"
"testing"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
- "github.com/grafana/grafana/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1/fake"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
+ "github.com/grafana/grafana/apps/provisioning/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1/fake"
"github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
diff --git a/pkg/registry/apis/provisioning/extra.go b/pkg/registry/apis/provisioning/extra.go
index 0c5f9e921dd..e7f888a914b 100644
--- a/pkg/registry/apis/provisioning/extra.go
+++ b/pkg/registry/apis/provisioning/extra.go
@@ -3,7 +3,7 @@ package provisioning
import (
"context"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/controller"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/jobs"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
diff --git a/pkg/registry/apis/provisioning/files.go b/pkg/registry/apis/provisioning/files.go
index 4dd6604c989..1df860b9738 100644
--- a/pkg/registry/apis/provisioning/files.go
+++ b/pkg/registry/apis/provisioning/files.go
@@ -12,8 +12,8 @@ import (
authlib "github.com/grafana/authlib/types"
"github.com/grafana/grafana-app-sdk/logging"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/apimachinery/identity"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/resources"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/safepath"
diff --git a/pkg/registry/apis/provisioning/history.go b/pkg/registry/apis/provisioning/history.go
index e177295ca23..7770e44fe6d 100644
--- a/pkg/registry/apis/provisioning/history.go
+++ b/pkg/registry/apis/provisioning/history.go
@@ -11,7 +11,7 @@ import (
"k8s.io/apiserver/pkg/registry/rest"
"github.com/grafana/grafana-app-sdk/logging"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/safepath"
)
diff --git a/pkg/registry/apis/provisioning/jobs.go b/pkg/registry/apis/provisioning/jobs.go
index d19dd61722c..e563cc94fa9 100644
--- a/pkg/registry/apis/provisioning/jobs.go
+++ b/pkg/registry/apis/provisioning/jobs.go
@@ -11,7 +11,7 @@ import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/registry/rest"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/jobs"
)
diff --git a/pkg/registry/apis/provisioning/jobs/delete/worker.go b/pkg/registry/apis/provisioning/jobs/delete/worker.go
index c1f6739405e..63db00ef224 100644
--- a/pkg/registry/apis/provisioning/jobs/delete/worker.go
+++ b/pkg/registry/apis/provisioning/jobs/delete/worker.go
@@ -8,7 +8,7 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/jobs"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/resources"
diff --git a/pkg/registry/apis/provisioning/jobs/delete/worker_test.go b/pkg/registry/apis/provisioning/jobs/delete/worker_test.go
index 5d63687dd2c..7f2fa3c22bb 100644
--- a/pkg/registry/apis/provisioning/jobs/delete/worker_test.go
+++ b/pkg/registry/apis/provisioning/jobs/delete/worker_test.go
@@ -9,7 +9,7 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/validation/field"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/jobs"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/resources"
diff --git a/pkg/registry/apis/provisioning/jobs/driver.go b/pkg/registry/apis/provisioning/jobs/driver.go
index 8d31d1dde1a..988422aacff 100644
--- a/pkg/registry/apis/provisioning/jobs/driver.go
+++ b/pkg/registry/apis/provisioning/jobs/driver.go
@@ -9,8 +9,8 @@ import (
"k8s.io/apiserver/pkg/endpoints/request"
"github.com/grafana/grafana-app-sdk/logging"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/apimachinery/identity"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/apifmt"
)
diff --git a/pkg/registry/apis/provisioning/jobs/export/all.go b/pkg/registry/apis/provisioning/jobs/export/all.go
index 1010c02f630..c2cb0f85eb5 100644
--- a/pkg/registry/apis/provisioning/jobs/export/all.go
+++ b/pkg/registry/apis/provisioning/jobs/export/all.go
@@ -3,7 +3,7 @@ package export
import (
"context"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/jobs"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/resources"
)
diff --git a/pkg/registry/apis/provisioning/jobs/export/folders.go b/pkg/registry/apis/provisioning/jobs/export/folders.go
index c8b108d6023..48a4f7839ae 100644
--- a/pkg/registry/apis/provisioning/jobs/export/folders.go
+++ b/pkg/registry/apis/provisioning/jobs/export/folders.go
@@ -8,8 +8,8 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/client-go/dynamic"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/apimachinery/utils"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/jobs"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/resources"
diff --git a/pkg/registry/apis/provisioning/jobs/export/folders_test.go b/pkg/registry/apis/provisioning/jobs/export/folders_test.go
index 49c07a64347..2eaeeabd373 100644
--- a/pkg/registry/apis/provisioning/jobs/export/folders_test.go
+++ b/pkg/registry/apis/provisioning/jobs/export/folders_test.go
@@ -6,8 +6,8 @@ import (
"fmt"
"testing"
+ v0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/apimachinery/utils"
- v0alpha1 "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/jobs"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/resources"
diff --git a/pkg/registry/apis/provisioning/jobs/export/mock_export_fn.go b/pkg/registry/apis/provisioning/jobs/export/mock_export_fn.go
index e861e7da3c3..e748228da2b 100644
--- a/pkg/registry/apis/provisioning/jobs/export/mock_export_fn.go
+++ b/pkg/registry/apis/provisioning/jobs/export/mock_export_fn.go
@@ -10,7 +10,7 @@ import (
resources "github.com/grafana/grafana/pkg/registry/apis/provisioning/resources"
- v0alpha1 "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ v0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
)
// MockExportFn is an autogenerated mock type for the ExportFn type
diff --git a/pkg/registry/apis/provisioning/jobs/export/resources.go b/pkg/registry/apis/provisioning/jobs/export/resources.go
index 54805784cee..8b1baff08ca 100644
--- a/pkg/registry/apis/provisioning/jobs/export/resources.go
+++ b/pkg/registry/apis/provisioning/jobs/export/resources.go
@@ -10,8 +10,8 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/client-go/dynamic"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/apimachinery/utils"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/jobs"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/resources"
diff --git a/pkg/registry/apis/provisioning/jobs/export/resources_test.go b/pkg/registry/apis/provisioning/jobs/export/resources_test.go
index e77ae6105ed..8be32cebed9 100644
--- a/pkg/registry/apis/provisioning/jobs/export/resources_test.go
+++ b/pkg/registry/apis/provisioning/jobs/export/resources_test.go
@@ -11,7 +11,7 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
- provisioningV0 "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ provisioningV0 "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/jobs"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/resources"
diff --git a/pkg/registry/apis/provisioning/jobs/export/worker.go b/pkg/registry/apis/provisioning/jobs/export/worker.go
index 13536a62aa5..524b01dce4c 100644
--- a/pkg/registry/apis/provisioning/jobs/export/worker.go
+++ b/pkg/registry/apis/provisioning/jobs/export/worker.go
@@ -6,7 +6,7 @@ import (
"fmt"
"time"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/jobs"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/resources"
diff --git a/pkg/registry/apis/provisioning/jobs/export/worker_test.go b/pkg/registry/apis/provisioning/jobs/export/worker_test.go
index 930b5f051a9..d78b0ec5b8b 100644
--- a/pkg/registry/apis/provisioning/jobs/export/worker_test.go
+++ b/pkg/registry/apis/provisioning/jobs/export/worker_test.go
@@ -11,7 +11,7 @@ import (
"github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- v0alpha1 "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ v0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/jobs"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/resources"
diff --git a/pkg/registry/apis/provisioning/jobs/history.go b/pkg/registry/apis/provisioning/jobs/history.go
index 88351e12bad..0a7897319f9 100644
--- a/pkg/registry/apis/provisioning/jobs/history.go
+++ b/pkg/registry/apis/provisioning/jobs/history.go
@@ -7,7 +7,7 @@ import (
apierrors "k8s.io/apimachinery/pkg/api/errors"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
)
// History keeps track of completed jobs
diff --git a/pkg/registry/apis/provisioning/jobs/history_mock.go b/pkg/registry/apis/provisioning/jobs/history_mock.go
index dbb61568566..f6697d33bcf 100644
--- a/pkg/registry/apis/provisioning/jobs/history_mock.go
+++ b/pkg/registry/apis/provisioning/jobs/history_mock.go
@@ -5,7 +5,7 @@ package jobs
import (
context "context"
- v0alpha1 "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ v0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
mock "github.com/stretchr/testify/mock"
)
diff --git a/pkg/registry/apis/provisioning/jobs/job_progress_recorder_mock.go b/pkg/registry/apis/provisioning/jobs/job_progress_recorder_mock.go
index 54b07e6a38a..be0b6d3d9b0 100644
--- a/pkg/registry/apis/provisioning/jobs/job_progress_recorder_mock.go
+++ b/pkg/registry/apis/provisioning/jobs/job_progress_recorder_mock.go
@@ -5,7 +5,7 @@ package jobs
import (
context "context"
- v0alpha1 "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ v0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
mock "github.com/stretchr/testify/mock"
)
diff --git a/pkg/registry/apis/provisioning/jobs/migrate/legacy.go b/pkg/registry/apis/provisioning/jobs/migrate/legacy.go
index 8b253bebd7f..929c7a6b9cf 100644
--- a/pkg/registry/apis/provisioning/jobs/migrate/legacy.go
+++ b/pkg/registry/apis/provisioning/jobs/migrate/legacy.go
@@ -7,7 +7,7 @@ import (
"time"
"github.com/grafana/grafana-app-sdk/logging"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/jobs"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
)
diff --git a/pkg/registry/apis/provisioning/jobs/migrate/legacy_resources.go b/pkg/registry/apis/provisioning/jobs/migrate/legacy_resources.go
index 34391f3e56f..03711c485d6 100644
--- a/pkg/registry/apis/provisioning/jobs/migrate/legacy_resources.go
+++ b/pkg/registry/apis/provisioning/jobs/migrate/legacy_resources.go
@@ -6,8 +6,8 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/apimachinery/utils"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/dashboard/legacy"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/jobs"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/jobs/export"
diff --git a/pkg/registry/apis/provisioning/jobs/migrate/legacy_resources_test.go b/pkg/registry/apis/provisioning/jobs/migrate/legacy_resources_test.go
index aff308a4a9b..88e60341f6b 100644
--- a/pkg/registry/apis/provisioning/jobs/migrate/legacy_resources_test.go
+++ b/pkg/registry/apis/provisioning/jobs/migrate/legacy_resources_test.go
@@ -13,8 +13,8 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/apimachinery/utils"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/dashboard/legacy"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/jobs"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/jobs/export"
diff --git a/pkg/registry/apis/provisioning/jobs/migrate/legacy_test.go b/pkg/registry/apis/provisioning/jobs/migrate/legacy_test.go
index 3b25de7aeaf..319984391d6 100644
--- a/pkg/registry/apis/provisioning/jobs/migrate/legacy_test.go
+++ b/pkg/registry/apis/provisioning/jobs/migrate/legacy_test.go
@@ -10,7 +10,7 @@ import (
"github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/jobs"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
)
diff --git a/pkg/registry/apis/provisioning/jobs/migrate/mock_legacy_resources_migrator.go b/pkg/registry/apis/provisioning/jobs/migrate/mock_legacy_resources_migrator.go
index 4a0d27fafb5..4e70afef693 100644
--- a/pkg/registry/apis/provisioning/jobs/migrate/mock_legacy_resources_migrator.go
+++ b/pkg/registry/apis/provisioning/jobs/migrate/mock_legacy_resources_migrator.go
@@ -10,7 +10,7 @@ import (
repository "github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
- v0alpha1 "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ v0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
)
// MockLegacyResourcesMigrator is an autogenerated mock type for the LegacyResourcesMigrator type
diff --git a/pkg/registry/apis/provisioning/jobs/migrate/mock_migrator.go b/pkg/registry/apis/provisioning/jobs/migrate/mock_migrator.go
index 0128ce741f4..685c5ab7c47 100644
--- a/pkg/registry/apis/provisioning/jobs/migrate/mock_migrator.go
+++ b/pkg/registry/apis/provisioning/jobs/migrate/mock_migrator.go
@@ -10,7 +10,7 @@ import (
repository "github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
- v0alpha1 "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ v0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
)
// MockMigrator is an autogenerated mock type for the Migrator type
diff --git a/pkg/registry/apis/provisioning/jobs/migrate/unifiedstorage.go b/pkg/registry/apis/provisioning/jobs/migrate/unifiedstorage.go
index 4f9f33b96ec..aa6ce0b596d 100644
--- a/pkg/registry/apis/provisioning/jobs/migrate/unifiedstorage.go
+++ b/pkg/registry/apis/provisioning/jobs/migrate/unifiedstorage.go
@@ -4,7 +4,7 @@ import (
"context"
"fmt"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/jobs"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
)
diff --git a/pkg/registry/apis/provisioning/jobs/migrate/unifiedstorage_test.go b/pkg/registry/apis/provisioning/jobs/migrate/unifiedstorage_test.go
index 3cb48119842..5bc028f138d 100644
--- a/pkg/registry/apis/provisioning/jobs/migrate/unifiedstorage_test.go
+++ b/pkg/registry/apis/provisioning/jobs/migrate/unifiedstorage_test.go
@@ -8,7 +8,7 @@ import (
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/jobs"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
diff --git a/pkg/registry/apis/provisioning/jobs/migrate/worker.go b/pkg/registry/apis/provisioning/jobs/migrate/worker.go
index 625b49ea32c..1293f18afa4 100644
--- a/pkg/registry/apis/provisioning/jobs/migrate/worker.go
+++ b/pkg/registry/apis/provisioning/jobs/migrate/worker.go
@@ -4,7 +4,7 @@ import (
"context"
"errors"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/jobs"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
"github.com/grafana/grafana/pkg/storage/legacysql/dualwrite"
diff --git a/pkg/registry/apis/provisioning/jobs/migrate/worker_test.go b/pkg/registry/apis/provisioning/jobs/migrate/worker_test.go
index e7d8c7c6175..3993b422492 100644
--- a/pkg/registry/apis/provisioning/jobs/migrate/worker_test.go
+++ b/pkg/registry/apis/provisioning/jobs/migrate/worker_test.go
@@ -9,7 +9,7 @@ import (
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/jobs"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository/local"
diff --git a/pkg/registry/apis/provisioning/jobs/move/worker.go b/pkg/registry/apis/provisioning/jobs/move/worker.go
index 5c2bba7e4e4..b2ff1912bd5 100644
--- a/pkg/registry/apis/provisioning/jobs/move/worker.go
+++ b/pkg/registry/apis/provisioning/jobs/move/worker.go
@@ -9,7 +9,7 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/jobs"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/resources"
diff --git a/pkg/registry/apis/provisioning/jobs/move/worker_test.go b/pkg/registry/apis/provisioning/jobs/move/worker_test.go
index 3be6d2d4619..c398cedc830 100644
--- a/pkg/registry/apis/provisioning/jobs/move/worker_test.go
+++ b/pkg/registry/apis/provisioning/jobs/move/worker_test.go
@@ -11,7 +11,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/jobs"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/resources"
diff --git a/pkg/registry/apis/provisioning/jobs/persistentstore.go b/pkg/registry/apis/provisioning/jobs/persistentstore.go
index abfdf2cecc7..e172e106ecf 100644
--- a/pkg/registry/apis/provisioning/jobs/persistentstore.go
+++ b/pkg/registry/apis/provisioning/jobs/persistentstore.go
@@ -18,8 +18,8 @@ import (
"k8s.io/apiserver/pkg/registry/rest"
"github.com/grafana/grafana-app-sdk/logging"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/apimachinery/identity"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/apifmt"
)
diff --git a/pkg/registry/apis/provisioning/jobs/progress.go b/pkg/registry/apis/provisioning/jobs/progress.go
index 081ac9a3b86..4f7bb2ad53d 100644
--- a/pkg/registry/apis/provisioning/jobs/progress.go
+++ b/pkg/registry/apis/provisioning/jobs/progress.go
@@ -7,7 +7,7 @@ import (
"time"
"github.com/grafana/grafana-app-sdk/logging"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
)
diff --git a/pkg/registry/apis/provisioning/jobs/progress_fn_mock.go b/pkg/registry/apis/provisioning/jobs/progress_fn_mock.go
index fd634d60844..8c68bec33f7 100644
--- a/pkg/registry/apis/provisioning/jobs/progress_fn_mock.go
+++ b/pkg/registry/apis/provisioning/jobs/progress_fn_mock.go
@@ -5,7 +5,7 @@ package jobs
import (
context "context"
- v0alpha1 "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ v0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
mock "github.com/stretchr/testify/mock"
)
diff --git a/pkg/registry/apis/provisioning/jobs/queue.go b/pkg/registry/apis/provisioning/jobs/queue.go
index 32b935e4be8..3a9757d93a1 100644
--- a/pkg/registry/apis/provisioning/jobs/queue.go
+++ b/pkg/registry/apis/provisioning/jobs/queue.go
@@ -3,7 +3,7 @@ package jobs
import (
"context"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
)
diff --git a/pkg/registry/apis/provisioning/jobs/queue_mock.go b/pkg/registry/apis/provisioning/jobs/queue_mock.go
index 41552749cad..37fd66f214b 100644
--- a/pkg/registry/apis/provisioning/jobs/queue_mock.go
+++ b/pkg/registry/apis/provisioning/jobs/queue_mock.go
@@ -5,7 +5,7 @@ package jobs
import (
context "context"
- v0alpha1 "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ v0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
mock "github.com/stretchr/testify/mock"
)
diff --git a/pkg/registry/apis/provisioning/jobs/store_mock.go b/pkg/registry/apis/provisioning/jobs/store_mock.go
index 8a6bb364f80..114a621561e 100644
--- a/pkg/registry/apis/provisioning/jobs/store_mock.go
+++ b/pkg/registry/apis/provisioning/jobs/store_mock.go
@@ -5,7 +5,7 @@ package jobs
import (
context "context"
- v0alpha1 "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ v0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
mock "github.com/stretchr/testify/mock"
)
diff --git a/pkg/registry/apis/provisioning/jobs/sync/changes.go b/pkg/registry/apis/provisioning/jobs/sync/changes.go
index 4c1f5db9136..4555e670a47 100644
--- a/pkg/registry/apis/provisioning/jobs/sync/changes.go
+++ b/pkg/registry/apis/provisioning/jobs/sync/changes.go
@@ -5,7 +5,7 @@ import (
"fmt"
"strings"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/resources"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/safepath"
diff --git a/pkg/registry/apis/provisioning/jobs/sync/changes_test.go b/pkg/registry/apis/provisioning/jobs/sync/changes_test.go
index d2a31696688..d14e9f0c872 100644
--- a/pkg/registry/apis/provisioning/jobs/sync/changes_test.go
+++ b/pkg/registry/apis/provisioning/jobs/sync/changes_test.go
@@ -8,7 +8,7 @@ import (
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/resources"
)
diff --git a/pkg/registry/apis/provisioning/jobs/sync/full_test.go b/pkg/registry/apis/provisioning/jobs/sync/full_test.go
index 7524fe27f2a..3885700b331 100644
--- a/pkg/registry/apis/provisioning/jobs/sync/full_test.go
+++ b/pkg/registry/apis/provisioning/jobs/sync/full_test.go
@@ -6,7 +6,7 @@ import (
"fmt"
"testing"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/jobs"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/resources"
diff --git a/pkg/registry/apis/provisioning/jobs/sync/repository_patch_fn_mock.go b/pkg/registry/apis/provisioning/jobs/sync/repository_patch_fn_mock.go
index fb44f7eda29..97a5c70c05b 100644
--- a/pkg/registry/apis/provisioning/jobs/sync/repository_patch_fn_mock.go
+++ b/pkg/registry/apis/provisioning/jobs/sync/repository_patch_fn_mock.go
@@ -5,7 +5,7 @@ package sync
import (
context "context"
- v0alpha1 "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ v0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
mock "github.com/stretchr/testify/mock"
)
diff --git a/pkg/registry/apis/provisioning/jobs/sync/sync.go b/pkg/registry/apis/provisioning/jobs/sync/sync.go
index 9aca33ebcd3..a82423ecbd7 100644
--- a/pkg/registry/apis/provisioning/jobs/sync/sync.go
+++ b/pkg/registry/apis/provisioning/jobs/sync/sync.go
@@ -4,7 +4,7 @@ import (
"context"
"fmt"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/jobs"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/resources"
diff --git a/pkg/registry/apis/provisioning/jobs/sync/sync_test.go b/pkg/registry/apis/provisioning/jobs/sync/sync_test.go
index f7bb4e0c961..c0398925b04 100644
--- a/pkg/registry/apis/provisioning/jobs/sync/sync_test.go
+++ b/pkg/registry/apis/provisioning/jobs/sync/sync_test.go
@@ -5,7 +5,7 @@ import (
"fmt"
"testing"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/jobs"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/resources"
diff --git a/pkg/registry/apis/provisioning/jobs/sync/syncer_mock.go b/pkg/registry/apis/provisioning/jobs/sync/syncer_mock.go
index 3eb3e4de7bb..0dedab34cc1 100644
--- a/pkg/registry/apis/provisioning/jobs/sync/syncer_mock.go
+++ b/pkg/registry/apis/provisioning/jobs/sync/syncer_mock.go
@@ -12,7 +12,7 @@ import (
resources "github.com/grafana/grafana/pkg/registry/apis/provisioning/resources"
- v0alpha1 "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ v0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
)
// MockSyncer is an autogenerated mock type for the Syncer type
diff --git a/pkg/registry/apis/provisioning/jobs/sync/worker.go b/pkg/registry/apis/provisioning/jobs/sync/worker.go
index 5b8eb701b34..66e96673051 100644
--- a/pkg/registry/apis/provisioning/jobs/sync/worker.go
+++ b/pkg/registry/apis/provisioning/jobs/sync/worker.go
@@ -5,7 +5,7 @@ import (
"fmt"
"github.com/grafana/grafana-app-sdk/logging"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/jobs"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/resources"
diff --git a/pkg/registry/apis/provisioning/jobs/sync/worker_test.go b/pkg/registry/apis/provisioning/jobs/sync/worker_test.go
index 02e152afb6a..5b8bf67166f 100644
--- a/pkg/registry/apis/provisioning/jobs/sync/worker_test.go
+++ b/pkg/registry/apis/provisioning/jobs/sync/worker_test.go
@@ -5,7 +5,7 @@ import (
"errors"
"testing"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/jobs"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/resources"
diff --git a/pkg/registry/apis/provisioning/jobs/worker_mock.go b/pkg/registry/apis/provisioning/jobs/worker_mock.go
index b264b258d1c..60a710016d5 100644
--- a/pkg/registry/apis/provisioning/jobs/worker_mock.go
+++ b/pkg/registry/apis/provisioning/jobs/worker_mock.go
@@ -8,7 +8,7 @@ import (
repository "github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
mock "github.com/stretchr/testify/mock"
- v0alpha1 "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ v0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
)
// MockWorker is an autogenerated mock type for the Worker type
diff --git a/pkg/registry/apis/provisioning/list.go b/pkg/registry/apis/provisioning/list.go
index 202a063736f..ddd851b1bb7 100644
--- a/pkg/registry/apis/provisioning/list.go
+++ b/pkg/registry/apis/provisioning/list.go
@@ -10,7 +10,7 @@ import (
"k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/apiserver/pkg/registry/rest"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/resources"
)
diff --git a/pkg/registry/apis/provisioning/refs.go b/pkg/registry/apis/provisioning/refs.go
index 47fe2b24324..8eed640fdd1 100644
--- a/pkg/registry/apis/provisioning/refs.go
+++ b/pkg/registry/apis/provisioning/refs.go
@@ -9,7 +9,7 @@ import (
"k8s.io/apiserver/pkg/registry/rest"
"github.com/grafana/grafana-app-sdk/logging"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
)
diff --git a/pkg/registry/apis/provisioning/register.go b/pkg/registry/apis/provisioning/register.go
index 9bccd675dd5..48656eba416 100644
--- a/pkg/registry/apis/provisioning/register.go
+++ b/pkg/registry/apis/provisioning/register.go
@@ -29,15 +29,15 @@ import (
"github.com/grafana/grafana-app-sdk/logging"
dashboard "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v0alpha1"
folders "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
+ clientset "github.com/grafana/grafana/apps/provisioning/pkg/generated/clientset/versioned"
+ client "github.com/grafana/grafana/apps/provisioning/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1"
+ informers "github.com/grafana/grafana/apps/provisioning/pkg/generated/informers/externalversions"
+ listers "github.com/grafana/grafana/apps/provisioning/pkg/generated/listers/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/apimachinery/identity"
apiutils "github.com/grafana/grafana/pkg/apimachinery/utils"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/apiserver/readonly"
grafanaregistry "github.com/grafana/grafana/pkg/apiserver/registry/generic"
- clientset "github.com/grafana/grafana/pkg/generated/clientset/versioned"
- client "github.com/grafana/grafana/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1"
- informers "github.com/grafana/grafana/pkg/generated/informers/externalversions"
- listers "github.com/grafana/grafana/pkg/generated/listers/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/infra/usagestats"
"github.com/grafana/grafana/pkg/registry/apis/dashboard/legacy"
@@ -680,7 +680,7 @@ func (b *APIBuilder) PostProcessOpenAPI(oas *spec3.OpenAPI) (*spec3.OpenAPI, err
repoprefix := root + "namespaces/{namespace}/repositories/{name}"
defs := b.GetOpenAPIDefinitions()(func(path string) spec.Ref { return spec.Ref{} })
- defsBase := "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1."
+ defsBase := "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1."
refsBase := "com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1."
sub := oas.Paths.Paths[repoprefix+"/test"]
diff --git a/pkg/registry/apis/provisioning/repository/config_repository_mock.go b/pkg/registry/apis/provisioning/repository/config_repository_mock.go
index 40ea3dcd3de..38d2f4617ca 100644
--- a/pkg/registry/apis/provisioning/repository/config_repository_mock.go
+++ b/pkg/registry/apis/provisioning/repository/config_repository_mock.go
@@ -8,7 +8,7 @@ import (
mock "github.com/stretchr/testify/mock"
field "k8s.io/apimachinery/pkg/util/validation/field"
- v0alpha1 "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ v0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
)
// MockConfigRepository is an autogenerated mock type for the Repository type
diff --git a/pkg/registry/apis/provisioning/repository/git/git_repository_mock.go b/pkg/registry/apis/provisioning/repository/git/git_repository_mock.go
index 4b914b50f19..81b4eecf671 100644
--- a/pkg/registry/apis/provisioning/repository/git/git_repository_mock.go
+++ b/pkg/registry/apis/provisioning/repository/git/git_repository_mock.go
@@ -10,7 +10,7 @@ import (
repository "github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
- v0alpha1 "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ v0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
)
// MockGitRepository is an autogenerated mock type for the GitRepository type
diff --git a/pkg/registry/apis/provisioning/repository/git/mutator.go b/pkg/registry/apis/provisioning/repository/git/mutator.go
index e68940fafb8..c4eded27f44 100644
--- a/pkg/registry/apis/provisioning/repository/git/mutator.go
+++ b/pkg/registry/apis/provisioning/repository/git/mutator.go
@@ -7,7 +7,7 @@ import (
"k8s.io/apimachinery/pkg/runtime"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/controller"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/secrets"
)
diff --git a/pkg/registry/apis/provisioning/repository/git/mutator_test.go b/pkg/registry/apis/provisioning/repository/git/mutator_test.go
index 87196d6bcb0..de52ab0c0f6 100644
--- a/pkg/registry/apis/provisioning/repository/git/mutator_test.go
+++ b/pkg/registry/apis/provisioning/repository/git/mutator_test.go
@@ -5,7 +5,7 @@ import (
"errors"
"testing"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/secrets"
"github.com/stretchr/testify/assert"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
diff --git a/pkg/registry/apis/provisioning/repository/git/repository.go b/pkg/registry/apis/provisioning/repository/git/repository.go
index 7ca9ed8998e..41c3a328788 100644
--- a/pkg/registry/apis/provisioning/repository/git/repository.go
+++ b/pkg/registry/apis/provisioning/repository/git/repository.go
@@ -16,7 +16,7 @@ import (
"k8s.io/apimachinery/pkg/util/validation/field"
"github.com/grafana/grafana-app-sdk/logging"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/safepath"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/secrets"
diff --git a/pkg/registry/apis/provisioning/repository/git/repository_test.go b/pkg/registry/apis/provisioning/repository/git/repository_test.go
index f70346825f6..4236352f94c 100644
--- a/pkg/registry/apis/provisioning/repository/git/repository_test.go
+++ b/pkg/registry/apis/provisioning/repository/git/repository_test.go
@@ -12,7 +12,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/validation/field"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/secrets"
"github.com/grafana/nanogit"
diff --git a/pkg/registry/apis/provisioning/repository/git/staged_test.go b/pkg/registry/apis/provisioning/repository/git/staged_test.go
index 77c1d8b5edb..2b18fd3445f 100644
--- a/pkg/registry/apis/provisioning/repository/git/staged_test.go
+++ b/pkg/registry/apis/provisioning/repository/git/staged_test.go
@@ -8,7 +8,7 @@ import (
"testing"
"time"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
"github.com/grafana/nanogit"
"github.com/grafana/nanogit/mocks"
diff --git a/pkg/registry/apis/provisioning/repository/github/github_repository_mock.go b/pkg/registry/apis/provisioning/repository/github/github_repository_mock.go
index 5ccd5d2e319..7b5509935f1 100644
--- a/pkg/registry/apis/provisioning/repository/github/github_repository_mock.go
+++ b/pkg/registry/apis/provisioning/repository/github/github_repository_mock.go
@@ -10,7 +10,7 @@ import (
repository "github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
- v0alpha1 "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ v0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
)
// MockGithubRepository is an autogenerated mock type for the GithubRepository type
diff --git a/pkg/registry/apis/provisioning/repository/github/mutator.go b/pkg/registry/apis/provisioning/repository/github/mutator.go
index 4aec95aa7d6..b0ee0e54a24 100644
--- a/pkg/registry/apis/provisioning/repository/github/mutator.go
+++ b/pkg/registry/apis/provisioning/repository/github/mutator.go
@@ -6,7 +6,7 @@ import (
"k8s.io/apimachinery/pkg/runtime"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/controller"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/secrets"
)
diff --git a/pkg/registry/apis/provisioning/repository/github/mutator_test.go b/pkg/registry/apis/provisioning/repository/github/mutator_test.go
index a6bbbe22c11..5094ed07206 100644
--- a/pkg/registry/apis/provisioning/repository/github/mutator_test.go
+++ b/pkg/registry/apis/provisioning/repository/github/mutator_test.go
@@ -5,7 +5,7 @@ import (
"errors"
"testing"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/secrets"
"github.com/stretchr/testify/assert"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
diff --git a/pkg/registry/apis/provisioning/repository/github/repository.go b/pkg/registry/apis/provisioning/repository/github/repository.go
index d2da1de41d1..ac95e54312f 100644
--- a/pkg/registry/apis/provisioning/repository/github/repository.go
+++ b/pkg/registry/apis/provisioning/repository/github/repository.go
@@ -10,7 +10,7 @@ import (
"github.com/grafana/grafana-app-sdk/logging"
"k8s.io/apimachinery/pkg/util/validation/field"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository/git"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/safepath"
diff --git a/pkg/registry/apis/provisioning/repository/github/repository_test.go b/pkg/registry/apis/provisioning/repository/github/repository_test.go
index 962a7a4e2b1..3a11127e9da 100644
--- a/pkg/registry/apis/provisioning/repository/github/repository_test.go
+++ b/pkg/registry/apis/provisioning/repository/github/repository_test.go
@@ -14,7 +14,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
field "k8s.io/apimachinery/pkg/util/validation/field"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository/git"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/secrets"
diff --git a/pkg/registry/apis/provisioning/repository/local/local.go b/pkg/registry/apis/provisioning/repository/local/local.go
index 0ab620e6639..98413cc1094 100644
--- a/pkg/registry/apis/provisioning/repository/local/local.go
+++ b/pkg/registry/apis/provisioning/repository/local/local.go
@@ -22,7 +22,7 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/validation/field"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/safepath"
)
diff --git a/pkg/registry/apis/provisioning/repository/local/local_test.go b/pkg/registry/apis/provisioning/repository/local/local_test.go
index 77a0b99fa09..378a3170478 100644
--- a/pkg/registry/apis/provisioning/repository/local/local_test.go
+++ b/pkg/registry/apis/provisioning/repository/local/local_test.go
@@ -18,7 +18,7 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema"
field "k8s.io/apimachinery/pkg/util/validation/field"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
)
diff --git a/pkg/registry/apis/provisioning/repository/reader_mock.go b/pkg/registry/apis/provisioning/repository/reader_mock.go
index dd6a73ab567..c7f56f5847c 100644
--- a/pkg/registry/apis/provisioning/repository/reader_mock.go
+++ b/pkg/registry/apis/provisioning/repository/reader_mock.go
@@ -8,7 +8,7 @@ import (
mock "github.com/stretchr/testify/mock"
field "k8s.io/apimachinery/pkg/util/validation/field"
- v0alpha1 "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ v0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
)
// MockReader is an autogenerated mock type for the Reader type
diff --git a/pkg/registry/apis/provisioning/repository/repository.go b/pkg/registry/apis/provisioning/repository/repository.go
index 718660c9fab..5608366060b 100644
--- a/pkg/registry/apis/provisioning/repository/repository.go
+++ b/pkg/registry/apis/provisioning/repository/repository.go
@@ -8,7 +8,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/validation/field"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
)
// FIXME: the name of the mock is different because there is another generated mock for Repository
diff --git a/pkg/registry/apis/provisioning/repository/repository_mock.go b/pkg/registry/apis/provisioning/repository/repository_mock.go
index 05fc5a3832a..e78a7467884 100644
--- a/pkg/registry/apis/provisioning/repository/repository_mock.go
+++ b/pkg/registry/apis/provisioning/repository/repository_mock.go
@@ -10,7 +10,7 @@ import (
mock "github.com/stretchr/testify/mock"
- v0alpha1 "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ v0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
)
// MockRepository is an autogenerated mock type for the Repository type
diff --git a/pkg/registry/apis/provisioning/repository/staged_repository_mock.go b/pkg/registry/apis/provisioning/repository/staged_repository_mock.go
index 85bfb4f3012..bd5b8785326 100644
--- a/pkg/registry/apis/provisioning/repository/staged_repository_mock.go
+++ b/pkg/registry/apis/provisioning/repository/staged_repository_mock.go
@@ -8,7 +8,7 @@ import (
mock "github.com/stretchr/testify/mock"
field "k8s.io/apimachinery/pkg/util/validation/field"
- v0alpha1 "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ v0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
)
// MockStagedRepository is an autogenerated mock type for the StagedRepository type
diff --git a/pkg/registry/apis/provisioning/repository/test.go b/pkg/registry/apis/provisioning/repository/test.go
index f8b72d5b2af..403ac24fa38 100644
--- a/pkg/registry/apis/provisioning/repository/test.go
+++ b/pkg/registry/apis/provisioning/repository/test.go
@@ -9,7 +9,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/validation/field"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
)
// Tester is a struct that implements the Tester interface
diff --git a/pkg/registry/apis/provisioning/repository/test_test.go b/pkg/registry/apis/provisioning/repository/test_test.go
index 9168d529ea0..56d73e13a90 100644
--- a/pkg/registry/apis/provisioning/repository/test_test.go
+++ b/pkg/registry/apis/provisioning/repository/test_test.go
@@ -11,7 +11,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/validation/field"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
)
func TestValidateRepository(t *testing.T) {
diff --git a/pkg/registry/apis/provisioning/repository/versioned_mock.go b/pkg/registry/apis/provisioning/repository/versioned_mock.go
index dc711f10f30..36ef2fe9a96 100644
--- a/pkg/registry/apis/provisioning/repository/versioned_mock.go
+++ b/pkg/registry/apis/provisioning/repository/versioned_mock.go
@@ -5,7 +5,7 @@ package repository
import (
context "context"
- v0alpha1 "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ v0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
mock "github.com/stretchr/testify/mock"
)
diff --git a/pkg/registry/apis/provisioning/repository/workflows.go b/pkg/registry/apis/provisioning/repository/workflows.go
index 9750bde0962..48ba1319a62 100644
--- a/pkg/registry/apis/provisioning/repository/workflows.go
+++ b/pkg/registry/apis/provisioning/repository/workflows.go
@@ -3,7 +3,7 @@ package repository
import (
apierrors "k8s.io/apimachinery/pkg/api/errors"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
)
func IsWriteAllowed(repo *provisioning.Repository, ref string) error {
diff --git a/pkg/registry/apis/provisioning/repository/workflows_test.go b/pkg/registry/apis/provisioning/repository/workflows_test.go
index 6f2e337494d..995c85e0eb5 100644
--- a/pkg/registry/apis/provisioning/repository/workflows_test.go
+++ b/pkg/registry/apis/provisioning/repository/workflows_test.go
@@ -8,7 +8,7 @@ import (
"github.com/stretchr/testify/require"
apierrors "k8s.io/apimachinery/pkg/api/errors"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
)
func TestIsWriteAllowed(t *testing.T) {
diff --git a/pkg/registry/apis/provisioning/resources/dualwriter.go b/pkg/registry/apis/provisioning/resources/dualwriter.go
index 50efaa13048..78042c35a12 100644
--- a/pkg/registry/apis/provisioning/resources/dualwriter.go
+++ b/pkg/registry/apis/provisioning/resources/dualwriter.go
@@ -10,10 +10,10 @@ import (
authlib "github.com/grafana/authlib/types"
"github.com/grafana/grafana-app-sdk/logging"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/apimachinery/utils"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/safepath"
)
diff --git a/pkg/registry/apis/provisioning/resources/fileformat.go b/pkg/registry/apis/provisioning/resources/fileformat.go
index 0ed0b287199..b35afd74781 100644
--- a/pkg/registry/apis/provisioning/resources/fileformat.go
+++ b/pkg/registry/apis/provisioning/resources/fileformat.go
@@ -14,7 +14,7 @@ import (
"github.com/grafana/grafana-app-sdk/logging"
dashboard "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v0alpha1"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
)
diff --git a/pkg/registry/apis/provisioning/resources/fileformat_test.go b/pkg/registry/apis/provisioning/resources/fileformat_test.go
index 611578abb45..3013927edc9 100644
--- a/pkg/registry/apis/provisioning/resources/fileformat_test.go
+++ b/pkg/registry/apis/provisioning/resources/fileformat_test.go
@@ -10,7 +10,7 @@ import (
"github.com/stretchr/testify/require"
"k8s.io/apimachinery/pkg/runtime/schema"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
)
diff --git a/pkg/registry/apis/provisioning/resources/id.go b/pkg/registry/apis/provisioning/resources/id.go
index b32355b4b5f..2ffaa0cc5bc 100644
--- a/pkg/registry/apis/provisioning/resources/id.go
+++ b/pkg/registry/apis/provisioning/resources/id.go
@@ -5,7 +5,7 @@ import (
"encoding/base64"
"strings"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/safepath"
)
diff --git a/pkg/registry/apis/provisioning/resources/object.go b/pkg/registry/apis/provisioning/resources/object.go
index 9faa4412fce..a30694417bb 100644
--- a/pkg/registry/apis/provisioning/resources/object.go
+++ b/pkg/registry/apis/provisioning/resources/object.go
@@ -8,8 +8,8 @@ import (
dashboard "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v0alpha1"
folders "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/apimachinery/utils"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/dashboard/legacy"
"github.com/grafana/grafana/pkg/storage/legacysql/dualwrite"
"github.com/grafana/grafana/pkg/storage/unified/resource"
diff --git a/pkg/registry/apis/provisioning/resources/parser.go b/pkg/registry/apis/provisioning/resources/parser.go
index 352e92fa27a..2b3f0c2deac 100644
--- a/pkg/registry/apis/provisioning/resources/parser.go
+++ b/pkg/registry/apis/provisioning/resources/parser.go
@@ -16,10 +16,10 @@ import (
"github.com/grafana/grafana-app-sdk/logging"
dashboard "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v0alpha1"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/apimachinery/utils"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/safepath"
"github.com/grafana/grafana/pkg/util"
diff --git a/pkg/registry/apis/provisioning/resources/parser_test.go b/pkg/registry/apis/provisioning/resources/parser_test.go
index d718445fc57..61f374a8e05 100644
--- a/pkg/registry/apis/provisioning/resources/parser_test.go
+++ b/pkg/registry/apis/provisioning/resources/parser_test.go
@@ -9,7 +9,7 @@ import (
dashboardV0 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v0alpha1"
dashboardV1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
)
diff --git a/pkg/registry/apis/provisioning/resources/repository.go b/pkg/registry/apis/provisioning/resources/repository.go
index d12747b7bc8..12747b9834d 100644
--- a/pkg/registry/apis/provisioning/resources/repository.go
+++ b/pkg/registry/apis/provisioning/resources/repository.go
@@ -9,8 +9,8 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/apimachinery/utils"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/safepath"
)
diff --git a/pkg/registry/apis/provisioning/resources/repository_resources_mock.go b/pkg/registry/apis/provisioning/resources/repository_resources_mock.go
index 9ff7171f68c..978d91f1d4d 100644
--- a/pkg/registry/apis/provisioning/resources/repository_resources_mock.go
+++ b/pkg/registry/apis/provisioning/resources/repository_resources_mock.go
@@ -10,7 +10,7 @@ import (
unstructured "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
- v0alpha1 "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ v0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
)
// MockRepositoryResources is an autogenerated mock type for the RepositoryResources type
diff --git a/pkg/registry/apis/provisioning/resources/resource_lister_mock.go b/pkg/registry/apis/provisioning/resources/resource_lister_mock.go
index 29fb1712f51..2bf441cd93d 100644
--- a/pkg/registry/apis/provisioning/resources/resource_lister_mock.go
+++ b/pkg/registry/apis/provisioning/resources/resource_lister_mock.go
@@ -5,7 +5,7 @@ package resources
import (
context "context"
- v0alpha1 "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ v0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
mock "github.com/stretchr/testify/mock"
)
diff --git a/pkg/registry/apis/provisioning/resources/tree.go b/pkg/registry/apis/provisioning/resources/tree.go
index 427384e8753..ce92f104b8b 100644
--- a/pkg/registry/apis/provisioning/resources/tree.go
+++ b/pkg/registry/apis/provisioning/resources/tree.go
@@ -7,8 +7,8 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
folders "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/apimachinery/utils"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/safepath"
)
diff --git a/pkg/registry/apis/provisioning/routes.go b/pkg/registry/apis/provisioning/routes.go
index 5e222882bd0..749c4a8d2ed 100644
--- a/pkg/registry/apis/provisioning/routes.go
+++ b/pkg/registry/apis/provisioning/routes.go
@@ -12,7 +12,7 @@ import (
"k8s.io/kube-openapi/pkg/validation/spec"
authlib "github.com/grafana/authlib/types"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/services/apiserver/builder"
"github.com/grafana/grafana/pkg/storage/legacysql/dualwrite"
"github.com/grafana/grafana/pkg/util/errhttp"
diff --git a/pkg/registry/apis/provisioning/secrets/repository.go b/pkg/registry/apis/provisioning/secrets/repository.go
index 29a470dd993..09ba78e1389 100644
--- a/pkg/registry/apis/provisioning/secrets/repository.go
+++ b/pkg/registry/apis/provisioning/secrets/repository.go
@@ -6,7 +6,7 @@ import (
"strings"
"github.com/grafana/grafana-app-sdk/logging"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/secret"
"github.com/grafana/grafana/pkg/registry/apis/secret/contracts"
"github.com/grafana/grafana/pkg/services/featuremgmt"
diff --git a/pkg/registry/apis/provisioning/secrets/repository_secrets_mock.go b/pkg/registry/apis/provisioning/secrets/repository_secrets_mock.go
index 763422ef7c5..7108be570f2 100644
--- a/pkg/registry/apis/provisioning/secrets/repository_secrets_mock.go
+++ b/pkg/registry/apis/provisioning/secrets/repository_secrets_mock.go
@@ -5,7 +5,7 @@ package secrets
import (
context "context"
- v0alpha1 "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ v0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
mock "github.com/stretchr/testify/mock"
)
diff --git a/pkg/registry/apis/provisioning/secrets/repository_test.go b/pkg/registry/apis/provisioning/secrets/repository_test.go
index 028efd10853..bd53089fc75 100644
--- a/pkg/registry/apis/provisioning/secrets/repository_test.go
+++ b/pkg/registry/apis/provisioning/secrets/repository_test.go
@@ -5,7 +5,7 @@ import (
"errors"
"testing"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/secret/contracts"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/stretchr/testify/assert"
diff --git a/pkg/registry/apis/provisioning/test.go b/pkg/registry/apis/provisioning/test.go
index 9813c37a0ad..9abb1d38a7f 100644
--- a/pkg/registry/apis/provisioning/test.go
+++ b/pkg/registry/apis/provisioning/test.go
@@ -13,8 +13,8 @@ import (
"k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/apiserver/pkg/registry/rest"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
- client "github.com/grafana/grafana/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
+ client "github.com/grafana/grafana/apps/provisioning/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
)
diff --git a/pkg/registry/apis/provisioning/types.go b/pkg/registry/apis/provisioning/types.go
index d804643cd21..8c030b652e1 100644
--- a/pkg/registry/apis/provisioning/types.go
+++ b/pkg/registry/apis/provisioning/types.go
@@ -3,8 +3,8 @@ package provisioning
import (
"context"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
- client "github.com/grafana/grafana/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
+ client "github.com/grafana/grafana/apps/provisioning/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
)
diff --git a/pkg/registry/apis/provisioning/usage/usage.go b/pkg/registry/apis/provisioning/usage/usage.go
index 485c9fe19e4..b9a1ed938c9 100644
--- a/pkg/registry/apis/provisioning/usage/usage.go
+++ b/pkg/registry/apis/provisioning/usage/usage.go
@@ -9,8 +9,8 @@ import (
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apiserver/pkg/endpoints/request"
+ listers "github.com/grafana/grafana/apps/provisioning/pkg/generated/listers/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/apimachinery/identity"
- listers "github.com/grafana/grafana/pkg/generated/listers/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/infra/usagestats"
"github.com/grafana/grafana/pkg/storage/unified/resource"
diff --git a/pkg/registry/apis/provisioning/webhooks/mutator.go b/pkg/registry/apis/provisioning/webhooks/mutator.go
index 10ad8288503..b93677228b0 100644
--- a/pkg/registry/apis/provisioning/webhooks/mutator.go
+++ b/pkg/registry/apis/provisioning/webhooks/mutator.go
@@ -3,7 +3,7 @@ package webhooks
import (
"context"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/controller"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/secrets"
"k8s.io/apimachinery/pkg/runtime"
diff --git a/pkg/registry/apis/provisioning/webhooks/mutator_test.go b/pkg/registry/apis/provisioning/webhooks/mutator_test.go
index 07581398fdc..209d95d5284 100644
--- a/pkg/registry/apis/provisioning/webhooks/mutator_test.go
+++ b/pkg/registry/apis/provisioning/webhooks/mutator_test.go
@@ -5,7 +5,7 @@ import (
"errors"
"testing"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/secrets"
"github.com/stretchr/testify/assert"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
diff --git a/pkg/registry/apis/provisioning/webhooks/pullrequest/changes.go b/pkg/registry/apis/provisioning/webhooks/pullrequest/changes.go
index 4bd6d5bf844..12bc6815085 100644
--- a/pkg/registry/apis/provisioning/webhooks/pullrequest/changes.go
+++ b/pkg/registry/apis/provisioning/webhooks/pullrequest/changes.go
@@ -9,7 +9,7 @@ import (
"github.com/grafana/grafana-app-sdk/logging"
dashboard "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/cmd/grafana-cli/logger"
"github.com/grafana/grafana/pkg/infra/slugify"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/jobs"
diff --git a/pkg/registry/apis/provisioning/webhooks/pullrequest/changes_test.go b/pkg/registry/apis/provisioning/webhooks/pullrequest/changes_test.go
index d31198eb595..6692d6248f3 100644
--- a/pkg/registry/apis/provisioning/webhooks/pullrequest/changes_test.go
+++ b/pkg/registry/apis/provisioning/webhooks/pullrequest/changes_test.go
@@ -13,8 +13,8 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/apimachinery/utils"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/jobs"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/resources"
diff --git a/pkg/registry/apis/provisioning/webhooks/pullrequest/comment_test.go b/pkg/registry/apis/provisioning/webhooks/pullrequest/comment_test.go
index fdd8e3f6dd4..e7f1d686f9f 100644
--- a/pkg/registry/apis/provisioning/webhooks/pullrequest/comment_test.go
+++ b/pkg/registry/apis/provisioning/webhooks/pullrequest/comment_test.go
@@ -12,7 +12,7 @@ import (
"github.com/stretchr/testify/require"
"k8s.io/apimachinery/pkg/runtime/schema"
- "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/resources"
)
diff --git a/pkg/registry/apis/provisioning/webhooks/pullrequest/mock_evaluator.go b/pkg/registry/apis/provisioning/webhooks/pullrequest/mock_evaluator.go
index 9d328127af4..e0f57d8ce0d 100644
--- a/pkg/registry/apis/provisioning/webhooks/pullrequest/mock_evaluator.go
+++ b/pkg/registry/apis/provisioning/webhooks/pullrequest/mock_evaluator.go
@@ -10,7 +10,7 @@ import (
repository "github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
- v0alpha1 "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ v0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
)
// MockEvaluator is an autogenerated mock type for the Evaluator type
diff --git a/pkg/registry/apis/provisioning/webhooks/pullrequest/mock_pullrequest_repo.go b/pkg/registry/apis/provisioning/webhooks/pullrequest/mock_pullrequest_repo.go
index f8c4b9ba5a9..6803341ecbc 100644
--- a/pkg/registry/apis/provisioning/webhooks/pullrequest/mock_pullrequest_repo.go
+++ b/pkg/registry/apis/provisioning/webhooks/pullrequest/mock_pullrequest_repo.go
@@ -8,7 +8,7 @@ import (
repository "github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
mock "github.com/stretchr/testify/mock"
- v0alpha1 "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ v0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
)
// MockPullRequestRepo is an autogenerated mock type for the PullRequestRepo type
diff --git a/pkg/registry/apis/provisioning/webhooks/pullrequest/render.go b/pkg/registry/apis/provisioning/webhooks/pullrequest/render.go
index 897f73a2e74..a6de6435c7a 100644
--- a/pkg/registry/apis/provisioning/webhooks/pullrequest/render.go
+++ b/pkg/registry/apis/provisioning/webhooks/pullrequest/render.go
@@ -10,8 +10,8 @@ import (
"strings"
"time"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/apimachinery/identity"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/rendering"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
diff --git a/pkg/registry/apis/provisioning/webhooks/pullrequest/render_mock.go b/pkg/registry/apis/provisioning/webhooks/pullrequest/render_mock.go
index e2a4ed6db94..3c099d6e543 100644
--- a/pkg/registry/apis/provisioning/webhooks/pullrequest/render_mock.go
+++ b/pkg/registry/apis/provisioning/webhooks/pullrequest/render_mock.go
@@ -8,7 +8,7 @@ import (
mock "github.com/stretchr/testify/mock"
- v0alpha1 "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ v0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
)
// MockScreenshotRenderer is an autogenerated mock type for the ScreenshotRenderer type
diff --git a/pkg/registry/apis/provisioning/webhooks/pullrequest/render_test.go b/pkg/registry/apis/provisioning/webhooks/pullrequest/render_test.go
index 3cb2ac1c3c8..a54d12af1b6 100644
--- a/pkg/registry/apis/provisioning/webhooks/pullrequest/render_test.go
+++ b/pkg/registry/apis/provisioning/webhooks/pullrequest/render_test.go
@@ -12,7 +12,7 @@ import (
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/rendering"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
diff --git a/pkg/registry/apis/provisioning/webhooks/pullrequest/worker.go b/pkg/registry/apis/provisioning/webhooks/pullrequest/worker.go
index b52c4854b2a..1d838faa0d4 100644
--- a/pkg/registry/apis/provisioning/webhooks/pullrequest/worker.go
+++ b/pkg/registry/apis/provisioning/webhooks/pullrequest/worker.go
@@ -8,7 +8,7 @@ import (
apierrors "k8s.io/apimachinery/pkg/api/errors"
"github.com/grafana/grafana-app-sdk/logging"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/jobs"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/resources"
diff --git a/pkg/registry/apis/provisioning/webhooks/pullrequest/worker_test.go b/pkg/registry/apis/provisioning/webhooks/pullrequest/worker_test.go
index 4ba1cae7cb3..858a3675bfa 100644
--- a/pkg/registry/apis/provisioning/webhooks/pullrequest/worker_test.go
+++ b/pkg/registry/apis/provisioning/webhooks/pullrequest/worker_test.go
@@ -9,7 +9,7 @@ import (
"github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/jobs"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
)
diff --git a/pkg/registry/apis/provisioning/webhooks/register.go b/pkg/registry/apis/provisioning/webhooks/register.go
index 0452030f27f..7de867fed9a 100644
--- a/pkg/registry/apis/provisioning/webhooks/register.go
+++ b/pkg/registry/apis/provisioning/webhooks/register.go
@@ -7,7 +7,7 @@ import (
"strings"
"github.com/grafana/grafana-app-sdk/logging"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
provisioningapis "github.com/grafana/grafana/pkg/registry/apis/provisioning"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/controller"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/jobs"
diff --git a/pkg/registry/apis/provisioning/webhooks/render.go b/pkg/registry/apis/provisioning/webhooks/render.go
index 06a19923a8c..77cda32198e 100644
--- a/pkg/registry/apis/provisioning/webhooks/render.go
+++ b/pkg/registry/apis/provisioning/webhooks/render.go
@@ -15,7 +15,7 @@ import (
"k8s.io/apiserver/pkg/registry/rest"
"k8s.io/kube-openapi/pkg/spec3"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/cmd/grafana-cli/logger"
provisioningapis "github.com/grafana/grafana/pkg/registry/apis/provisioning"
"github.com/grafana/grafana/pkg/storage/unified/resource"
diff --git a/pkg/registry/apis/provisioning/webhooks/repository.go b/pkg/registry/apis/provisioning/webhooks/repository.go
index 4e08111dec5..c122bdba2f4 100644
--- a/pkg/registry/apis/provisioning/webhooks/repository.go
+++ b/pkg/registry/apis/provisioning/webhooks/repository.go
@@ -11,7 +11,7 @@ import (
"github.com/google/go-github/v70/github"
"github.com/google/uuid"
"github.com/grafana/grafana-app-sdk/logging"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
pgh "github.com/grafana/grafana/pkg/registry/apis/provisioning/repository/github"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/secrets"
diff --git a/pkg/registry/apis/provisioning/webhooks/repository_test.go b/pkg/registry/apis/provisioning/webhooks/repository_test.go
index 76de9f50f02..38b561fe0eb 100644
--- a/pkg/registry/apis/provisioning/webhooks/repository_test.go
+++ b/pkg/registry/apis/provisioning/webhooks/repository_test.go
@@ -14,7 +14,7 @@ import (
"strings"
"testing"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository/github"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/secrets"
"github.com/stretchr/testify/mock"
diff --git a/pkg/registry/apis/provisioning/webhooks/webhook.go b/pkg/registry/apis/provisioning/webhooks/webhook.go
index 4c1ef54ddfe..bf239f31e24 100644
--- a/pkg/registry/apis/provisioning/webhooks/webhook.go
+++ b/pkg/registry/apis/provisioning/webhooks/webhook.go
@@ -14,8 +14,8 @@ import (
"k8s.io/kube-openapi/pkg/spec3"
"github.com/grafana/grafana-app-sdk/logging"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/apimachinery/identity"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
provisioningapis "github.com/grafana/grafana/pkg/registry/apis/provisioning"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/webhooks/pullrequest"
diff --git a/pkg/services/authn/clients/provisioning.go b/pkg/services/authn/clients/provisioning.go
index d50a0ff600c..21031ea1031 100644
--- a/pkg/services/authn/clients/provisioning.go
+++ b/pkg/services/authn/clients/provisioning.go
@@ -7,7 +7,7 @@ import (
"time"
claims "github.com/grafana/authlib/types"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/services/authn"
)
diff --git a/pkg/services/authn/clients/provisioning_test.go b/pkg/services/authn/clients/provisioning_test.go
index 065e24a7343..02afae68347 100644
--- a/pkg/services/authn/clients/provisioning_test.go
+++ b/pkg/services/authn/clients/provisioning_test.go
@@ -11,7 +11,7 @@ import (
"github.com/stretchr/testify/require"
"github.com/grafana/authlib/types"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/services/authn"
)
diff --git a/pkg/services/live/features/watch.go b/pkg/services/live/features/watch.go
index 05a7f875518..51e6253ac17 100644
--- a/pkg/services/live/features/watch.go
+++ b/pkg/services/live/features/watch.go
@@ -11,7 +11,7 @@ import (
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/dynamic"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/authlib/types"
"github.com/grafana/grafana-app-sdk/logging"
diff --git a/pkg/tests/apis/openapi_snapshots/provisioning.grafana.app-v0alpha1.json b/pkg/tests/apis/openapi_snapshots/provisioning.grafana.app-v0alpha1.json
index 4aa160ab2e9..ab1e77d03b9 100644
--- a/pkg/tests/apis/openapi_snapshots/provisioning.grafana.app-v0alpha1.json
+++ b/pkg/tests/apis/openapi_snapshots/provisioning.grafana.app-v0alpha1.json
@@ -49,27 +49,27 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.JobList"
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.JobList"
}
},
"application/json;stream=watch": {
"schema": {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.JobList"
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.JobList"
}
},
"application/vnd.kubernetes.protobuf": {
"schema": {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.JobList"
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.JobList"
}
},
"application/vnd.kubernetes.protobuf;stream=watch": {
"schema": {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.JobList"
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.JobList"
}
},
"application/yaml": {
"schema": {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.JobList"
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.JobList"
}
}
}
@@ -207,17 +207,17 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.Job"
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Job"
}
},
"application/vnd.kubernetes.protobuf": {
"schema": {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.Job"
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Job"
}
},
"application/yaml": {
"schema": {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.Job"
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Job"
}
}
}
@@ -367,27 +367,27 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.RepositoryList"
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.RepositoryList"
}
},
"application/json;stream=watch": {
"schema": {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.RepositoryList"
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.RepositoryList"
}
},
"application/vnd.kubernetes.protobuf": {
"schema": {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.RepositoryList"
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.RepositoryList"
}
},
"application/vnd.kubernetes.protobuf;stream=watch": {
"schema": {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.RepositoryList"
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.RepositoryList"
}
},
"application/yaml": {
"schema": {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.RepositoryList"
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.RepositoryList"
}
}
}
@@ -439,17 +439,17 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.Repository"
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Repository"
}
},
"application/vnd.kubernetes.protobuf": {
"schema": {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.Repository"
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Repository"
}
},
"application/yaml": {
"schema": {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.Repository"
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Repository"
}
}
},
@@ -461,17 +461,17 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.Repository"
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Repository"
}
},
"application/vnd.kubernetes.protobuf": {
"schema": {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.Repository"
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Repository"
}
},
"application/yaml": {
"schema": {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.Repository"
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Repository"
}
}
}
@@ -481,17 +481,17 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.Repository"
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Repository"
}
},
"application/vnd.kubernetes.protobuf": {
"schema": {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.Repository"
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Repository"
}
},
"application/yaml": {
"schema": {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.Repository"
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Repository"
}
}
}
@@ -501,17 +501,17 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.Repository"
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Repository"
}
},
"application/vnd.kubernetes.protobuf": {
"schema": {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.Repository"
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Repository"
}
},
"application/yaml": {
"schema": {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.Repository"
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Repository"
}
}
}
@@ -713,17 +713,17 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.Repository"
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Repository"
}
},
"application/vnd.kubernetes.protobuf": {
"schema": {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.Repository"
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Repository"
}
},
"application/yaml": {
"schema": {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.Repository"
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Repository"
}
}
}
@@ -775,17 +775,17 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.Repository"
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Repository"
}
},
"application/vnd.kubernetes.protobuf": {
"schema": {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.Repository"
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Repository"
}
},
"application/yaml": {
"schema": {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.Repository"
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Repository"
}
}
},
@@ -797,17 +797,17 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.Repository"
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Repository"
}
},
"application/vnd.kubernetes.protobuf": {
"schema": {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.Repository"
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Repository"
}
},
"application/yaml": {
"schema": {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.Repository"
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Repository"
}
}
}
@@ -817,17 +817,17 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.Repository"
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Repository"
}
},
"application/vnd.kubernetes.protobuf": {
"schema": {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.Repository"
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Repository"
}
},
"application/yaml": {
"schema": {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.Repository"
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Repository"
}
}
}
@@ -1017,17 +1017,17 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.Repository"
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Repository"
}
},
"application/vnd.kubernetes.protobuf": {
"schema": {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.Repository"
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Repository"
}
},
"application/yaml": {
"schema": {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.Repository"
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Repository"
}
}
}
@@ -1037,17 +1037,17 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.Repository"
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Repository"
}
},
"application/vnd.kubernetes.protobuf": {
"schema": {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.Repository"
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Repository"
}
},
"application/yaml": {
"schema": {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.Repository"
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Repository"
}
}
}
@@ -1225,7 +1225,7 @@
"content": {
"*/*": {
"schema": {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.ResourceWrapper"
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.ResourceWrapper"
}
}
}
@@ -1337,7 +1337,7 @@
"content": {
"*/*": {
"schema": {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.ResourceWrapper"
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.ResourceWrapper"
}
}
}
@@ -1449,7 +1449,7 @@
"content": {
"*/*": {
"schema": {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.ResourceWrapper"
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.ResourceWrapper"
}
}
}
@@ -1521,7 +1521,7 @@
"content": {
"*/*": {
"schema": {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.ResourceWrapper"
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.ResourceWrapper"
}
}
}
@@ -2024,7 +2024,7 @@
"content": {
"*/*": {
"schema": {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.ResourceList"
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.ResourceList"
}
}
}
@@ -2073,17 +2073,17 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.Repository"
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Repository"
}
},
"application/vnd.kubernetes.protobuf": {
"schema": {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.Repository"
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Repository"
}
},
"application/yaml": {
"schema": {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.Repository"
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Repository"
}
}
}
@@ -2135,17 +2135,17 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.Repository"
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Repository"
}
},
"application/vnd.kubernetes.protobuf": {
"schema": {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.Repository"
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Repository"
}
},
"application/yaml": {
"schema": {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.Repository"
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Repository"
}
}
},
@@ -2157,17 +2157,17 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.Repository"
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Repository"
}
},
"application/vnd.kubernetes.protobuf": {
"schema": {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.Repository"
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Repository"
}
},
"application/yaml": {
"schema": {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.Repository"
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Repository"
}
}
}
@@ -2177,17 +2177,17 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.Repository"
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Repository"
}
},
"application/vnd.kubernetes.protobuf": {
"schema": {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.Repository"
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Repository"
}
},
"application/yaml": {
"schema": {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.Repository"
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Repository"
}
}
}
@@ -2275,17 +2275,17 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.Repository"
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Repository"
}
},
"application/vnd.kubernetes.protobuf": {
"schema": {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.Repository"
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Repository"
}
},
"application/yaml": {
"schema": {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.Repository"
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Repository"
}
}
}
@@ -2295,17 +2295,17 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.Repository"
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Repository"
}
},
"application/vnd.kubernetes.protobuf": {
"schema": {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.Repository"
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Repository"
}
},
"application/yaml": {
"schema": {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.Repository"
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Repository"
}
}
}
@@ -2392,7 +2392,7 @@
"content": {
"*/*": {
"schema": {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.TestResults"
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.TestResults"
}
}
}
@@ -2441,7 +2441,7 @@
"content": {
"*/*": {
"schema": {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.WebhookResponse"
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.WebhookResponse"
}
}
}
@@ -2466,7 +2466,7 @@
"content": {
"*/*": {
"schema": {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.WebhookResponse"
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.WebhookResponse"
}
}
}
@@ -2571,6 +2571,1432 @@
},
"components": {
"schemas": {
+ "com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.BitbucketRepositoryConfig": {
+ "type": "object",
+ "required": [
+ "branch"
+ ],
+ "properties": {
+ "branch": {
+ "description": "The branch to use in the repository.",
+ "type": "string",
+ "default": ""
+ },
+ "encryptedToken": {
+ "description": "Token for accessing the repository, but encrypted. This is not possible to read back to a user decrypted.",
+ "type": "string",
+ "format": "byte",
+ "x-kubernetes-list-type": "atomic"
+ },
+ "path": {
+ "description": "Path is the subdirectory for the Grafana data. If specified, Grafana will ignore anything that is outside this directory in the repository. This is usually something like `grafana/`. Trailing and leading slash are not required. They are always added when needed. The path is relative to the root of the repository, regardless of the leading slash.\n\nWhen specifying something like `grafana-`, we will not look for `grafana-*`; we will only look for files under the directory `/grafana-/`. That means `/grafana-example.json` would not be found.",
+ "type": "string"
+ },
+ "token": {
+ "description": "Token for accessing the repository. If set, it will be encrypted into encryptedToken, then set to an empty string again.",
+ "type": "string"
+ },
+ "tokenUser": {
+ "description": "TokenUser is the user that will be used to access the repository if it's a personal access token.",
+ "type": "string"
+ },
+ "url": {
+ "description": "The repository URL (e.g. `https://bitbucket.org/example/test`).",
+ "type": "string"
+ }
+ }
+ },
+ "com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.DeleteJobOptions": {
+ "type": "object",
+ "properties": {
+ "paths": {
+ "description": "Paths to be deleted. Examples: - dashboard.json (for a file) - a/b/c/other-dashboard.json (for a file) - nested/deep/ (for a directory) FIXME: we should validate this in admission hooks",
+ "type": "array",
+ "items": {
+ "type": "string",
+ "default": ""
+ }
+ },
+ "ref": {
+ "description": "Ref to the branch or commit hash to delete from",
+ "type": "string"
+ },
+ "resources": {
+ "description": "Resources to delete This option has been created because currently the frontend does not use standarized app platform APIs. For performance and API consistency reasons, the preferred option is it to use the paths.",
+ "type": "array",
+ "items": {
+ "default": {},
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.ResourceRef"
+ }
+ ]
+ }
+ }
+ }
+ },
+ "com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.ErrorDetails": {
+ "type": "object",
+ "required": [
+ "type"
+ ],
+ "properties": {
+ "detail": {
+ "type": "string"
+ },
+ "field": {
+ "type": "string"
+ },
+ "type": {
+ "type": "string",
+ "default": ""
+ }
+ }
+ },
+ "com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.ExportJobOptions": {
+ "type": "object",
+ "properties": {
+ "branch": {
+ "description": "FIXME: we should validate this in admission hooks Target branch for export (only git)",
+ "type": "string"
+ },
+ "folder": {
+ "description": "The source folder (or empty) to export",
+ "type": "string"
+ },
+ "message": {
+ "description": "Message to use when committing the changes in a single commit",
+ "type": "string"
+ },
+ "path": {
+ "description": "FIXME: we should validate this in admission hooks Prefix in target file system",
+ "type": "string"
+ }
+ }
+ },
+ "com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.GitHubRepositoryConfig": {
+ "type": "object",
+ "required": [
+ "branch"
+ ],
+ "properties": {
+ "branch": {
+ "description": "The branch to use in the repository.",
+ "type": "string",
+ "default": ""
+ },
+ "encryptedToken": {
+ "description": "Token for accessing the repository, but encrypted. This is not possible to read back to a user decrypted.",
+ "type": "string",
+ "format": "byte",
+ "x-kubernetes-list-type": "atomic"
+ },
+ "generateDashboardPreviews": {
+ "description": "Whether we should show dashboard previews for pull requests. By default, this is false (i.e. we will not create previews).",
+ "type": "boolean"
+ },
+ "path": {
+ "description": "Path is the subdirectory for the Grafana data. If specified, Grafana will ignore anything that is outside this directory in the repository. This is usually something like `grafana/`. Trailing and leading slash are not required. They are always added when needed. The path is relative to the root of the repository, regardless of the leading slash.\n\nWhen specifying something like `grafana-`, we will not look for `grafana-*`; we will only look for files under the directory `/grafana-/`. That means `/grafana-example.json` would not be found.",
+ "type": "string"
+ },
+ "token": {
+ "description": "Token for accessing the repository. If set, it will be encrypted into encryptedToken, then set to an empty string again.",
+ "type": "string"
+ },
+ "url": {
+ "description": "The repository URL (e.g. `https://github.com/example/test`).",
+ "type": "string"
+ }
+ }
+ },
+ "com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.GitLabRepositoryConfig": {
+ "type": "object",
+ "required": [
+ "branch"
+ ],
+ "properties": {
+ "branch": {
+ "description": "The branch to use in the repository.",
+ "type": "string",
+ "default": ""
+ },
+ "encryptedToken": {
+ "description": "Token for accessing the repository, but encrypted. This is not possible to read back to a user decrypted.",
+ "type": "string",
+ "format": "byte",
+ "x-kubernetes-list-type": "atomic"
+ },
+ "path": {
+ "description": "Path is the subdirectory for the Grafana data. If specified, Grafana will ignore anything that is outside this directory in the repository. This is usually something like `grafana/`. Trailing and leading slash are not required. They are always added when needed. The path is relative to the root of the repository, regardless of the leading slash.\n\nWhen specifying something like `grafana-`, we will not look for `grafana-*`; we will only look for files under the directory `/grafana-/`. That means `/grafana-example.json` would not be found.",
+ "type": "string"
+ },
+ "token": {
+ "description": "Token for accessing the repository. If set, it will be encrypted into encryptedToken, then set to an empty string again.",
+ "type": "string"
+ },
+ "url": {
+ "description": "The repository URL (e.g. `https://gitlab.com/example/test`).",
+ "type": "string"
+ }
+ }
+ },
+ "com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.GitRepositoryConfig": {
+ "type": "object",
+ "required": [
+ "branch"
+ ],
+ "properties": {
+ "branch": {
+ "description": "The branch to use in the repository.",
+ "type": "string",
+ "default": ""
+ },
+ "encryptedToken": {
+ "description": "Token for accessing the repository, but encrypted. This is not possible to read back to a user decrypted.",
+ "type": "string",
+ "format": "byte",
+ "x-kubernetes-list-type": "atomic"
+ },
+ "path": {
+ "description": "Path is the subdirectory for the Grafana data. If specified, Grafana will ignore anything that is outside this directory in the repository. This is usually something like `grafana/`. Trailing and leading slash are not required. They are always added when needed. The path is relative to the root of the repository, regardless of the leading slash.\n\nWhen specifying something like `grafana-`, we will not look for `grafana-*`; we will only look for files under the directory `/grafana-/`. That means `/grafana-example.json` would not be found.",
+ "type": "string"
+ },
+ "token": {
+ "description": "Token for accessing the repository. If set, it will be encrypted into encryptedToken, then set to an empty string again.",
+ "type": "string"
+ },
+ "tokenUser": {
+ "description": "TokenUser is the user that will be used to access the repository if it's a personal access token.",
+ "type": "string"
+ },
+ "url": {
+ "description": "The repository URL (e.g. `https://github.com/example/test.git`).",
+ "type": "string"
+ }
+ }
+ },
+ "com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.HealthStatus": {
+ "type": "object",
+ "required": [
+ "healthy"
+ ],
+ "properties": {
+ "checked": {
+ "description": "When the health was checked last time",
+ "type": "integer",
+ "format": "int64"
+ },
+ "healthy": {
+ "description": "When not healthy, requests will not be executed",
+ "type": "boolean",
+ "default": false
+ },
+ "message": {
+ "description": "Summary messages (can be shown to users) Will only be populated when not healthy",
+ "type": "array",
+ "items": {
+ "type": "string",
+ "default": ""
+ },
+ "x-kubernetes-list-type": "atomic"
+ }
+ }
+ },
+ "com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Job": {
+ "description": "The repository name and type are stored as labels",
+ "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"
+ },
+ "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": {
+ "default": {},
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.JobSpec"
+ }
+ ]
+ },
+ "status": {
+ "default": {},
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.JobStatus"
+ }
+ ]
+ }
+ },
+ "x-kubernetes-group-version-kind": [
+ {
+ "group": "provisioning.grafana.app",
+ "kind": "Job",
+ "version": "v0alpha1"
+ }
+ ]
+ },
+ "com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.JobList": {
+ "type": "object",
+ "required": [
+ "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.provisioning.pkg.apis.provisioning.v0alpha1.Job"
+ }
+ ]
+ }
+ },
+ "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": "provisioning.grafana.app",
+ "kind": "JobList",
+ "version": "v0alpha1"
+ }
+ ]
+ },
+ "com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.JobResourceSummary": {
+ "type": "object",
+ "properties": {
+ "create": {
+ "type": "integer",
+ "format": "int64"
+ },
+ "delete": {
+ "type": "integer",
+ "format": "int64"
+ },
+ "error": {
+ "description": "Create or update (export)",
+ "type": "integer",
+ "format": "int64"
+ },
+ "errors": {
+ "description": "Report errors for this resource type This may not be an exhaustive list and recommend looking at the logs for more info",
+ "type": "array",
+ "items": {
+ "type": "string",
+ "default": ""
+ }
+ },
+ "group": {
+ "type": "string"
+ },
+ "noop": {
+ "description": "No action required (useful for sync)",
+ "type": "integer",
+ "format": "int64"
+ },
+ "resource": {
+ "type": "string"
+ },
+ "total": {
+ "type": "integer",
+ "format": "int64"
+ },
+ "update": {
+ "type": "integer",
+ "format": "int64"
+ },
+ "write": {
+ "type": "integer",
+ "format": "int64"
+ }
+ }
+ },
+ "com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.JobSpec": {
+ "type": "object",
+ "properties": {
+ "action": {
+ "description": "Possible enum values:\n - `\"delete\"` deletes files in the remote repository\n - `\"migrate\"` acts like JobActionExport, then JobActionPull. It also tries to preserve the history.\n - `\"move\"` moves files in the remote repository\n - `\"pr\"` adds additional useful information to a PR, such as comments with preview links and rendered images.\n - `\"pull\"` replicates the remote branch in the local copy of the repository.\n - `\"push\"` replicates the local copy of the repository in the remote branch.",
+ "type": "string",
+ "enum": [
+ "delete",
+ "migrate",
+ "move",
+ "pr",
+ "pull",
+ "push"
+ ]
+ },
+ "delete": {
+ "description": "Delete when the action is `delete`",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.DeleteJobOptions"
+ }
+ ]
+ },
+ "migrate": {
+ "description": "Required when the action is `migrate`",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.MigrateJobOptions"
+ }
+ ]
+ },
+ "move": {
+ "description": "Move when the action is `move`",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.MoveJobOptions"
+ }
+ ]
+ },
+ "pr": {
+ "description": "Pull request options",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.PullRequestJobOptions"
+ }
+ ]
+ },
+ "pull": {
+ "description": "Required when the action is `pull`",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.SyncJobOptions"
+ }
+ ]
+ },
+ "push": {
+ "description": "Required when the action is `push`",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.ExportJobOptions"
+ }
+ ]
+ },
+ "repository": {
+ "description": "The the repository reference (for now also in labels) This value is required, but will be popuplated from the job making the request",
+ "type": "string"
+ }
+ }
+ },
+ "com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.JobStatus": {
+ "description": "The job status",
+ "type": "object",
+ "properties": {
+ "errors": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "default": ""
+ }
+ },
+ "finished": {
+ "type": "integer",
+ "format": "int64"
+ },
+ "message": {
+ "type": "string"
+ },
+ "progress": {
+ "description": "Optional value 0-100 that can be set while running",
+ "type": "number",
+ "format": "double"
+ },
+ "started": {
+ "type": "integer",
+ "format": "int64"
+ },
+ "state": {
+ "description": "Possible enum values:\n - `\"error\"` Finished with errors\n - `\"pending\"` Job has been submitted, but not processed yet\n - `\"success\"` Finished with success\n - `\"warning\"` Finished with some non-critical errors\n - `\"working\"` The job is running",
+ "type": "string",
+ "enum": [
+ "error",
+ "pending",
+ "success",
+ "warning",
+ "working"
+ ]
+ },
+ "summary": {
+ "description": "Summary of processed actions",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.JobResourceSummary"
+ }
+ }
+ }
+ },
+ "com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.LocalRepositoryConfig": {
+ "type": "object",
+ "properties": {
+ "path": {
+ "type": "string"
+ }
+ }
+ },
+ "com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.MigrateJobOptions": {
+ "type": "object",
+ "properties": {
+ "history": {
+ "description": "Preserve history (if possible)",
+ "type": "boolean"
+ },
+ "message": {
+ "description": "Message to use when committing the changes in a single commit",
+ "type": "string"
+ }
+ }
+ },
+ "com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.MoveJobOptions": {
+ "type": "object",
+ "properties": {
+ "paths": {
+ "description": "Paths to be deleted. Examples: - dashboard.json (for a file) - a/b/c/other-dashboard.json (for a file) - nested/deep/ (for a directory) FIXME: we should validate this in admission hooks",
+ "type": "array",
+ "items": {
+ "type": "string",
+ "default": ""
+ }
+ },
+ "ref": {
+ "description": "Ref to the branch or commit hash that should move",
+ "type": "string"
+ },
+ "resources": {
+ "description": "Resources to move This option has been created because currently the frontend does not use standarized app platform APIs. For performance and API consistency reasons, the preferred option is it to use the paths.",
+ "type": "array",
+ "items": {
+ "default": {},
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.ResourceRef"
+ }
+ ]
+ }
+ },
+ "targetPath": {
+ "description": "Destination path for the move (e.g. \"new-location/\")",
+ "type": "string"
+ }
+ }
+ },
+ "com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.PullRequestJobOptions": {
+ "type": "object",
+ "properties": {
+ "hash": {
+ "description": "The specific commit hash that triggered this notice",
+ "type": "string"
+ },
+ "pr": {
+ "description": "Pull request number (when appropriate)",
+ "type": "integer",
+ "format": "int32"
+ },
+ "ref": {
+ "description": "The branch of commit hash",
+ "type": "string"
+ },
+ "url": {
+ "description": "URL to the originator (eg, PR URL)",
+ "type": "string"
+ }
+ }
+ },
+ "com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.RefItem": {
+ "type": "object",
+ "required": [
+ "name"
+ ],
+ "properties": {
+ "hash": {
+ "description": "The SHA hash of the commit this ref points to",
+ "type": "string"
+ },
+ "name": {
+ "description": "The name of the reference (branch or tag)",
+ "type": "string",
+ "default": ""
+ },
+ "refURL": {
+ "description": "The URL to the reference (branch or tag)",
+ "type": "string"
+ }
+ }
+ },
+ "com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.RefList": {
+ "type": "object",
+ "required": [
+ "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.provisioning.pkg.apis.provisioning.v0alpha1.RefItem"
+ }
+ ]
+ },
+ "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"
+ },
+ "metadata": {
+ "default": {},
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta"
+ }
+ ]
+ }
+ },
+ "x-kubernetes-group-version-kind": [
+ {
+ "group": "provisioning.grafana.app",
+ "kind": "RefList",
+ "version": "v0alpha1"
+ }
+ ]
+ },
+ "com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Repository": {
+ "description": "When this code is changed, make sure to update the code generation. As of writing, this can be done via the hack dir in the root of the repo: ./hack/update-codegen.sh provisioning If you've opened the generated files in this dir at some point in VSCode, you may also have to re-open them to clear errors.",
+ "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"
+ },
+ "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": {
+ "default": {},
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.RepositorySpec"
+ }
+ ]
+ },
+ "status": {
+ "default": {},
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.RepositoryStatus"
+ }
+ ]
+ }
+ },
+ "x-kubernetes-group-version-kind": [
+ {
+ "group": "provisioning.grafana.app",
+ "kind": "Repository",
+ "version": "v0alpha1"
+ }
+ ]
+ },
+ "com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.RepositoryList": {
+ "type": "object",
+ "required": [
+ "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.provisioning.pkg.apis.provisioning.v0alpha1.Repository"
+ }
+ ]
+ },
+ "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"
+ },
+ "metadata": {
+ "default": {},
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta"
+ }
+ ]
+ }
+ },
+ "x-kubernetes-group-version-kind": [
+ {
+ "group": "provisioning.grafana.app",
+ "kind": "RepositoryList",
+ "version": "v0alpha1"
+ }
+ ]
+ },
+ "com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.RepositorySpec": {
+ "type": "object",
+ "required": [
+ "title",
+ "workflows",
+ "sync",
+ "type"
+ ],
+ "properties": {
+ "bitbucket": {
+ "description": "The repository on Bitbucket. Mutually exclusive with local | github | git.",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.BitbucketRepositoryConfig"
+ }
+ ]
+ },
+ "description": {
+ "description": "Repository description",
+ "type": "string"
+ },
+ "git": {
+ "description": "The repository on Git. Mutually exclusive with local | github | git.",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.GitRepositoryConfig"
+ }
+ ]
+ },
+ "github": {
+ "description": "The repository on GitHub. Mutually exclusive with local | github | git.",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.GitHubRepositoryConfig"
+ }
+ ]
+ },
+ "gitlab": {
+ "description": "The repository on GitLab. Mutually exclusive with local | github | git.",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.GitLabRepositoryConfig"
+ }
+ ]
+ },
+ "local": {
+ "description": "The repository on the local file system. Mutually exclusive with local | github.",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.LocalRepositoryConfig"
+ }
+ ]
+ },
+ "sync": {
+ "description": "Sync settings -- how values are pulled from the repository into grafana",
+ "default": {},
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.SyncOptions"
+ }
+ ]
+ },
+ "title": {
+ "description": "The repository display name (shown in the UI)",
+ "type": "string",
+ "default": ""
+ },
+ "type": {
+ "description": "The repository type. When selected oneOf the values below should be non-nil\n\nPossible enum values:\n - `\"bitbucket\"`\n - `\"git\"`\n - `\"github\"`\n - `\"gitlab\"`\n - `\"local\"`",
+ "type": "string",
+ "default": "",
+ "enum": [
+ "bitbucket",
+ "git",
+ "github",
+ "gitlab",
+ "local"
+ ]
+ },
+ "workflows": {
+ "description": "UI driven Workflow that allow changes to the contends of the repository. The order is relevant for defining the precedence of the workflows. When empty, the repository does not support any edits (eg, readonly)",
+ "type": "array",
+ "items": {
+ "type": "string",
+ "default": "",
+ "enum": [
+ "branch",
+ "write"
+ ]
+ }
+ }
+ }
+ },
+ "com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.RepositoryStatus": {
+ "description": "The status of a Repository. This is expected never to be created by a kubectl call or similar, and is expected to rarely (if ever) be edited manually. As such, it is also a little less well structured than the spec, such as conditional-but-ever-present fields.",
+ "type": "object",
+ "required": [
+ "observedGeneration",
+ "health",
+ "sync",
+ "webhook"
+ ],
+ "properties": {
+ "health": {
+ "description": "This will get updated with the current health status (and updated periodically)",
+ "default": {},
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.HealthStatus"
+ }
+ ]
+ },
+ "observedGeneration": {
+ "description": "The generation of the spec last time reconciliation ran",
+ "type": "integer",
+ "format": "int64",
+ "default": 0
+ },
+ "stats": {
+ "description": "The object count when sync last ran",
+ "type": "array",
+ "items": {
+ "default": {},
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.ResourceCount"
+ }
+ ]
+ },
+ "x-kubernetes-list-type": "atomic"
+ },
+ "sync": {
+ "description": "Sync information with the last sync information",
+ "default": {},
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.SyncStatus"
+ }
+ ]
+ },
+ "webhook": {
+ "description": "Webhook Information (if applicable)",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.WebhookStatus"
+ }
+ ]
+ }
+ }
+ },
+ "com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.ResourceCount": {
+ "type": "object",
+ "required": [
+ "group",
+ "resource",
+ "count"
+ ],
+ "properties": {
+ "count": {
+ "type": "integer",
+ "format": "int64",
+ "default": 0
+ },
+ "group": {
+ "type": "string",
+ "default": ""
+ },
+ "resource": {
+ "type": "string",
+ "default": ""
+ }
+ }
+ },
+ "com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.ResourceList": {
+ "description": "Information we can get just from the file listing",
+ "type": "object",
+ "required": [
+ "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.provisioning.pkg.apis.provisioning.v0alpha1.ResourceListItem"
+ }
+ ]
+ },
+ "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"
+ },
+ "metadata": {
+ "default": {},
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta"
+ }
+ ]
+ }
+ },
+ "x-kubernetes-group-version-kind": [
+ {
+ "group": "provisioning.grafana.app",
+ "kind": "ResourceList",
+ "version": "v0alpha1"
+ }
+ ]
+ },
+ "com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.ResourceListItem": {
+ "type": "object",
+ "required": [
+ "path",
+ "group",
+ "resource",
+ "name",
+ "hash"
+ ],
+ "properties": {
+ "folder": {
+ "type": "string"
+ },
+ "group": {
+ "type": "string",
+ "default": ""
+ },
+ "hash": {
+ "description": "the k8s identifier",
+ "type": "string",
+ "default": ""
+ },
+ "name": {
+ "type": "string",
+ "default": ""
+ },
+ "path": {
+ "type": "string",
+ "default": ""
+ },
+ "resource": {
+ "type": "string",
+ "default": ""
+ },
+ "time": {
+ "type": "integer",
+ "format": "int64"
+ },
+ "title": {
+ "type": "string"
+ }
+ }
+ },
+ "com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.ResourceObjects": {
+ "type": "object",
+ "required": [
+ "type"
+ ],
+ "properties": {
+ "action": {
+ "description": "The action required/used for dryRun\n\nPossible enum values:\n - `\"create\"`\n - `\"delete\"`\n - `\"move\"`\n - `\"update\"`",
+ "type": "string",
+ "enum": [
+ "create",
+ "delete",
+ "move",
+ "update"
+ ]
+ },
+ "dryRun": {
+ "description": "The value returned from a dryRun request",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apimachinery.apis.common.v0alpha1.Unstructured"
+ }
+ ]
+ },
+ "existing": {
+ "description": "The same value, currently saved in the grafana database",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apimachinery.apis.common.v0alpha1.Unstructured"
+ }
+ ]
+ },
+ "file": {
+ "description": "The resource from the repository with all modifications applied eg, the name, folder etc will all be applied to this object",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apimachinery.apis.common.v0alpha1.Unstructured"
+ }
+ ]
+ },
+ "type": {
+ "description": "The identified type for this object",
+ "default": {},
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.ResourceType"
+ }
+ ]
+ },
+ "upsert": {
+ "description": "For write events, this will return the value that was added or updated",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apimachinery.apis.common.v0alpha1.Unstructured"
+ }
+ ]
+ }
+ }
+ },
+ "com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.ResourceRef": {
+ "type": "object",
+ "properties": {
+ "group": {
+ "description": "Group is the group of the resource, such as \"dashboard.grafana.app\".",
+ "type": "string"
+ },
+ "kind": {
+ "description": "Kind is the type of resource, for example, \"Dashboard\".",
+ "type": "string"
+ },
+ "name": {
+ "description": "Name is the name of the resource, such as a dashboard UID.",
+ "type": "string"
+ }
+ }
+ },
+ "com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.ResourceRepositoryInfo": {
+ "type": "object",
+ "required": [
+ "type",
+ "title",
+ "namespace",
+ "name"
+ ],
+ "properties": {
+ "name": {
+ "description": "The name (identifier)",
+ "type": "string",
+ "default": ""
+ },
+ "namespace": {
+ "description": "The namespace this belongs to",
+ "type": "string",
+ "default": ""
+ },
+ "title": {
+ "description": "The display name for this repository",
+ "type": "string",
+ "default": ""
+ },
+ "type": {
+ "description": "The repository type\n\nPossible enum values:\n - `\"bitbucket\"`\n - `\"git\"`\n - `\"github\"`\n - `\"gitlab\"`\n - `\"local\"`",
+ "type": "string",
+ "default": "",
+ "enum": [
+ "bitbucket",
+ "git",
+ "github",
+ "gitlab",
+ "local"
+ ]
+ }
+ }
+ },
+ "com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.ResourceType": {
+ "type": "object",
+ "properties": {
+ "classic": {
+ "description": "For non-k8s native formats, what did this start as\n\nPossible enum values:\n - `\"access-control\"` Access control https://github.com/grafana/grafana/blob/v11.3.1/conf/provisioning/access-control/sample.yaml\n - `\"alerting\"` Alert configuration https://github.com/grafana/grafana/blob/v11.3.1/conf/provisioning/alerting/sample.yaml\n - `\"dashboard\"` Dashboard JSON\n - `\"datasources\"` Datasource definitions eg: https://github.com/grafana/grafana/blob/v11.3.1/conf/provisioning/datasources/sample.yaml",
+ "type": "string",
+ "enum": [
+ "access-control",
+ "alerting",
+ "dashboard",
+ "datasources"
+ ]
+ },
+ "group": {
+ "type": "string"
+ },
+ "kind": {
+ "type": "string"
+ },
+ "resource": {
+ "type": "string"
+ },
+ "version": {
+ "type": "string"
+ }
+ }
+ },
+ "com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.ResourceURLs": {
+ "type": "object",
+ "properties": {
+ "compareURL": {
+ "description": "Compare this version to the target branch",
+ "type": "string"
+ },
+ "newPullRequestURL": {
+ "description": "A URL that will create a new pull requeset for this branch",
+ "type": "string"
+ },
+ "repositoryURL": {
+ "description": "A URL pointing to the repository this lives in",
+ "type": "string"
+ },
+ "sourceURL": {
+ "description": "A URL pointing to the this file in the repository",
+ "type": "string"
+ }
+ }
+ },
+ "com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.ResourceWrapper": {
+ "description": "This is a container type for any resource type",
+ "type": "object",
+ "required": [
+ "repository",
+ "resource"
+ ],
+ "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"
+ },
+ "errors": {
+ "description": "If errors exist, show them here",
+ "type": "array",
+ "items": {
+ "type": "string",
+ "default": ""
+ },
+ "x-kubernetes-list-type": "atomic"
+ },
+ "hash": {
+ "description": "The repo hash value",
+ "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"
+ },
+ "path": {
+ "description": "Path to the remote file",
+ "type": "string"
+ },
+ "ref": {
+ "description": "The request ref (or branch if exists)",
+ "type": "string"
+ },
+ "repository": {
+ "description": "Basic repository info",
+ "default": {},
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.ResourceRepositoryInfo"
+ }
+ ]
+ },
+ "resource": {
+ "description": "Different flavors of the same object",
+ "default": {},
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.ResourceObjects"
+ }
+ ]
+ },
+ "timestamp": {
+ "description": "The modified time in the remote file system",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Time"
+ }
+ ]
+ },
+ "urls": {
+ "description": "Typed links for this file (only supported by external systems, github etc)",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.ResourceURLs"
+ }
+ ]
+ }
+ },
+ "x-kubernetes-group-version-kind": [
+ {
+ "group": "provisioning.grafana.app",
+ "kind": "ResourceWrapper",
+ "version": "v0alpha1"
+ }
+ ]
+ },
+ "com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.SyncJobOptions": {
+ "type": "object",
+ "required": [
+ "incremental"
+ ],
+ "properties": {
+ "incremental": {
+ "description": "Incremental synchronization for versioned repositories",
+ "type": "boolean",
+ "default": false
+ }
+ }
+ },
+ "com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.SyncOptions": {
+ "type": "object",
+ "required": [
+ "enabled",
+ "target"
+ ],
+ "properties": {
+ "enabled": {
+ "description": "Enabled must be saved as true before any sync job will run",
+ "type": "boolean",
+ "default": false
+ },
+ "intervalSeconds": {
+ "description": "When non-zero, the sync will run periodically",
+ "type": "integer",
+ "format": "int64"
+ },
+ "target": {
+ "description": "Where values should be saved\n\nPossible enum values:\n - `\"folder\"` Resources will be saved into a folder managed by this repository It will contain a copy of everything from the remote The folder k8s name will be the same as the repository k8s name\n - `\"instance\"` Resources are saved in the global context Only one repository may specify the `instance` target When this exists, the UI will promote writing to the instance repo rather than the grafana database (where possible)",
+ "type": "string",
+ "default": "",
+ "enum": [
+ "folder",
+ "instance"
+ ]
+ }
+ }
+ },
+ "com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.SyncStatus": {
+ "type": "object",
+ "required": [
+ "state",
+ "message"
+ ],
+ "properties": {
+ "finished": {
+ "description": "When the sync job finished",
+ "type": "integer",
+ "format": "int64"
+ },
+ "incremental": {
+ "description": "Incremental synchronization for versioned repositories",
+ "type": "boolean"
+ },
+ "job": {
+ "description": "The ID for the job that ran this sync",
+ "type": "string"
+ },
+ "lastRef": {
+ "description": "The repository ref when the last successful sync ran",
+ "type": "string"
+ },
+ "message": {
+ "description": "Summary messages (will be shown to users)",
+ "type": "array",
+ "items": {
+ "type": "string",
+ "default": ""
+ },
+ "x-kubernetes-list-type": "atomic"
+ },
+ "scheduled": {
+ "description": "When the next sync check is scheduled",
+ "type": "integer",
+ "format": "int64"
+ },
+ "started": {
+ "description": "When the sync job started",
+ "type": "integer",
+ "format": "int64"
+ },
+ "state": {
+ "description": "pending, running, success, error\n\nPossible enum values:\n - `\"error\"` Finished with errors\n - `\"pending\"` Job has been submitted, but not processed yet\n - `\"success\"` Finished with success\n - `\"warning\"` Finished with some non-critical errors\n - `\"working\"` The job is running",
+ "type": "string",
+ "default": "",
+ "enum": [
+ "error",
+ "pending",
+ "success",
+ "warning",
+ "working"
+ ]
+ }
+ }
+ },
+ "com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.TestResults": {
+ "description": "HistoryList is a list of versions of a resource",
+ "type": "object",
+ "required": [
+ "code",
+ "success"
+ ],
+ "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": "HTTP status code",
+ "type": "integer",
+ "format": "int32",
+ "default": 0
+ },
+ "errors": {
+ "description": "Field related errors",
+ "type": "array",
+ "items": {
+ "default": {},
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.ErrorDetails"
+ }
+ ]
+ }
+ },
+ "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"
+ },
+ "success": {
+ "description": "Is the connection healthy",
+ "type": "boolean",
+ "default": false
+ }
+ },
+ "x-kubernetes-group-version-kind": [
+ {
+ "group": "provisioning.grafana.app",
+ "kind": "TestResults",
+ "version": "v0alpha1"
+ }
+ ]
+ },
+ "com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.WebhookResponse": {
+ "type": "object",
+ "properties": {
+ "added": {
+ "description": "Optional message",
+ "type": "string"
+ },
+ "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": "HTTP Status code 200 implies that the payload was understood but nothing is required 202 implies that an async job has been scheduled to handle the request",
+ "type": "integer",
+ "format": "int32"
+ },
+ "job": {
+ "description": "Jobs to be processed When the response is 202 (Accepted) the queued jobs will be returned",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.JobSpec"
+ }
+ ]
+ },
+ "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"
+ }
+ },
+ "x-kubernetes-group-version-kind": [
+ {
+ "group": "provisioning.grafana.app",
+ "kind": "WebhookResponse",
+ "version": "v0alpha1"
+ }
+ ]
+ },
+ "com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.WebhookStatus": {
+ "type": "object",
+ "properties": {
+ "encryptedSecret": {
+ "type": "string",
+ "format": "byte"
+ },
+ "id": {
+ "type": "integer",
+ "format": "int64"
+ },
+ "lastEvent": {
+ "type": "integer",
+ "format": "int64"
+ },
+ "secret": {
+ "type": "string"
+ },
+ "subscribedEvents": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "default": ""
+ }
+ },
+ "url": {
+ "type": "string"
+ }
+ }
+ },
"com.github.grafana.grafana.pkg.apimachinery.apis.common.v0alpha1.Unstructured": {
"type": "object",
"additionalProperties": true,
@@ -2650,12 +4076,7 @@
"description": "Resources to delete This option has been created because currently the frontend does not use standarized app platform APIs. For performance and API consistency reasons, the preferred option is it to use the paths.",
"type": "array",
"items": {
- "default": {},
- "allOf": [
- {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.ResourceRef"
- }
- ]
+ "default": {}
}
}
}
@@ -2951,37 +4372,15 @@
"type": "string"
},
"metadata": {
- "default": {},
- "allOf": [
- {
- "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta"
- }
- ]
+ "default": {}
},
"spec": {
- "default": {},
- "allOf": [
- {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.JobSpec"
- }
- ]
+ "default": {}
},
"status": {
- "default": {},
- "allOf": [
- {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.JobStatus"
- }
- ]
+ "default": {}
}
- },
- "x-kubernetes-group-version-kind": [
- {
- "group": "provisioning.grafana.app",
- "kind": "Job",
- "version": "v0alpha1"
- }
- ]
+ }
},
"com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.JobList": {
"type": "object",
@@ -2996,12 +4395,7 @@
"items": {
"type": "array",
"items": {
- "default": {},
- "allOf": [
- {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.Job"
- }
- ]
+ "default": {}
}
},
"kind": {
@@ -3009,21 +4403,9 @@
"type": "string"
},
"metadata": {
- "default": {},
- "allOf": [
- {
- "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta"
- }
- ]
+ "default": {}
}
- },
- "x-kubernetes-group-version-kind": [
- {
- "group": "provisioning.grafana.app",
- "kind": "JobList",
- "version": "v0alpha1"
- }
- ]
+ }
},
"com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.JobResourceSummary": {
"type": "object",
@@ -3090,52 +4472,22 @@
]
},
"delete": {
- "description": "Delete when the action is `delete`",
- "allOf": [
- {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.DeleteJobOptions"
- }
- ]
+ "description": "Delete when the action is `delete`"
},
"migrate": {
- "description": "Required when the action is `migrate`",
- "allOf": [
- {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.MigrateJobOptions"
- }
- ]
+ "description": "Required when the action is `migrate`"
},
"move": {
- "description": "Move when the action is `move`",
- "allOf": [
- {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.MoveJobOptions"
- }
- ]
+ "description": "Move when the action is `move`"
},
"pr": {
- "description": "Pull request options",
- "allOf": [
- {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.PullRequestJobOptions"
- }
- ]
+ "description": "Pull request options"
},
"pull": {
- "description": "Required when the action is `pull`",
- "allOf": [
- {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.SyncJobOptions"
- }
- ]
+ "description": "Required when the action is `pull`"
},
"push": {
- "description": "Required when the action is `push`",
- "allOf": [
- {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.ExportJobOptions"
- }
- ]
+ "description": "Required when the action is `push`"
},
"repository": {
"description": "The the repository reference (for now also in labels) This value is required, but will be popuplated from the job making the request",
@@ -3184,9 +4536,7 @@
"summary": {
"description": "Summary of processed actions",
"type": "array",
- "items": {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.JobResourceSummary"
- }
+ "items": {}
}
}
},
@@ -3257,12 +4607,7 @@
"description": "Resources to move This option has been created because currently the frontend does not use standarized app platform APIs. For performance and API consistency reasons, the preferred option is it to use the paths.",
"type": "array",
"items": {
- "default": {},
- "allOf": [
- {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.ResourceRef"
- }
- ]
+ "default": {}
}
},
"targetPath": {
@@ -3327,12 +4672,7 @@
"items": {
"type": "array",
"items": {
- "default": {},
- "allOf": [
- {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.RefItem"
- }
- ]
+ "default": {}
},
"x-kubernetes-list-type": "atomic"
},
@@ -3341,21 +4681,9 @@
"type": "string"
},
"metadata": {
- "default": {},
- "allOf": [
- {
- "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta"
- }
- ]
+ "default": {}
}
- },
- "x-kubernetes-group-version-kind": [
- {
- "group": "provisioning.grafana.app",
- "kind": "RefList",
- "version": "v0alpha1"
- }
- ]
+ }
},
"com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.Repository": {
"description": "When this code is changed, make sure to update the code generation. As of writing, this can be done via the hack dir in the root of the repo: ./hack/update-codegen.sh provisioning If you've opened the generated files in this dir at some point in VSCode, you may also have to re-open them to clear errors.",
@@ -3370,37 +4698,15 @@
"type": "string"
},
"metadata": {
- "default": {},
- "allOf": [
- {
- "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta"
- }
- ]
+ "default": {}
},
"spec": {
- "default": {},
- "allOf": [
- {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.RepositorySpec"
- }
- ]
+ "default": {}
},
"status": {
- "default": {},
- "allOf": [
- {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.RepositoryStatus"
- }
- ]
+ "default": {}
}
- },
- "x-kubernetes-group-version-kind": [
- {
- "group": "provisioning.grafana.app",
- "kind": "Repository",
- "version": "v0alpha1"
- }
- ]
+ }
},
"com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.RepositoryList": {
"type": "object",
@@ -3415,12 +4721,7 @@
"items": {
"type": "array",
"items": {
- "default": {},
- "allOf": [
- {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.Repository"
- }
- ]
+ "default": {}
},
"x-kubernetes-list-type": "atomic"
},
@@ -3429,21 +4730,9 @@
"type": "string"
},
"metadata": {
- "default": {},
- "allOf": [
- {
- "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta"
- }
- ]
+ "default": {}
}
- },
- "x-kubernetes-group-version-kind": [
- {
- "group": "provisioning.grafana.app",
- "kind": "RepositoryList",
- "version": "v0alpha1"
- }
- ]
+ }
},
"com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.RepositorySpec": {
"type": "object",
@@ -3455,57 +4744,27 @@
],
"properties": {
"bitbucket": {
- "description": "The repository on Bitbucket. Mutually exclusive with local | github | git.",
- "allOf": [
- {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.BitbucketRepositoryConfig"
- }
- ]
+ "description": "The repository on Bitbucket. Mutually exclusive with local | github | git."
},
"description": {
"description": "Repository description",
"type": "string"
},
"git": {
- "description": "The repository on Git. Mutually exclusive with local | github | git.",
- "allOf": [
- {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.GitRepositoryConfig"
- }
- ]
+ "description": "The repository on Git. Mutually exclusive with local | github | git."
},
"github": {
- "description": "The repository on GitHub. Mutually exclusive with local | github | git.",
- "allOf": [
- {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.GitHubRepositoryConfig"
- }
- ]
+ "description": "The repository on GitHub. Mutually exclusive with local | github | git."
},
"gitlab": {
- "description": "The repository on GitLab. Mutually exclusive with local | github | git.",
- "allOf": [
- {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.GitLabRepositoryConfig"
- }
- ]
+ "description": "The repository on GitLab. Mutually exclusive with local | github | git."
},
"local": {
- "description": "The repository on the local file system. Mutually exclusive with local | github.",
- "allOf": [
- {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.LocalRepositoryConfig"
- }
- ]
+ "description": "The repository on the local file system. Mutually exclusive with local | github."
},
"sync": {
"description": "Sync settings -- how values are pulled from the repository into grafana",
- "default": {},
- "allOf": [
- {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.SyncOptions"
- }
- ]
+ "default": {}
},
"title": {
"description": "The repository display name (shown in the UI)",
@@ -3550,12 +4809,7 @@
"properties": {
"health": {
"description": "This will get updated with the current health status (and updated periodically)",
- "default": {},
- "allOf": [
- {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.HealthStatus"
- }
- ]
+ "default": {}
},
"observedGeneration": {
"description": "The generation of the spec last time reconciliation ran",
@@ -3567,31 +4821,16 @@
"description": "The object count when sync last ran",
"type": "array",
"items": {
- "default": {},
- "allOf": [
- {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.ResourceCount"
- }
- ]
+ "default": {}
},
"x-kubernetes-list-type": "atomic"
},
"sync": {
"description": "Sync information with the last sync information",
- "default": {},
- "allOf": [
- {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.SyncStatus"
- }
- ]
+ "default": {}
},
"webhook": {
- "description": "Webhook Information (if applicable)",
- "allOf": [
- {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.WebhookStatus"
- }
- ]
+ "description": "Webhook Information (if applicable)"
}
}
},
@@ -3738,12 +4977,7 @@
"items": {
"type": "array",
"items": {
- "default": {},
- "allOf": [
- {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.ResourceListItem"
- }
- ]
+ "default": {}
},
"x-kubernetes-list-type": "atomic"
},
@@ -3752,21 +4986,9 @@
"type": "string"
},
"metadata": {
- "default": {},
- "allOf": [
- {
- "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta"
- }
- ]
+ "default": {}
}
- },
- "x-kubernetes-group-version-kind": [
- {
- "group": "provisioning.grafana.app",
- "kind": "ResourceList",
- "version": "v0alpha1"
- }
- ]
+ }
},
"com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.ResourceListItem": {
"type": "object",
@@ -3828,45 +5050,20 @@
]
},
"dryRun": {
- "description": "The value returned from a dryRun request",
- "allOf": [
- {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apimachinery.apis.common.v0alpha1.Unstructured"
- }
- ]
+ "description": "The value returned from a dryRun request"
},
"existing": {
- "description": "The same value, currently saved in the grafana database",
- "allOf": [
- {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apimachinery.apis.common.v0alpha1.Unstructured"
- }
- ]
+ "description": "The same value, currently saved in the grafana database"
},
"file": {
- "description": "The resource from the repository with all modifications applied eg, the name, folder etc will all be applied to this object",
- "allOf": [
- {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apimachinery.apis.common.v0alpha1.Unstructured"
- }
- ]
+ "description": "The resource from the repository with all modifications applied eg, the name, folder etc will all be applied to this object"
},
"type": {
"description": "The identified type for this object",
- "default": {},
- "allOf": [
- {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.ResourceType"
- }
- ]
+ "default": {}
},
"upsert": {
- "description": "For write events, this will return the value that was added or updated",
- "allOf": [
- {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apimachinery.apis.common.v0alpha1.Unstructured"
- }
- ]
+ "description": "For write events, this will return the value that was added or updated"
}
}
},
@@ -4053,46 +5250,19 @@
},
"repository": {
"description": "Basic repository info",
- "default": {},
- "allOf": [
- {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.ResourceRepositoryInfo"
- }
- ]
+ "default": {}
},
"resource": {
"description": "Different flavors of the same object",
- "default": {},
- "allOf": [
- {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.ResourceObjects"
- }
- ]
+ "default": {}
},
"timestamp": {
- "description": "The modified time in the remote file system",
- "allOf": [
- {
- "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Time"
- }
- ]
+ "description": "The modified time in the remote file system"
},
"urls": {
- "description": "Typed links for this file (only supported by external systems, github etc)",
- "allOf": [
- {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.ResourceURLs"
- }
- ]
+ "description": "Typed links for this file (only supported by external systems, github etc)"
}
- },
- "x-kubernetes-group-version-kind": [
- {
- "group": "provisioning.grafana.app",
- "kind": "ResourceWrapper",
- "version": "v0alpha1"
- }
- ]
+ }
},
"com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.SyncJobOptions": {
"type": "object",
@@ -4214,12 +5384,7 @@
"description": "Field related errors",
"type": "array",
"items": {
- "default": {},
- "allOf": [
- {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.ErrorDetails"
- }
- ]
+ "default": {}
}
},
"kind": {
@@ -4231,14 +5396,7 @@
"type": "boolean",
"default": false
}
- },
- "x-kubernetes-group-version-kind": [
- {
- "group": "provisioning.grafana.app",
- "kind": "TestResults",
- "version": "v0alpha1"
- }
- ]
+ }
},
"com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.WebhookResponse": {
"type": "object",
@@ -4257,25 +5415,13 @@
"format": "int32"
},
"job": {
- "description": "Jobs to be processed When the response is 202 (Accepted) the queued jobs will be returned",
- "allOf": [
- {
- "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.JobSpec"
- }
- ]
+ "description": "Jobs to be processed When the response is 202 (Accepted) the queued jobs will be returned"
},
"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"
}
- },
- "x-kubernetes-group-version-kind": [
- {
- "group": "provisioning.grafana.app",
- "kind": "WebhookResponse",
- "version": "v0alpha1"
- }
- ]
+ }
},
"com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.WebhookStatus": {
"type": "object",
diff --git a/pkg/tests/apis/provisioning/helper_test.go b/pkg/tests/apis/provisioning/helper_test.go
index 03ca9342f31..f1252af3662 100644
--- a/pkg/tests/apis/provisioning/helper_test.go
+++ b/pkg/tests/apis/provisioning/helper_test.go
@@ -27,7 +27,7 @@ import (
dashboardsV2alpha1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1"
dashboardsV2beta1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1"
folder "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
grafanarest "github.com/grafana/grafana/pkg/apiserver/rest"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/jobs"
"github.com/grafana/grafana/pkg/services/featuremgmt"
diff --git a/pkg/tests/apis/provisioning/provisioning_test.go b/pkg/tests/apis/provisioning/provisioning_test.go
index f9a5c40a14e..a7b28d877ce 100644
--- a/pkg/tests/apis/provisioning/provisioning_test.go
+++ b/pkg/tests/apis/provisioning/provisioning_test.go
@@ -20,8 +20,8 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/apimachinery/utils"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/extensions"
"github.com/grafana/grafana/pkg/infra/usagestats"
"github.com/grafana/grafana/pkg/tests/apis"
diff --git a/pkg/tests/apis/provisioning/secrets_test.go b/pkg/tests/apis/provisioning/secrets_test.go
index a85b8a56f62..331d4bc3067 100644
--- a/pkg/tests/apis/provisioning/secrets_test.go
+++ b/pkg/tests/apis/provisioning/secrets_test.go
@@ -9,7 +9,7 @@ import (
"testing"
"time"
- provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
+ provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/provisioning/secrets"
"github.com/grafana/grafana/pkg/registry/apis/secret/contracts"
"github.com/stretchr/testify/require"
From 8b5b9b68c2c5c33d12399b79a5f8beacedf9c0ce Mon Sep 17 00:00:00 2001
From: Alexander Akhmetov
Date: Fri, 1 Aug 2025 22:39:54 +0200
Subject: [PATCH 39/89] Alerting: Document Accept header in Prometheus
conversion API (#109080)
---
docs/sources/alerting/alerting-rules/alerting-migration.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/sources/alerting/alerting-rules/alerting-migration.md b/docs/sources/alerting/alerting-rules/alerting-migration.md
index 08a301c24c3..2e63db00136 100644
--- a/docs/sources/alerting/alerting-rules/alerting-migration.md
+++ b/docs/sources/alerting/alerting-rules/alerting-migration.md
@@ -281,7 +281,7 @@ The `POST` endpoints can be used to import data source–managed alert rules. Th
| POST | `/convert/prometheus/config/v1/rules` | [Create or update multiple rule groups](#create-or-update-multiple-rule-groups) across multiple namespaces. Requires [`X-Grafana-Alerting-Datasource-UID`](#x-grafana-alerting-datasource-uid). | None |
| POST | `/convert/prometheus/config/v1/rules/:namespaceTitle` | Create or update a single rule group in a namespace. Requires [`X-Grafana-Alerting-Datasource-UID`](#x-grafana-alerting-datasource-uid). | [Set rule group](/docs/mimir/latest/references/http-api/#set-rule-group) |
-The `GET` and `DELETE` endpoints work only with provisioned and imported alert rules.
+The `GET` and `DELETE` endpoints work only with provisioned and imported alert rules. All `GET` endpoints support both JSON and YAML response formats based on the `Accept` header: use `application/json` for JSON responses, or `application/yaml` for YAML responses. YAML is the default format when no `Accept` header is specified.
| Endpoint | Method | Summary | Mimir equivalent |
| -------- | ------------------------------------------------------------ | --------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
From bd5c83bc11d880a77751c8b27730ec2fd041a0ec Mon Sep 17 00:00:00 2001
From: Stephanie Hingtgen
Date: Fri, 1 Aug 2025 15:49:54 -0500
Subject: [PATCH 40/89] Revert "Chore: Use proper database type from env in
testinfra integration tests" (#109081)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Revert "Chore: Use proper database type from env in testinfra integration tes…"
This reverts commit 772f647210cfe3c4ac95002e21888db9c0ed9f5d.
---
apps/advisor/go.mod | 3 +-
apps/advisor/go.sum | 10 ++++---
apps/investigations/go.mod | 2 +-
apps/investigations/go.sum | 6 ++--
go.mod | 4 ++-
go.sum | 6 ++--
pkg/services/sqlstore/sqlutil/sqlutil.go | 15 ----------
.../apis/provisioning/provisioning_test.go | 29 +++++++------------
pkg/tests/testinfra/testinfra.go | 15 ----------
9 files changed, 30 insertions(+), 60 deletions(-)
diff --git a/apps/advisor/go.mod b/apps/advisor/go.mod
index 2a232b42873..5c3f927de43 100644
--- a/apps/advisor/go.mod
+++ b/apps/advisor/go.mod
@@ -10,7 +10,7 @@ require (
github.com/grafana/grafana-app-sdk v0.40.2
github.com/grafana/grafana-app-sdk/logging v0.40.1
github.com/grafana/grafana-plugin-sdk-go v0.278.0
- github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250725144121-b1592b5e36d2
+ github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250801162753-7e4796893956
github.com/stretchr/testify v1.10.0
k8s.io/apimachinery v0.33.3
k8s.io/apiserver v0.33.3
@@ -132,6 +132,7 @@ require (
github.com/grafana/dskit v0.0.0-20250611075409-46f51e1ce914 // indirect
github.com/grafana/grafana-aws-sdk v1.0.4 // indirect
github.com/grafana/grafana-azure-sdk-go/v2 v2.2.0 // indirect
+ github.com/grafana/grafana/apps/provisioning v0.0.0-20250801193518-9f4773c9a5a3 // indirect
github.com/grafana/grafana/pkg/apiserver v0.0.0-20250627191313-2f1a6ae1712b // indirect
github.com/grafana/otel-profiling-go v0.5.1 // indirect
github.com/grafana/pyroscope-go/godeltaprof v0.1.8 // indirect
diff --git a/apps/advisor/go.sum b/apps/advisor/go.sum
index 653ca2c8c71..c6263714349 100644
--- a/apps/advisor/go.sum
+++ b/apps/advisor/go.sum
@@ -494,8 +494,8 @@ github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw=
github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4=
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.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
-github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
+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-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:9wScpmSP5A3Bk8V3XHWUcJmYTh+ZnlHVyc+A4oZYS3Y=
github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:56xuuqnHyryaerycW3BfssRdxQstACi0Epw/yC5E2xM=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
@@ -677,12 +677,14 @@ github.com/grafana/grafana/apps/dashboard v0.0.0-20250730164619-34019e5ec017 h1:
github.com/grafana/grafana/apps/dashboard v0.0.0-20250730164619-34019e5ec017/go.mod h1:/iuseD/cEpXDiy7MpL+4qBFZ3H6esnUJTYzpoJMw9dw=
github.com/grafana/grafana/apps/folder v0.0.0-20250627191313-2f1a6ae1712b h1:31MwoIKKT9Ay0ZjbT4lkfcPijiWogUWzXs2EjrCgodI=
github.com/grafana/grafana/apps/folder v0.0.0-20250627191313-2f1a6ae1712b/go.mod h1:dLtYBp1pza5HYalezNvzlP8JDeKrZ5BKTonDgEOE0NY=
+github.com/grafana/grafana/apps/provisioning v0.0.0-20250801193518-9f4773c9a5a3 h1:MUyJnJE3GFj9QqA0sq7VOqWQkywYvH0/T4kwJfVQzJE=
+github.com/grafana/grafana/apps/provisioning v0.0.0-20250801193518-9f4773c9a5a3/go.mod h1:qzFUVwLI1b5UIbVFxFydUYAsnOK27AgtA5so3EW8jM0=
github.com/grafana/grafana/apps/secret v0.0.0-20250731151929-0aac22a9e2d3 h1:16eaVEucbwis3TxS4CYZxxg5wfPAP/6u7Ji2+wbiHyk=
github.com/grafana/grafana/apps/secret v0.0.0-20250731151929-0aac22a9e2d3/go.mod h1:pS2M5ILsHx9VNTM96glLtCjCVXHWyfGcT34WHvbbMtM=
github.com/grafana/grafana/pkg/aggregator v0.0.0-20250627191313-2f1a6ae1712b h1:ei01IFqmnXkOrrVvsT3CYe+i5xYra3SCX7Wsu3PMsDU=
github.com/grafana/grafana/pkg/aggregator v0.0.0-20250627191313-2f1a6ae1712b/go.mod h1:+H4Va9jDJlGQJjAN+OFD/hLx2I/yEzDRMQLaKecvgAc=
-github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250725144121-b1592b5e36d2 h1:lvmcK9XOJUJiYhl2kH4nwAKOUdq+ug+ueIGqfKlip3E=
-github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250725144121-b1592b5e36d2/go.mod h1:3ZgUe0E3rIhI026xF4DKFptOst/jpDHJ/Sn+bRODzI4=
+github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250801162753-7e4796893956 h1:FzReg7qT3G+11ZsFFbtguMdx+w1w76bJCOOH1fWfDKs=
+github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250801162753-7e4796893956/go.mod h1:RRvSjHH12/PnQaXraMO65jUhVu8n59mzvhfIMBETnV4=
github.com/grafana/grafana/pkg/apiserver v0.0.0-20250627191313-2f1a6ae1712b h1:QyJLJn3xwFTIXu9KPZujsrIUN0X8DdiR9b2h75L0AfI=
github.com/grafana/grafana/pkg/apiserver v0.0.0-20250627191313-2f1a6ae1712b/go.mod h1:6OKkPWDB8PetDXqMVMOWL35iTCEUdpATwwpuew0k8+o=
github.com/grafana/grafana/pkg/promlib v0.0.8 h1:VUWsqttdf0wMI4j9OX9oNrykguQpZcruudDAFpJJVw0=
diff --git a/apps/investigations/go.mod b/apps/investigations/go.mod
index fe28636873c..d31f7a36956 100644
--- a/apps/investigations/go.mod
+++ b/apps/investigations/go.mod
@@ -5,7 +5,7 @@ go 1.24.5
require (
github.com/grafana/grafana v0.0.0-00010101000000-000000000000
github.com/grafana/grafana-app-sdk v0.40.2
- github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250725152715-69d3b9023cec
+ github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250801162753-7e4796893956
github.com/stretchr/testify v1.10.0
k8s.io/apimachinery v0.33.3
k8s.io/apiserver v0.33.3
diff --git a/apps/investigations/go.sum b/apps/investigations/go.sum
index 00347e0818c..3a9e0faf11a 100644
--- a/apps/investigations/go.sum
+++ b/apps/investigations/go.sum
@@ -355,8 +355,10 @@ github.com/grafana/grafana-plugin-sdk-go v0.278.0 h1:5/rIYparLi02pofdaag8wnjspMM
github.com/grafana/grafana-plugin-sdk-go v0.278.0/go.mod h1:+8NXT/XUJ/89GV6FxGQ366NZ3nU+cAXDMd0OUESF9H4=
github.com/grafana/grafana/apps/dashboard v0.0.0-20250730164619-34019e5ec017 h1:Niy+KRDWHsUVqfhZQg0oZbAQFO6QcO6a4l9V/ouDEEs=
github.com/grafana/grafana/apps/dashboard v0.0.0-20250730164619-34019e5ec017/go.mod h1:/iuseD/cEpXDiy7MpL+4qBFZ3H6esnUJTYzpoJMw9dw=
-github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250725152715-69d3b9023cec h1:cg1GbDVZ7goqDrqoMzqeN4AeAcD271MGYjOvdVTDwfw=
-github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250725152715-69d3b9023cec/go.mod h1:3ZgUe0E3rIhI026xF4DKFptOst/jpDHJ/Sn+bRODzI4=
+github.com/grafana/grafana/apps/provisioning v0.0.0-20250801193518-9f4773c9a5a3 h1:MUyJnJE3GFj9QqA0sq7VOqWQkywYvH0/T4kwJfVQzJE=
+github.com/grafana/grafana/apps/provisioning v0.0.0-20250801193518-9f4773c9a5a3/go.mod h1:qzFUVwLI1b5UIbVFxFydUYAsnOK27AgtA5so3EW8jM0=
+github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250801162753-7e4796893956 h1:FzReg7qT3G+11ZsFFbtguMdx+w1w76bJCOOH1fWfDKs=
+github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250801162753-7e4796893956/go.mod h1:RRvSjHH12/PnQaXraMO65jUhVu8n59mzvhfIMBETnV4=
github.com/grafana/grafana/pkg/apiserver v0.0.0-20250627191313-2f1a6ae1712b h1:QyJLJn3xwFTIXu9KPZujsrIUN0X8DdiR9b2h75L0AfI=
github.com/grafana/grafana/pkg/apiserver v0.0.0-20250627191313-2f1a6ae1712b/go.mod h1:6OKkPWDB8PetDXqMVMOWL35iTCEUdpATwwpuew0k8+o=
github.com/grafana/otel-profiling-go v0.5.1 h1:stVPKAFZSa7eGiqbYuG25VcqYksR6iWvF3YH66t4qL8=
diff --git a/go.mod b/go.mod
index f8c2a0a7b16..3ec9e8a1029 100644
--- a/go.mod
+++ b/go.mod
@@ -238,7 +238,7 @@ require (
github.com/grafana/grafana/apps/playlist v0.0.0-20250627191313-2f1a6ae1712b // @grafana/grafana-app-platform-squad
github.com/grafana/grafana/apps/secret v0.0.0-20250731151929-0aac22a9e2d3 // @grafana/grafana-operator-experience-squad
github.com/grafana/grafana/pkg/aggregator v0.0.0-20250627191313-2f1a6ae1712b // @grafana/grafana-app-platform-squad
- github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250711114246-c9b2126c4ad5 // @grafana/grafana-app-platform-squad
+ github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250801162753-7e4796893956 // @grafana/grafana-app-platform-squad
github.com/grafana/grafana/pkg/apiserver v0.0.0-20250627191313-2f1a6ae1712b // @grafana/grafana-app-platform-squad
// This needs to be here for other projects that import grafana/grafana
@@ -249,6 +249,8 @@ require (
github.com/thomaspoignant/go-feature-flag v1.42.0 // @grafana/grafana-backend-group
)
+require github.com/grafana/grafana/apps/provisioning v0.0.0-20250801193518-9f4773c9a5a3 // @grafana/grafana-app-platform-squad
+
require (
cel.dev/expr v0.24.0 // indirect
cloud.google.com/go v0.121.1 // indirect
diff --git a/go.sum b/go.sum
index d3ce4af5ca7..e6e246c13a2 100644
--- a/go.sum
+++ b/go.sum
@@ -1630,12 +1630,14 @@ github.com/grafana/grafana/apps/investigations v0.0.0-20250627191313-2f1a6ae1712
github.com/grafana/grafana/apps/investigations v0.0.0-20250627191313-2f1a6ae1712b/go.mod h1:8RlQ4U9lccPEBD/QxV4zyIMh9+lzjS/7xGpiqn3cHLY=
github.com/grafana/grafana/apps/playlist v0.0.0-20250627191313-2f1a6ae1712b h1:elfpvk06igCjE0yL+/urc69UDOt1B/sPfdNg9X9kUMc=
github.com/grafana/grafana/apps/playlist v0.0.0-20250627191313-2f1a6ae1712b/go.mod h1:fPtx6dwGm0PweQRVbgtthMapJMvXobBcORbndb7Dgd4=
+github.com/grafana/grafana/apps/provisioning v0.0.0-20250801193518-9f4773c9a5a3 h1:MUyJnJE3GFj9QqA0sq7VOqWQkywYvH0/T4kwJfVQzJE=
+github.com/grafana/grafana/apps/provisioning v0.0.0-20250801193518-9f4773c9a5a3/go.mod h1:qzFUVwLI1b5UIbVFxFydUYAsnOK27AgtA5so3EW8jM0=
github.com/grafana/grafana/apps/secret v0.0.0-20250731151929-0aac22a9e2d3 h1:16eaVEucbwis3TxS4CYZxxg5wfPAP/6u7Ji2+wbiHyk=
github.com/grafana/grafana/apps/secret v0.0.0-20250731151929-0aac22a9e2d3/go.mod h1:pS2M5ILsHx9VNTM96glLtCjCVXHWyfGcT34WHvbbMtM=
github.com/grafana/grafana/pkg/aggregator v0.0.0-20250627191313-2f1a6ae1712b h1:ei01IFqmnXkOrrVvsT3CYe+i5xYra3SCX7Wsu3PMsDU=
github.com/grafana/grafana/pkg/aggregator v0.0.0-20250627191313-2f1a6ae1712b/go.mod h1:+H4Va9jDJlGQJjAN+OFD/hLx2I/yEzDRMQLaKecvgAc=
-github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250711114246-c9b2126c4ad5 h1:f4fopIH6eQRoZ/E7bstn69UtDAHleIdQ6DrdzEs++Ug=
-github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250711114246-c9b2126c4ad5/go.mod h1:eAlOam2uWhrsEZlOoAr7XZ9hbBP7SyYGYn31/aQAPs8=
+github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250801162753-7e4796893956 h1:FzReg7qT3G+11ZsFFbtguMdx+w1w76bJCOOH1fWfDKs=
+github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250801162753-7e4796893956/go.mod h1:RRvSjHH12/PnQaXraMO65jUhVu8n59mzvhfIMBETnV4=
github.com/grafana/grafana/pkg/apiserver v0.0.0-20250627191313-2f1a6ae1712b h1:QyJLJn3xwFTIXu9KPZujsrIUN0X8DdiR9b2h75L0AfI=
github.com/grafana/grafana/pkg/apiserver v0.0.0-20250627191313-2f1a6ae1712b/go.mod h1:6OKkPWDB8PetDXqMVMOWL35iTCEUdpATwwpuew0k8+o=
github.com/grafana/grafana/pkg/promlib v0.0.8 h1:VUWsqttdf0wMI4j9OX9oNrykguQpZcruudDAFpJJVw0=
diff --git a/pkg/services/sqlstore/sqlutil/sqlutil.go b/pkg/services/sqlstore/sqlutil/sqlutil.go
index 7d3d09a5ff3..8506acdab79 100644
--- a/pkg/services/sqlstore/sqlutil/sqlutil.go
+++ b/pkg/services/sqlstore/sqlutil/sqlutil.go
@@ -22,11 +22,6 @@ type TestDB struct {
DriverName string
ConnStr string
Path string
- Host string
- Port string
- User string
- Password string
- Database string
Cleanup func()
}
@@ -137,11 +132,6 @@ func mySQLTestDB() (*TestDB, error) {
return &TestDB{
DriverName: "mysql",
ConnStr: conn_str,
- Host: host,
- Port: port,
- User: "grafana",
- Password: "password",
- Database: "grafana_tests",
Cleanup: func() {},
}, nil
}
@@ -159,11 +149,6 @@ func postgresTestDB() (*TestDB, error) {
return &TestDB{
DriverName: "postgres",
ConnStr: connStr,
- Host: host,
- Port: port,
- User: "grafanatest",
- Password: "grafanatest",
- Database: "grafanatest",
Cleanup: func() {},
}, nil
}
diff --git a/pkg/tests/apis/provisioning/provisioning_test.go b/pkg/tests/apis/provisioning/provisioning_test.go
index a7b28d877ce..6ed7dbb5702 100644
--- a/pkg/tests/apis/provisioning/provisioning_test.go
+++ b/pkg/tests/apis/provisioning/provisioning_test.go
@@ -161,21 +161,14 @@ func TestIntegrationProvisioning_CreatingAndGetting(t *testing.T) {
// Viewer can see settings listing
t.Run("viewer has access to list", func(t *testing.T) {
settings := &provisioning.RepositoryViewList{}
- // Wait for unified storage to make the data available
- require.Eventually(t, func() bool {
- rsp := helper.ViewerREST.Get().
- Namespace("default").
- Suffix("settings").
- Do(context.Background())
- if rsp.Error() != nil {
- return false
- }
- err := rsp.Into(settings)
- if err != nil {
- return false
- }
- return len(settings.Items) == len(inputFiles)
- }, time.Second*10, time.Millisecond*100, "Expected settings to have len(inputFiles) items")
+ rsp := helper.ViewerREST.Get().
+ Namespace("default").
+ Suffix("settings").
+ Do(context.Background())
+ require.NoError(t, rsp.Error())
+ err := rsp.Into(settings)
+ require.NoError(t, err)
+ require.Len(t, settings.Items, len(inputFiles))
// FIXME: this should be an enterprise integration test
if extensions.IsEnterprise {
@@ -1832,10 +1825,8 @@ func TestIntegrationProvisioning_MoveResources(t *testing.T) {
// Verify dashboard still exists in Grafana with same content but may have updated path references
helper.SyncAndWait(t, repo, nil)
- require.Eventually(t, func() bool {
- _, err = helper.DashboardsV1.Resource.Get(ctx, allPanelsUID, metav1.GetOptions{})
- return err == nil
- }, 10*time.Second, 100*time.Millisecond, "dashboard should still exist in Grafana after move") // Using Eventually to account for potential delays in dashboards APIs.
+ _, err = helper.DashboardsV1.Resource.Get(ctx, allPanelsUID, metav1.GetOptions{})
+ require.NoError(t, err, "dashboard should still exist in Grafana after move")
})
t.Run("move file to nested path without ref", func(t *testing.T) {
diff --git a/pkg/tests/testinfra/testinfra.go b/pkg/tests/testinfra/testinfra.go
index 56f9f3000e2..eb9b460c6b8 100644
--- a/pkg/tests/testinfra/testinfra.go
+++ b/pkg/tests/testinfra/testinfra.go
@@ -13,7 +13,6 @@ import (
"time"
"github.com/grafana/grafana/pkg/services/featuremgmt"
- "github.com/grafana/grafana/pkg/services/sqlstore/sqlutil"
"github.com/prometheus/client_golang/prometheus"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -86,20 +85,6 @@ func StartGrafanaEnv(t *testing.T, grafDir, cfgPath string) (string, *server.Tes
err = featuremgmt.InitOpenFeatureWithCfg(cfg)
require.NoError(t, err)
-
- // Use proper database type based on the environment variable GRAFANA_TEST_DB in tests
- testDB, err := sqlutil.GetTestDB(sqlutil.GetTestDBType())
- require.NoError(t, err)
- t.Cleanup(testDB.Cleanup)
-
- dbCfg := cfg.Raw.Section("database")
- dbCfg.Key("type").SetValue(testDB.DriverName)
- dbCfg.Key("host").SetValue(testDB.Host)
- dbCfg.Key("port").SetValue(testDB.Port)
- dbCfg.Key("user").SetValue(testDB.User)
- dbCfg.Key("password").SetValue(testDB.Password)
- dbCfg.Key("name").SetValue(testDB.Database)
-
env, err := server.InitializeForTest(t, t, cfg, serverOpts, apiServerOpts)
require.NoError(t, err)
From cbf256120e955a17f517a183588b7536e54e3342 Mon Sep 17 00:00:00 2001
From: Yuri Tseretyan
Date: Fri, 1 Aug 2025 17:34:31 -0400
Subject: [PATCH 41/89] Revert "Alerting: Remote Alertmanager to calculate hash
of the request payload instead of just the configuration" (#109086)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Revert "Alerting: Remote Alertmanager to calculate hash of the request payloa…"
This reverts commit 32434810e16e9126d05ea9cd5f96493f3e8b2d0f.
---
pkg/services/ngalert/remote/alertmanager.go | 151 ++++++++++--------
.../ngalert/remote/alertmanager_test.go | 60 ++++---
.../client/alertmanager_configuration.go | 16 +-
pkg/services/ngalert/remote/client/mimir.go | 31 ++--
4 files changed, 156 insertions(+), 102 deletions(-)
diff --git a/pkg/services/ngalert/remote/alertmanager.go b/pkg/services/ngalert/remote/alertmanager.go
index 8329c7bb8de..7c1e1dd4060 100644
--- a/pkg/services/ngalert/remote/alertmanager.go
+++ b/pkg/services/ngalert/remote/alertmanager.go
@@ -2,10 +2,10 @@ package remote
import (
"context"
+ "crypto/md5"
"encoding/base64"
"encoding/json"
"fmt"
- "hash/fnv"
"net/http"
"net/url"
"strings"
@@ -16,12 +16,12 @@ import (
"github.com/grafana/alerting/definition"
alertingModels "github.com/grafana/alerting/models"
alertingNotify "github.com/grafana/alerting/notify"
- "github.com/grafana/alerting/utils/hash"
amalert "github.com/prometheus/alertmanager/api/v2/client/alert"
amalertgroup "github.com/prometheus/alertmanager/api/v2/client/alertgroup"
amgeneral "github.com/prometheus/alertmanager/api/v2/client/general"
amsilence "github.com/prometheus/alertmanager/api/v2/client/silence"
"github.com/prometheus/client_golang/prometheus"
+
"gopkg.in/yaml.v3"
"github.com/grafana/grafana/pkg/infra/log"
@@ -73,9 +73,6 @@ type Alertmanager struct {
amClient *remoteClient.Alertmanager
mimirClient remoteClient.MimirClient
-
- promoteConfig bool
- externalURL string
}
type AlertmanagerConfig struct {
@@ -130,10 +127,13 @@ func NewAlertmanager(ctx context.Context, cfg AlertmanagerConfig, store stateSto
logger := log.New("ngalert.remote.alertmanager")
mcCfg := &remoteClient.Config{
- Logger: logger,
- Password: cfg.BasicAuthPassword,
- TenantID: cfg.TenantID,
- URL: u,
+ Logger: logger,
+ Password: cfg.BasicAuthPassword,
+ TenantID: cfg.TenantID,
+ URL: u,
+ PromoteConfig: cfg.PromoteConfig,
+ ExternalURL: cfg.ExternalURL,
+ Smtp: cfg.SmtpConfig,
}
mc, err := remoteClient.New(mcCfg, metrics, tracer)
if err != nil {
@@ -188,10 +188,7 @@ func NewAlertmanager(ctx context.Context, cfg AlertmanagerConfig, store stateSto
syncInterval: cfg.SyncInterval,
tenantID: cfg.TenantID,
url: cfg.URL,
-
- externalURL: cfg.ExternalURL,
- promoteConfig: cfg.PromoteConfig,
- smtp: cfg.SmtpConfig,
+ smtp: cfg.SmtpConfig,
}
// Parse the default configuration once and remember its hash so we can compare it later.
@@ -199,11 +196,15 @@ func NewAlertmanager(ctx context.Context, cfg AlertmanagerConfig, store stateSto
// (grouping, group timing, time intervals etc) changes the autogenerated configuration.
// The `default` flag is sent to the remote Alertmanager for informational purposes, so we can tolerate this.
err = func() error {
- defaultCfg, err := am.buildConfiguration(ctx, []byte(cfg.DefaultConfig), 0)
+ defaultCfg, err := am.buildConfiguration(ctx, []byte(cfg.DefaultConfig))
if err != nil {
return fmt.Errorf("unable to build default configuration: %w", err)
}
- am.defaultConfigHash = defaultCfg.Hash
+ rawDefaultCfg, err := json.Marshal(defaultCfg)
+ if err != nil {
+ return fmt.Errorf("unable to marshal default configuration: %w", err)
+ }
+ am.defaultConfigHash = fmt.Sprintf("%x", md5.Sum(rawDefaultCfg))
return nil
}()
if err != nil {
@@ -264,16 +265,22 @@ func (am *Alertmanager) checkReadiness(ctx context.Context) error {
// CompareAndSendConfiguration checks whether a given configuration is being used by the remote Alertmanager.
// If not, it sends the configuration to the remote Alertmanager.
func (am *Alertmanager) CompareAndSendConfiguration(ctx context.Context, config *models.AlertConfiguration) error {
- payload, err := am.buildConfiguration(ctx, []byte(config.AlertmanagerConfiguration), config.CreatedAt)
+ payload, err := am.buildConfiguration(ctx, []byte(config.AlertmanagerConfiguration))
if err != nil {
return fmt.Errorf("unable to build configuration: %w", err)
}
+ rawPayload, err := json.Marshal(payload)
+ if err != nil {
+ return fmt.Errorf("unable to marshal decrypted configuration: %w", err)
+ }
+ configHash := fmt.Sprintf("%x", md5.Sum(rawPayload))
+
// Send the configuration only if we need to.
- if !am.shouldSendConfig(ctx, payload.Hash) {
+ if !am.shouldSendConfig(ctx, configHash) {
return nil
}
- return am.sendConfiguration(ctx, payload)
+ return am.sendConfiguration(ctx, payload, configHash, config.CreatedAt, am.isDefaultConfiguration(configHash))
}
func (am *Alertmanager) isDefaultConfiguration(configHash string) bool {
@@ -296,31 +303,31 @@ func decrypter(ctx context.Context, crypto Crypto) models.DecryptFn {
// buildConfiguration takes a raw Alertmanager configuration and returns a config that the remote Alertmanager can use.
// It parses the initial configuration, adds auto-generated routes, decrypts receivers, and merges the extra configs.
-func (am *Alertmanager) buildConfiguration(ctx context.Context, raw []byte, createdAtEpoch int64) (remoteClient.UserGrafanaConfig, error) {
+func (am *Alertmanager) buildConfiguration(ctx context.Context, raw []byte) (remoteClient.GrafanaAlertmanagerConfig, error) {
c, err := notifier.Load(raw)
if err != nil {
- return remoteClient.UserGrafanaConfig{}, err
+ return remoteClient.GrafanaAlertmanagerConfig{}, err
}
// Add auto-generated routes and decrypt before comparing.
if err := am.autogenFn(ctx, am.log, am.orgID, &c.AlertmanagerConfig, true); err != nil {
- return remoteClient.UserGrafanaConfig{}, err
+ return remoteClient.GrafanaAlertmanagerConfig{}, err
}
// Decrypt the receivers in the configuration.
decryptedReceivers, err := legacy_storage.DecryptedReceivers(c.AlertmanagerConfig.Receivers, decrypter(ctx, am.crypto))
if err != nil {
- return remoteClient.UserGrafanaConfig{}, fmt.Errorf("unable to decrypt receivers: %w", err)
+ return remoteClient.GrafanaAlertmanagerConfig{}, fmt.Errorf("unable to decrypt receivers: %w", err)
}
c.AlertmanagerConfig.Receivers = decryptedReceivers
if err := am.crypto.DecryptExtraConfigs(ctx, c); err != nil {
- return remoteClient.UserGrafanaConfig{}, fmt.Errorf("unable to decrypt extra configs: %w", err)
+ return remoteClient.GrafanaAlertmanagerConfig{}, fmt.Errorf("unable to decrypt extra configs: %w", err)
}
mergeResult, err := c.GetMergedAlertmanagerConfig()
if err != nil {
- return remoteClient.UserGrafanaConfig{}, fmt.Errorf("unable to get merged Alertmanager configuration: %w", err)
+ return remoteClient.GrafanaAlertmanagerConfig{}, fmt.Errorf("unable to get merged Alertmanager configuration: %w", err)
}
var templates []definition.PostableApiTemplate
@@ -328,31 +335,22 @@ func (am *Alertmanager) buildConfiguration(ctx context.Context, raw []byte, crea
templates = definition.TemplatesMapToPostableAPITemplates(c.ExtraConfigs[0].TemplateFiles, definition.MimirTemplateKind)
}
- payload := remoteClient.UserGrafanaConfig{
- GrafanaAlertmanagerConfig: remoteClient.GrafanaAlertmanagerConfig{
- TemplateFiles: c.TemplateFiles,
- AlertmanagerConfig: mergeResult.Config,
- Templates: templates,
- },
- CreatedAt: createdAtEpoch,
- Promoted: am.promoteConfig,
- ExternalURL: am.externalURL,
- SmtpConfig: am.smtp,
- }
-
- cfgHash, err := calculateUserGrafanaConfigHash(payload)
- if err != nil {
- am.log.Error("Unable to calculate hash of the configuration. Using the empty string", "error", err)
- cfgHash = ""
- }
- payload.Hash = cfgHash
- payload.Default = am.isDefaultConfiguration(cfgHash)
- return payload, nil
+ return remoteClient.GrafanaAlertmanagerConfig{
+ TemplateFiles: c.TemplateFiles,
+ AlertmanagerConfig: mergeResult.Config,
+ Templates: templates,
+ }, nil
}
-func (am *Alertmanager) sendConfiguration(ctx context.Context, cfg remoteClient.UserGrafanaConfig) error {
+func (am *Alertmanager) sendConfiguration(ctx context.Context, cfg remoteClient.GrafanaAlertmanagerConfig, hash string, createdAt int64, isDefault bool) error {
am.metrics.ConfigSyncsTotal.Inc()
- if err := am.mimirClient.CreateGrafanaAlertmanagerConfig(ctx, &cfg); err != nil {
+ if err := am.mimirClient.CreateGrafanaAlertmanagerConfig(
+ ctx,
+ cfg,
+ hash,
+ createdAt,
+ isDefault,
+ ); err != nil {
am.metrics.ConfigSyncErrorsTotal.Inc()
return err
}
@@ -424,25 +422,40 @@ func (am *Alertmanager) SaveAndApplyConfig(ctx context.Context, cfg *apimodels.P
return err
}
- payload, err := am.buildConfiguration(ctx, rawCopy, time.Now().Unix())
+ payload, err := am.buildConfiguration(ctx, rawCopy)
if err != nil {
return fmt.Errorf("unable to build configuration: %w", err)
}
- return am.sendConfiguration(ctx, payload)
+ rawCfg, err := json.Marshal(payload)
+ if err != nil {
+ return err
+ }
+ hash := fmt.Sprintf("%x", md5.Sum(rawCfg))
+
+ return am.sendConfiguration(ctx, payload, hash, time.Now().Unix(), false)
}
// SaveAndApplyDefaultConfig sends the default Grafana Alertmanager configuration to the remote Alertmanager.
func (am *Alertmanager) SaveAndApplyDefaultConfig(ctx context.Context) error {
am.log.Debug("Sending default configuration to a remote Alertmanager", "url", am.url)
- payload, err := am.buildConfiguration(ctx, []byte(am.defaultConfig), time.Now().Unix())
+ payload, err := am.buildConfiguration(ctx, []byte(am.defaultConfig))
if err != nil {
return fmt.Errorf("unable to build default configuration: %w", err)
}
- payload.Default = true // override default status
+
+ rawCfg, err := json.Marshal(payload)
+ if err != nil {
+ return err
+ }
+ hash := fmt.Sprintf("%x", md5.Sum(rawCfg))
+
return am.sendConfiguration(
ctx,
payload,
+ hash,
+ time.Now().Unix(),
+ true,
)
}
@@ -683,29 +696,37 @@ func (am *Alertmanager) getFullState(ctx context.Context) (string, error) {
// shouldSendConfig compares the remote Alertmanager configuration with our local one.
// It returns true if the configurations are different.
func (am *Alertmanager) shouldSendConfig(ctx context.Context, hash string) bool {
- if hash == "" { // empty hash means that something went wrong while calculating it. In this case, always send the config.
- return true
- }
rc, err := am.mimirClient.GetGrafanaAlertmanagerConfig(ctx)
if err != nil {
// Log the error and return true so we try to upload our config anyway.
am.log.Warn("Unable to get the remote Alertmanager configuration for comparison, sending the configuration without comparing", "err", err)
return true
}
- if rc.Hash != hash {
- am.log.Debug("Hash of the remote Alertmanager configuration is different, sending the configuration", "remote", rc.Hash, "local", hash)
+
+ if rc.Promoted != am.mimirClient.ShouldPromoteConfig() {
return true
}
- return false
-}
-func calculateUserGrafanaConfigHash(config remoteClient.UserGrafanaConfig) (string, error) {
- // Ignore some fields when calculating the hash. Make sure the original struct is not modified after that.
- config.Default = false
- config.CreatedAt = 0 // ignore createdAt to support comparison with hash of default config
- config.Hash = ""
+ // Compare SMTP configs.
+ if rc.SmtpConfig.EhloIdentity != am.smtp.EhloIdentity ||
+ rc.SmtpConfig.Password != am.smtp.Password ||
+ rc.SmtpConfig.FromAddress != am.smtp.FromAddress ||
+ rc.SmtpConfig.FromName != am.smtp.FromName ||
+ rc.SmtpConfig.Host != am.smtp.Host ||
+ rc.SmtpConfig.SkipVerify != am.smtp.SkipVerify ||
+ rc.SmtpConfig.StartTLSPolicy != am.smtp.StartTLSPolicy ||
+ len(rc.SmtpConfig.StaticHeaders) != len(am.smtp.StaticHeaders) ||
+ rc.SmtpConfig.User != am.smtp.User {
+ am.log.Debug("SMTP config is different, sending the configuration to the remote Alertmanager")
+ return true
+ }
- hasher := fnv.New64a()
- hash.DeepHashObject(hasher, &config)
- return fmt.Sprintf("%x", hasher.Sum64()), nil
+ for k, v := range rc.SmtpConfig.StaticHeaders {
+ if value, ok := am.smtp.StaticHeaders[k]; !ok || v != value {
+ am.log.Debug("SMTP static headers are different, sending the configuration to the remote Alertmanager")
+ return true
+ }
+ }
+
+ return rc.Hash != hash
}
diff --git a/pkg/services/ngalert/remote/alertmanager_test.go b/pkg/services/ngalert/remote/alertmanager_test.go
index 10ea5e45eaf..6ce6abb8994 100644
--- a/pkg/services/ngalert/remote/alertmanager_test.go
+++ b/pkg/services/ngalert/remote/alertmanager_test.go
@@ -19,13 +19,10 @@ import (
"time"
"github.com/go-openapi/strfmt"
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
amv2 "github.com/prometheus/alertmanager/api/v2/models"
"github.com/prometheus/alertmanager/config"
"github.com/prometheus/alertmanager/pkg/labels"
"github.com/prometheus/client_golang/prometheus"
- common_config "github.com/prometheus/common/config"
"github.com/stretchr/testify/require"
alertingClusterPB "github.com/grafana/alerting/cluster/clusterpb"
@@ -504,6 +501,15 @@ func TestCompareAndSendConfiguration(t *testing.T) {
AlertmanagerConfig: testAutogenRoutes.AlertmanagerConfig,
}
+ // Calculate hashes for expected configurations
+ cfgWithDecryptedSecretBytes, err := json.Marshal(cfgWithDecryptedSecret)
+ require.NoError(t, err)
+ cfgWithDecryptedSecretHash := fmt.Sprintf("%x", md5.Sum(cfgWithDecryptedSecretBytes))
+
+ cfgWithAutogenRoutesBytes, err := json.Marshal(cfgWithAutogenRoutes)
+ require.NoError(t, err)
+ cfgWithAutogenRoutesHash := fmt.Sprintf("%x", md5.Sum(cfgWithAutogenRoutesBytes))
+
cfgWithExtraUnmergedBytes, err := testData.ReadFile(path.Join("test-data", "config-with-extra.json"))
require.NoError(t, err)
cfgWithExtraUnmerged, err := notifier.Load(cfgWithExtraUnmergedBytes)
@@ -515,6 +521,9 @@ func TestCompareAndSendConfiguration(t *testing.T) {
AlertmanagerConfig: r.Config,
Templates: definition.TemplatesMapToPostableAPITemplates(cfgWithExtraUnmerged.ExtraConfigs[0].TemplateFiles, definition.MimirTemplateKind),
}
+ cfgWithExtraMergedBytes, err := json.Marshal(cfgWithExtraMerged)
+ require.NoError(t, err)
+ cfgWithExtraMergedHash := fmt.Sprintf("%x", md5.Sum(cfgWithExtraMergedBytes))
tests := []struct {
name string
@@ -557,6 +566,7 @@ func TestCompareAndSendConfiguration(t *testing.T) {
NoopAutogenFn,
&client.UserGrafanaConfig{
GrafanaAlertmanagerConfig: cfgWithDecryptedSecret,
+ Hash: cfgWithDecryptedSecretHash,
},
nil,
},
@@ -566,6 +576,7 @@ func TestCompareAndSendConfiguration(t *testing.T) {
testAutogenFn,
&client.UserGrafanaConfig{
GrafanaAlertmanagerConfig: cfgWithAutogenRoutes,
+ Hash: cfgWithAutogenRoutesHash,
},
nil,
},
@@ -575,6 +586,7 @@ func TestCompareAndSendConfiguration(t *testing.T) {
autogenFn: NoopAutogenFn,
expCfg: &client.UserGrafanaConfig{
GrafanaAlertmanagerConfig: cfgWithExtraMerged,
+ Hash: cfgWithExtraMergedHash,
},
},
}
@@ -602,26 +614,9 @@ func TestCompareAndSendConfiguration(t *testing.T) {
err = am.CompareAndSendConfiguration(ctx, &cfg)
if len(test.expErrContains) == 0 {
require.NoError(tt, err)
-
- var gotCfg client.UserGrafanaConfig
- require.NoError(tt, json.Unmarshal([]byte(got), &gotCfg))
-
- require.NotEmpty(tt, gotCfg.Hash)
- require.Empty(tt, cmp.Diff(test.expCfg, &gotCfg,
- cmpopts.IgnoreFields(client.UserGrafanaConfig{}, "Hash"), // do not compare hashes because the config is processed slightly different: empty maps are nils.
- cmpopts.EquateEmpty(),
- cmpopts.IgnoreUnexported(
- time.Location{},
- labels.Matcher{},
- common_config.ProxyConfig{})))
-
- got1 := got
- got = ""
- err = am.CompareAndSendConfiguration(ctx, &cfg)
+ rawCfg, err := json.Marshal(test.expCfg)
require.NoError(tt, err)
-
- got2 := got
- require.Equalf(tt, got1, got2, "Configuration is not idempotent")
+ require.JSONEq(tt, string(rawCfg), got)
return
}
for _, expErr := range test.expErrContains {
@@ -820,7 +815,12 @@ receivers:
require.NotNil(t, extraReceiver)
require.Len(t, extraReceiver.EmailConfigs, 1)
require.Equal(t, "alerts@grafana.com", extraReceiver.EmailConfigs[0].To)
- require.NotEmpty(t, configSent.Hash)
+
+ // Verify the config hash
+ expectedConfigBytes, err := json.Marshal(configSent.GrafanaAlertmanagerConfig)
+ require.NoError(t, err)
+ expectedHash := fmt.Sprintf("%x", md5.Sum(expectedConfigBytes))
+ require.Equal(t, expectedHash, configSent.Hash)
}
func TestCompareAndSendConfigurationWithExtraConfigs(t *testing.T) {
@@ -934,7 +934,10 @@ receivers:
require.True(t, found)
// Verify the config hash
- require.NotEmpty(t, configSent.Hash)
+ expectedConfigBytes, err := json.Marshal(configSent.GrafanaAlertmanagerConfig)
+ require.NoError(t, err)
+ expectedHash := fmt.Sprintf("%x", md5.Sum(expectedConfigBytes))
+ require.Equal(t, expectedHash, configSent.Hash)
}
func TestIntegrationRemoteAlertmanagerConfiguration(t *testing.T) {
@@ -958,10 +961,11 @@ func TestIntegrationRemoteAlertmanagerConfiguration(t *testing.T) {
DefaultConfig: defaultGrafanaConfig,
}
+ testConfigHash := fmt.Sprintf("%x", md5.Sum([]byte(testGrafanaConfig)))
testConfigCreatedAt := time.Now().Unix()
testConfig := &ngmodels.AlertConfiguration{
AlertmanagerConfiguration: testGrafanaConfig,
- ConfigurationHash: "",
+ ConfigurationHash: testConfigHash,
ConfigurationVersion: "v2",
CreatedAt: testConfigCreatedAt,
OrgID: 1,
@@ -1008,6 +1012,7 @@ func TestIntegrationRemoteAlertmanagerConfiguration(t *testing.T) {
rawCfg, err := json.Marshal(config.GrafanaAlertmanagerConfig)
require.NoError(t, err)
require.JSONEq(t, testGrafanaConfig, string(rawCfg))
+ require.Equal(t, testConfigHash, config.Hash)
require.Equal(t, testConfigCreatedAt, config.CreatedAt)
require.Equal(t, testConfig.Default, config.Default)
@@ -1033,6 +1038,7 @@ func TestIntegrationRemoteAlertmanagerConfiguration(t *testing.T) {
rawCfg, err := json.Marshal(config.GrafanaAlertmanagerConfig)
require.NoError(t, err)
require.JSONEq(t, testGrafanaConfig, string(rawCfg))
+ require.Equal(t, testConfigHash, config.Hash)
require.Equal(t, testConfigCreatedAt, config.CreatedAt)
require.False(t, config.Default)
@@ -1079,6 +1085,9 @@ func TestIntegrationRemoteAlertmanagerConfiguration(t *testing.T) {
require.JSONEq(t, testGrafanaConfigWithSecret, string(got))
+ // Verify that the hash is calculated from the final configuration, including simplified routing
+ expectedHash := fmt.Sprintf("%x", md5.Sum(got))
+ require.Equal(t, expectedHash, config.Hash, "Hash should be calculated from the final processed configuration")
require.False(t, config.Default)
// An error while adding auto-generated rutes should be returned.
@@ -1105,6 +1114,7 @@ func TestIntegrationRemoteAlertmanagerConfiguration(t *testing.T) {
require.NoError(t, err)
require.JSONEq(t, string(want), string(got))
+ require.Equal(t, fmt.Sprintf("%x", md5.Sum(want)), config.Hash)
require.True(t, config.Default)
// An error while adding auto-generated rutes should be returned.
diff --git a/pkg/services/ngalert/remote/client/alertmanager_configuration.go b/pkg/services/ngalert/remote/client/alertmanager_configuration.go
index a53132a8812..b812c146687 100644
--- a/pkg/services/ngalert/remote/client/alertmanager_configuration.go
+++ b/pkg/services/ngalert/remote/client/alertmanager_configuration.go
@@ -39,6 +39,10 @@ type UserGrafanaConfig struct {
SmtpConfig SmtpConfig `json:"smtp_config"`
}
+func (mc *Mimir) ShouldPromoteConfig() bool {
+ return mc.promoteConfig
+}
+
func (mc *Mimir) GetGrafanaAlertmanagerConfig(ctx context.Context) (*UserGrafanaConfig, error) {
gc := &UserGrafanaConfig{}
response := successResponse{
@@ -58,8 +62,16 @@ func (mc *Mimir) GetGrafanaAlertmanagerConfig(ctx context.Context) (*UserGrafana
return gc, nil
}
-func (mc *Mimir) CreateGrafanaAlertmanagerConfig(ctx context.Context, cfg *UserGrafanaConfig) error {
- payload, err := definition.MarshalJSONWithSecrets(cfg)
+func (mc *Mimir) CreateGrafanaAlertmanagerConfig(ctx context.Context, cfg GrafanaAlertmanagerConfig, hash string, createdAt int64, isDefault bool) error {
+ payload, err := definition.MarshalJSONWithSecrets(&UserGrafanaConfig{
+ GrafanaAlertmanagerConfig: cfg,
+ Hash: hash,
+ CreatedAt: createdAt,
+ Default: isDefault,
+ Promoted: mc.promoteConfig,
+ ExternalURL: mc.externalURL,
+ SmtpConfig: mc.smtpConfig,
+ })
if err != nil {
return err
}
diff --git a/pkg/services/ngalert/remote/client/mimir.go b/pkg/services/ngalert/remote/client/mimir.go
index d8f9a51f327..1533e24c208 100644
--- a/pkg/services/ngalert/remote/client/mimir.go
+++ b/pkg/services/ngalert/remote/client/mimir.go
@@ -30,21 +30,26 @@ type MimirClient interface {
DeleteGrafanaAlertmanagerState(ctx context.Context) error
GetGrafanaAlertmanagerConfig(ctx context.Context) (*UserGrafanaConfig, error)
- CreateGrafanaAlertmanagerConfig(ctx context.Context, config *UserGrafanaConfig) error
+ CreateGrafanaAlertmanagerConfig(ctx context.Context, configuration GrafanaAlertmanagerConfig, hash string, createdAt int64, isDefault bool) error
DeleteGrafanaAlertmanagerConfig(ctx context.Context) error
TestTemplate(ctx context.Context, c alertingNotify.TestTemplatesConfigBodyParams) (*alertingNotify.TestTemplatesResults, error)
TestReceivers(ctx context.Context, c alertingNotify.TestReceiversConfigBodyParams) (*alertingNotify.TestReceiversResult, int, error)
+ ShouldPromoteConfig() bool
+
// Mimir implements an extended version of the receivers API under a different path.
GetReceivers(ctx context.Context) ([]apimodels.Receiver, error)
}
type Mimir struct {
- client client.Requester
- endpoint *url.URL
- logger log.Logger
- metrics *metrics.RemoteAlertmanager
+ client client.Requester
+ endpoint *url.URL
+ logger log.Logger
+ metrics *metrics.RemoteAlertmanager
+ promoteConfig bool
+ externalURL string
+ smtpConfig SmtpConfig
}
type SmtpConfig struct {
@@ -64,7 +69,10 @@ type Config struct {
TenantID string
Password string
- Logger log.Logger
+ Logger log.Logger
+ PromoteConfig bool
+ ExternalURL string
+ Smtp SmtpConfig
}
// successResponse represents a successful response from the Mimir API.
@@ -102,10 +110,13 @@ func New(cfg *Config, metrics *metrics.RemoteAlertmanager, tracer tracing.Tracer
trc := client.NewTracedClient(tc, tracer, "remote.alertmanager.client")
return &Mimir{
- endpoint: cfg.URL,
- client: trc,
- logger: cfg.Logger,
- metrics: metrics,
+ endpoint: cfg.URL,
+ client: trc,
+ logger: cfg.Logger,
+ metrics: metrics,
+ promoteConfig: cfg.PromoteConfig,
+ externalURL: cfg.ExternalURL,
+ smtpConfig: cfg.Smtp,
}, nil
}
From a5ceac4474318c5bc9220313d7d55df7f586b714 Mon Sep 17 00:00:00 2001
From: Paul Marbach
Date: Fri, 1 Aug 2025 19:56:12 -0400
Subject: [PATCH 42/89] TableNG: Markdown cell, plus auto row height (#107549)
* TableNG: Markdown cell, plus custom row height
* tab indentation in cue file
* fix i18n
* trying an auto height with the updated RDG
* get auto cellHeight working
* i18n updates
* hoor disable_sanitize_html flag in MarkdownCell
* update react-data-grid version to attempt to support page up and down
* removing custom height
* use the latest experimental RDG with paging up and down
* TableNG: Wrap text for DataLinks and Pills; groundwork for max wrap length
* disable editing max wrapped lines for now
* disable wrap text line limit e2e
* new i18n extract after commenting out input
* wip
* kill max wrapped lines for now
* more cleanup
* remove targeting classes added for max wrapped lines
* fix Pill test
* couple more style cleanups
* fix e2es given these updates
* add a couple tests
* wip: tests
* add tests
* bump up capital letters in lorem ipsum
* fix copy-pasta mistake
* whoops, mis-merged the selector
* use a local count instead of getCellLinks
* use react-data-grid on react-18 branch
* fix linting on test
* gdev dashboard and smoketest for Markdown table
* remove cellHeightCustom
* reorganize in light of recent and upcoming changes
* remove one more reference to cellHeightCustom
* put getDefaultRowHeight back into a util
* clean up test
* swap cell height back to a radio
* revert ImageCell change, we'll do it in the getStyles PR
* don't memo defaultRowHeight
* final couple of style cleanups
* different approach to managing the auto height part of this
* kill console.log
* update i18n
* reorganized once more
* i18n
* guard against rowHeight being auto for virtualization
* may as well memoize the defaultRowHeight
* get rid of the enableVirtualization initializer thing
* fixes from CI
* fix test
* fix test
* just omit third arg for that test
* remove nonsensical test case
* this file didn't get re-gen'd
* fixes from review
* row expander doesn't need height
* remove console.log
* fix e2e after we fixed pagination toggle bug
---
.betterer.results | 3 +
.../panel-table/table_markdown.json | 126 ++++++++++++++++++
devenv/jsonnet/dev-dashboards.libsonnet | 1 +
.../panels-suite/table-kitchenSink.spec.ts | 4 +-
.../panels-suite/table-markdown.spec.ts | 25 ++++
.../grafana-schema/src/common/common.gen.ts | 8 +-
packages/grafana-schema/src/common/table.cue | 9 +-
packages/grafana-ui/package.json | 2 +-
.../Table/TableNG/Cells/MarkdownCell.tsx | 17 +++
.../Table/TableNG/Cells/RowExpander.tsx | 7 +-
.../Table/TableNG/Cells/renderers.tsx | 15 ++-
.../src/components/Table/TableNG/TableNG.tsx | 75 +++++++++--
.../src/components/Table/TableNG/hooks.ts | 18 ++-
.../src/components/Table/TableNG/types.ts | 10 +-
.../components/Table/TableNG/utils.test.ts | 49 +++++--
.../src/components/Table/TableNG/utils.ts | 14 +-
.../table/table-new/PaginationEditor.tsx | 5 +-
.../table/table-new/TableCellOptionEditor.tsx | 5 +
.../panel/table/table-new/TablePanel.tsx | 4 +
.../cells/MarkdownCellOptionsEditor.tsx | 39 ++++++
public/locales/en-US/grafana.json | 8 ++
yarn.lock | 8 +-
22 files changed, 394 insertions(+), 58 deletions(-)
create mode 100644 devenv/dev-dashboards/panel-table/table_markdown.json
create mode 100644 e2e-playwright/panels-suite/table-markdown.spec.ts
create mode 100644 packages/grafana-ui/src/components/Table/TableNG/Cells/MarkdownCell.tsx
create mode 100644 public/app/plugins/panel/table/table-new/cells/MarkdownCellOptionsEditor.tsx
diff --git a/.betterer.results b/.betterer.results
index 0493a87d6d3..b11063eae47 100644
--- a/.betterer.results
+++ b/.betterer.results
@@ -3932,6 +3932,9 @@ exports[`better eslint`] = {
[0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"],
[0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "1"]
],
+ "public/app/plugins/panel/table/table-new/cells/MarkdownCellOptionsEditor.tsx:5381": [
+ [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"]
+ ],
"public/app/plugins/panel/table/table-new/cells/SparklineCellOptionsEditor.tsx:5381": [
[0, 0, 0, "\'VerticalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"],
[0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "1"]
diff --git a/devenv/dev-dashboards/panel-table/table_markdown.json b/devenv/dev-dashboards/panel-table/table_markdown.json
new file mode 100644
index 00000000000..b6aa28ca3b0
--- /dev/null
+++ b/devenv/dev-dashboards/panel-table/table_markdown.json
@@ -0,0 +1,126 @@
+{
+ "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": 1735,
+ "links": [],
+ "panels": [
+ {
+ "datasource": {
+ "type": "grafana-testdata-datasource",
+ "uid": "gdev-testdata"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "thresholds"
+ },
+ "custom": {
+ "align": "auto",
+ "cellOptions": {
+ "type": "auto"
+ },
+ "inspect": false,
+ "wrapHeaderText": false
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": 0
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ }
+ },
+ "overrides": [
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "markdown"
+ },
+ "properties": [
+ {
+ "id": "custom.cellOptions",
+ "value": {
+ "type": "markdown",
+ "dynamicHeight": true
+ }
+ }
+ ]
+ }
+ ]
+ },
+ "gridPos": {
+ "h": 18,
+ "w": 24,
+ "x": 0,
+ "y": 0
+ },
+ "id": 1,
+ "options": {
+ "cellHeight": "lg",
+ "footer": {
+ "enablePagination": true,
+ "countRows": false,
+ "fields": "",
+ "reducer": [
+ "sum"
+ ],
+ "show": false
+ },
+ "showHeader": true
+ },
+ "pluginVersion": "12.1.0-pre",
+ "targets": [
+ {
+ "csvContent": "id,markdown\n1,\"\n - Definition list
\n - Is something people use sometimes.
\n - Markdown in HTML
\n - Does *not* work **very** well. Use HTML tags.
\n
\"\n2,\"Three or more...\n\n---\n\nHyphens\n\n***\n\nAsterisks\n\n___\n\nUnderscores\"\n3,\"Here's a line for us to start with.\n\nThis line is separated from the one above by two newlines, so it will be a *separate paragraph*.\n\nThis line is also a separate paragraph, but...\nThis line is only separated by a single newline, so it's a separate line in the *same paragraph*.\"\n4,\"red, green, blue\"\n5,\"
\"\n6,\"[Link](https://grafana.com), or HTML link\"\n7,\"1. foo\n1. bar\n - baz\n * bim\n3. bip\"\n8,\"# heading 1\n## heading 2\n### heading 3\n#### heading 4\n##### heading 5\n###### heading 6\"\n9,\"Emphasis, aka italics, with *asterisks* or _underscores_.\n\nStrong emphasis, aka bold, with **asterisks** or __underscores__.\n\nCombined emphasis with **asterisks and _underscores_**.\n\nStrikethrough uses two tildes. ~~Scratch this.~~\n\nunderline does require an HTML element tho.\"\n10,\"```javascript\nvar s = 'JavaScript syntax highlighting';\nalert(s);\n```\"\n11,\"\n| Month | Savings |\n| -------- | ------- |\n| I heard | $250 |\n| you like | $80 |\n| tables | $365 |\"\n",
+ "datasource": {
+ "type": "grafana-testdata-datasource",
+ "uid": "gdev-testdata"
+ },
+ "refId": "A",
+ "scenarioId": "csv_content"
+ }
+ ],
+ "title": "Markdown Table",
+ "type": "table"
+ }
+ ],
+ "preload": false,
+ "schemaVersion": 41,
+ "tags": [],
+ "templating": {
+ "list": []
+ },
+ "time": {
+ "from": "now-6h",
+ "to": "now"
+ },
+ "timepicker": {},
+ "timezone": "",
+ "title": "Panel Tests - Table - Markdown",
+ "uid": "2769f5d8-0094-4ac4-a4f0-f68f620339cc",
+ "version": 1
+}
diff --git a/devenv/jsonnet/dev-dashboards.libsonnet b/devenv/jsonnet/dev-dashboards.libsonnet
index d16f9b96c84..c050297d430 100644
--- a/devenv/jsonnet/dev-dashboards.libsonnet
+++ b/devenv/jsonnet/dev-dashboards.libsonnet
@@ -94,6 +94,7 @@
"shared_queries": (import '../dev-dashboards/panel-common/shared_queries.json'),
"slow_queries_and_annotations": (import '../dev-dashboards/scenarios/slow_queries_and_annotations.json'),
"table_kitchen_sink": (import '../dev-dashboards/panel-table/table_kitchen_sink.json'),
+ "table_markdown": (import '../dev-dashboards/panel-table/table_markdown.json'),
"table_pagination": (import '../dev-dashboards/panel-table/table_pagination.json'),
"table_sparkline_cell": (import '../dev-dashboards/panel-table/table_sparkline_cell.json'),
"table_tests": (import '../dev-dashboards/panel-table/table_tests.json'),
diff --git a/e2e-playwright/panels-suite/table-kitchenSink.spec.ts b/e2e-playwright/panels-suite/table-kitchenSink.spec.ts
index c7947fcd281..8ac43ceebb1 100644
--- a/e2e-playwright/panels-suite/table-kitchenSink.spec.ts
+++ b/e2e-playwright/panels-suite/table-kitchenSink.spec.ts
@@ -225,7 +225,7 @@ test.describe('Panels test: Table - Kitchen Sink', { tag: ['@panels', '@table']
// in a second, though.
const smallRowStatus = await getRowStatus(page);
expect(smallRowStatus.end).toBeGreaterThan(1);
- expect(page.getByRole('grid').getByRole('row')).toHaveCount(smallRowStatus.end + 1);
+ expect(page.getByRole('grid').getByRole('row')).toHaveCount(smallRowStatus.end + 2); // +2 for header and footer rows
// change cell height to Large
await dashboardPage
@@ -235,7 +235,7 @@ test.describe('Panels test: Table - Kitchen Sink', { tag: ['@panels', '@table']
.click();
const largeRowStatus = await getRowStatus(page);
expect(largeRowStatus.end).toBeLessThan(smallRowStatus.end);
- expect(page.getByRole('grid').getByRole('row')).toHaveCount(largeRowStatus.end + 1);
+ expect(page.getByRole('grid').getByRole('row')).toHaveCount(largeRowStatus.end + 2); // +2 for header and footer rows
// click a page over with the directional nav
await page.getByLabel('next page').click();
diff --git a/e2e-playwright/panels-suite/table-markdown.spec.ts b/e2e-playwright/panels-suite/table-markdown.spec.ts
new file mode 100644
index 00000000000..b2e127fc92c
--- /dev/null
+++ b/e2e-playwright/panels-suite/table-markdown.spec.ts
@@ -0,0 +1,25 @@
+import { test, expect } from '@grafana/plugin-e2e';
+
+test.use({
+ viewport: { width: 1280, height: 1080 },
+ featureToggles: {
+ tableNextGen: true,
+ },
+});
+
+test.describe(
+ 'Panels test: Table - Markdown',
+ {
+ tag: ['@panels', '@table'],
+ },
+ () => {
+ test('Tests Markdown tables are successfully rendered', async ({ gotoDashboardPage, page }) => {
+ await gotoDashboardPage({
+ uid: '2769f5d8-0094-4ac4-a4f0-f68f620339cc',
+ queryParams: new URLSearchParams({ editPanel: '1' }),
+ });
+
+ await expect(page.getByRole('grid')).toBeVisible();
+ });
+ }
+);
diff --git a/packages/grafana-schema/src/common/common.gen.ts b/packages/grafana-schema/src/common/common.gen.ts
index 9b229c4d1e9..c256e8ed6bd 100644
--- a/packages/grafana-schema/src/common/common.gen.ts
+++ b/packages/grafana-schema/src/common/common.gen.ts
@@ -708,6 +708,7 @@ export enum TableCellDisplayMode {
Image = 'image',
JSONView = 'json-view',
LcdGauge = 'lcd-gauge',
+ Markdown = 'markdown',
Pill = 'pill',
Sparkline = 'sparkline',
}
@@ -836,6 +837,11 @@ export interface TablePillCellOptions extends TableWrapTextOptions {
type: TableCellDisplayMode.Pill;
}
+export interface TableMarkdownCellOptions {
+ dynamicHeight?: boolean;
+ type: TableCellDisplayMode.Markdown;
+}
+
/**
* Height of a table cell
*/
@@ -850,7 +856,7 @@ export enum TableCellHeight {
* Table cell options. Each cell has a display mode
* and other potential options for that display.
*/
-export type TableCellOptions = (TableAutoCellOptions | TableSparklineCellOptions | TableBarGaugeCellOptions | TableColoredBackgroundCellOptions | TableColorTextCellOptions | TableImageCellOptions | TablePillCellOptions | TableDataLinksCellOptions | TableActionsCellOptions | TableJsonViewCellOptions);
+export type TableCellOptions = (TableAutoCellOptions | TableSparklineCellOptions | TableBarGaugeCellOptions | TableColoredBackgroundCellOptions | TableColorTextCellOptions | TableImageCellOptions | TablePillCellOptions | TableDataLinksCellOptions | TableActionsCellOptions | TableJsonViewCellOptions | TableMarkdownCellOptions);
/**
* Use UTC/GMT timezone
diff --git a/packages/grafana-schema/src/common/table.cue b/packages/grafana-schema/src/common/table.cue
index 0a15fb926b5..57b7b56dc44 100644
--- a/packages/grafana-schema/src/common/table.cue
+++ b/packages/grafana-schema/src/common/table.cue
@@ -4,7 +4,7 @@ package common
// in the table such as colored text, JSON, gauge, etc.
// The color-background-solid, gradient-gauge, and lcd-gauge
// modes are deprecated in favor of new cell subOptions
-TableCellDisplayMode: "auto" | "color-text" | "color-background" | "color-background-solid" | "gradient-gauge" | "lcd-gauge" | "json-view" | "basic" | "image" | "gauge" | "sparkline" | "data-links" | "custom" | "actions" | "pill" @cuetsy(kind="enum",memberNames="Auto|ColorText|ColorBackground|ColorBackgroundSolid|GradientGauge|LcdGauge|JSONView|BasicGauge|Image|Gauge|Sparkline|DataLinks|Custom|Actions|Pill")
+TableCellDisplayMode: "auto" | "color-text" | "color-background" | "color-background-solid" | "gradient-gauge" | "lcd-gauge" | "json-view" | "basic" | "image" | "gauge" | "sparkline" | "data-links" | "custom" | "actions" | "pill" | "markdown" @cuetsy(kind="enum",memberNames="Auto|ColorText|ColorBackground|ColorBackgroundSolid|GradientGauge|LcdGauge|JSONView|BasicGauge|Image|Gauge|Sparkline|DataLinks|Custom|Actions|Pill|Markdown")
// Display mode to the "Colored Background" display
// mode for table cells. Either displays a solid color (basic mode)
@@ -91,12 +91,17 @@ TablePillCellOptions: {
type: TableCellDisplayMode & "pill"
} & TableWrapTextOptions @cuetsy(kind="interface")
+TableMarkdownCellOptions: {
+ type: TableCellDisplayMode & "markdown"
+ dynamicHeight?: bool
+} @cuetsy(kind="interface")
+
// Height of a table cell
TableCellHeight: "sm" | "md" | "lg" | "auto" @cuetsy(kind="enum")
// Table cell options. Each cell has a display mode
// and other potential options for that display.
-TableCellOptions: TableAutoCellOptions | TableSparklineCellOptions | TableBarGaugeCellOptions | TableColoredBackgroundCellOptions | TableColorTextCellOptions | TableImageCellOptions | TablePillCellOptions | TableDataLinksCellOptions | TableActionsCellOptions | TableJsonViewCellOptions @cuetsy(kind="type")
+TableCellOptions: TableAutoCellOptions | TableSparklineCellOptions | TableBarGaugeCellOptions | TableColoredBackgroundCellOptions | TableColorTextCellOptions | TableImageCellOptions | TablePillCellOptions | TableDataLinksCellOptions | TableActionsCellOptions | TableJsonViewCellOptions | TableMarkdownCellOptions @cuetsy(kind="type")
// Field options for each field within a table (e.g 10, "The String", 64.20, etc.)
// Generally defines alignment, filtering capabilties, display options, etc.
diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json
index dfb0c9754ea..f04ad6287e1 100644
--- a/packages/grafana-ui/package.json
+++ b/packages/grafana-ui/package.json
@@ -109,7 +109,7 @@
"react-calendar": "^6.0.0",
"react-colorful": "5.6.1",
"react-custom-scrollbars-2": "4.5.0",
- "react-data-grid": "grafana/react-data-grid#de920f0105cb2b7d774444e7443a675f3b568ad6",
+ "react-data-grid": "grafana/react-data-grid#a922856b5ede21d55db3fdffb6d38dc76bdc7c58",
"react-dropzone": "14.3.8",
"react-highlight-words": "0.21.0",
"react-hook-form": "^7.49.2",
diff --git a/packages/grafana-ui/src/components/Table/TableNG/Cells/MarkdownCell.tsx b/packages/grafana-ui/src/components/Table/TableNG/Cells/MarkdownCell.tsx
new file mode 100644
index 00000000000..7d4922c2434
--- /dev/null
+++ b/packages/grafana-ui/src/components/Table/TableNG/Cells/MarkdownCell.tsx
@@ -0,0 +1,17 @@
+import { renderMarkdown } from '@grafana/data';
+
+import { MaybeWrapWithLink } from '../MaybeWrapWithLink';
+import { MarkdownCellProps } from '../types';
+
+export function MarkdownCell({ field, rowIdx, disableSanitizeHtml }: MarkdownCellProps) {
+ return (
+
+
+
+ );
+}
diff --git a/packages/grafana-ui/src/components/Table/TableNG/Cells/RowExpander.tsx b/packages/grafana-ui/src/components/Table/TableNG/Cells/RowExpander.tsx
index 019afb7e243..d1f64824ef3 100644
--- a/packages/grafana-ui/src/components/Table/TableNG/Cells/RowExpander.tsx
+++ b/packages/grafana-ui/src/components/Table/TableNG/Cells/RowExpander.tsx
@@ -7,8 +7,8 @@ import { useStyles2 } from '../../../../themes/ThemeContext';
import { Icon } from '../../../Icon/Icon';
import { RowExpanderNGProps } from '../types';
-export function RowExpander({ height, onCellExpand, isExpanded }: RowExpanderNGProps) {
- const styles = useStyles2(getStyles, height);
+export function RowExpander({ onCellExpand, isExpanded }: RowExpanderNGProps) {
+ const styles = useStyles2(getStyles);
function handleKeyDown(e: React.KeyboardEvent) {
if (e.key === ' ' || e.key === 'Enter') {
e.preventDefault();
@@ -30,12 +30,11 @@ export function RowExpander({ height, onCellExpand, isExpanded }: RowExpanderNGP
);
}
-const getStyles = (theme: GrafanaTheme2, rowHeight: number) => ({
+const getStyles = (_theme: GrafanaTheme2) => ({
expanderCell: css({
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
- height: `${rowHeight}px`,
cursor: 'pointer',
}),
});
diff --git a/packages/grafana-ui/src/components/Table/TableNG/Cells/renderers.tsx b/packages/grafana-ui/src/components/Table/TableNG/Cells/renderers.tsx
index 8d444eddf40..c406d7ffc3f 100644
--- a/packages/grafana-ui/src/components/Table/TableNG/Cells/renderers.tsx
+++ b/packages/grafana-ui/src/components/Table/TableNG/Cells/renderers.tsx
@@ -11,6 +11,7 @@ import { BarGaugeCell } from './BarGaugeCell';
import { DataLinksCell } from './DataLinksCell';
import { GeoCell } from './GeoCell';
import { ImageCell } from './ImageCell';
+import { MarkdownCell } from './MarkdownCell';
import { PillCell } from './PillCell';
import { SparklineCell } from './SparklineCell';
@@ -78,6 +79,10 @@ const CUSTOM_RENDERER: TableCellRenderer = (props) => {
return ;
};
+const MARKDOWN_RENDERER: TableCellRenderer = (props) => (
+
+);
+
const CELL_RENDERERS: Record = {
[TableCellDisplayMode.Sparkline]: SPARKLINE_RENDERER,
[TableCellDisplayMode.Gauge]: GAUGE_RENDERER,
@@ -89,9 +94,16 @@ const CELL_RENDERERS: Record = {
[TableCellDisplayMode.ColorText]: AUTO_RENDERER,
[TableCellDisplayMode.ColorBackground]: AUTO_RENDERER,
[TableCellDisplayMode.Auto]: AUTO_RENDERER,
+ [TableCellDisplayMode.Markdown]: MARKDOWN_RENDERER,
[TableCellDisplayMode.Pill]: PILL_RENDERER,
};
+// TODO: come up with a more elegant way to handle this.
+const STRING_ONLY_RENDERERS = new Set([
+ TableCellDisplayMode.Markdown,
+ TableCellDisplayMode.Pill,
+]);
+
/** @internal */
export function getCellRenderer(field: Field, cellOptions: TableCellOptions): TableCellRenderer {
const cellType = cellOptions?.type ?? TableCellDisplayMode.Auto;
@@ -99,8 +111,7 @@ export function getCellRenderer(field: Field, cellOptions: TableCellOptions): Ta
return getAutoRendererResult(field);
}
- // TODO: add support boolean, enum, (maybe int). but for now just string fields
- if (cellType === TableCellDisplayMode.Pill && field.type !== FieldType.string) {
+ if (STRING_ONLY_RENDERERS.has(cellType) && field.type !== FieldType.string) {
return AUTO_RENDERER;
}
diff --git a/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx b/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx
index 8d6c9bb5ef8..82ca9192066 100644
--- a/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx
+++ b/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx
@@ -84,6 +84,7 @@ export function TableNG(props: TableNGProps) {
const {
cellHeight,
data,
+ disableSanitizeHtml,
enablePagination = false,
enableSharedCrosshair = false,
enableVirtualization,
@@ -160,12 +161,15 @@ export function TableNG(props: TableNGProps) {
setSortColumns,
} = useSortedRows(filteredRows, data.fields, { hasNestedFrames, initialSortBy });
- const defaultRowHeight = getDefaultRowHeight(theme, cellHeight);
const [isInspecting, setIsInspecting] = useState(false);
const [expandedRows, setExpandedRows] = useState(() => new Set());
// vt scrollbar accounting for column auto-sizing
const visibleFields = useMemo(() => getVisibleFields(data.fields), [data.fields]);
+ const defaultRowHeight = useMemo(
+ () => getDefaultRowHeight(theme, visibleFields, cellHeight),
+ [theme, visibleFields, cellHeight]
+ );
const gridRef = useRef(null);
const scrollbarWidth = useScrollbarWidth(gridRef, height, sortedRows);
const availableWidth = useMemo(
@@ -212,7 +216,7 @@ export function TableNG(props: TableNGProps) {
width: availableWidth,
height,
headerHeight,
- footerHeight: hasFooter ? defaultRowHeight : 0,
+ footerHeight: hasFooter ? (typeof defaultRowHeight === 'number' ? defaultRowHeight : TABLE.MAX_CELL_HEIGHT) : 0,
rowHeight,
});
@@ -224,6 +228,17 @@ export function TableNG(props: TableNGProps) {
});
const applyToRowBgFn = useMemo(() => getApplyToRowBgFn(data.fields, theme) ?? undefined, [data.fields, theme]);
+ // normalize the row height into a function which returns a number, so we avoid a bunch of conditionals during rendering.
+ const rowHeightFn = useMemo((): ((row: TableRow) => number) => {
+ if (typeof rowHeight === 'function') {
+ return rowHeight;
+ }
+ if (typeof rowHeight === 'string') {
+ return () => TABLE.MAX_CELL_HEIGHT;
+ }
+ return () => rowHeight;
+ }, [rowHeight]);
+
const renderRow = useMemo(
() => renderRowFactory(data.fields, panelContext, expandedRows, enableSharedCrosshair),
[data, enableSharedCrosshair, expandedRows, panelContext]
@@ -232,7 +247,7 @@ export function TableNG(props: TableNGProps) {
const commonDataGridProps = useMemo(
() =>
({
- enableVirtualization,
+ enableVirtualization: enableVirtualization !== false && rowHeight !== 'auto',
defaultColumnOptions: {
minWidth: 50,
resizable: true,
@@ -348,8 +363,8 @@ export function TableNG(props: TableNGProps) {
)
: undefined;
- const shouldOverflow = shouldTextOverflow(field);
- const shouldWrap = shouldTextWrap(field);
+ const shouldOverflow = rowHeight !== 'auto' && shouldTextOverflow(field);
+ const shouldWrap = rowHeight === 'auto' || shouldTextWrap(field);
const withTooltip = withDataLinksActionsTooltip(field, cellType);
const canBeColorized =
cellType === TableCellDisplayMode.ColorBackground || cellType === TableCellDisplayMode.ColorText;
@@ -368,6 +383,7 @@ export function TableNG(props: TableNGProps) {
case TableCellDisplayMode.DataLinks:
case TableCellDisplayMode.JSONView:
case TableCellDisplayMode.Pill:
+ case TableCellDisplayMode.Markdown:
cellClass = getCellStyles(
theme,
cellType,
@@ -428,7 +444,9 @@ export function TableNG(props: TableNGProps) {
const value = props.row[props.column.key];
// TODO: it would be nice to get rid of passing height down as a prop. but this value
// is cached so the cost of calling for every cell is low.
- const height = typeof rowHeight === 'function' ? rowHeight(props.row) : rowHeight;
+ // NOTE: some cell types still require a height to be passed down, so that's why string-based
+ // cell types are going to just pass down the max cell height as a numeric height for those cells.
+ const height = rowHeightFn(props.row);
const frame = data;
return (
@@ -446,6 +464,7 @@ export function TableNG(props: TableNGProps) {
cellInspect,
showFilters,
getActions: getCellActions,
+ disableSanitizeHtml,
})}
{showActions && (
{
if (expandedRows.has(rowIdx)) {
@@ -608,7 +626,7 @@ export function TableNG(props: TableNGProps) {
crossFilterOrder,
crossFilterRows,
data,
- defaultRowHeight,
+ disableSanitizeHtml,
enableSharedCrosshair,
expandedRows,
filter,
@@ -618,6 +636,7 @@ export function TableNG(props: TableNGProps) {
onCellFilterAdded,
panelContext,
rowHeight,
+ rowHeightFn,
rows,
setFilter,
showTypeIcons,
@@ -947,16 +966,26 @@ const getCellStyles = (
shouldOverflow: boolean,
isColorized: boolean,
isMonospace: boolean
-) =>
- css({
+) => {
+ const whiteSpace: CSSProperties['whiteSpace'] = (() => {
+ if (isMonospace) {
+ return 'pre';
+ }
+ if (cellType === TableCellDisplayMode.Markdown) {
+ return 'normal';
+ }
+ return 'pre-line';
+ })();
+
+ return css({
display: 'flex',
alignItems: 'center',
textAlign,
justifyContent: getJustifyContent(textAlign),
- minHeight: '100%',
- backgroundClip: 'padding-box !important', // helps when cells have a bg color
- ...(shouldWrap && { whiteSpace: isMonospace ? 'pre' : 'pre-line' }),
+ ...(isColorized && { backgroundClip: 'padding-box !important' }),
+ ...(shouldOverflow && { minHeight: '100%' }),
+ ...(shouldWrap && { whiteSpace }),
...(isMonospace && { fontFamily: 'monospace' }),
'&:hover, &[aria-selected=true]': {
@@ -965,7 +994,7 @@ const getCellStyles = (
},
...(shouldOverflow && {
zIndex: theme.zIndex.tooltip - 2,
- whiteSpace: isMonospace ? 'pre' : 'pre-line',
+ whiteSpace,
height: 'fit-content',
minWidth: 'fit-content',
...(cellType === TableCellDisplayMode.Pill && {
@@ -1025,4 +1054,22 @@ const getCellStyles = (
whiteSpace: 'nowrap',
},
}),
+
+ ...(cellType === TableCellDisplayMode.Markdown && {
+ '& ol, & ul': {
+ paddingLeft: theme.spacing(1.5),
+ },
+ '& p': {
+ whiteSpace: 'pre-line',
+ },
+ '& a': {
+ color: theme.colors.primary.text,
+ },
+ // for elements like `p`, `h*`, etc. which have an inherent margin,
+ // we want to remove the bottom margin for the last one in the container.
+ '& > .markdown-container > *:last-child': {
+ marginBottom: 0,
+ },
+ }),
});
+};
diff --git a/packages/grafana-ui/src/components/Table/TableNG/hooks.ts b/packages/grafana-ui/src/components/Table/TableNG/hooks.ts
index 5c349bdd1ab..47efa394409 100644
--- a/packages/grafana-ui/src/components/Table/TableNG/hooks.ts
+++ b/packages/grafana-ui/src/components/Table/TableNG/hooks.ts
@@ -1,4 +1,4 @@
-import { useState, useMemo, useEffect, useCallback, useRef, useLayoutEffect, RefObject } from 'react';
+import { useState, useMemo, useCallback, useRef, useLayoutEffect, RefObject, CSSProperties } from 'react';
import { Column, DataGridHandle, DataGridProps, SortColumn } from 'react-data-grid';
import { Field, fieldReducers, FieldType, formattedValueToString, reduceField } from '@grafana/data';
@@ -135,7 +135,7 @@ export function useSortedRows(
export interface PaginatedRowsOptions {
height: number;
width: number;
- rowHeight: number | ((row: TableRow) => number);
+ rowHeight: NonNullable | ((row: TableRow) => number);
headerHeight: number;
footerHeight: number;
paginationHeight?: number;
@@ -174,6 +174,12 @@ export function usePaginatedRows(
return rowHeight;
}
+ // when using auto-sized rows, we're just going to have to pick a number. the alternative
+ // is to measure each row, which we could do but would be expensive.
+ if (typeof rowHeight === 'string') {
+ return TABLE.MAX_CELL_HEIGHT;
+ }
+
// we'll just measure 100 rows to estimate
return rows.slice(0, 100).reduce((avg, row, _, { length }) => avg + rowHeight(row) / length, 0);
}, [rows, rowHeight, enabled]);
@@ -214,7 +220,7 @@ export function usePaginatedRows(
}, [width, height, headerHeight, footerHeight, avgRowHeight, enabled, numRows, page]);
// safeguard against page overflow on panel resize or other factors
- useEffect(() => {
+ useLayoutEffect(() => {
if (!enabled) {
return;
}
@@ -381,7 +387,7 @@ interface UseRowHeightOptions {
columnWidths: number[];
fields: Field[];
hasNestedFrames: boolean;
- defaultHeight: number;
+ defaultHeight: NonNullable;
expandedRows: Set;
typographyCtx: TypographyCtx;
}
@@ -393,7 +399,7 @@ export function useRowHeight({
defaultHeight,
expandedRows,
typographyCtx,
-}: UseRowHeightOptions): number | ((row: TableRow) => number) {
+}: UseRowHeightOptions): NonNullable | ((row: TableRow) => number) {
const lineCounters = useMemo(() => buildRowLineCounters(fields, typographyCtx), [fields, typographyCtx]);
const hasWrappedCols = useMemo(() => lineCounters?.length ?? 0 > 0, [lineCounters]);
@@ -404,7 +410,7 @@ export function useRowHeight({
const rowHeight = useMemo(() => {
// row height is only complicated when there are nested frames or wrapped columns.
- if (!hasNestedFrames && !hasWrappedCols) {
+ if ((!hasNestedFrames && !hasWrappedCols) || typeof defaultHeight === 'string') {
return defaultHeight;
}
diff --git a/packages/grafana-ui/src/components/Table/TableNG/types.ts b/packages/grafana-ui/src/components/Table/TableNG/types.ts
index d28bab7b668..eaca19734ea 100644
--- a/packages/grafana-ui/src/components/Table/TableNG/types.ts
+++ b/packages/grafana-ui/src/components/Table/TableNG/types.ts
@@ -141,6 +141,8 @@ export interface BaseTableProps {
getActions?: GetActionsFunction;
// Used solely for testing as RTL can't correctly render the table otherwise
enableVirtualization?: boolean;
+ // for MarkdownCell, this flag disables sanitization of HTML content. Configured via config.ini.
+ disableSanitizeHtml?: boolean;
}
/* ---------------------------- Table cell props ---------------------------- */
@@ -161,6 +163,7 @@ export interface TableCellRendererProps {
showFilters: boolean;
justifyContent: Property.JustifyContent;
getActions?: GetActionsFunctionLocal;
+ disableSanitizeHtml?: boolean;
}
export type ContextMenuProps = {
@@ -186,7 +189,6 @@ export interface TableCellActionsProps {
/* ------------------------- Specialized Cell Props ------------------------- */
export interface RowExpanderNGProps {
- height: number;
onCellExpand: (e: SyntheticEvent) => void;
isExpanded?: boolean;
}
@@ -242,6 +244,12 @@ export interface AutoCellProps {
rowIdx: number;
}
+export interface MarkdownCellProps {
+ field: Field;
+ rowIdx: number;
+ disableSanitizeHtml?: boolean;
+}
+
export interface ActionCellProps {
field: Field;
rowIdx: number;
diff --git a/packages/grafana-ui/src/components/Table/TableNG/utils.test.ts b/packages/grafana-ui/src/components/Table/TableNG/utils.test.ts
index 4dd20cac215..e159c9933cb 100644
--- a/packages/grafana-ui/src/components/Table/TableNG/utils.test.ts
+++ b/packages/grafana-ui/src/components/Table/TableNG/utils.test.ts
@@ -27,7 +27,6 @@ import {
getCellLinks,
getCellOptions,
getComparator,
- getDefaultRowHeight,
getIsNestedTable,
getAlignment,
getJustifyContent,
@@ -44,6 +43,7 @@ import {
wrapUwrapCount,
getDataLinksCounter,
getPillLineCounter,
+ getDefaultRowHeight,
} from './utils';
describe('TableNG utils', () => {
@@ -787,23 +787,46 @@ describe('TableNG utils', () => {
describe('getDefaultRowHeight', () => {
const theme = createTheme();
- it('returns correct height for TableCellHeight.Sm', () => {
- const result = getDefaultRowHeight(theme, TableCellHeight.Sm);
- expect(result).toBe(36);
+ it.each([
+ { input: TableCellHeight.Sm, expected: 36 },
+ { input: TableCellHeight.Md, expected: 42 },
+ { input: TableCellHeight.Lg, expected: TABLE.MAX_CELL_HEIGHT },
+ ])('returns "$expected" for "$input"', ({ input, expected }) => {
+ const result = getDefaultRowHeight(theme, [], input);
+ expect(result).toBe(expected);
});
- it('returns correct height for TableCellHeight.Md', () => {
- const result = getDefaultRowHeight(theme, TableCellHeight.Md);
- expect(result).toBe(42);
- });
-
- it('returns correct height for TableCellHeight.Lg', () => {
- const result = getDefaultRowHeight(theme, TableCellHeight.Lg);
- expect(result).toBe(TABLE.MAX_CELL_HEIGHT);
+ it('returns "auto" if a field is present with the dynamicHeight cellOption is false', () => {
+ expect(
+ getDefaultRowHeight(
+ theme,
+ [
+ {
+ name: 'test1',
+ type: FieldType.string,
+ config: {},
+ values: ['value1'],
+ },
+ {
+ name: 'test2',
+ type: FieldType.string,
+ config: { custom: { cellOptions: { type: TableCellDisplayMode.Markdown, dynamicHeight: true } } },
+ values: ['value1'],
+ },
+ {
+ name: 'test3',
+ type: FieldType.number,
+ config: { custom: { cellOptions: { type: TableCellDisplayMode.JSONView } } },
+ values: [3],
+ },
+ ],
+ TableCellHeight.Sm
+ )
+ ).toBe('auto');
});
it('calculates height based on theme when cellHeight is undefined', () => {
- const result = getDefaultRowHeight(theme, undefined as unknown as TableCellHeight);
+ const result = getDefaultRowHeight(theme, []);
// Calculate the expected result based on the theme values
const expected = TABLE.CELL_PADDING * 2 + theme.typography.fontSize * theme.typography.body.lineHeight;
diff --git a/packages/grafana-ui/src/components/Table/TableNG/utils.ts b/packages/grafana-ui/src/components/Table/TableNG/utils.ts
index 96e6c71c018..9ba4a5eddca 100644
--- a/packages/grafana-ui/src/components/Table/TableNG/utils.ts
+++ b/packages/grafana-ui/src/components/Table/TableNG/utils.ts
@@ -1,4 +1,5 @@
import { Property } from 'csstype';
+import { CSSProperties } from 'react';
import { SortColumn } from 'react-data-grid';
import tinycolor from 'tinycolor2';
import { Count, varPreLine } from 'uwrap';
@@ -45,9 +46,14 @@ export type CellNumLinesCalculator = (text: string, cellWidth: number) => number
* @internal
* Returns the default row height based on the theme and cell height setting.
*/
-export function getDefaultRowHeight(theme: GrafanaTheme2, cellHeight?: TableCellHeight): number {
- const bodyFontSize = theme.typography.fontSize;
- const lineHeight = theme.typography.body.lineHeight;
+export function getDefaultRowHeight(
+ theme: GrafanaTheme2,
+ fields?: Field[],
+ cellHeight?: TableCellHeight
+): NonNullable {
+ if (fields?.some((field) => field.config?.custom?.cellOptions?.dynamicHeight)) {
+ return 'auto';
+ }
switch (cellHeight) {
case TableCellHeight.Sm:
@@ -58,7 +64,7 @@ export function getDefaultRowHeight(theme: GrafanaTheme2, cellHeight?: TableCell
return TABLE.MAX_CELL_HEIGHT;
}
- return TABLE.CELL_PADDING * 2 + bodyFontSize * lineHeight;
+ return TABLE.CELL_PADDING * 2 + theme.typography.fontSize * theme.typography.body.lineHeight;
}
/**
diff --git a/public/app/plugins/panel/table/table-new/PaginationEditor.tsx b/public/app/plugins/panel/table/table-new/PaginationEditor.tsx
index 6434c75e1b9..ccd9451ee3f 100644
--- a/public/app/plugins/panel/table/table-new/PaginationEditor.tsx
+++ b/public/app/plugins/panel/table/table-new/PaginationEditor.tsx
@@ -4,11 +4,8 @@ import { StandardEditorProps } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { Switch } from '@grafana/ui';
-export function PaginationEditor({ onChange, value, context }: StandardEditorProps) {
+export function PaginationEditor({ onChange, value }: StandardEditorProps) {
const changeValue = (event: React.FormEvent | undefined) => {
- if (event?.currentTarget.checked) {
- context.options.footer.show = false;
- }
onChange(event?.currentTarget.checked);
};
diff --git a/public/app/plugins/panel/table/table-new/TableCellOptionEditor.tsx b/public/app/plugins/panel/table/table-new/TableCellOptionEditor.tsx
index 92cbbf0f264..b70723695cb 100644
--- a/public/app/plugins/panel/table/table-new/TableCellOptionEditor.tsx
+++ b/public/app/plugins/panel/table/table-new/TableCellOptionEditor.tsx
@@ -10,6 +10,7 @@ import { Combobox, ComboboxOption, Field, TableCellDisplayMode, useStyles2 } fro
import { BarGaugeCellOptionsEditor } from './cells/BarGaugeCellOptionsEditor';
import { ColorBackgroundCellOptionsEditor } from './cells/ColorBackgroundCellOptionsEditor';
import { ImageCellOptionsEditor } from './cells/ImageCellOptionsEditor';
+import { MarkdownCellOptionsEditor } from './cells/MarkdownCellOptionsEditor';
import { SparklineCellOptionsEditor } from './cells/SparklineCellOptionsEditor';
import { TextWrapOptionsEditor } from './cells/TextWrapOptionsEditor';
@@ -54,6 +55,7 @@ export const TableCellOptionEditor = ({ value, onChange }: Props) => {
{ value: TableCellDisplayMode.Sparkline, label: t('table.cell-types.sparkline', 'Sparkline') },
{ value: TableCellDisplayMode.JSONView, label: t('table.cell-types.json', 'JSON View') },
{ value: TableCellDisplayMode.Pill, label: t('table.cell-types.pill', 'Pill') },
+ { value: TableCellDisplayMode.Markdown, label: t('table.cell-types.markdown', 'Markdown + HTML') },
{ value: TableCellDisplayMode.Image, label: t('table.cell-types.image', 'Image') },
{ value: TableCellDisplayMode.Actions, label: t('table.cell-types.actions', 'Actions') },
];
@@ -105,6 +107,9 @@ export const TableCellOptionEditor = ({ value, onChange }: Props) => {
{cellType === TableCellDisplayMode.Image && (
)}
+ {cellType === TableCellDisplayMode.Markdown && (
+
+ )}
);
};
diff --git a/public/app/plugins/panel/table/table-new/TablePanel.tsx b/public/app/plugins/panel/table/table-new/TablePanel.tsx
index 5af5472f19b..6859e6368fb 100644
--- a/public/app/plugins/panel/table/table-new/TablePanel.tsx
+++ b/public/app/plugins/panel/table/table-new/TablePanel.tsx
@@ -17,6 +17,7 @@ import { config, PanelDataErrorView } from '@grafana/runtime';
import { Select, usePanelContext, useTheme2 } from '@grafana/ui';
import { TableSortByFieldState } from '@grafana/ui/internal';
import { TableNG } from '@grafana/ui/unstable';
+import { getConfig } from 'app/core/config';
import { getActions } from '../../../../features/actions/utils';
@@ -61,6 +62,8 @@ export function TablePanel(props: Props) {
const enableSharedCrosshair = panelContext.sync && panelContext.sync() !== DashboardCursorSync.Off;
+ const disableSanitizeHtml = getConfig().disableSanitizeHtml;
+
const tableElement = (