From bb45dc88ac06f26e78daa98f1439cf9bf4d9cd66 Mon Sep 17 00:00:00 2001 From: Dominik Prokop Date: Wed, 30 Jul 2025 15:23:48 +0200 Subject: [PATCH 001/114] Dashboard: Tweak interaction performance tracking (#108658) * Dashboard: Tweak interaction tracking * Bump scenes to canary (https://github.com/grafana/scenes/pull/1195) * Update naming * Use latest canary * Update cenes, introduce singleton dashboard profiler, instrument v2 schema dashboard * Fix an oooooqpsiiii * Bmp scenes to 6.28.5 * Add documentation * Add event start/end time stamps, update documentation. Bump scenes * Docs * Move file * Typos --- package.json | 4 +- .../pages/DashboardScenePageStateManager.ts | 2 +- .../transformSaveModelSchemaV2ToScene.ts | 15 +- .../transformSaveModelToScene.ts | 59 ++---- .../dashboard/services/DashboardProfiler.ts | 34 +++ .../dashboard-render-performance-profiling.md | 199 ++++++++++++++++++ yarn.lock | 22 +- 7 files changed, 276 insertions(+), 59 deletions(-) create mode 100644 public/app/features/dashboard/services/DashboardProfiler.ts create mode 100644 public/app/features/dashboard/services/dashboard-render-performance-profiling.md diff --git a/package.json b/package.json index 278ffb6efcc..08f37ed064e 100644 --- a/package.json +++ b/package.json @@ -290,8 +290,8 @@ "@grafana/plugin-ui": "0.10.7", "@grafana/prometheus": "workspace:*", "@grafana/runtime": "workspace:*", - "@grafana/scenes": "^6.27.2", - "@grafana/scenes-react": "^6.27.2", + "@grafana/scenes": "6.28.6", + "@grafana/scenes-react": "6.28.6", "@grafana/schema": "workspace:*", "@grafana/sql": "workspace:*", "@grafana/ui": "workspace:*", diff --git a/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts b/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts index d91cf0d2230..63f1aaa0bcd 100644 --- a/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts +++ b/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts @@ -267,7 +267,7 @@ abstract class DashboardScenePageStateManagerBase const queryController = sceneGraph.getQueryController(dashboard); trackDashboardSceneLoaded(dashboard, measure?.duration); - queryController?.startProfile('DashboardScene'); + queryController?.startProfile('dashboard_view'); if (options.route !== DashboardRoutes.New) { emitDashboardViewEvent({ diff --git a/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts b/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts index 986e0018329..2b6dc7d5864 100644 --- a/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts +++ b/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts @@ -51,6 +51,10 @@ import { DeprecatedInternalId, } from 'app/features/apiserver/types'; import { DashboardWithAccessInfo } from 'app/features/dashboard/api/types'; +import { + getDashboardInteractionCallback, + getDashboardSceneProfiler, +} from 'app/features/dashboard/services/DashboardProfiler'; import { DashboardMeta } from 'app/types/dashboard'; import { addPanelsOnLoadBehavior } from '../addToDashboard/addPanelsOnLoadBehavior'; @@ -157,6 +161,15 @@ export function transformSaveModelSchemaV2ToScene(dto: DashboardWithAccessInfo uid === '*' || uid === metadata.name) !== -1, + onProfileComplete: getDashboardInteractionCallback(metadata.name, dashboard.title), + }, + getDashboardSceneProfiler() + ); + const dashboardScene = new DashboardScene( { description: dashboard.description, @@ -184,7 +197,7 @@ export function transformSaveModelSchemaV2ToScene(dto: DashboardWithAccessInfo uid === '*' || uid === oldModel.uid) !== -1, + onProfileComplete: getDashboardInteractionCallback(oldModel.uid, oldModel.title), + }, + getDashboardSceneProfiler() + ); + const behaviorList: SceneObjectState['$behaviors'] = [ new behaviors.CursorSync({ sync: oldModel.graphTooltip, }), - new behaviors.SceneQueryController({ - enableProfiling: - config.dashboardPerformanceMetrics.findIndex((uid) => uid === '*' || uid === oldModel.uid) !== -1, - onProfileComplete: getDashboardInteractionCallback(oldModel.uid, oldModel.title), - }), + queryController, registerDashboardMacro, registerPanelInteractionsReporter, new behaviors.LiveNowTimer({ enabled: oldModel.liveNow }), @@ -499,40 +507,3 @@ export const convertOldSnapshotToScenesSnapshot = (panel: PanelModel) => { panel.snapshotData = []; } }; - -function getDashboardInteractionCallback(uid: string, title: string) { - return (e: SceneInteractionProfileEvent) => { - let interactionType = ''; - - if (e.origin === 'SceneTimeRange') { - interactionType = 'time-range-change'; - } else if (e.origin === 'SceneRefreshPicker') { - interactionType = 'refresh'; - } else if (e.origin === 'DashboardScene') { - interactionType = 'view'; - } else if (e.origin.indexOf('Variable') > -1) { - interactionType = 'variable-change'; - } - reportInteraction('dashboard-render', { - interactionType, - duration: e.duration, - networkDuration: e.networkDuration, - totalJSHeapSize: e.totalJSHeapSize, - usedJSHeapSize: e.usedJSHeapSize, - jsHeapSizeLimit: e.jsHeapSizeLimit, - }); - - logMeasurement( - `dashboard.${interactionType}`, - { - duration: e.duration, - networkDuration: e.networkDuration, - totalJSHeapSize: e.totalJSHeapSize, - usedJSHeapSize: e.usedJSHeapSize, - jsHeapSizeLimit: e.jsHeapSizeLimit, - timeSinceBoot: performance.measure('time_since_boot', 'frontend_boot_js_done_time_seconds').duration, - }, - { dashboard: uid, title: title } - ); - }; -} diff --git a/public/app/features/dashboard/services/DashboardProfiler.ts b/public/app/features/dashboard/services/DashboardProfiler.ts new file mode 100644 index 00000000000..aa4ce4d2caa --- /dev/null +++ b/public/app/features/dashboard/services/DashboardProfiler.ts @@ -0,0 +1,34 @@ +import { logMeasurement, reportInteraction } from '@grafana/runtime'; +import { SceneInteractionProfileEvent, SceneRenderProfiler } from '@grafana/scenes'; + +let dashboardSceneProfiler: SceneRenderProfiler | undefined; + +export function getDashboardSceneProfiler() { + if (!dashboardSceneProfiler) { + dashboardSceneProfiler = new SceneRenderProfiler(); + } + return dashboardSceneProfiler; +} + +export function getDashboardInteractionCallback(uid: string, title: string) { + return (e: SceneInteractionProfileEvent) => { + const payload = { + duration: e.duration, + networkDuration: e.networkDuration, + startTs: e.startTs, + endTs: e.endTs, + totalJSHeapSize: e.totalJSHeapSize, + usedJSHeapSize: e.usedJSHeapSize, + jsHeapSizeLimit: e.jsHeapSizeLimit, + timeSinceBoot: performance.measure('time_since_boot', 'frontend_boot_js_done_time_seconds').duration, + }; + + reportInteraction('dashboard_render', { + interactionType: e.origin, + uid, + ...payload, + }); + + logMeasurement(`dashboard_render`, payload, { interactionType: e.origin, dashboard: uid, title: title }); + }; +} diff --git a/public/app/features/dashboard/services/dashboard-render-performance-profiling.md b/public/app/features/dashboard/services/dashboard-render-performance-profiling.md new file mode 100644 index 00000000000..574e4f7eb90 --- /dev/null +++ b/public/app/features/dashboard/services/dashboard-render-performance-profiling.md @@ -0,0 +1,199 @@ +# Grafana Dashboard Render Performance Metrics + +This documentation describes the dashboard render performance metrics exposed from Grafana's frontend. + +## Overview + +The exposed dashboard performance metrics feature provides comprehensive tracking and profiling of dashboard interactions, allowing administrators and developers to analyze dashboard render performance, user interactions, and identify performance bottlenecks. + +## Configuration + +### Enabling Performance Metrics + +Dashboard performance metrics are configured in the Grafana configuration file (`grafana.ini`) under the `[dashboards]` section: + +```ini +[dashboards] +# Dashboards UIDs to report performance metrics for. * can be used to report metrics for all dashboards +dashboard_performance_metrics = * +``` + +**Configuration Options:** + +- **`*`** - Enable profiling on all dashboards +- **``** - Enable profiling on specific dashboards only +- **`""` (empty)** - Disable performance metrics (default) + +**Examples:** + +```ini +# Enable for all dashboards +dashboard_performance_metrics = * + +# Enable for specific dashboards +dashboard_performance_metrics = dashboard-uid-1,dashboard-uid-2,dashboard-uid-3 + +# Disable performance metrics +dashboard_performance_metrics = +``` + +## Tracked Interactions + +The system tracks various dashboard interaction types automatically using the [`@grafana/scenes`](https://github.com/grafana/scenes) library. Each interaction is captured with a specific origin identifier that describes the type of user action performed. In Grafana, these interaction events are then reported as `dashboard_render` events with interaction type information included. + +### Core Performance-Tracked Interactions + +The following dashboard interaction types are tracked for dashboard render performance profiling: + +| Interaction Type | Trigger | When Measured | +| ------------------------ | -------------------------- | -------------------------------------------------------- | +| `dashboard_view` | Dashboard view | When user loads or navigates to a dashboard | +| `refresh` | Manual/Auto refresh | When user clicks refresh button or auto-refresh triggers | +| `time_range_change` | Time picker changes | When user changes time range in time picker | +| `filter_added` | Ad-hoc filter addition | When user adds a new filter to the dashboard | +| `filter_removed` | Ad-hoc filter removal | When user removes a filter from the dashboard | +| `filter_changed` | Ad-hoc filter modification | When user changes filter values or operators | +| `filter_restored` | Ad-hoc filter restoration | When user restores a previously applied filter | +| `variable_value_changed` | Variable value changes | When user changes dashboard variable values | +| `scopes_changed` | Scopes modifications | When user modifies dashboard scopes | + +The interactions mentioned above are reported to Echo service as well as sent to [Faro](https://grafana.com/docs/grafana-cloud/monitor-applications/frontend-observability/) as `dashboard_render` measurements: + +```ts +const payload = { + duration: e.duration, + networkDuration: e.networkDuration, + startTs: e.startTs, + endTs: e.endTs, + totalJSHeapSize: e.totalJSHeapSize, + usedJSHeapSize: e.usedJSHeapSize, + jsHeapSizeLimit: e.jsHeapSizeLimit, + timeSinceBoot: performance.measure('time_since_boot', 'frontend_boot_js_done_time_seconds').duration, +}; + +reportInteraction('dashboard_render', { + interactionType: e.origin, + uid, + ...payload, +}); + +logMeasurement(`dashboard_render`, payload, { interactionType: e.origin, dashboard: uid, title: title }); +``` + +### Interaction Origin Mapping + +The profiling system uses profiler event's `origin` directly as the `interactionType`, providing direct mapping between user actions and performance measurements. + +## Profiling Implementation + +### Profile Data Structure + +Each interaction profile event captures: + +```typescript +interface SceneInteractionProfileEvent { + origin: string; // Interaction type + duration: number; // Total interaction duration + networkDuration: number; // Network requests duration + totalJSHeapSize: number; // JavaScript heap size metrics + usedJSHeapSize: number; // Used JavaScript heap size + jsHeapSizeLimit: number; // JavaScript heap size limit + startTs: number; // Profile start timestamp + endTs: number; // Profile end timestamp +} +``` + +### Collected Metrics + +For each tracked interaction, the system collects: + +- **Dashboard Metadata**: UID, title +- **Performance Metrics**: Duration, network duration +- **Memory Metrics**: JavaScript heap usage statistics +- **Timing Information**: Time since boot, profile start and end timestamps +- **Interaction Context**: Type of user interaction + +## Debugging and Development + +### Enable Profiler Debug Logging + +To observe profiling events in the browser console: + +```javascript +localStorage.setItem('grafana.debug.scenes', 'true'); +``` + +#### Console Output + +When debug logging is enabled, you'll see console logs for each profiling event: + +``` +SceneRenderProfiler: Profile started: {origin: , crumbs: Array(0)} +... // intermediate steps adding profile crumbs +SceneRenderProfiler: Stopped recording, total measured time (network included): 2123 +``` + +### Enable Echo Service Debug Logging + +To observe Echo events in the browser console: + +```javascript +_debug.echo.enable(); +``` + +#### Console Output + +When Echo debug logging is enabled, you'll see console logs for each profiling event captured by Echo service: + +``` +[EchoSrv: interaction event]: {interactionName: 'dashboard_render', properties: {…}, meta: {…}} +``` + +### Browser Performance Profiler + +Dashboard interactions can be recorded in the browser's performance profiler, where they appear as: + +``` +Dashboard Interaction +``` + +## Analytics Integration + +### Interaction Reporting + +Performance data is integrated with Grafana's analytics system through: + +- **`reportInteraction`**: Reports interaction events to Echo service with performance data +- **`logMeasurement`**: Records Faro's performance measurements with metadata + +### Data Collection + +The system reports the following data for each interaction: + +```typescript +{ + interactionType: string, // Type of interaction + uid: string, // Dashboard UID + duration: number, // Total duration + networkDuration: number, // Network time + startTs: number, // Profile start timestamp + endTs: number, // Profile end timestamp + totalJSHeapSize: number, // Memory metrics + usedJSHeapSize: number, + jsHeapSizeLimit: number, + timeSinceBoot: number // Time since frontend boot +} +``` + +## Implementation Details + +The profiler is integrated into dashboard creation paths and uses a singleton pattern to share profiler instances across dashboard reloads. The performance tracking is implemented using the `SceneRenderProfiler` from the `@grafana/scenes` library. + +## Related Documentation + +- [PR #858 - Add SceneRenderProfiler to scenes](https://github.com/grafana/scenes/pull/858) +- [PR #99629 - Dashboard render performance metrics](https://github.com/grafana/grafana/pull/99629) +- [PR #108658 - Dashboard: Tweak interaction tracking](https://github.com/grafana/grafana/pull/108658) +- [PR #1195 - Enhance SceneRenderProfiler with additional interaction tracking](https://github.com/grafana/scenes/pull/1195) +- [PR #1198 - Make SceneRenderProfiler optional and injectable](https://github.com/grafana/scenes/pull/1198) +- [PR #1199 - SceneRenderProfiler: add start and end timestamps to profile events](https://github.com/grafana/scenes/pull/1199) diff --git a/yarn.lock b/yarn.lock index 7d72c331da7..d8d007fda69 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3600,11 +3600,11 @@ __metadata: languageName: unknown linkType: soft -"@grafana/scenes-react@npm:^6.27.2": - version: 6.28.2 - resolution: "@grafana/scenes-react@npm:6.28.2" +"@grafana/scenes-react@npm:6.28.6": + version: 6.28.6 + resolution: "@grafana/scenes-react@npm:6.28.6" dependencies: - "@grafana/scenes": "npm:6.28.2" + "@grafana/scenes": "npm:6.28.6" lru-cache: "npm:^10.2.2" react-use: "npm:^17.4.0" peerDependencies: @@ -3616,13 +3616,13 @@ __metadata: react: ^18.0.0 react-dom: ^18.0.0 react-router-dom: ^6.28.0 - checksum: 10/c00730312828639f8a596c9fd9b0336ec57ca5cec624ac4f09a5fa0be93282d180451e3bcbf83c0209ff04486def25ff98d7b0ea6c370e91b8990112c3e903f2 + checksum: 10/55dfe20a6454f218b7c5ab2dc728678885d48e53656d046c125b555b201891e121e0960e108229372c2d70336cfe3dade9b392ed001fef90aec57cbcc13f76ca languageName: node linkType: hard -"@grafana/scenes@npm:6.28.2, @grafana/scenes@npm:^6.27.2": - version: 6.28.2 - resolution: "@grafana/scenes@npm:6.28.2" +"@grafana/scenes@npm:6.28.6": + version: 6.28.6 + resolution: "@grafana/scenes@npm:6.28.6" dependencies: "@floating-ui/react": "npm:^0.26.16" "@leeoniya/ufuzzy": "npm:^1.0.16" @@ -3642,7 +3642,7 @@ __metadata: react: ^18.0.0 react-dom: ^18.0.0 react-router-dom: ^6.28.0 - checksum: 10/53370553ac4ac38d41ab1d782ee14cf05f483e35e5338406d619efb61e4c9881fd361c98ee116ae8bf40ab1a5fa2c82ca859fc519f6d532853c275e71949c654 + checksum: 10/fcfcf663e2eb63ad25fad27ec50af8590b1a6c9e802927e8e457bf2f81d04cf971c2f12f456a04a502c929ec7a1d50ef7118baee095134b133ecc92b269a6907 languageName: node linkType: hard @@ -18187,8 +18187,8 @@ __metadata: "@grafana/plugin-ui": "npm:0.10.7" "@grafana/prometheus": "workspace:*" "@grafana/runtime": "workspace:*" - "@grafana/scenes": "npm:^6.27.2" - "@grafana/scenes-react": "npm:^6.27.2" + "@grafana/scenes": "npm:6.28.6" + "@grafana/scenes-react": "npm:6.28.6" "@grafana/schema": "workspace:*" "@grafana/sql": "workspace:*" "@grafana/test-utils": "workspace:*" From 006ea8e1b94b77988a104d24183628c107fdb3a8 Mon Sep 17 00:00:00 2001 From: Ezequiel Victorero Date: Wed, 30 Jul 2025 10:31:11 -0300 Subject: [PATCH 002/114] ShortURL: Fix tests and small refactor (#108871) --- pkg/api/api.go | 7 +++-- pkg/api/short_url.go | 8 ++++++ pkg/tests/api/shorturl/short_url_test.go | 34 ++++++++++++++++++++---- 3 files changed, 40 insertions(+), 9 deletions(-) diff --git a/pkg/api/api.go b/pkg/api/api.go index 12de28eaa90..1561c28ea21 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -176,7 +176,6 @@ func (hs *HTTPServer) registerRoutes() { r.Get("/import/dashboard", reqSignedIn, hs.Index) r.Get("/dashboards/", reqSignedIn, hs.Index) r.Get("/dashboards/*", reqSignedIn, hs.Index) - r.Get("/goto/:uid", reqSignedIn, hs.redirectFromShortURL, hs.Index) if hs.Cfg.PublicDashboardsEnabled { // list public dashboards @@ -264,6 +263,9 @@ func (hs *HTTPServer) registerRoutes() { providerParam := ac.Parameter(":provider") r.Get("/admin/authentication/:provider", authorize(ac.EvalPermission(ac.ActionSettingsRead, ac.ScopeSettingsOAuth(providerParam))), hs.Index) + // ShortURL API + hs.registerShortURLAPI(r) + // authed api r.Group("/api", func(apiRoute routing.RouteRegister) { // user (signed in) @@ -549,9 +551,6 @@ func (hs *HTTPServer) registerRoutes() { // Some channels may have info liveRoute.Get("/info/*", routing.Wrap(hs.Live.HandleInfoHTTP)) }, requestmeta.SetSLOGroup(requestmeta.SLOGroupNone)) - - // short urls - apiRoute.Post("/short-urls", routing.Wrap(hs.createShortURL)) }, reqSignedIn) // admin api diff --git a/pkg/api/short_url.go b/pkg/api/short_url.go index 22cb57a31f8..9be4bdab028 100644 --- a/pkg/api/short_url.go +++ b/pkg/api/short_url.go @@ -7,6 +7,8 @@ import ( "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/response" + "github.com/grafana/grafana/pkg/api/routing" + "github.com/grafana/grafana/pkg/middleware" contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" "github.com/grafana/grafana/pkg/services/shorturls" "github.com/grafana/grafana/pkg/setting" @@ -14,6 +16,12 @@ import ( "github.com/grafana/grafana/pkg/web" ) +func (hs *HTTPServer) registerShortURLAPI(apiRoute routing.RouteRegister) { + reqSignedIn := middleware.ReqSignedIn + apiRoute.Post("/api/short-urls", reqSignedIn, hs.createShortURL) + apiRoute.Get("/goto/:uid", reqSignedIn, hs.redirectFromShortURL, hs.Index) +} + // createShortURL handles requests to create short URLs. func (hs *HTTPServer) createShortURL(c *contextmodel.ReqContext) response.Response { cmd := dtos.CreateShortURLCmd{} diff --git a/pkg/tests/api/shorturl/short_url_test.go b/pkg/tests/api/shorturl/short_url_test.go index e598c36ea7a..a938b861281 100644 --- a/pkg/tests/api/shorturl/short_url_test.go +++ b/pkg/tests/api/shorturl/short_url_test.go @@ -7,8 +7,10 @@ import ( "fmt" "io" "net/http" + "net/url" "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/grafana/grafana/pkg/infra/db" @@ -31,10 +33,12 @@ func TestMain(m *testing.M) { func TestShortURL(t *testing.T) { dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{ AppModeProduction: true, + DisableAnonymous: true, }) grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, path) + // Test that the endpoint is accessible with authentication. username, password := "viewer", "viewer" createUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{ DefaultOrgRole: string(org.RoleEditor), @@ -50,7 +54,7 @@ func TestShortURL(t *testing.T) { defer func() { _ = res.Body.Close() }() - require.Equal(t, http.StatusOK, res.StatusCode) + assert.Equal(t, http.StatusOK, res.StatusCode) bodyRaw, err := io.ReadAll(res.Body) require.NoError(t, err) @@ -67,8 +71,8 @@ func TestShortURL(t *testing.T) { defer func() { _ = res.Body.Close() }() - require.Equal(t, "http://localhost:3000/explore", res.Header.Get("Location")) - require.Equal(t, http.StatusFound, res.StatusCode) + assert.Equal(t, "http://localhost:3000/explore", res.Header.Get("Location")) + assert.Equal(t, http.StatusFound, res.StatusCode) // If the go-to does not exist, it should redirect to the home page and return 308. res, err = c.get("/goto/DoesNotExist") @@ -76,8 +80,28 @@ func TestShortURL(t *testing.T) { defer func() { _ = res.Body.Close() }() - require.Equal(t, "http://localhost:3000/", res.Header.Get("Location")) - require.Equal(t, http.StatusPermanentRedirect, res.StatusCode) + assert.Equal(t, "http://localhost:3000/", res.Header.Get("Location")) + assert.Equal(t, http.StatusPermanentRedirect, res.StatusCode) + + // Create a client that does not have authentication. + notLoggedInClient := client(grafanaListedAddr, "", "") + // Test that the short-urls endpoint is not accessible without authentication. + res, err = notLoggedInClient.post("/api/short-urls", bytes.NewReader([]byte(`{"path":"explore"}`))) + require.NoError(t, err) + assert.Equal(t, http.StatusUnauthorized, res.StatusCode) + defer func() { + _ = res.Body.Close() + }() + + // If the user is not logged in, it should redirect to the login page and return 302. + res, err = notLoggedInClient.get(fmt.Sprintf("/goto/%s", resParsed.UID)) + require.NoError(t, err) + defer func() { + _ = res.Body.Close() + }() + expectedRedirect := "/login?redirectTo=" + url.QueryEscape("/goto/"+resParsed.UID) + assert.Equal(t, expectedRedirect, res.Header.Get("Location")) + assert.Equal(t, http.StatusFound, res.StatusCode) } func createUser(t *testing.T, db db.DB, cfg *setting.Cfg, cmd user.CreateUserCommand) int64 { From d790fdf081a7d440eee3aed596af264b9f636fb9 Mon Sep 17 00:00:00 2001 From: Dominik Prokop Date: Wed, 30 Jul 2025 15:38:43 +0200 Subject: [PATCH 003/114] Schema V2: Fix apiVersion for conversion to v2beta1 (#108916) SchemaV2: Fix apiVersion for conversion to v2beta1 Co-authored-by: Ivan Ortega --- .../conversion/testdata/output/v2alpha1.complete.v2beta1.json | 2 +- .../testdata/output/v2alpha1.ds-data-query.v2beta1.json | 2 +- .../testdata/output/v2alpha1.groupby-adhoc-vars.v2beta1.json | 2 +- .../conversion/testdata/output/v2alpha1.viz-config.v2beta1.json | 2 +- apps/dashboard/pkg/migration/conversion/v2alpha1_to_v2beta1.go | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/v2alpha1.complete.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/v2alpha1.complete.v2beta1.json index 617b33a8108..7662f66d855 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/v2alpha1.complete.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/v2alpha1.complete.v2beta1.json @@ -1,6 +1,6 @@ { "kind": "Dashboard", - "apiVersion": "v2beta1", + "apiVersion": "dashboard.grafana.app/v2beta1", "metadata": { "name": "test-v2alpha1-complete", "creationTimestamp": null, diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/v2alpha1.ds-data-query.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/v2alpha1.ds-data-query.v2beta1.json index a7f9231756a..ba6bc946dc0 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/v2alpha1.ds-data-query.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/v2alpha1.ds-data-query.v2beta1.json @@ -1,6 +1,6 @@ { "kind": "Dashboard", - "apiVersion": "v2beta1", + "apiVersion": "dashboard.grafana.app/v2beta1", "metadata": { "name": "test-v2alpha1-annotations", "creationTimestamp": null diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/v2alpha1.groupby-adhoc-vars.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/v2alpha1.groupby-adhoc-vars.v2beta1.json index ff37296efe6..9d34353a32b 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/v2alpha1.groupby-adhoc-vars.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/v2alpha1.groupby-adhoc-vars.v2beta1.json @@ -1,6 +1,6 @@ { "kind": "Dashboard", - "apiVersion": "v2beta1", + "apiVersion": "dashboard.grafana.app/v2beta1", "metadata": { "name": "test-v2alpha1-groupby-adhoc-vars", "creationTimestamp": null diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/v2alpha1.viz-config.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/v2alpha1.viz-config.v2beta1.json index 0fe38667e6e..b867057085e 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/v2alpha1.viz-config.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/v2alpha1.viz-config.v2beta1.json @@ -1,6 +1,6 @@ { "kind": "Dashboard", - "apiVersion": "v2beta1", + "apiVersion": "dashboard.grafana.app/v2beta1", "metadata": { "name": "test-v2alpha1-viz-config", "creationTimestamp": null diff --git a/apps/dashboard/pkg/migration/conversion/v2alpha1_to_v2beta1.go b/apps/dashboard/pkg/migration/conversion/v2alpha1_to_v2beta1.go index 05d7aa0ada0..ec673c80137 100644 --- a/apps/dashboard/pkg/migration/conversion/v2alpha1_to_v2beta1.go +++ b/apps/dashboard/pkg/migration/conversion/v2alpha1_to_v2beta1.go @@ -39,7 +39,7 @@ import ( func ConvertDashboard_V2alpha1_to_V2beta1(in *dashv2alpha1.Dashboard, out *dashv2beta1.Dashboard, scope conversion.Scope) error { out.ObjectMeta = in.ObjectMeta - out.APIVersion = dashv2beta1.VERSION + out.APIVersion = dashv2beta1.APIVERSION out.Kind = in.Kind return convertDashboardSpec_V2alpha1_to_V2beta1(&in.Spec, &out.Spec, scope) From 788e91c57b57f5434921ef68c8b8c6f2f0347cc6 Mon Sep 17 00:00:00 2001 From: Kevin Minehart <5140827+kminehart@users.noreply.github.com> Date: Wed, 30 Jul 2025 08:43:47 -0500 Subject: [PATCH 004/114] CI: remove nightly builds in drone (#108913) remove nightly builds in drone --- .drone.yml | 260 +---------------------------------------- scripts/drone/rgm.star | 1 - 2 files changed, 1 insertion(+), 260 deletions(-) diff --git a/.drone.yml b/.drone.yml index 345aae7d4bd..2ca377f28a9 100644 --- a/.drone.yml +++ b/.drone.yml @@ -2049,264 +2049,6 @@ image_pull_secrets: - gcr - gar kind: pipeline -name: rgm-nightly-build -node: - type: no-parallel -platform: - arch: amd64 - os: linux -services: [] -steps: -- commands: - - wget -qO- https://github.com/dagger/dagger/releases/download/v0.18.8/dagger_v0.18.8_linux_amd64.tar.gz - | tar zx -C /bin - - apk add docker - - export GRAFANA_DIR=$$(pwd) - - export GITHUB_TOKEN=$(cat /github-app/token) - - ./pkg/build/daggerbuild/scripts/drone_build_nightly_grafana.sh - environment: - _EXPERIMENTAL_DAGGER_CLOUD_TOKEN: - from_secret: dagger_token - ALPINE_BASE: alpine:3.21.3 - CDN_DESTINATION: - from_secret: rgm_cdn_destination - DESTINATION: - from_secret: destination - DOCKER_PASSWORD: - from_secret: docker_password - DOCKER_USERNAME: - from_secret: docker_username - DOWNLOADS_DESTINATION: - from_secret: rgm_downloads_destination - GCOM_API_KEY: - from_secret: grafana_api_key - GCP_KEY_BASE64: - from_secret: gcp_key_base64 - GPG_PASSPHRASE: - from_secret: packages_gpg_passphrase - GPG_PRIVATE_KEY: - from_secret: packages_gpg_private_key - GPG_PUBLIC_KEY: - from_secret: packages_gpg_public_key - NPM_TOKEN: - from_secret: npm_token - STORYBOOK_DESTINATION: - from_secret: rgm_storybook_destination - UBUNTU_BASE: ubuntu:22.04 - image: golang:1.24.5-alpine - name: rgm-build - pull: always - volumes: - - name: docker - path: /var/run/docker.sock - - name: github-app - path: /github-app -- commands: - - mkdir -p $${DESTINATION}/$${DRONE_BUILD_EVENT} - - printenv GCP_KEY_BASE64 | base64 -d > /tmp/key.json - - gcloud auth activate-service-account --key-file=/tmp/key.json - - gcloud storage cp -r $${DRONE_WORKSPACE}/dist/* $${DESTINATION}/$${DRONE_BUILD_EVENT} - depends_on: - - rgm-build - environment: - _EXPERIMENTAL_DAGGER_CLOUD_TOKEN: - from_secret: dagger_token - CDN_DESTINATION: - from_secret: rgm_cdn_destination - DESTINATION: - from_secret: destination - DOCKER_PASSWORD: - from_secret: docker_password - DOCKER_USERNAME: - from_secret: docker_username - DOWNLOADS_DESTINATION: - from_secret: rgm_downloads_destination - GCOM_API_KEY: - from_secret: grafana_api_key - GCP_KEY_BASE64: - from_secret: gcp_key_base64 - GPG_PASSPHRASE: - from_secret: packages_gpg_passphrase - GPG_PRIVATE_KEY: - from_secret: packages_gpg_private_key - GPG_PUBLIC_KEY: - from_secret: packages_gpg_public_key - NPM_TOKEN: - from_secret: npm_token - STORYBOOK_DESTINATION: - from_secret: rgm_storybook_destination - image: google/cloud-sdk:alpine - name: rgm-copy -trigger: - cron: - include: - - nightly-release - event: - include: - - cron -type: docker -volumes: -- host: - path: /var/run/docker.sock - name: docker ---- -clone: - retries: 3 -depends_on: -- rgm-nightly-build -image_pull_secrets: -- gcr -- gar -kind: pipeline -name: rgm-nightly-publish -node: - type: no-parallel -platform: - arch: amd64 - os: linux -services: [] -steps: -- commands: - - mkdir -p $${DRONE_WORKSPACE}/dist - - printenv GCP_KEY_BASE64 | base64 -d > /tmp/key.json - - gcloud auth activate-service-account --key-file=/tmp/key.json - - gcloud storage cp -r $${DESTINATION}/$${DRONE_BUILD_EVENT}/*_$${DRONE_BUILD_NUMBER}_* - $${DRONE_WORKSPACE}/dist - environment: - _EXPERIMENTAL_DAGGER_CLOUD_TOKEN: - from_secret: dagger_token - CDN_DESTINATION: - from_secret: rgm_cdn_destination - DESTINATION: - from_secret: destination - DOCKER_PASSWORD: - from_secret: docker_password - DOCKER_USERNAME: - from_secret: docker_username - DOWNLOADS_DESTINATION: - from_secret: rgm_downloads_destination - GCOM_API_KEY: - from_secret: grafana_api_key - GCP_KEY_BASE64: - from_secret: gcp_key_base64 - GPG_PASSPHRASE: - from_secret: packages_gpg_passphrase - GPG_PRIVATE_KEY: - from_secret: packages_gpg_private_key - GPG_PUBLIC_KEY: - from_secret: packages_gpg_public_key - NPM_TOKEN: - from_secret: npm_token - STORYBOOK_DESTINATION: - from_secret: rgm_storybook_destination - image: google/cloud-sdk:alpine - name: rgm-copy -- commands: - - wget -qO- https://github.com/dagger/dagger/releases/download/v0.18.8/dagger_v0.18.8_linux_amd64.tar.gz - | tar zx -C /bin - - apk add docker - - export GRAFANA_DIR=$$(pwd) - - export GITHUB_TOKEN=$(cat /github-app/token) - - ./pkg/build/daggerbuild/scripts/drone_publish_nightly_grafana.sh - depends_on: - - rgm-copy - environment: - _EXPERIMENTAL_DAGGER_CLOUD_TOKEN: - from_secret: dagger_token - ALPINE_BASE: alpine:3.21.3 - CDN_DESTINATION: - from_secret: rgm_cdn_destination - DESTINATION: - from_secret: destination - DOCKER_PASSWORD: - from_secret: docker_password - DOCKER_USERNAME: - from_secret: docker_username - DOWNLOADS_DESTINATION: - from_secret: rgm_downloads_destination - GCOM_API_KEY: - from_secret: grafana_api_key - GCP_KEY_BASE64: - from_secret: gcp_key_base64 - GPG_PASSPHRASE: - from_secret: packages_gpg_passphrase - GPG_PRIVATE_KEY: - from_secret: packages_gpg_private_key - GPG_PUBLIC_KEY: - from_secret: packages_gpg_public_key - NPM_TOKEN: - from_secret: npm_token - STORYBOOK_DESTINATION: - from_secret: rgm_storybook_destination - UBUNTU_BASE: ubuntu:22.04 - image: golang:1.24.5-alpine - name: rgm-publish - pull: always - volumes: - - name: docker - path: /var/run/docker.sock - - name: github-app - path: /github-app -- depends_on: - - rgm-publish - image: us.gcr.io/kubernetes-dev/package-publish:latest - name: publish-deb - privileged: true - settings: - access_key_id: - from_secret: packages_access_key_id - gpg_passphrase: - from_secret: packages_gpg_passphrase - gpg_private_key: - from_secret: packages_gpg_private_key - gpg_public_key: - from_secret: packages_gpg_public_key - package_path: file:///drone/src/dist/*.deb - secret_access_key: - from_secret: packages_secret_access_key - service_account_json: - from_secret: packages_service_account - target_bucket: grafana-packages -- depends_on: - - rgm-publish - image: us.gcr.io/kubernetes-dev/package-publish:latest - name: publish-rpm - privileged: true - settings: - access_key_id: - from_secret: packages_access_key_id - gpg_passphrase: - from_secret: packages_gpg_passphrase - gpg_private_key: - from_secret: packages_gpg_private_key - gpg_public_key: - from_secret: packages_gpg_public_key - package_path: file:///drone/src/dist/*.rpm - secret_access_key: - from_secret: packages_secret_access_key - service_account_json: - from_secret: packages_service_account - target_bucket: grafana-packages -trigger: - cron: - include: - - nightly-release - event: - include: - - cron -type: docker -volumes: -- host: - path: /var/run/docker.sock - name: docker ---- -clone: - retries: 3 -depends_on: [] -image_pull_secrets: -- gcr -- gar -kind: pipeline name: rgm-promotion node: type: no-parallel @@ -2625,6 +2367,6 @@ kind: secret name: gcr_credentials --- kind: signature -hmac: aef043aae7394d3160a7147c8b57599bf1a2f4ba5c596ffb795a0e6a049c73a6 +hmac: bfbdbe4aeedbcdc8778dd67abdfe94a75a20bdb314d1c53418cf2c2ea7a9f0c9 ... diff --git a/scripts/drone/rgm.star b/scripts/drone/rgm.star index abaf82b9127..604df62766e 100644 --- a/scripts/drone/rgm.star +++ b/scripts/drone/rgm.star @@ -378,6 +378,5 @@ def rgm(): rgm_main_pipeline() + rgm_tag_pipeline() + rgm_version_branch_pipeline() + - rgm_nightly_pipeline() + rgm_promotion_pipeline() ) From 87f587507a65538bd4eb3ca1ca20be6a8cbadfb5 Mon Sep 17 00:00:00 2001 From: Ashley Harrison Date: Wed, 30 Jul 2025 15:01:35 +0100 Subject: [PATCH 005/114] Frontend service: Ensure we're always loading either the light or dark assets for any theme (#108902) ensure we're always loading either the light or dark assets for other themes --- pkg/services/frontend/index.html | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/pkg/services/frontend/index.html b/pkg/services/frontend/index.html index 2ece6114fa5..f582df4604e 100644 --- a/pkg/services/frontend/index.html +++ b/pkg/services/frontend/index.html @@ -86,7 +86,7 @@ .fs-hidden { display: none; } - + .fs-spinner { animation: spin 1500ms linear infinite; width: 32px; @@ -100,7 +100,7 @@ .fs-spinner-arc { stroke: #F55F3E; } - + .fs-loader-text { opacity: 0; font-size: 16px; @@ -199,7 +199,7 @@ async function fetchBootData() { const resp = await fetch("/bootdata"); const textResponse = await resp.text(); - + let rawBootData; try { rawBootData = JSON.parse(textResponse); @@ -211,7 +211,7 @@ if (resp.status === 503 && rawBootData.code === 'Loading') { return; } - + if (!resp.ok) { throw new Error("Unexpected response body: " + textResponse); } @@ -259,21 +259,20 @@ const cssLink = document.createElement("link"); cssLink.rel = 'stylesheet'; - let theme = window.grafanaBootData.user.theme; + const theme = window.grafanaBootData.user.theme; if (theme === "system") { const darkQuery = window.matchMedia("(prefers-color-scheme: dark)"); - theme = darkQuery.matches ? 'dark' : 'light'; - } - if (theme === "light") { - document.body.classList.add("theme-light"); - cssLink.href = window.grafanaBootData.assets.light; - window.grafanaBootData.user.lightTheme = true; - } else if (theme === "dark") { - document.body.classList.add("theme-dark"); - cssLink.href = window.grafanaBootData.assets.dark; - window.grafanaBootData.user.lightTheme = false; + if (darkQuery.matches) { + document.body.classList.add("theme-dark"); + window.grafanaBootData.user.lightTheme = false; + } else { + document.body.classList.add("theme-light"); + window.grafanaBootData.user.lightTheme = true; + } } + const isLightTheme = window.grafanaBootData.user.lightTheme; + cssLink.href = window.grafanaBootData.assets[isLightTheme ? 'light' : 'dark']; document.head.appendChild(cssLink); } From 04c53678fa3e66da71407f9af1a40714dc156d9f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 30 Jul 2025 15:03:10 +0100 Subject: [PATCH 006/114] Update React Aria (#108895) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 8 +- packages/grafana-ui/package.json | 8 +- yarn.lock | 210 +++++++++++++++---------------- 3 files changed, 113 insertions(+), 113 deletions(-) diff --git a/package.json b/package.json index 08f37ed064e..6abb79b38ea 100644 --- a/package.json +++ b/package.json @@ -310,10 +310,10 @@ "@opentelemetry/exporter-collector": "0.25.0", "@opentelemetry/semantic-conventions": "1.36.0", "@popperjs/core": "2.11.8", - "@react-aria/dialog": "3.5.27", - "@react-aria/focus": "3.20.5", - "@react-aria/overlays": "3.27.3", - "@react-aria/utils": "3.29.1", + "@react-aria/dialog": "3.5.28", + "@react-aria/focus": "3.21.0", + "@react-aria/overlays": "3.28.0", + "@react-aria/utils": "3.30.0", "@react-awesome-query-builder/ui": "6.6.15", "@reduxjs/toolkit": "2.8.2", "@visx/event": "3.12.0", diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json index 8738aab4d7c..e1ef60e75b3 100644 --- a/packages/grafana-ui/package.json +++ b/packages/grafana-ui/package.json @@ -75,10 +75,10 @@ "@hello-pangea/dnd": "18.0.1", "@monaco-editor/react": "4.7.0", "@popperjs/core": "2.11.8", - "@react-aria/dialog": "3.5.27", - "@react-aria/focus": "3.20.5", - "@react-aria/overlays": "3.27.3", - "@react-aria/utils": "3.29.1", + "@react-aria/dialog": "3.5.28", + "@react-aria/focus": "3.21.0", + "@react-aria/overlays": "3.28.0", + "@react-aria/utils": "3.30.0", "@tanstack/react-virtual": "^3.5.1", "@types/jquery": "3.5.32", "@types/lodash": "4.17.20", diff --git a/yarn.lock b/yarn.lock index d8d007fda69..c98bba2096b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3750,10 +3750,10 @@ __metadata: "@hello-pangea/dnd": "npm:18.0.1" "@monaco-editor/react": "npm:4.7.0" "@popperjs/core": "npm:2.11.8" - "@react-aria/dialog": "npm:3.5.27" - "@react-aria/focus": "npm:3.20.5" - "@react-aria/overlays": "npm:3.27.3" - "@react-aria/utils": "npm:3.29.1" + "@react-aria/dialog": "npm:3.5.28" + "@react-aria/focus": "npm:3.21.0" + "@react-aria/overlays": "npm:3.28.0" + "@react-aria/utils": "npm:3.30.0" "@rollup/plugin-node-resolve": "npm:16.0.1" "@storybook/addon-a11y": "npm:^8.6.2" "@storybook/addon-actions": "npm:^8.6.2" @@ -4049,12 +4049,12 @@ __metadata: languageName: node linkType: hard -"@internationalized/number@npm:^3.6.3": - version: 3.6.3 - resolution: "@internationalized/number@npm:3.6.3" +"@internationalized/number@npm:^3.6.4": + version: 3.6.4 + resolution: "@internationalized/number@npm:3.6.4" dependencies: "@swc/helpers": "npm:^0.5.0" - checksum: 10/b6f1793d12f89aca4c5bd88b60643739c4b02cade50a42c61192a2a5e0a913088ee35e4013b7ef3f675bdc919ebaf9a1631f1c40b1ceffd998f3fc14201a01c3 + checksum: 10/1a7f1e56ec689c56d10a2b8cbbfb8be09126048a624ac338963041cc793789ee3ac0f5fb67d1808cabb5de2d287a3bee0d9a156984d0fb291c1b5552c4139579 languageName: node linkType: hard @@ -6443,136 +6443,136 @@ __metadata: languageName: node linkType: hard -"@react-aria/dialog@npm:3.5.27": - version: 3.5.27 - resolution: "@react-aria/dialog@npm:3.5.27" +"@react-aria/dialog@npm:3.5.28": + version: 3.5.28 + resolution: "@react-aria/dialog@npm:3.5.28" dependencies: - "@react-aria/interactions": "npm:^3.25.3" - "@react-aria/overlays": "npm:^3.27.3" - "@react-aria/utils": "npm:^3.29.1" - "@react-types/dialog": "npm:^3.5.19" - "@react-types/shared": "npm:^3.30.0" + "@react-aria/interactions": "npm:^3.25.4" + "@react-aria/overlays": "npm:^3.28.0" + "@react-aria/utils": "npm:^3.30.0" + "@react-types/dialog": "npm:^3.5.20" + "@react-types/shared": "npm:^3.31.0" "@swc/helpers": "npm:^0.5.0" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/21257bb14352ab611e5659ce555cd845d8de51d30eea0cf7882b5119848c2136cd35f796a3031bbbcbc9640bffadeb35eee6f6254b34f2791c5a739c550e60da + checksum: 10/b76b89456d1a5b7a24aa5d41c1828b3167b700477baf3b9f401acf2bbb5ad6c393390086a41b331b192a433efbe0ab194c42f945316637c50b54db302095f982 languageName: node linkType: hard -"@react-aria/focus@npm:3.20.5, @react-aria/focus@npm:^3.20.5": - version: 3.20.5 - resolution: "@react-aria/focus@npm:3.20.5" +"@react-aria/focus@npm:3.21.0, @react-aria/focus@npm:^3.21.0": + version: 3.21.0 + resolution: "@react-aria/focus@npm:3.21.0" dependencies: - "@react-aria/interactions": "npm:^3.25.3" - "@react-aria/utils": "npm:^3.29.1" - "@react-types/shared": "npm:^3.30.0" + "@react-aria/interactions": "npm:^3.25.4" + "@react-aria/utils": "npm:^3.30.0" + "@react-types/shared": "npm:^3.31.0" "@swc/helpers": "npm:^0.5.0" clsx: "npm:^2.0.0" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/543c2f18c1d4f10940662608ca67947d1bc22630ae272e73136b36c2aefec61243148668201442132825bf4e9c0ab64add86c9870b9a51cb7bde2dfab7453a6e + checksum: 10/e894bcdd6c842326f84e764528a9de76b41c7771a768b041118b7858163713bd75a95abdd4158b5dcd20e72205f3679ba35c7cddf8deb7a73349c2a3fbe938ff languageName: node linkType: hard -"@react-aria/i18n@npm:^3.12.10": - version: 3.12.10 - resolution: "@react-aria/i18n@npm:3.12.10" +"@react-aria/i18n@npm:^3.12.11": + version: 3.12.11 + resolution: "@react-aria/i18n@npm:3.12.11" dependencies: "@internationalized/date": "npm:^3.8.2" "@internationalized/message": "npm:^3.1.8" - "@internationalized/number": "npm:^3.6.3" + "@internationalized/number": "npm:^3.6.4" "@internationalized/string": "npm:^3.2.7" - "@react-aria/ssr": "npm:^3.9.9" - "@react-aria/utils": "npm:^3.29.1" - "@react-types/shared": "npm:^3.30.0" + "@react-aria/ssr": "npm:^3.9.10" + "@react-aria/utils": "npm:^3.30.0" + "@react-types/shared": "npm:^3.31.0" "@swc/helpers": "npm:^0.5.0" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/6f331ceb62a1364d32f2336257ed82715f712e27d3e04a173296efc320f6532626561e883c2c3639ef4efdcd03498005e87d7160d8942ea8255d18d57770d3a0 + checksum: 10/2b6a904b032df9c4230c2deb723ae68b3c427468b041288ac80df6347a9a09c521d2d146716ec41db2a4a415298e2c5b6aba8d9aaf0e97f2af19c6babe92a9d2 languageName: node linkType: hard -"@react-aria/interactions@npm:^3.25.3": - version: 3.25.3 - resolution: "@react-aria/interactions@npm:3.25.3" +"@react-aria/interactions@npm:^3.25.4": + version: 3.25.4 + resolution: "@react-aria/interactions@npm:3.25.4" dependencies: - "@react-aria/ssr": "npm:^3.9.9" - "@react-aria/utils": "npm:^3.29.1" + "@react-aria/ssr": "npm:^3.9.10" + "@react-aria/utils": "npm:^3.30.0" "@react-stately/flags": "npm:^3.1.2" - "@react-types/shared": "npm:^3.30.0" + "@react-types/shared": "npm:^3.31.0" "@swc/helpers": "npm:^0.5.0" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/21754f3e15c35430262b8e283d00010543c8855bac9b9feeb701c967394940d4063b63380b59637c7da8e9b922f29edb3525d15d6005202a6dc22fd32a0d951f + checksum: 10/3ac422fe44a6d39d718a0b30cee00047de580562b928b2f620b4a641ab4f20aceafc30da1561303502a22666493af4311802ff5b5134946ae462e9181a6dc35c languageName: node linkType: hard -"@react-aria/overlays@npm:3.27.3, @react-aria/overlays@npm:^3.27.3": - version: 3.27.3 - resolution: "@react-aria/overlays@npm:3.27.3" +"@react-aria/overlays@npm:3.28.0, @react-aria/overlays@npm:^3.28.0": + version: 3.28.0 + resolution: "@react-aria/overlays@npm:3.28.0" dependencies: - "@react-aria/focus": "npm:^3.20.5" - "@react-aria/i18n": "npm:^3.12.10" - "@react-aria/interactions": "npm:^3.25.3" - "@react-aria/ssr": "npm:^3.9.9" - "@react-aria/utils": "npm:^3.29.1" - "@react-aria/visually-hidden": "npm:^3.8.25" - "@react-stately/overlays": "npm:^3.6.17" - "@react-types/button": "npm:^3.12.2" - "@react-types/overlays": "npm:^3.8.16" - "@react-types/shared": "npm:^3.30.0" + "@react-aria/focus": "npm:^3.21.0" + "@react-aria/i18n": "npm:^3.12.11" + "@react-aria/interactions": "npm:^3.25.4" + "@react-aria/ssr": "npm:^3.9.10" + "@react-aria/utils": "npm:^3.30.0" + "@react-aria/visually-hidden": "npm:^3.8.26" + "@react-stately/overlays": "npm:^3.6.18" + "@react-types/button": "npm:^3.13.0" + "@react-types/overlays": "npm:^3.9.0" + "@react-types/shared": "npm:^3.31.0" "@swc/helpers": "npm:^0.5.0" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/8cee1a99bc9ff4ecf0577b76f54cccaf684dde2278d0c270e82c683ef6f42bb8100de4596ae411e775643a3765abbd87fdb1c5d46bf05481ed9df9ed9b82276a + checksum: 10/4b4a682a72e7eabced1ae2cfb81b7715b23c915bc844d8c83f852924c432be430779463b76f533093cba4c3a9affc33b189094874d4fe6c4990d1e6fda8c5773 languageName: node linkType: hard -"@react-aria/ssr@npm:^3.9.9": - version: 3.9.9 - resolution: "@react-aria/ssr@npm:3.9.9" +"@react-aria/ssr@npm:^3.9.10": + version: 3.9.10 + resolution: "@react-aria/ssr@npm:3.9.10" dependencies: "@swc/helpers": "npm:^0.5.0" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/d0675357b51784e9dbe3add97aa0e6acc810ab0ef01d1c7a317ff8ead5eae64d66c60cc751ea3d10f874bb381e445099bb31cb7f1955801848ce6dce91b588a2 + checksum: 10/3b414b5b174769e874014604749d9beaf2556f360f61d3df3223bca6150c16dd37fbf16800e669a2b0045bd221df70212756991a426a7a472c56aac6c0dabd1b languageName: node linkType: hard -"@react-aria/utils@npm:3.29.1, @react-aria/utils@npm:^3.29.1": - version: 3.29.1 - resolution: "@react-aria/utils@npm:3.29.1" +"@react-aria/utils@npm:3.30.0, @react-aria/utils@npm:^3.30.0": + version: 3.30.0 + resolution: "@react-aria/utils@npm:3.30.0" dependencies: - "@react-aria/ssr": "npm:^3.9.9" + "@react-aria/ssr": "npm:^3.9.10" "@react-stately/flags": "npm:^3.1.2" - "@react-stately/utils": "npm:^3.10.7" - "@react-types/shared": "npm:^3.30.0" + "@react-stately/utils": "npm:^3.10.8" + "@react-types/shared": "npm:^3.31.0" "@swc/helpers": "npm:^0.5.0" clsx: "npm:^2.0.0" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/90af5ee5b9c7db063ba66a28cfecb8594f356c2b0fc4cce2fcee94543f3b160214e4a9c280e83355af89452b88b1def8856493705c770874da7121ba1d3d0cc0 + checksum: 10/62870808fe53277eadef3d758759a18e16746eb00332db3dc74f50dd56ea7a2a73fbe1297f138cfb605a80a7e71faf8f5b158e79e35690d82fd301ff21a05ad1 languageName: node linkType: hard -"@react-aria/visually-hidden@npm:^3.8.25": - version: 3.8.25 - resolution: "@react-aria/visually-hidden@npm:3.8.25" +"@react-aria/visually-hidden@npm:^3.8.26": + version: 3.8.26 + resolution: "@react-aria/visually-hidden@npm:3.8.26" dependencies: - "@react-aria/interactions": "npm:^3.25.3" - "@react-aria/utils": "npm:^3.29.1" - "@react-types/shared": "npm:^3.30.0" + "@react-aria/interactions": "npm:^3.25.4" + "@react-aria/utils": "npm:^3.30.0" + "@react-types/shared": "npm:^3.31.0" "@swc/helpers": "npm:^0.5.0" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/362a793beac35fde101e598f4cac9cd98394b9a0171309842e2428b25074ee696358c45d2ded8244bc40a9db8471440994be59b0f347d2c643092c6445aaca61 + checksum: 10/2056c0c3e2ced5d7c73364dac36f6122e62cac794313a4935e87e6c776c1f5219a64c9343499b808343b09112d086e150be9d97b67491743993742e8e60d6ed3 languageName: node linkType: hard @@ -6620,27 +6620,27 @@ __metadata: languageName: node linkType: hard -"@react-stately/overlays@npm:^3.6.17": - version: 3.6.17 - resolution: "@react-stately/overlays@npm:3.6.17" +"@react-stately/overlays@npm:^3.6.18": + version: 3.6.18 + resolution: "@react-stately/overlays@npm:3.6.18" dependencies: - "@react-stately/utils": "npm:^3.10.7" - "@react-types/overlays": "npm:^3.8.16" + "@react-stately/utils": "npm:^3.10.8" + "@react-types/overlays": "npm:^3.9.0" "@swc/helpers": "npm:^0.5.0" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/9c44f181cd8f0b99a40142aebf3d7e1d1df1e96a789b636a5ee877529e60fddece4ebcf32d9812d9848ce0298bf5cb6202df3b680552b32c89f1276bce3f8a7b + checksum: 10/dfba96e1a1ddac6b94a30536f657972cec9dda17a139c739f7eddc294a413b7012abfe6230136ad688077d21daf10165009ad2fc952b593ddbf9b948cb39596e languageName: node linkType: hard -"@react-stately/utils@npm:^3.10.7": - version: 3.10.7 - resolution: "@react-stately/utils@npm:3.10.7" +"@react-stately/utils@npm:^3.10.8": + version: 3.10.8 + resolution: "@react-stately/utils@npm:3.10.8" dependencies: "@swc/helpers": "npm:^0.5.0" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/b9c1a62d4f179e252d08a2f18120a02ffc8704424f257e81e5e74da1222e3acd1e821ce84220d87d9d7b4b6d4dc2fd58d5bb380546d198bf434b700cda8ac3d8 + checksum: 10/7878ec47b132075566708bae630cb86d8237dde976eb3793bba43695abbb29fcaea9d00ea3f4f7244fcda253368f5b2b85263c37665c24e390500cdcc978c6fe languageName: node linkType: hard @@ -6655,26 +6655,26 @@ __metadata: languageName: node linkType: hard -"@react-types/button@npm:^3.12.2": - version: 3.12.2 - resolution: "@react-types/button@npm:3.12.2" +"@react-types/button@npm:^3.13.0": + version: 3.13.0 + resolution: "@react-types/button@npm:3.13.0" dependencies: - "@react-types/shared": "npm:^3.30.0" + "@react-types/shared": "npm:^3.31.0" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/d00f7e23fd6cfa863c2d9f0f91ec47fc017666b55775842af3c001fabcf949dfa55b116674bcb841de568eacaf677c3a25e079b4810dcf857547bd9cf6d60917 + checksum: 10/11af29a2a5cb777629aa959a05dcd3d1c5a1ad396017e0d81be8837479114c4e7fd0aec283b878f8721883c86a59f4b6497f5e4a892c52b97bd8b9d9c7929ae9 languageName: node linkType: hard -"@react-types/dialog@npm:^3.5.19": - version: 3.5.19 - resolution: "@react-types/dialog@npm:3.5.19" +"@react-types/dialog@npm:^3.5.20": + version: 3.5.20 + resolution: "@react-types/dialog@npm:3.5.20" dependencies: - "@react-types/overlays": "npm:^3.8.16" - "@react-types/shared": "npm:^3.30.0" + "@react-types/overlays": "npm:^3.9.0" + "@react-types/shared": "npm:^3.31.0" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/07bd55d214d8162f1a700f1ac80cd51468d0defc0d9753309d5e58a0fffbc1eb65a588b6c0709593a41fa8d72cc1f8f8dba0a65b2a336968fdc2ff92afec3596 + checksum: 10/467ea8dbcd3ba4fc1557e42109172d01fda941d35b8e5534da62173a1936645e58b1e4240190da2a792621389c9f2e2cd2bd938a03e0f641a101427ca2e590c1 languageName: node linkType: hard @@ -6701,14 +6701,14 @@ __metadata: languageName: node linkType: hard -"@react-types/overlays@npm:^3.8.12, @react-types/overlays@npm:^3.8.16": - version: 3.8.16 - resolution: "@react-types/overlays@npm:3.8.16" +"@react-types/overlays@npm:^3.8.12, @react-types/overlays@npm:^3.9.0": + version: 3.9.0 + resolution: "@react-types/overlays@npm:3.9.0" dependencies: - "@react-types/shared": "npm:^3.30.0" + "@react-types/shared": "npm:^3.31.0" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/12bedf8e79d31827cd9868dcb115d07b9fee7954af5e00c1a46fe87b9f2b08e971af4b1b238faa2ce7ed173600786a83c7b9cfcdee5c7464a42bc581d7dee3a6 + checksum: 10/96dbc368f2bba4698d89dd9f7f5c51307686fb7cf0b2d89a51755ca552a9525f5e7f0fda7710d7a1a48e827f1d232024e937aa185deb4127a7da67277d9e580e languageName: node linkType: hard @@ -6721,12 +6721,12 @@ __metadata: languageName: node linkType: hard -"@react-types/shared@npm:^3.27.0, @react-types/shared@npm:^3.30.0": - version: 3.30.0 - resolution: "@react-types/shared@npm:3.30.0" +"@react-types/shared@npm:^3.27.0, @react-types/shared@npm:^3.31.0": + version: 3.31.0 + resolution: "@react-types/shared@npm:3.31.0" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/58af4134275a347bf18ac5d15e3dcfd07dcf623e1d494f140303e06f2149b7aad86408979e163393346df613f6390a3a387f77a2937f0bf1908c6672a44f7b69 + checksum: 10/906f9f05563b9698485b93168ddd693aef39569753967de832f4fdabc1895c8da0e5add06b8fd23403a4feac42f2b81bd2401454255860144fdf871fbf92fa3f languageName: node linkType: hard @@ -18213,10 +18213,10 @@ __metadata: "@playwright/test": "npm:1.54.1" "@pmmmwh/react-refresh-webpack-plugin": "npm:0.6.1" "@popperjs/core": "npm:2.11.8" - "@react-aria/dialog": "npm:3.5.27" - "@react-aria/focus": "npm:3.20.5" - "@react-aria/overlays": "npm:3.27.3" - "@react-aria/utils": "npm:3.29.1" + "@react-aria/dialog": "npm:3.5.28" + "@react-aria/focus": "npm:3.21.0" + "@react-aria/overlays": "npm:3.28.0" + "@react-aria/utils": "npm:3.30.0" "@react-awesome-query-builder/ui": "npm:6.6.15" "@react-types/button": "npm:3.10.2" "@react-types/menu": "npm:3.9.14" From f7f8a52bda6528d0b25d7264b3694bddc6fcb651 Mon Sep 17 00:00:00 2001 From: Usman Ahmad Date: Wed, 30 Jul 2025 16:03:44 +0200 Subject: [PATCH 007/114] MySQL documentation major improvements (#107690) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * added keywords and fixed description * Update (dateColumn) description Signed-off-by: Sheikh-Abubaker * Mount docs from the current repository Signed-off-by: Jack Baldry * Clean up variables Signed-off-by: Jack Baldry * Improve timeFilter and timeGroup macro description Signed-off-by: Sheikh-Abubaker * Fix timefilter macro example formatting Signed-off-by: Sheikh-Abubaker * Fix time macro example formatting Signed-off-by: Sheikh-Abubaker * Remove extra spaces from timefilter macro example Signed-off-by: Sheikh-Abubaker * Introduce new table and Add examples for time, timeFilter and timeGroup Macros Signed-off-by: Sheikh-Abubaker * Improve existing examples of timeGroupAlias Macro Signed-off-by: Sheikh-Abubaker * Remove data frame description Signed-off-by: Sheikh-Abubaker * fixed the homepage with valid links Signed-off-by: Usman Ahmad * Add Table Panel results for Macros Signed-off-by: Sheikh-Abubaker * fixed and define the configuration part for grafana mysql user Signed-off-by: Usman Ahmad * fixed port typo Signed-off-by: Usman Ahmad * remove the sqlDatasourceDatabaseSelection based https://github.com/grafana/grafana/issues/105232 Signed-off-by: Usman Ahmad * added annotation single tag example Signed-off-by: Usman Ahmad * Change format type Signed-off-by: Sheikh-Abubaker * fix minor typos and run prettier Signed-off-by: Usman Ahmad * Address Macros specific changes Signed-off-by: Sheikh-Abubaker * Revert column name and replace URL with version substitution Signed-off-by: Sheikh-Abubaker * re-added the text for the feature flag Signed-off-by: Usman Ahmad * revert back the changes for the grafana mysql usage Signed-off-by: Usman Ahmad * fixed links as per writers toolkit guide * run prettier Signed-off-by: Usman Ahmad * Update docs/sources/datasources/mysql/query-editor/_index.md thanks Co-authored-by: Zoltán Bedi --------- Signed-off-by: Sheikh-Abubaker Signed-off-by: Jack Baldry Signed-off-by: Usman Ahmad Co-authored-by: Sheikh-Abubaker Co-authored-by: Jack Baldry Co-authored-by: Zoltán Bedi --- docs/sources/datasources/mysql/_index.md | 5 +- .../datasources/mysql/configuration/_index.md | 12 +- .../datasources/mysql/query-editor/_index.md | 183 +++++++++++------- docs/variables.mk | 16 +- 4 files changed, 128 insertions(+), 88 deletions(-) diff --git a/docs/sources/datasources/mysql/_index.md b/docs/sources/datasources/mysql/_index.md index 33067e51983..f1152a4a762 100644 --- a/docs/sources/datasources/mysql/_index.md +++ b/docs/sources/datasources/mysql/_index.md @@ -2,10 +2,11 @@ aliases: - ../data-sources/mysql/ - ../features/datasources/mysql/ -description: introduction to the MySQL data source in Grafana +description: Introduction to the MySQL data source in Grafana keywords: - grafana - mysql + - data source - guide labels: products: @@ -45,7 +46,7 @@ refs: # MySQL data source -Grafana ships with a built-in MySQL data source plugin that allows you to query and visualize data from a MySQL-compatible database like MariaDB or Percona Server. You don't need to install a plugin in order to add the MySQL data source to your Grafana instance. +Grafana ships with a built-in MySQL data source plugin that allows you to query and visualize data from a MySQL-compatible database like [MariaDB](https://mariadb.org/) or [Percona Server](https://www.percona.com/). You don't need to install a plugin in order to add the MySQL data source to your Grafana instance. Grafana offers several configuration options for this data source as well as a visual and code-based query editor. diff --git a/docs/sources/datasources/mysql/configuration/_index.md b/docs/sources/datasources/mysql/configuration/_index.md index d98aff95019..424345476ad 100644 --- a/docs/sources/datasources/mysql/configuration/_index.md +++ b/docs/sources/datasources/mysql/configuration/_index.md @@ -45,10 +45,14 @@ This document provides instructions for configuring the MySQL data source and ex You must have the `Organization administrator` role in order to configure the MySQL data source. Administrators can also [configure the data source via YAML](#provision-the-data-source) with Grafana's provisioning system. -Grafana ships with the MySQL plugin, so no additional installation is required. - {{< admonition type="note" >}} -When adding a data source, ensure the database user you specify has only `SELECT` permissions on the relevant database and tables. Grafana does not validate the safety of queries, which means they can include potentially harmful SQL statements, such as `USE otherdb;` or `DROP TABLE user;`, which could get executed. To minimize this risk, Grafana strongly recommends creating a dedicated MySQL user with restricted permissions. +Grafana ships with the MySQL data source by default, so no additional installation is required. +{{< /admonition >}} + +{{< admonition type="caution" >}} +When adding a data source, ensure the database user you specify has only `SELECT` permissions on the relevant database and tables. Grafana does not validate the safety of queries, which means they can include potentially harmful SQL statements, such as `USE otherdb;` or `DROP TABLE user;`, which could get executed. + +To minimize this risk, Grafana strongly recommends creating a dedicated MySQL user with restricted permissions. {{< /admonition >}} Example: @@ -81,7 +85,7 @@ Following is a list of MySQL configuration options: **Connection:** -- **Host URL** - Enter the IP address/hostname and optional port of your MySQL instance. If the port is omitted the default 3306 port will be used. +- **Host URL** - Enter the IP address/hostname and optional port of your MySQL instance. If the port is omitted the default `3306` port will be used. - **Database** - Enter the name of your MySQL database. **Authentication:** diff --git a/docs/sources/datasources/mysql/query-editor/_index.md b/docs/sources/datasources/mysql/query-editor/_index.md index 8ec12f2789e..b34bcf65935 100644 --- a/docs/sources/datasources/mysql/query-editor/_index.md +++ b/docs/sources/datasources/mysql/query-editor/_index.md @@ -58,6 +58,9 @@ refs: destination: /docs/grafana//alerting/alerting-rules/templates/ - pattern: /docs/grafana-cloud/ destination: /docs/grafana-cloud/alerting-and-irm/alerting/alerting-rules/templates/ + configure-standard-options: + - pattern: /docs/grafana/ + - destination: /docs/grafana//panels-visualizations/configure-standard-options/ --- # MySQL query editor @@ -125,26 +128,26 @@ Changes made to a query in Code mode will not transfer to Builder mode and will You can add macros to your queries to simplify the syntax and enable dynamic elements, such as date range filters. -| Macro example | Description | -| ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `$__time(dateColumn)` | Replaces the value with an expression to convert to a UNIX timestamp and renames the column to `time_sec`. Example: _UNIX_TIMESTAMP(dateColumn) AS time_sec_. | -| `$__timeEpoch(dateColumn)` | Replaces the value with an expression to convert to a UNIX Epoch timestamp and renames the column to `time_sec`. Example: _UNIX_TIMESTAMP(dateColumn) AS time_sec_. | -| `$__timeFilter(dateColumn)` | Replaces the value a time range filter using the specified column name. Example: _dateColumn BETWEEN FROM_UNIXTIME(1494410783) AND FROM_UNIXTIME(1494410983)_ | -| `$__timeFrom()` | Replaces the value with the start of the currently active time selection. Example: _FROM_UNIXTIME(1494410783)_ | -| `$__timeTo()` | Replaces the value with the end of the currently active time selection. Example: _FROM_UNIXTIME(1494410983)_ | -| `$__timeGroup(dateColumn,'5m')` | Replaces the value with an expression suitable for use in a GROUP BY clause. Example: *cast(cast(UNIX_TIMESTAMP(dateColumn)/(300) as signed)*300 as signed),\* | -| `$__timeGroup(dateColumn,'5m', 0)` | Same as the `$__timeGroup(dateColumn,'5m')` macro, but includes a fill parameter to ensure missing points in the series are added by Grafana, using 0 as the default value. **This applies only to time series queries.** | -| `$__timeGroup(dateColumn,'5m', NULL)` | Same as the `$__timeGroup(dateColumn,'5m', 0)` but NULL is used as the value for missing points. **This applies only to time series queries.** | -| `$__timeGroup(dateColumn,'5m', previous)` | Same as the `$__timeGroup(dateColumn,'5m', previous)` macro, but uses the previous value in the series as the fill value. If no previous value exists,`NULL` will be used. **This applies only to time series queries.** | -| `$__timeGroupAlias(dateColumn,'5m')` | Replaces the value identical to $\_\_timeGroup but with an added column alias. | -| `$__unixEpochFilter(dateColumn)` | Replaces the value by a time range filter using the specified column name with times represented as a UNIX timestamp. Example: _dateColumn > 1494410783 AND dateColumn < 1494497183_ | -| `$__unixEpochFrom()` | Replaces the value with the start of the currently active time selection as a UNIX timestamp. Example: _1494410783_ | -| `$__unixEpochTo()` | Replaces the value with the end of the currently active time selection as UNIX timestamp. Example: _1494497183_ | -| `$__unixEpochNanoFilter(dateColumn)` | Replaces the value with a time range filter using the specified column name with time represented as a nanosecond timestamp. Example: _dateColumn > 1494410783152415214 AND dateColumn < 1494497183142514872_ | -| `$__unixEpochNanoFrom()` | Replaces the value with the start of the currently active time selection as nanosecond timestamp. Example: _1494410783152415214_ | -| `$__unixEpochNanoTo()` | Replaces the value with the end of the currently active time selection as nanosecond timestamp. Example: _1494497183142514872_ | -| `$__unixEpochGroup(dateColumn,'5m', [fillmode])` | Same as $\_\_timeGroup but for times stored as Unix timestamp. **Note that `fillMode` only works with time series queries.** | -| `$__unixEpochGroupAlias(dateColumn,'5m', [fillmode])` | Same as $\_\_timeGroup but also adds a column alias. **Note that `fillMode` only works with time series queries.** | +| Macro example | Description | +| ----------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `$__time(dateColumn)` | Replaces the value with an expression to convert to a UNIX timestamp and renames the column to `time_sec`. It also helps to recognize the `time` column, as required in Time Series format. Example: _UNIX_TIMESTAMP(dateColumn) AS time_sec_. | +| `$__timeEpoch(dateColumn)` | Replaces the value with an expression to convert to a UNIX Epoch timestamp and renames the column to `time_sec`. Example: _UNIX_TIMESTAMP(dateColumn) AS time_sec_. | +| `$__timeFilter(dateColumn)` | Applies a time range filter using the specified column name and fetches only the data that falls within that range. Example: _dateColumn BETWEEN FROM_UNIXTIME(1494410783) AND FROM_UNIXTIME(1494410983)_ | +| `$__timeFrom()` | Replaces the value with the start of the currently active time selection. Example: _FROM_UNIXTIME(1494410783)_ | +| `$__timeTo()` | Replaces the value with the end of the currently active time selection. Example: _FROM_UNIXTIME(1494410983)_ | +| `$__timeGroup(dateColumn,'5m')` | Replaces the value with an expression suitable for use in a GROUP BY clause and creates the bucket timestamps at a fixed interval. Example: *cast(cast(UNIX_TIMESTAMP(dateColumn)/(300) as signed)*300 as signed),\* | +| `$__timeGroup(dateColumn,'5m', 0)` | Same as the `$__timeGroup(dateColumn,'5m')` macro, but includes a fill parameter to ensure missing points in the series are added by Grafana, using 0 as the default value. **This applies only to time series queries.** | +| `$__timeGroup(dateColumn,'5m', NULL)` | Same as the `$__timeGroup(dateColumn,'5m', 0)` but NULL is used as the value for missing points. **This applies only to time series queries.** | +| `$__timeGroup(dateColumn,'5m', previous)` | Same as the `$__timeGroup(dateColumn,'5m', previous)` macro, but uses the previous value in the series as the fill value. If no previous value exists,`NULL` will be used. **This applies only to time series queries.** | +| `$__timeGroupAlias(dateColumn,'5m')` | Replaces the value identical to $\_\_timeGroup but with an added column alias. | +| `$__unixEpochFilter(dateColumn)` | Replaces the value by a time range filter using the specified column name with times represented as a UNIX timestamp. Example: _dateColumn > 1494410783 AND dateColumn < 1494497183_ | +| `$__unixEpochFrom()` | Replaces the value with the start of the currently active time selection as a UNIX timestamp. Example: _1494410783_ | +| `$__unixEpochTo()` | Replaces the value with the end of the currently active time selection as UNIX timestamp. Example: _1494497183_ | +| `$__unixEpochNanoFilter(dateColumn)` | Replaces the value with a time range filter using the specified column name with time represented as a nanosecond timestamp. Example: _dateColumn > 1494410783152415214 AND dateColumn < 1494497183142514872_ | +| `$__unixEpochNanoFrom()` | Replaces the value with the start of the currently active time selection as nanosecond timestamp. Example: _1494410783152415214_ | +| `$__unixEpochNanoTo()` | Replaces the value with the end of the currently active time selection as nanosecond timestamp. Example: _1494497183142514872_ | +| `$__unixEpochGroup(dateColumn,'5m', [fillmode])` | Same as $\_\_timeGroup but for times stored as Unix timestamp. **Note that `fillMode` only works with time series queries.** | +| `$__unixEpochGroupAlias(dateColumn,'5m', [fillmode])` | Same as $\_\_timeGroup but also adds a column alias. **Note that `fillMode` only works with time series queries.** | ## Table SQL queries @@ -180,106 +183,127 @@ The examples in this section refer to the data in the following table: +---------------------+--------------+---------------------+----------+ | time_date_time | value_double | CreatedAt | hostname | +---------------------+--------------+---------------------+----------+ -| 2020-01-02 03:05:00 | 3.0 | 2020-01-02 03:05:00 | 10.0.1.1 | -| 2020-01-02 03:06:00 | 4.0 | 2020-01-02 03:06:00 | 10.0.1.2 | -| 2020-01-02 03:10:00 | 6.0 | 2020-01-02 03:10:00 | 10.0.1.1 | -| 2020-01-02 03:11:00 | 7.0 | 2020-01-02 03:11:00 | 10.0.1.2 | -| 2020-01-02 03:20:00 | 5.0 | 2020-01-02 03:20:00 | 10.0.1.2 | +| 2025-01-02 03:05:00 | 3.0 | 2025-01-02 03:05:00 | 10.0.1.1 | +| 2025-01-02 03:06:00 | 4.0 | 2025-01-02 03:06:00 | 10.0.1.2 | +| 2025-01-02 03:10:00 | 6.0 | 2025-01-02 03:10:00 | 10.0.1.1 | +| 2025-01-02 03:11:00 | 7.0 | 2025-01-02 03:11:00 | 10.0.1.2 | +| 2025-01-02 03:20:00 | 5.0 | 2025-01-02 03:20:00 | 10.0.1.2 | +---------------------+--------------+---------------------+----------+ ``` -A time series query result is returned in a [wide data frame format](https://grafana.com/developers/plugin-tools/key-concepts/data-frames#wide-format). Any column except time or of type string transforms into value fields in the data frame query result. Any string column transforms into field labels in the data frame query result. - {{< admonition type="note" >}} For backward compatibility, an exception to the aforementioned rule applies to queries returning three columns, including a string column named `metric`. Instead of converting the metric column into field labels, it is used as the field name, and the series name is set to the value of the metric column. Refer to the following example with a metric column. {{< /admonition >}} -**Example with `metric` column:** +**Example with `$__time(dateColumn)` Macro:** + +```sql +SELECT + $__time(time_date_time), + value_double +FROM my_data +ORDER BY time_date_time +``` + +Table panel result: + +{{< figure alt="output of time macro" src="/media/docs/grafana/data-sources/mysql/screenshot-time-and-timefilter-macro.png" >}} + +In the following example, the result includes two columns, `Time` and `value_double`, which represent the data associated with fixed timestamps. This query does not apply a time range filter and returns all rows from the table. + +**Example with `$__timeFilter(dateColumn)` Macro:** + +```sql +SELECT + $__time(time_date_time), + value_double +FROM my_data +WHERE $__timeFilter(time_date_time) +ORDER BY time_date_time +``` + +Table panel result: + +{{< figure alt="output of time filter macro" src="/media/docs/grafana/data-sources/mysql/screenshot-time-and-timefilter-macro.png" >}} + +This example returns the same result as the previous one, but adds support for filtering data using the Grafana time picker. + +**Example with `$__timeGroup(dateColumn,'5m')` Macro:** + +```sql +SELECT + $__timeGroup(time_date_time, '5m') AS time, + sum(value_double) AS sum_value +FROM my_data +WHERE $__timeFilter(time_date_time) +GROUP BY time +ORDER BY time +``` + +Table panel result: + +{{< figure alt="output of time group macro" src="/media/docs/grafana/data-sources/mysql/screenshot-timegroup-macro.png" >}} + +Given the result in the following example, the data is grouped and aggregated within buckets with timestamps of fixed interval i.e. 5 mins. To customize the default series name formatting (optional), refer to [Standard options definitions](ref:configure-standard-options). + +**Example with `$__timeGroupAlias(dateColumn,'5m')` Macro:** ```sql SELECT $__timeGroupAlias(time_date_time,'5m'), min(value_double), 'min' as metric -FROM test_data +FROM my_data WHERE $__timeFilter(time_date_time) GROUP BY time ORDER BY time ``` -Data frame result: +Table panel result: -```text -+---------------------+-----------------+ -| Name: time | Name: min | -| Labels: | Labels: | -| Type: []time.Time | Type: []float64 | -+---------------------+-----------------+ -| 2020-01-02 03:05:00 | 3 | -| 2020-01-02 03:10:00 | 6 | -| 2020-01-02 03:20:00 | 5 | -+---------------------+-----------------+ -``` +{{< figure alt="output of time group alias macro" src="/media/docs/grafana/data-sources/mysql/screenshot-timeGroupAlias-macro.png" >}} -To customize the default series name formatting (optional), refer to [Standard options definitions](ref:configure-standard-options-display-name). +The following result is similar to the result of the `$__timeGroup(dateColumn,'5m')` macro, except it uses a built-in alias for the time column. +To customize the default series name formatting (optional), refer to [Standard options definitions](ref:configure-standard-options). -**Example using the fill parameter in the $\_\_timeGroupAlias macro to convert null values to be zero instead:** +**Example with `$__timeGroupAlias` Macro to convert null values to zero instead:** ```sql SELECT $__timeGroupAlias(createdAt,'5m',0), sum(value_double) as value, hostname -FROM test_data +FROM my_data WHERE $__timeFilter(createdAt) GROUP BY time, hostname ORDER BY time ``` -Given the data frame result in the following example and using the graph panel, you will get two series named _value 10.0.1.1_ and _value 10.0.1.2_. To render the series with a name of _10.0.1.1_ and _10.0.1.2_ , use a [Standard options definitions](ref:configure-standard-options-display-name) display value of `${__field.labels.hostname}`. +Table panel result: -Data frame result: +{{< figure alt="output of null values to zero case, for time group alias macro" src="/media/docs/grafana/data-sources/mysql/screenshot-timeGroupAlias-macro-conv-null-to-zero.png" >}} -```text -+---------------------+---------------------------+---------------------------+ -| Name: time | Name: value | Name: value | -| Labels: | Labels: hostname=10.0.1.1 | Labels: hostname=10.0.1.2 | -| Type: []time.Time | Type: []float64 | Type: []float64 | -+---------------------+---------------------------+---------------------------+ -| 2020-01-02 03:05:00 | 3 | 4 | -| 2020-01-02 03:10:00 | 6 | 7 | -| 2020-01-02 03:15:00 | 0 | 0 | -| 2020-01-02 03:20:00 | 0 | 5 | -+---------------------+---------------------------+---------------------------+ -``` +Given the result in the following example, null values within bucket timestamps are replaced by zero and also add the `Time` column alias by default. To customize the default series name formatting (optional), refer to [Standard options definitions](ref:configure-standard-options) to display the value of `${__field.labels.hostname}`. -**Example with multiple columns:** +**Example with multiple columns for `$__timeGroupAlias(dateColumn,'5m')` Macro:** ```sql SELECT $__timeGroupAlias(time_date_time,'5m'), min(value_double) as min_value, max(value_double) as max_value -FROM test_data +FROM my_data WHERE $__timeFilter(time_date_time) GROUP BY time ORDER BY time ``` -Data frame result: +Table panel result: -```text -+---------------------+-----------------+-----------------+ -| Name: time | Name: min_value | Name: max_value | -| Labels: | Labels: | Labels: | -| Type: []time.Time | Type: []float64 | Type: []float64 | -+---------------------+-----------------+-----------------+ -| 2020-01-02 03:05:00 | 3 | 4 | -| 2020-01-02 03:10:00 | 6 | 7 | -| 2020-01-02 03:20:00 | 5 | 5 | -+---------------------+-----------------+-----------------+ -``` +{{< figure alt="output with multiple colummns for time group alias macro" src="/media/docs/grafana/data-sources/mysql/screenshot-timeGroupAlias-macro-multiple-columns.png" >}} + +The query returns multiple columns representing minimum and maximum values within the defined range. ## Templating @@ -392,6 +416,21 @@ WHERE $__unixEpochFilter(epoch_time) ``` +You may use one or more tags to show them as annotations in a common-separate string. + +**Example query using a `time` column with epoch values for a single tag:** + +```sql +SELECT + epoch_time as time, + metric1 as text, + tag1 as tag +FROM + my_data +WHERE + $__unixEpochFilter(epoch_time) +``` + **Example region query using `time` and `timeend` columns with epoch values:** ```sql diff --git a/docs/variables.mk b/docs/variables.mk index a3302041ce5..1a8c9cb6691 100644 --- a/docs/variables.mk +++ b/docs/variables.mk @@ -1,11 +1,7 @@ # List of projects to provide to the make-docs script. -PROJECTS := grafana - -# Use the doc-validator image defined in CI by default. -export DOC_VALIDATOR_IMAGE := $(shell sed -En 's, *image: "(grafana/doc-validator[^"]+)",\1,p' "$(shell git rev-parse --show-toplevel)/.github/workflows/doc-validator.yml") - -# Skip some doc-validator checks. -export DOC_VALIDATOR_SKIP_CHECKS := $(shell sed -En "s, *'--skip-checks=(.+)',\1,p" "$(shell git rev-parse --show-toplevel)/.github/workflows/doc-validator.yml") - -# Only run on sections that have been enabled in CI. -export DOC_VALIDATOR_INCLUDE := $(shell sed -En "s, *'--include=\\^docs/sources/(.+)',/hugo/content/docs/grafana/latest/\1,p" "$(shell git rev-parse --show-toplevel)/.github/workflows/doc-validator.yml") +# Format is PROJECT[:[VERSION][:[REPOSITORY][:[DIRECTORY]]]] +# The following PROJECTS value mounts content into the "grafana" project, at the "latest" version, which is the default if not explicitly set. +# This results in the content being served at /docs/grafana/latest/. +# The source of the content is the current repository which is determined by the name of the parent directory of the git root. +# This overrides the default behavior of assuming the repository directory is the same as the project name. +PROJECTS := grafana::$(notdir $(basename $(shell git rev-parse --show-toplevel))) From 4a34a2313c08607dfe1b38649df633ce8ccf9b76 Mon Sep 17 00:00:00 2001 From: Adam Yeats <16296989+adamyeats@users.noreply.github.com> Date: Wed, 30 Jul 2025 15:14:44 +0100 Subject: [PATCH 008/114] InfluxDB: Fix backend queries sharing time ranges (#108873) Fix backend queries sharing time ranges --- pkg/tsdb/influxdb/flux/flux.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pkg/tsdb/influxdb/flux/flux.go b/pkg/tsdb/influxdb/flux/flux.go index cab5c0d1067..6643f064910 100644 --- a/pkg/tsdb/influxdb/flux/flux.go +++ b/pkg/tsdb/influxdb/flux/flux.go @@ -27,9 +27,8 @@ func Query(ctx context.Context, dsInfo *models.DatasourceInfo, tsdbQuery backend } defer r.client.Close() - timeRange := tsdbQuery.Queries[0].TimeRange for _, query := range tsdbQuery.Queries { - qm, err := getQueryModel(query, timeRange, dsInfo) + qm, err := getQueryModel(query, query.TimeRange, dsInfo) if err != nil { tRes.Responses[query.RefID] = backend.DataResponse{ Error: err, From 98e37f2ca9b4fa1a6cac0c3a2ae355a5ff60dbd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mustafa=20Sencer=20=C3=96zcan?= <32759850+mustafasencer@users.noreply.github.com> Date: Wed, 30 Jul 2025 16:18:38 +0200 Subject: [PATCH 009/114] fix: special handling of unmarshalling for invalid json dashboards (#108519) * implement special dashboard fallback logic when dashboard data has invalid json behind feature flag --------- Co-authored-by: Will Assis --- .../src/types/featureToggles.gen.ts | 4 + .../datamigrations/to_unified_storage.go | 16 ++-- pkg/registry/apis/dashboard/legacy/migrate.go | 7 +- .../dashboard/legacy/query_dashboards.sql | 1 + .../apis/dashboard/legacy/sql_dashboards.go | 83 ++++++++++++++--- .../dashboard/legacy/sql_dashboards_test.go | 89 ++++++++++++++++--- .../mysql--query_dashboards-dashboard.sql | 1 + ...--query_dashboards-dashboard_next_page.sql | 1 + ...--query_dashboards-export_with_history.sql | 1 + .../mysql--query_dashboards-folders.sql | 1 + .../mysql--query_dashboards-history_uid.sql | 1 + ...uery_dashboards-history_uid_at_version.sql | 1 + ...ery_dashboards-history_uid_second_page.sql | 1 + ...ery_dashboards-migration_with_fallback.sql | 1 + .../postgres--query_dashboards-dashboard.sql | 1 + ...--query_dashboards-dashboard_next_page.sql | 1 + ...--query_dashboards-export_with_history.sql | 1 + .../postgres--query_dashboards-folders.sql | 1 + ...postgres--query_dashboards-history_uid.sql | 1 + ...uery_dashboards-history_uid_at_version.sql | 1 + ...ery_dashboards-history_uid_second_page.sql | 1 + ...ery_dashboards-migration_with_fallback.sql | 1 + .../sqlite--query_dashboards-dashboard.sql | 1 + ...--query_dashboards-dashboard_next_page.sql | 1 + ...--query_dashboards-export_with_history.sql | 1 + .../sqlite--query_dashboards-folders.sql | 1 + .../sqlite--query_dashboards-history_uid.sql | 1 + ...uery_dashboards-history_uid_at_version.sql | 1 + ...ery_dashboards-history_uid_second_page.sql | 1 + ...ery_dashboards-migration_with_fallback.sql | 1 + pkg/registry/apis/dashboard/register.go | 2 +- pkg/server/wire_gen.go | 4 +- pkg/services/featuremgmt/registry.go | 6 ++ pkg/services/featuremgmt/toggles_gen.csv | 1 + pkg/services/featuremgmt/toggles_gen.go | 4 + pkg/services/featuremgmt/toggles_gen.json | 12 +++ 36 files changed, 218 insertions(+), 35 deletions(-) diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index 90565ef3eb0..138e05f187e 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -302,6 +302,10 @@ export interface FeatureToggles { */ dashboardSchemaValidationLogging?: boolean; /** + * Enable fallback parsing behavior when scan row encounters invalid dashboard JSON + */ + scanRowInvalidDashboardParseFallbackEnabled?: boolean; + /** * Show query type endpoints in datasource API servers (currently hardcoded for testdata, expressions, and prometheus) */ datasourceQueryTypes?: boolean; diff --git a/pkg/cmd/grafana-cli/commands/datamigrations/to_unified_storage.go b/pkg/cmd/grafana-cli/commands/datamigrations/to_unified_storage.go index 8e46423be87..89053e6b932 100644 --- a/pkg/cmd/grafana-cli/commands/datamigrations/to_unified_storage.go +++ b/pkg/cmd/grafana-cli/commands/datamigrations/to_unified_storage.go @@ -63,6 +63,12 @@ func ToUnifiedStorage(c utils.CommandLine, cfg *setting.Cfg, sqlStore db.DB) err }, } + featureManager, err := featuremgmt.ProvideManagerService(cfg) + if err != nil { + return err + } + featureToggles := featuremgmt.ProvideToggles(featureManager) + provisioning, err := newStubProvisioning(cfg.ProvisioningPath) if err != nil { return err @@ -76,9 +82,10 @@ func ToUnifiedStorage(c utils.CommandLine, cfg *setting.Cfg, sqlStore db.DB) err nil, // no librarypanels.Service sort.ProvideService(), acimpl.ProvideAccessControl(featuremgmt.WithFeatures()), + featureToggles, ) - client, err := newUnifiedClient(cfg, sqlStore) + client, err := newUnifiedClient(cfg, sqlStore, featureToggles) if err != nil { return err } @@ -215,12 +222,7 @@ func promptYesNo(prompt string) (bool, error) { } } -func newUnifiedClient(cfg *setting.Cfg, sqlStore db.DB) (resource.ResourceClient, error) { - featureManager, err := featuremgmt.ProvideManagerService(cfg) - if err != nil { - return nil, err - } - featureToggles := featuremgmt.ProvideToggles(featureManager) +func newUnifiedClient(cfg *setting.Cfg, sqlStore db.DB, featureToggles featuremgmt.FeatureToggles) (resource.ResourceClient, error) { return unified.ProvideUnifiedStorageClient(&unified.Options{ Cfg: cfg, Features: featureToggles, diff --git a/pkg/registry/apis/dashboard/legacy/migrate.go b/pkg/registry/apis/dashboard/legacy/migrate.go index 3da3a01aab8..73d01caaade 100644 --- a/pkg/registry/apis/dashboard/legacy/migrate.go +++ b/pkg/registry/apis/dashboard/legacy/migrate.go @@ -16,6 +16,7 @@ import ( "github.com/grafana/grafana/pkg/apimachinery/utils" "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/services/accesscontrol" + "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/librarypanels" "github.com/grafana/grafana/pkg/services/provisioning" "github.com/grafana/grafana/pkg/services/search/sort" @@ -51,9 +52,10 @@ func ProvideLegacyMigrator( provisioning provisioning.ProvisioningService, // only needed for dashboard settings libraryPanelSvc librarypanels.Service, accessControl accesscontrol.AccessControl, + features featuremgmt.FeatureToggles, ) LegacyMigrator { dbp := legacysql.NewDatabaseProvider(sql) - return NewDashboardAccess(dbp, authlib.OrgNamespaceFormatter, nil, provisioning, libraryPanelSvc, sort.ProvideService(), accessControl) + return NewDashboardAccess(dbp, authlib.OrgNamespaceFormatter, nil, provisioning, libraryPanelSvc, sort.ProvideService(), accessControl, features) } type BlobStoreInfo struct { @@ -309,11 +311,12 @@ func (a *dashboardSqlAccess) migrateDashboards(ctx context.Context, orgId int64, for _, row := range rows.rejected { id := row.Dash.Labels[utils.LabelKeyDeprecatedInternalID] a.log.Warn("rejected dashboard", + "namespace", opts.Namespace, "dashboard", row.Dash.Name, "uid", row.Dash.UID, "id", id, + "version", row.Dash.Generation, "stackId", opts.StackID, - "namespace", opts.Namespace, ) opts.Progress(-2, fmt.Sprintf("rejected: id:%s, uid:%s", id, row.Dash.Name)) } diff --git a/pkg/registry/apis/dashboard/legacy/query_dashboards.sql b/pkg/registry/apis/dashboard/legacy/query_dashboards.sql index 75439c60d03..e8503170a2c 100644 --- a/pkg/registry/apis/dashboard/legacy/query_dashboards.sql +++ b/pkg/registry/apis/dashboard/legacy/query_dashboards.sql @@ -2,6 +2,7 @@ SELECT dashboard.org_id, dashboard.id, dashboard.uid, + dashboard.title, dashboard.folder_uid, dashboard.deleted, plugin_id, diff --git a/pkg/registry/apis/dashboard/legacy/sql_dashboards.go b/pkg/registry/apis/dashboard/legacy/sql_dashboards.go index 0771f0fc3ee..f019a39a799 100644 --- a/pkg/registry/apis/dashboard/legacy/sql_dashboards.go +++ b/pkg/registry/apis/dashboard/legacy/sql_dashboards.go @@ -29,6 +29,7 @@ import ( "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" gapiutil "github.com/grafana/grafana/pkg/services/apiserver/utils" "github.com/grafana/grafana/pkg/services/dashboards" + "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/libraryelements" "github.com/grafana/grafana/pkg/services/librarypanels" "github.com/grafana/grafana/pkg/services/provisioning" @@ -62,6 +63,8 @@ type dashboardSqlAccess struct { namespacer request.NamespaceMapper provisioning provisioning.ProvisioningService + invalidDashboardParseFallbackEnabled bool + // Use for writing (not reading) dashStore dashboards.Store dashboardSearchClient legacysearcher.DashboardSearchClient @@ -82,17 +85,19 @@ func NewDashboardAccess(sql legacysql.LegacyDatabaseProvider, libraryPanelSvc librarypanels.Service, sorter sort.Service, accessControl accesscontrol.AccessControl, + features featuremgmt.FeatureToggles, ) DashboardAccess { dashboardSearchClient := legacysearcher.NewDashboardSearchClient(dashStore, sorter) return &dashboardSqlAccess{ - sql: sql, - namespacer: namespacer, - dashStore: dashStore, - provisioning: provisioning, - dashboardSearchClient: *dashboardSearchClient, - libraryPanelSvc: libraryPanelSvc, - accessControl: accessControl, - log: log.New("dashboard.legacysql"), + sql: sql, + namespacer: namespacer, + dashStore: dashStore, + provisioning: provisioning, + dashboardSearchClient: *dashboardSearchClient, + libraryPanelSvc: libraryPanelSvc, + accessControl: accessControl, + log: log.New("dashboard.legacysql"), + invalidDashboardParseFallbackEnabled: features.IsEnabled(context.Background(), featuremgmt.FlagScanRowInvalidDashboardParseFallbackEnabled), } } @@ -176,7 +181,7 @@ func (r *rowsWrapper) Next() bool { r.row, err = r.a.scanRow(r.rows, r.history) if err != nil { r.a.log.Error("error scanning dashboard", "error", err) - if len(r.rejected) > 0 || r.row == nil { + if len(r.rejected) > 100 || r.row == nil { r.err = fmt.Errorf("too many rejected rows (%d) %w", len(r.rejected), err) return false } @@ -228,6 +233,51 @@ func (r *rowsWrapper) Value() []byte { return b } +func generateFallbackDashboard(data []byte, title, uid string) ([]byte, error) { + generatedDashboard := map[string]interface{}{ + "editable": true, + "id": 1, + "panels": []map[string]interface{}{ + { + "description": "The JSON is invalid. You can import it again after fixing it.", + "gridPos": map[string]interface{}{"h": 8, "w": 24, "x": 0, "y": 0}, + "id": 1, + "options": map[string]interface{}{ + "code": map[string]interface{}{"language": "plaintext", "showLineNumbers": false, "showMiniMap": false}, + "content": string(data), + "mode": "code", + }, + "title": "Invalid dashboard", + "type": "text", + }, + }, + "schemaVersion": 41, + "title": title, + "uid": uid, + "version": 3, + } + return json.Marshal(generatedDashboard) +} + +func (a *dashboardSqlAccess) parseDashboard(dash *dashboardV1.Dashboard, data []byte, id int64, title string) error { + if err := dash.Spec.UnmarshalJSON(data); err != nil { + a.log.Warn("error unmarshalling dashboard spec. Generating fallback dashboard data", "error", err, "uid", dash.UID, "name", dash.Name) + dash.Spec = *dashboardV0.NewDashboardSpec() + + dashboardData, err := generateFallbackDashboard(data, title, string(dash.UID)) + if err != nil { + a.log.Warn("error generating fallback dashboard data", "error", err, "uid", dash.UID, "name", dash.Name) + return err + } + + if err = dash.Spec.UnmarshalJSON(dashboardData); err != nil { + a.log.Warn("error unmarshalling fallback dashboard data", "error", err, "uid", dash.UID, "name", dash.Name) + return err + } + } + return nil +} + func (a *dashboardSqlAccess) scanRow(rows *sql.Rows, history bool) (*dashboardRow, error) { dash := &dashboardV1.Dashboard{ TypeMeta: dashboardV1.DashboardResourceInfo.TypeMeta(), @@ -238,6 +288,7 @@ func (a *dashboardSqlAccess) scanRow(rows *sql.Rows, history bool) (*dashboardRo var dashboard_id int64 var orgId int64 var folder_uid sql.NullString + var title string var updated time.Time var updatedBy sql.NullString var updatedByID sql.NullInt64 @@ -257,7 +308,7 @@ func (a *dashboardSqlAccess) scanRow(rows *sql.Rows, history bool) (*dashboardRo var data []byte // the dashboard JSON var version int64 - err := rows.Scan(&orgId, &dashboard_id, &dash.Name, &folder_uid, + err := rows.Scan(&orgId, &dashboard_id, &dash.Name, &title, &folder_uid, &deleted, &plugin_id, &origin_name, &origin_path, &origin_hash, &origin_ts, &created, &createdBy, &createdByID, @@ -286,6 +337,7 @@ func (a *dashboardSqlAccess) scanRow(rows *sql.Rows, history bool) (*dashboardRo dash.SetCreationTimestamp(metav1.NewTime(created)) meta, err := utils.MetaAccessor(dash) if err != nil { + a.log.Debug("failed to get meta accessor for dashboard", "error", err, "uid", dash.UID, "name", dash.Name, "version", version) return nil, err } meta.SetUpdatedTimestamp(&updated) @@ -331,9 +383,14 @@ func (a *dashboardSqlAccess) scanRow(rows *sql.Rows, history bool) (*dashboardRo } if len(data) > 0 { - err = dash.Spec.UnmarshalJSON(data) - if err != nil { - return row, fmt.Errorf("JSON unmarshal error for: %s // %w", dash.Name, err) + if a.invalidDashboardParseFallbackEnabled { + if err := a.parseDashboard(dash, data, dashboard_id, title); err != nil { + return row, err + } + } else { + if err := dash.Spec.UnmarshalJSON(data); err != nil { + return row, fmt.Errorf("JSON unmarshal error for: %s // %w", dash.Name, err) + } } } // Ignore any saved values for id/version/uid diff --git a/pkg/registry/apis/dashboard/legacy/sql_dashboards_test.go b/pkg/registry/apis/dashboard/legacy/sql_dashboards_test.go index 41da615b6ff..da8d2bfe25d 100644 --- a/pkg/registry/apis/dashboard/legacy/sql_dashboards_test.go +++ b/pkg/registry/apis/dashboard/legacy/sql_dashboards_test.go @@ -32,13 +32,15 @@ func TestScanRow(t *testing.T) { provisioner := provisioning.NewProvisioningServiceMock(context.Background()) provisioner.GetDashboardProvisionerResolvedPathFunc = func(name string) string { return "provisioner" } store := &dashboardSqlAccess{ - namespacer: func(_ int64) string { return "default" }, - provisioning: provisioner, - log: log.New("test"), + namespacer: func(_ int64) string { return "default" }, + provisioning: provisioner, + log: log.New("test"), + invalidDashboardParseFallbackEnabled: false, } - columns := []string{"orgId", "dashboard_id", "name", "folder_uid", "deleted", "plugin_id", "origin_name", "origin_path", "origin_hash", "origin_ts", "created", "createdBy", "createdByID", "updated", "updatedBy", "updatedByID", "version", "message", "data", "api_version"} + columns := []string{"orgId", "dashboard_id", "name", "title", "folder_uid", "deleted", "plugin_id", "origin_name", "origin_path", "origin_hash", "origin_ts", "created", "createdBy", "createdByID", "updated", "updatedBy", "updatedByID", "version", "message", "data", "api_version"} id := int64(100) + uid := "someuid" title := "Test Dashboard" folderUID := "folder123" timestamp := time.Now() @@ -49,7 +51,7 @@ func TestScanRow(t *testing.T) { updatedUser := "updator" t.Run("Should scan a valid row correctly", func(t *testing.T) { - rows := sqlmock.NewRows(columns).AddRow(1, id, title, folderUID, nil, "", "", "", "", 0, timestamp, createdUser, 0, timestamp, updatedUser, 0, version, message, []byte(`{"key": "value"}`), "vXyz") + rows := sqlmock.NewRows(columns).AddRow(1, id, uid, title, folderUID, nil, "", "", "", "", 0, timestamp, createdUser, 0, timestamp, updatedUser, 0, version, message, []byte(`{"key": "value"}`), "vXyz") mock.ExpectQuery("SELECT *").WillReturnRows(rows) resultRows, err := mockDB.Query("SELECT *") require.NoError(t, err) @@ -59,7 +61,7 @@ func TestScanRow(t *testing.T) { row, err := store.scanRow(resultRows, false) require.NoError(t, err) require.NotNil(t, row) - require.Equal(t, "Test Dashboard", row.Dash.Name) + require.Equal(t, uid, row.Dash.Name) require.Equal(t, version, row.RV) // rv should be the dashboard version require.Equal(t, common.Unstructured{ Object: map[string]interface{}{"key": "value"}, @@ -80,7 +82,7 @@ func TestScanRow(t *testing.T) { }) t.Run("File provisioned dashboard should have annotations", func(t *testing.T) { - rows := sqlmock.NewRows(columns).AddRow(1, id, title, folderUID, nil, "", "provisioner", pathToFile, "hashing", 100000, timestamp, createdUser, 0, timestamp, updatedUser, 0, version, message, []byte(`{"key": "value"}`), "vXyz") + rows := sqlmock.NewRows(columns).AddRow(1, id, uid, title, folderUID, nil, "", "provisioner", pathToFile, "hashing", 100000, timestamp, createdUser, 0, timestamp, updatedUser, 0, version, message, []byte(`{"key": "value"}`), "vXyz") mock.ExpectQuery("SELECT *").WillReturnRows(rows) resultRows, err := mockDB.Query("SELECT *") require.NoError(t, err) @@ -108,7 +110,7 @@ func TestScanRow(t *testing.T) { }) t.Run("Plugin provisioned dashboard should have annotations", func(t *testing.T) { - rows := sqlmock.NewRows(columns).AddRow(1, id, title, folderUID, nil, "slo", "", "", "", 0, timestamp, createdUser, 0, timestamp, updatedUser, 0, version, message, []byte(`{"key": "value"}`), "vXyz") + rows := sqlmock.NewRows(columns).AddRow(1, id, uid, title, folderUID, nil, "slo", "", "", "", 0, timestamp, createdUser, 0, timestamp, updatedUser, 0, version, message, []byte(`{"key": "value"}`), "vXyz") mock.ExpectQuery("SELECT *").WillReturnRows(rows) resultRows, err := mockDB.Query("SELECT *") require.NoError(t, err) @@ -144,7 +146,7 @@ func TestScanRow(t *testing.T) { // In migration scenario, COALESCE functions return dashboard table values // when dashboard_version values are NULL, ensuring all dashboards are migrated rows := sqlmock.NewRows(columns).AddRow( - 1, id, title, folderUID, nil, "", // basic dashboard fields + 1, id, uid, title, folderUID, nil, "", // basic dashboard fields "", "", "", 0, // origin fields timestamp, createdUser, 0, // created fields // These represent COALESCED values from dashboard table (not version table) @@ -163,7 +165,8 @@ func TestScanRow(t *testing.T) { require.NotNil(t, row) // Verify migration scenario works correctly with fallback data - require.Equal(t, title, row.Dash.Name) + require.Equal(t, uid, row.Dash.Name) + require.Equal(t, "Migrated Dashboard", row.Dash.Spec.Object["title"]) require.Equal(t, migrationVersion, row.RV) // Should use COALESCEd dashboard table version require.Equal(t, common.Unstructured{ Object: map[string]interface{}{ @@ -187,6 +190,72 @@ func TestScanRow(t *testing.T) { require.Equal(t, folderUID, meta.GetFolder()) require.Equal(t, "dashboard.grafana.app/"+migrationAPIVersion, row.Dash.APIVersion) }) + + t.Run("should follow dashboard template when failing to unmarshal dashboard if feature flag X is enabled", func(t *testing.T) { + // row with bad data + badData := []byte(`{"rows":[{"panels":[{"targets":[{"refId":"A","target":"aliasSub(alias, '^(.{27}).+', '\1...')"}]}]}]}`) + rows := sqlmock.NewRows(columns).AddRow(1, id, uid, title, folderUID, nil, "", "", "", "", 0, timestamp, createdUser, 0, timestamp, updatedUser, 0, version, message, badData, "vXyz") + mock.ExpectQuery("SELECT *").WillReturnRows(rows) + resultRows, err := mockDB.Query("SELECT *") + require.NoError(t, err) + defer resultRows.Close() // nolint:errcheck + resultRows.Next() + + row, err := store.scanRow(resultRows, false) + require.Error(t, err, "JSON unmarshal error for: Test Dashboard // invalid character '1' in string escape code") + require.NotNil(t, row) + // correctly scans these + require.Equal(t, uid, row.Dash.Name) + require.Equal(t, version, row.RV) + require.Equal(t, "default", row.Dash.Namespace) + require.Equal(t, &continueToken{orgId: int64(1), id: id}, row.token) + + // failure case: does NOT parse the dashboard itself + require.Equal(t, common.Unstructured{ + Object: nil, + }, row.Dash.Spec) + + // store with feature flag enabled + store = &dashboardSqlAccess{ + namespacer: func(_ int64) string { return "default" }, + provisioning: provisioner, + log: log.New("test"), + invalidDashboardParseFallbackEnabled: true, + } + + row, err = store.scanRow(resultRows, false) + require.NoError(t, err) + require.NotNil(t, row) + require.Equal(t, uid, row.Dash.Name) + require.Equal(t, version, row.RV) + require.Equal(t, "default", row.Dash.Namespace) + require.Equal(t, &continueToken{orgId: int64(1), id: id}, row.token) + + // instead of failing, create dummy dashboard with broken json inlined in text panel + require.Equal(t, title, row.Dash.Spec.Object["title"]) + panels, exists := row.Dash.Spec.Object["panels"] + require.True(t, exists, "panels property should exist") + + panelsSlice, ok := panels.([]interface{}) + require.True(t, ok, "panels should be a slice") + require.Len(t, panelsSlice, 1, "panels should have exactly one element") + + panel, ok := panelsSlice[0].(map[string]interface{}) + require.True(t, ok, "panel should be a map") + + options, exists := panel["options"] + require.True(t, exists, "panel should have options property") + + optionsMap, ok := options.(map[string]interface{}) + require.True(t, ok, "options should be a map") + + content, exists := optionsMap["content"] + require.True(t, exists, "options should have content property") + + contentStr, ok := content.(string) + require.True(t, ok, "content should be a string") + require.Equal(t, string(badData), contentStr, "content should match bad json data") + }) } func TestBuildSaveDashboardCommand(t *testing.T) { diff --git a/pkg/registry/apis/dashboard/legacy/testdata/mysql--query_dashboards-dashboard.sql b/pkg/registry/apis/dashboard/legacy/testdata/mysql--query_dashboards-dashboard.sql index e3a945a53db..63f1231205e 100755 --- a/pkg/registry/apis/dashboard/legacy/testdata/mysql--query_dashboards-dashboard.sql +++ b/pkg/registry/apis/dashboard/legacy/testdata/mysql--query_dashboards-dashboard.sql @@ -2,6 +2,7 @@ SELECT dashboard.org_id, dashboard.id, dashboard.uid, + dashboard.title, dashboard.folder_uid, dashboard.deleted, plugin_id, diff --git a/pkg/registry/apis/dashboard/legacy/testdata/mysql--query_dashboards-dashboard_next_page.sql b/pkg/registry/apis/dashboard/legacy/testdata/mysql--query_dashboards-dashboard_next_page.sql index 862725168b0..0992bbd3ce2 100755 --- a/pkg/registry/apis/dashboard/legacy/testdata/mysql--query_dashboards-dashboard_next_page.sql +++ b/pkg/registry/apis/dashboard/legacy/testdata/mysql--query_dashboards-dashboard_next_page.sql @@ -2,6 +2,7 @@ SELECT dashboard.org_id, dashboard.id, dashboard.uid, + dashboard.title, dashboard.folder_uid, dashboard.deleted, plugin_id, diff --git a/pkg/registry/apis/dashboard/legacy/testdata/mysql--query_dashboards-export_with_history.sql b/pkg/registry/apis/dashboard/legacy/testdata/mysql--query_dashboards-export_with_history.sql index fd54e6dc949..1d57a86d450 100755 --- a/pkg/registry/apis/dashboard/legacy/testdata/mysql--query_dashboards-export_with_history.sql +++ b/pkg/registry/apis/dashboard/legacy/testdata/mysql--query_dashboards-export_with_history.sql @@ -2,6 +2,7 @@ SELECT dashboard.org_id, dashboard.id, dashboard.uid, + dashboard.title, dashboard.folder_uid, dashboard.deleted, plugin_id, diff --git a/pkg/registry/apis/dashboard/legacy/testdata/mysql--query_dashboards-folders.sql b/pkg/registry/apis/dashboard/legacy/testdata/mysql--query_dashboards-folders.sql index 5935eb3422c..38cf8f56bea 100755 --- a/pkg/registry/apis/dashboard/legacy/testdata/mysql--query_dashboards-folders.sql +++ b/pkg/registry/apis/dashboard/legacy/testdata/mysql--query_dashboards-folders.sql @@ -2,6 +2,7 @@ SELECT dashboard.org_id, dashboard.id, dashboard.uid, + dashboard.title, dashboard.folder_uid, dashboard.deleted, plugin_id, diff --git a/pkg/registry/apis/dashboard/legacy/testdata/mysql--query_dashboards-history_uid.sql b/pkg/registry/apis/dashboard/legacy/testdata/mysql--query_dashboards-history_uid.sql index ec0a40a5934..5c1a6974590 100755 --- a/pkg/registry/apis/dashboard/legacy/testdata/mysql--query_dashboards-history_uid.sql +++ b/pkg/registry/apis/dashboard/legacy/testdata/mysql--query_dashboards-history_uid.sql @@ -2,6 +2,7 @@ SELECT dashboard.org_id, dashboard.id, dashboard.uid, + dashboard.title, dashboard.folder_uid, dashboard.deleted, plugin_id, diff --git a/pkg/registry/apis/dashboard/legacy/testdata/mysql--query_dashboards-history_uid_at_version.sql b/pkg/registry/apis/dashboard/legacy/testdata/mysql--query_dashboards-history_uid_at_version.sql index e04b1ff7430..bf85c6ba3d5 100755 --- a/pkg/registry/apis/dashboard/legacy/testdata/mysql--query_dashboards-history_uid_at_version.sql +++ b/pkg/registry/apis/dashboard/legacy/testdata/mysql--query_dashboards-history_uid_at_version.sql @@ -2,6 +2,7 @@ SELECT dashboard.org_id, dashboard.id, dashboard.uid, + dashboard.title, dashboard.folder_uid, dashboard.deleted, plugin_id, diff --git a/pkg/registry/apis/dashboard/legacy/testdata/mysql--query_dashboards-history_uid_second_page.sql b/pkg/registry/apis/dashboard/legacy/testdata/mysql--query_dashboards-history_uid_second_page.sql index ec0a40a5934..5c1a6974590 100755 --- a/pkg/registry/apis/dashboard/legacy/testdata/mysql--query_dashboards-history_uid_second_page.sql +++ b/pkg/registry/apis/dashboard/legacy/testdata/mysql--query_dashboards-history_uid_second_page.sql @@ -2,6 +2,7 @@ SELECT dashboard.org_id, dashboard.id, dashboard.uid, + dashboard.title, dashboard.folder_uid, dashboard.deleted, plugin_id, diff --git a/pkg/registry/apis/dashboard/legacy/testdata/mysql--query_dashboards-migration_with_fallback.sql b/pkg/registry/apis/dashboard/legacy/testdata/mysql--query_dashboards-migration_with_fallback.sql index ba38c048136..8c3801ba771 100755 --- a/pkg/registry/apis/dashboard/legacy/testdata/mysql--query_dashboards-migration_with_fallback.sql +++ b/pkg/registry/apis/dashboard/legacy/testdata/mysql--query_dashboards-migration_with_fallback.sql @@ -2,6 +2,7 @@ SELECT dashboard.org_id, dashboard.id, dashboard.uid, + dashboard.title, dashboard.folder_uid, dashboard.deleted, plugin_id, diff --git a/pkg/registry/apis/dashboard/legacy/testdata/postgres--query_dashboards-dashboard.sql b/pkg/registry/apis/dashboard/legacy/testdata/postgres--query_dashboards-dashboard.sql index 552a486f8cd..c04123f90ab 100755 --- a/pkg/registry/apis/dashboard/legacy/testdata/postgres--query_dashboards-dashboard.sql +++ b/pkg/registry/apis/dashboard/legacy/testdata/postgres--query_dashboards-dashboard.sql @@ -2,6 +2,7 @@ SELECT dashboard.org_id, dashboard.id, dashboard.uid, + dashboard.title, dashboard.folder_uid, dashboard.deleted, plugin_id, diff --git a/pkg/registry/apis/dashboard/legacy/testdata/postgres--query_dashboards-dashboard_next_page.sql b/pkg/registry/apis/dashboard/legacy/testdata/postgres--query_dashboards-dashboard_next_page.sql index fb09d57d08d..5916a9b315f 100755 --- a/pkg/registry/apis/dashboard/legacy/testdata/postgres--query_dashboards-dashboard_next_page.sql +++ b/pkg/registry/apis/dashboard/legacy/testdata/postgres--query_dashboards-dashboard_next_page.sql @@ -2,6 +2,7 @@ SELECT dashboard.org_id, dashboard.id, dashboard.uid, + dashboard.title, dashboard.folder_uid, dashboard.deleted, plugin_id, diff --git a/pkg/registry/apis/dashboard/legacy/testdata/postgres--query_dashboards-export_with_history.sql b/pkg/registry/apis/dashboard/legacy/testdata/postgres--query_dashboards-export_with_history.sql index 4d0affb337f..5eec54770c7 100755 --- a/pkg/registry/apis/dashboard/legacy/testdata/postgres--query_dashboards-export_with_history.sql +++ b/pkg/registry/apis/dashboard/legacy/testdata/postgres--query_dashboards-export_with_history.sql @@ -2,6 +2,7 @@ SELECT dashboard.org_id, dashboard.id, dashboard.uid, + dashboard.title, dashboard.folder_uid, dashboard.deleted, plugin_id, diff --git a/pkg/registry/apis/dashboard/legacy/testdata/postgres--query_dashboards-folders.sql b/pkg/registry/apis/dashboard/legacy/testdata/postgres--query_dashboards-folders.sql index b994d617708..5c9cca1e30e 100755 --- a/pkg/registry/apis/dashboard/legacy/testdata/postgres--query_dashboards-folders.sql +++ b/pkg/registry/apis/dashboard/legacy/testdata/postgres--query_dashboards-folders.sql @@ -2,6 +2,7 @@ SELECT dashboard.org_id, dashboard.id, dashboard.uid, + dashboard.title, dashboard.folder_uid, dashboard.deleted, plugin_id, diff --git a/pkg/registry/apis/dashboard/legacy/testdata/postgres--query_dashboards-history_uid.sql b/pkg/registry/apis/dashboard/legacy/testdata/postgres--query_dashboards-history_uid.sql index 9bcbb168149..876fca02fe3 100755 --- a/pkg/registry/apis/dashboard/legacy/testdata/postgres--query_dashboards-history_uid.sql +++ b/pkg/registry/apis/dashboard/legacy/testdata/postgres--query_dashboards-history_uid.sql @@ -2,6 +2,7 @@ SELECT dashboard.org_id, dashboard.id, dashboard.uid, + dashboard.title, dashboard.folder_uid, dashboard.deleted, plugin_id, diff --git a/pkg/registry/apis/dashboard/legacy/testdata/postgres--query_dashboards-history_uid_at_version.sql b/pkg/registry/apis/dashboard/legacy/testdata/postgres--query_dashboards-history_uid_at_version.sql index 61311c5c99b..0fa23f2db08 100755 --- a/pkg/registry/apis/dashboard/legacy/testdata/postgres--query_dashboards-history_uid_at_version.sql +++ b/pkg/registry/apis/dashboard/legacy/testdata/postgres--query_dashboards-history_uid_at_version.sql @@ -2,6 +2,7 @@ SELECT dashboard.org_id, dashboard.id, dashboard.uid, + dashboard.title, dashboard.folder_uid, dashboard.deleted, plugin_id, diff --git a/pkg/registry/apis/dashboard/legacy/testdata/postgres--query_dashboards-history_uid_second_page.sql b/pkg/registry/apis/dashboard/legacy/testdata/postgres--query_dashboards-history_uid_second_page.sql index 9bcbb168149..876fca02fe3 100755 --- a/pkg/registry/apis/dashboard/legacy/testdata/postgres--query_dashboards-history_uid_second_page.sql +++ b/pkg/registry/apis/dashboard/legacy/testdata/postgres--query_dashboards-history_uid_second_page.sql @@ -2,6 +2,7 @@ SELECT dashboard.org_id, dashboard.id, dashboard.uid, + dashboard.title, dashboard.folder_uid, dashboard.deleted, plugin_id, diff --git a/pkg/registry/apis/dashboard/legacy/testdata/postgres--query_dashboards-migration_with_fallback.sql b/pkg/registry/apis/dashboard/legacy/testdata/postgres--query_dashboards-migration_with_fallback.sql index 364fbebf417..fee2c28d525 100755 --- a/pkg/registry/apis/dashboard/legacy/testdata/postgres--query_dashboards-migration_with_fallback.sql +++ b/pkg/registry/apis/dashboard/legacy/testdata/postgres--query_dashboards-migration_with_fallback.sql @@ -2,6 +2,7 @@ SELECT dashboard.org_id, dashboard.id, dashboard.uid, + dashboard.title, dashboard.folder_uid, dashboard.deleted, plugin_id, diff --git a/pkg/registry/apis/dashboard/legacy/testdata/sqlite--query_dashboards-dashboard.sql b/pkg/registry/apis/dashboard/legacy/testdata/sqlite--query_dashboards-dashboard.sql index 552a486f8cd..c04123f90ab 100755 --- a/pkg/registry/apis/dashboard/legacy/testdata/sqlite--query_dashboards-dashboard.sql +++ b/pkg/registry/apis/dashboard/legacy/testdata/sqlite--query_dashboards-dashboard.sql @@ -2,6 +2,7 @@ SELECT dashboard.org_id, dashboard.id, dashboard.uid, + dashboard.title, dashboard.folder_uid, dashboard.deleted, plugin_id, diff --git a/pkg/registry/apis/dashboard/legacy/testdata/sqlite--query_dashboards-dashboard_next_page.sql b/pkg/registry/apis/dashboard/legacy/testdata/sqlite--query_dashboards-dashboard_next_page.sql index fb09d57d08d..5916a9b315f 100755 --- a/pkg/registry/apis/dashboard/legacy/testdata/sqlite--query_dashboards-dashboard_next_page.sql +++ b/pkg/registry/apis/dashboard/legacy/testdata/sqlite--query_dashboards-dashboard_next_page.sql @@ -2,6 +2,7 @@ SELECT dashboard.org_id, dashboard.id, dashboard.uid, + dashboard.title, dashboard.folder_uid, dashboard.deleted, plugin_id, diff --git a/pkg/registry/apis/dashboard/legacy/testdata/sqlite--query_dashboards-export_with_history.sql b/pkg/registry/apis/dashboard/legacy/testdata/sqlite--query_dashboards-export_with_history.sql index 4d0affb337f..5eec54770c7 100755 --- a/pkg/registry/apis/dashboard/legacy/testdata/sqlite--query_dashboards-export_with_history.sql +++ b/pkg/registry/apis/dashboard/legacy/testdata/sqlite--query_dashboards-export_with_history.sql @@ -2,6 +2,7 @@ SELECT dashboard.org_id, dashboard.id, dashboard.uid, + dashboard.title, dashboard.folder_uid, dashboard.deleted, plugin_id, diff --git a/pkg/registry/apis/dashboard/legacy/testdata/sqlite--query_dashboards-folders.sql b/pkg/registry/apis/dashboard/legacy/testdata/sqlite--query_dashboards-folders.sql index b994d617708..5c9cca1e30e 100755 --- a/pkg/registry/apis/dashboard/legacy/testdata/sqlite--query_dashboards-folders.sql +++ b/pkg/registry/apis/dashboard/legacy/testdata/sqlite--query_dashboards-folders.sql @@ -2,6 +2,7 @@ SELECT dashboard.org_id, dashboard.id, dashboard.uid, + dashboard.title, dashboard.folder_uid, dashboard.deleted, plugin_id, diff --git a/pkg/registry/apis/dashboard/legacy/testdata/sqlite--query_dashboards-history_uid.sql b/pkg/registry/apis/dashboard/legacy/testdata/sqlite--query_dashboards-history_uid.sql index 9bcbb168149..876fca02fe3 100755 --- a/pkg/registry/apis/dashboard/legacy/testdata/sqlite--query_dashboards-history_uid.sql +++ b/pkg/registry/apis/dashboard/legacy/testdata/sqlite--query_dashboards-history_uid.sql @@ -2,6 +2,7 @@ SELECT dashboard.org_id, dashboard.id, dashboard.uid, + dashboard.title, dashboard.folder_uid, dashboard.deleted, plugin_id, diff --git a/pkg/registry/apis/dashboard/legacy/testdata/sqlite--query_dashboards-history_uid_at_version.sql b/pkg/registry/apis/dashboard/legacy/testdata/sqlite--query_dashboards-history_uid_at_version.sql index 61311c5c99b..0fa23f2db08 100755 --- a/pkg/registry/apis/dashboard/legacy/testdata/sqlite--query_dashboards-history_uid_at_version.sql +++ b/pkg/registry/apis/dashboard/legacy/testdata/sqlite--query_dashboards-history_uid_at_version.sql @@ -2,6 +2,7 @@ SELECT dashboard.org_id, dashboard.id, dashboard.uid, + dashboard.title, dashboard.folder_uid, dashboard.deleted, plugin_id, diff --git a/pkg/registry/apis/dashboard/legacy/testdata/sqlite--query_dashboards-history_uid_second_page.sql b/pkg/registry/apis/dashboard/legacy/testdata/sqlite--query_dashboards-history_uid_second_page.sql index 9bcbb168149..876fca02fe3 100755 --- a/pkg/registry/apis/dashboard/legacy/testdata/sqlite--query_dashboards-history_uid_second_page.sql +++ b/pkg/registry/apis/dashboard/legacy/testdata/sqlite--query_dashboards-history_uid_second_page.sql @@ -2,6 +2,7 @@ SELECT dashboard.org_id, dashboard.id, dashboard.uid, + dashboard.title, dashboard.folder_uid, dashboard.deleted, plugin_id, diff --git a/pkg/registry/apis/dashboard/legacy/testdata/sqlite--query_dashboards-migration_with_fallback.sql b/pkg/registry/apis/dashboard/legacy/testdata/sqlite--query_dashboards-migration_with_fallback.sql index 364fbebf417..fee2c28d525 100755 --- a/pkg/registry/apis/dashboard/legacy/testdata/sqlite--query_dashboards-migration_with_fallback.sql +++ b/pkg/registry/apis/dashboard/legacy/testdata/sqlite--query_dashboards-migration_with_fallback.sql @@ -2,6 +2,7 @@ SELECT dashboard.org_id, dashboard.id, dashboard.uid, + dashboard.title, dashboard.folder_uid, dashboard.deleted, plugin_id, diff --git a/pkg/registry/apis/dashboard/register.go b/pkg/registry/apis/dashboard/register.go index 19108edadf8..75e2ed5b518 100644 --- a/pkg/registry/apis/dashboard/register.go +++ b/pkg/registry/apis/dashboard/register.go @@ -143,7 +143,7 @@ func RegisterAPIService( folderClient: folderClient, legacy: &DashboardStorage{ - Access: legacy.NewDashboardAccess(dbp, namespacer, dashStore, provisioning, libraryPanelSvc, sorter, accessControl), + Access: legacy.NewDashboardAccess(dbp, namespacer, dashStore, provisioning, libraryPanelSvc, sorter, accessControl, features), DashboardService: dashboardService, }, reg: reg, diff --git a/pkg/server/wire_gen.go b/pkg/server/wire_gen.go index b398b85d5a9..aeff3aa1924 100644 --- a/pkg/server/wire_gen.go +++ b/pkg/server/wire_gen.go @@ -746,7 +746,7 @@ func Initialize(cfg *setting.Cfg, opts Options, apiOpts api.ServerOptions) (*Ser } userStorageAPIBuilder := userstorage.RegisterAPIService(featureToggles, apiserverService, registerer) factory := github.ProvideFactory() - legacyMigrator := legacy.ProvideLegacyMigrator(sqlStore, provisioningServiceImpl, libraryPanelService, accessControl) + legacyMigrator := legacy.ProvideLegacyMigrator(sqlStore, provisioningServiceImpl, libraryPanelService, accessControl, featureToggles) databaseDatabase := database5.ProvideDatabase(sqlStore, tracer) secureValueMetadataStorage, err := metadata.ProvideSecureValueMetadataStorage(databaseDatabase, tracer, registerer) if err != nil { @@ -1307,7 +1307,7 @@ func InitializeForTest(t sqlutil.ITestDB, testingT interface { } userStorageAPIBuilder := userstorage.RegisterAPIService(featureToggles, apiserverService, registerer) factory := github.ProvideFactory() - legacyMigrator := legacy.ProvideLegacyMigrator(sqlStore, provisioningServiceImpl, libraryPanelService, accessControl) + legacyMigrator := legacy.ProvideLegacyMigrator(sqlStore, provisioningServiceImpl, libraryPanelService, accessControl, featureToggles) databaseDatabase := database5.ProvideDatabase(sqlStore, tracer) secureValueMetadataStorage, err := metadata.ProvideSecureValueMetadataStorage(databaseDatabase, tracer, registerer) if err != nil { diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index 0ac3c4d9ce2..f50b7854ed6 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -501,6 +501,12 @@ var ( Stage: FeatureStageExperimental, Owner: grafanaAppPlatformSquad, }, + { + Name: "scanRowInvalidDashboardParseFallbackEnabled", + Description: "Enable fallback parsing behavior when scan row encounters invalid dashboard JSON", + Stage: FeatureStageExperimental, + Owner: grafanaSearchAndStorageSquad, + }, { Name: "datasourceQueryTypes", Description: "Show query type endpoints in datasource API servers (currently hardcoded for testdata, expressions, and prometheus)", diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index 7b5c27e5123..6c21f852785 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -65,6 +65,7 @@ kubernetesDashboards,experimental,@grafana/grafana-app-platform-squad,false,fals dashboardDisableSchemaValidationV1,experimental,@grafana/grafana-app-platform-squad,false,false,false dashboardDisableSchemaValidationV2,experimental,@grafana/grafana-app-platform-squad,false,false,false dashboardSchemaValidationLogging,experimental,@grafana/grafana-app-platform-squad,false,false,false +scanRowInvalidDashboardParseFallbackEnabled,experimental,@grafana/search-and-storage,false,false,false datasourceQueryTypes,experimental,@grafana/grafana-app-platform-squad,false,true,false queryService,experimental,@grafana/grafana-datasources-core-services,false,true,false queryServiceRewrite,experimental,@grafana/grafana-datasources-core-services,false,true,false diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go index b1cc2fb7f13..52552a0b8d9 100644 --- a/pkg/services/featuremgmt/toggles_gen.go +++ b/pkg/services/featuremgmt/toggles_gen.go @@ -271,6 +271,10 @@ const ( // Log schema validation errors so they can be analyzed later FlagDashboardSchemaValidationLogging = "dashboardSchemaValidationLogging" + // FlagScanRowInvalidDashboardParseFallbackEnabled + // Enable fallback parsing behavior when scan row encounters invalid dashboard JSON + FlagScanRowInvalidDashboardParseFallbackEnabled = "scanRowInvalidDashboardParseFallbackEnabled" + // FlagDatasourceQueryTypes // Show query type endpoints in datasource API servers (currently hardcoded for testdata, expressions, and prometheus) FlagDatasourceQueryTypes = "datasourceQueryTypes" diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json index 048f5512581..980f805d56d 100644 --- a/pkg/services/featuremgmt/toggles_gen.json +++ b/pkg/services/featuremgmt/toggles_gen.json @@ -2760,6 +2760,18 @@ "codeowner": "@grafana/identity-access-team" } }, + { + "metadata": { + "name": "scanRowInvalidDashboardParseFallbackEnabled", + "resourceVersion": "1753730899886", + "creationTimestamp": "2025-07-28T19:28:19Z" + }, + "spec": { + "description": "Enable fallback parsing behavior when scan row encounters invalid dashboard JSON", + "stage": "experimental", + "codeowner": "@grafana/search-and-storage" + } + }, { "metadata": { "name": "scopeApi", From c28b2215e06b37b6c468429617477af4da9a8c08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20=C5=A0tibran=C3=BD?= Date: Wed, 30 Jul 2025 16:34:15 +0200 Subject: [PATCH 010/114] Improve indexing observability (#108901) * Add details to tracing spans when creating index. * Log reason for building index. * Log reason for building index. * Remove initialization of labels to avoid unnecessary metrics. * Track succcessful, failed and skiped index builds. Track index build time for individual index, not all indexes. * Revert removal of labels initialization. --- .../unified/resource/bleve_index_metrics.go | 30 +++++++--- pkg/storage/unified/resource/bulk.go | 2 +- pkg/storage/unified/resource/search.go | 57 +++++++++++-------- pkg/storage/unified/resource/search_test.go | 10 ++-- pkg/storage/unified/search/bleve.go | 28 ++++++++- .../unified/search/bleve_search_test.go | 2 +- pkg/storage/unified/search/bleve_test.go | 28 ++++----- pkg/storage/unified/testing/benchmark.go | 2 +- pkg/storage/unified/testing/search_backend.go | 6 +- 9 files changed, 108 insertions(+), 57 deletions(-) diff --git a/pkg/storage/unified/resource/bleve_index_metrics.go b/pkg/storage/unified/resource/bleve_index_metrics.go index 73a18d4fabe..3f130eb0b1b 100644 --- a/pkg/storage/unified/resource/bleve_index_metrics.go +++ b/pkg/storage/unified/resource/bleve_index_metrics.go @@ -9,11 +9,14 @@ import ( ) type BleveIndexMetrics struct { - IndexLatency *prometheus.HistogramVec - IndexSize prometheus.Gauge - IndexedKinds *prometheus.GaugeVec - IndexCreationTime *prometheus.HistogramVec - OpenIndexes *prometheus.GaugeVec + IndexLatency *prometheus.HistogramVec + IndexSize prometheus.Gauge + IndexedKinds *prometheus.GaugeVec + IndexCreationTime *prometheus.HistogramVec + OpenIndexes *prometheus.GaugeVec + IndexBuilds *prometheus.CounterVec + IndexBuildFailures prometheus.Counter + IndexBuildSkipped prometheus.Counter } var IndexCreationBuckets = []float64{1, 5, 10, 25, 50, 75, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000} @@ -37,8 +40,8 @@ func ProvideIndexMetrics(reg prometheus.Registerer) *BleveIndexMetrics { Help: "Number of indexed documents by kind", }, []string{"kind"}), IndexCreationTime: promauto.With(reg).NewHistogramVec(prometheus.HistogramOpts{ - Name: "index_server_index_creation_time_seconds", - Help: "Time (in seconds) it takes until index is created", + Name: "index_server_index_build_time_seconds", + Help: "Time it takes to successfully build an index. Failed or skipped builds are not counted.", Buckets: IndexCreationBuckets, NativeHistogramBucketFactor: 1.1, // enable native histograms NativeHistogramMaxBucketNumber: 160, @@ -48,11 +51,22 @@ func ProvideIndexMetrics(reg prometheus.Registerer) *BleveIndexMetrics { Name: "index_server_open_indexes", Help: "Number of open indexes per storage type. An open index corresponds to single resource group.", }, []string{"index_storage"}), // index_storage is either "file" or "memory" + IndexBuilds: promauto.With(reg).NewCounterVec(prometheus.CounterOpts{ + Name: "index_server_index_build_total", + Help: "Number of times index build was attempted due to specific reason", + }, []string{"reason"}), + IndexBuildFailures: promauto.With(reg).NewCounter(prometheus.CounterOpts{ + Name: "index_server_index_build_failures_total", + Help: "Number of times index build failed", + }), + IndexBuildSkipped: promauto.With(reg).NewCounter(prometheus.CounterOpts{ + Name: "index_server_index_build_skipped_total", + Help: "Number of times index build has been skipped due to existing valid index being found on disk", + }), } // Initialize labels. m.OpenIndexes.WithLabelValues("file").Set(0) m.OpenIndexes.WithLabelValues("memory").Set(0) - return m } diff --git a/pkg/storage/unified/resource/bulk.go b/pkg/storage/unified/resource/bulk.go index f1dbb3131c2..c3f2e61e290 100644 --- a/pkg/storage/unified/resource/bulk.go +++ b/pkg/storage/unified/resource/bulk.go @@ -244,7 +244,7 @@ func (s *server) BulkProcess(stream resourcepb.BulkStore_BulkProcessServer) erro Namespace: summary.Namespace, Group: summary.Group, Resource: summary.Resource, - }, summary.Count, summary.ResourceVersion) + }, summary.Count, summary.ResourceVersion, "rebuildAfterBatchLoad") if err != nil { s.log.Warn("error building search index after batch load", "err", err) rsp.Error = &resourcepb.ErrorResult{ diff --git a/pkg/storage/unified/resource/search.go b/pkg/storage/unified/resource/search.go index bda857993ba..721f7105b30 100644 --- a/pkg/storage/unified/resource/search.go +++ b/pkg/storage/unified/resource/search.go @@ -90,7 +90,7 @@ type SearchBackend interface { // Depending on the size, the backend may choose different options (eg: memory vs disk). // The last known resource version can be used to detect that nothing has changed, and existing on-disk index can be reused. // The builder will write all documents before returning. - BuildIndex(ctx context.Context, key NamespacedResource, size int64, resourceVersion int64, nonStandardFields SearchableDocumentFields, builder func(index ResourceIndex) (int64, error)) (ResourceIndex, error) + BuildIndex(ctx context.Context, key NamespacedResource, size int64, resourceVersion int64, nonStandardFields SearchableDocumentFields, indexBuildReason string, builder func(index ResourceIndex) (int64, error)) (ResourceIndex, error) // TotalDocs returns the total number of documents across all indexes. TotalDocs() int64 @@ -196,7 +196,7 @@ func (s *searchSupport) ListManagedObjects(ctx context.Context, req *resourcepb. Namespace: req.Namespace, Group: info.Group, Resource: info.Resource, - }) + }, "listManagedObjects") if err != nil { rsp.Error = AsErrorResult(err) return rsp, nil @@ -237,7 +237,7 @@ func (s *searchSupport) CountManagedObjects(ctx context.Context, req *resourcepb Namespace: req.Namespace, Group: info.Group, Resource: info.Resource, - }) + }, "countManagedObjects") if err != nil { rsp.Error = AsErrorResult(err) return rsp, nil @@ -282,7 +282,7 @@ func (s *searchSupport) Search(ctx context.Context, req *resourcepb.ResourceSear Namespace: req.Options.Key.Namespace, Resource: req.Options.Key.Resource, } - idx, err := s.getOrCreateIndex(ctx, nsr) + idx, err := s.getOrCreateIndex(ctx, nsr, "search") if err != nil { return &resourcepb.ResourceSearchResponse{ Error: AsErrorResult(err), @@ -294,7 +294,7 @@ func (s *searchSupport) Search(ctx context.Context, req *resourcepb.ResourceSear for i, f := range req.Federated { nsr.Group = f.Group nsr.Resource = f.Resource - federate[i], err = s.getOrCreateIndex(ctx, nsr) + federate[i], err = s.getOrCreateIndex(ctx, nsr, "federatedSearch") if err != nil { return &resourcepb.ResourceSearchResponse{ Error: AsErrorResult(err), @@ -323,7 +323,7 @@ func (s *searchSupport) GetStats(ctx context.Context, req *resourcepb.ResourceSt Namespace: req.Namespace, Group: parts[0], Resource: parts[1], - }) + }, "getStats") if err != nil { rsp.Error = AsErrorResult(err) return rsp, nil @@ -367,7 +367,7 @@ func (s *searchSupport) GetStats(ctx context.Context, req *resourcepb.ResourceSt Namespace: req.Namespace, Group: stat.Group, Resource: stat.Resource, - }) + }, "getStats") if err != nil { rsp.Error = AsErrorResult(err) return rsp, nil @@ -449,8 +449,12 @@ func (s *searchSupport) buildIndexes(ctx context.Context, rebuild bool) (int, er return err } - s.log.Debug("building index", "namespace", info.Namespace, "group", info.Group, "resource", info.Resource) - _, _, err := s.build(ctx, info.NamespacedResource, info.Count, info.ResourceVersion) + s.log.Debug("building index", "namespace", info.Namespace, "group", info.Group, "resource", info.Resource, "rebuild", rebuild) + reason := "init" + if rebuild { + reason = "rebuild" + } + _, _, err := s.build(ctx, info.NamespacedResource, info.Count, info.ResourceVersion, reason) return err }) } @@ -504,9 +508,6 @@ func (s *searchSupport) init(ctx context.Context) error { end := time.Now().Unix() s.log.Info("search index initialized", "duration_secs", end-start, "total_docs", s.search.TotalDocs()) - if s.indexMetrics != nil { - s.indexMetrics.IndexCreationTime.WithLabelValues().Observe(float64(end - start)) - } return nil } @@ -537,7 +538,7 @@ func (s *searchSupport) dispatchEvent(ctx context.Context, evt *WrittenEvent) { Group: evt.Key.Group, Resource: evt.Key.Resource, } - index, err := s.getOrCreateIndex(ctx, nsr) + index, err := s.getOrCreateIndex(ctx, nsr, "dispatchEvent") if err != nil { s.log.Warn("error getting index for watch event", "error", err) span.RecordError(err) @@ -622,15 +623,10 @@ func (s *searchSupport) rebuildDashboardIndexes(ctx context.Context) error { "duration", duration, "rebuilt_indexes", totalBatchesIndexed, "total_docs", s.search.TotalDocs()) - - if s.indexMetrics != nil { - s.indexMetrics.IndexCreationTime.WithLabelValues().Observe(duration.Seconds()) - } - return nil } -func (s *searchSupport) getOrCreateIndex(ctx context.Context, key NamespacedResource) (ResourceIndex, error) { +func (s *searchSupport) getOrCreateIndex(ctx context.Context, key NamespacedResource, reason string) (ResourceIndex, error) { if s == nil || s.search == nil { return nil, fmt.Errorf("search is not configured properly (missing unifiedStorageSearch feature toggle?)") } @@ -672,7 +668,7 @@ func (s *searchSupport) getOrCreateIndex(ctx context.Context, key NamespacedReso } } - idx, _, err = s.build(ctx, key, size, rv) + idx, _, err = s.build(ctx, key, size, rv, reason) if err != nil { return nil, fmt.Errorf("error building search index, %w", err) } @@ -693,10 +689,18 @@ func (s *searchSupport) getOrCreateIndex(ctx context.Context, key NamespacedReso } } -func (s *searchSupport) build(ctx context.Context, nsr NamespacedResource, size int64, rv int64) (ResourceIndex, int64, error) { +func (s *searchSupport) build(ctx context.Context, nsr NamespacedResource, size int64, rv int64, indexBuildReason string) (ResourceIndex, int64, error) { ctx, span := s.tracer.Start(ctx, tracingPrexfixSearch+"Build") defer span.End() + span.SetAttributes( + attribute.String("namespace", nsr.Namespace), + attribute.String("group", nsr.Group), + attribute.String("resource", nsr.Resource), + attribute.Int64("size", size), + attribute.Int64("rv", rv), + ) + logger := s.log.With("namespace", nsr.Namespace, "group", nsr.Group, "resource", nsr.Resource) builder, err := s.builders.get(ctx, nsr) @@ -705,7 +709,10 @@ func (s *searchSupport) build(ctx context.Context, nsr NamespacedResource, size } fields := s.builders.GetFields(nsr) - index, err := s.search.BuildIndex(ctx, nsr, size, rv, fields, func(index ResourceIndex) (int64, error) { + index, err := s.search.BuildIndex(ctx, nsr, size, rv, fields, indexBuildReason, func(index ResourceIndex) (int64, error) { + span := trace.SpanFromContext(ctx) + span.AddEvent("building index", trace.WithAttributes(attribute.Int64("size", size), attribute.Int64("rv", rv), attribute.String("reason", indexBuildReason))) + rv, err = s.storage.ListIterator(ctx, &resourcepb.ListRequest{ Limit: 1000000000000, // big number Options: &resourcepb.ListOptions{ @@ -734,9 +741,11 @@ func (s *searchSupport) build(ctx context.Context, nsr NamespacedResource, size Name: iter.Name(), } + span.AddEvent("building document", trace.WithAttributes(attribute.String("name", iter.Name()))) // Convert it to an indexable document doc, err := builder.BuildDocument(ctx, key, iter.ResourceVersion(), iter.Value()) if err != nil { + span.RecordError(err) logger.Error("error building search document", "key", SearchID(key), "err", err) continue } @@ -749,6 +758,7 @@ func (s *searchSupport) build(ctx context.Context, nsr NamespacedResource, size // When we reach the batch size, perform bulk index and reset the batch. if len(items) >= maxBatchSize { + span.AddEvent("bulk indexing", trace.WithAttributes(attribute.Int("count", len(items)))) if err = index.BulkIndex(&BulkIndexRequest{ Items: items, }); err != nil { @@ -762,6 +772,7 @@ func (s *searchSupport) build(ctx context.Context, nsr NamespacedResource, size // Index any remaining items in the final batch. if len(items) > 0 { + span.AddEvent("bulk indexing", trace.WithAttributes(attribute.Int("count", len(items)))) if err = index.BulkIndex(&BulkIndexRequest{ Items: items, }); err != nil { @@ -799,7 +810,7 @@ func (s *searchSupport) buildEmptyIndex(ctx context.Context, nsr NamespacedResou s.log.Debug("Building empty index", "namespace", nsr.Namespace, "group", nsr.Group, "resource", nsr.Resource, "rv", rv) // Build an empty index by passing a builder function that doesn't add any documents - return s.search.BuildIndex(ctx, nsr, 0, rv, fields, func(index ResourceIndex) (int64, error) { + return s.search.BuildIndex(ctx, nsr, 0, rv, fields, "empty", func(index ResourceIndex) (int64, error) { // Return the resource version without adding any documents to the index return rv, nil }) diff --git a/pkg/storage/unified/resource/search_test.go b/pkg/storage/unified/resource/search_test.go index 07a9a464ae1..a05c74c8a4b 100644 --- a/pkg/storage/unified/resource/search_test.go +++ b/pkg/storage/unified/resource/search_test.go @@ -121,7 +121,7 @@ func (m *mockSearchBackend) GetIndex(ctx context.Context, key NamespacedResource return nil, nil } -func (m *mockSearchBackend) BuildIndex(ctx context.Context, key NamespacedResource, size int64, resourceVersion int64, fields SearchableDocumentFields, builder func(index ResourceIndex) (int64, error)) (ResourceIndex, error) { +func (m *mockSearchBackend) BuildIndex(ctx context.Context, key NamespacedResource, size int64, resourceVersion int64, fields SearchableDocumentFields, reason string, builder func(index ResourceIndex) (int64, error)) (ResourceIndex, error) { index := &MockResourceIndex{} index.On("BulkIndex", mock.Anything).Return(nil).Maybe() index.On("DocCount", mock.Anything, mock.Anything).Return(int64(0), nil).Maybe() @@ -317,7 +317,7 @@ func TestSearchGetOrCreateIndex(t *testing.T) { go func() { defer wg.Done() <-start - _, _ = support.getOrCreateIndex(context.Background(), NamespacedResource{Namespace: "ns", Group: "group", Resource: "resource"}) + _, _ = support.getOrCreateIndex(context.Background(), NamespacedResource{Namespace: "ns", Group: "group", Resource: "resource"}, "test") }() } @@ -365,7 +365,7 @@ func TestSearchGetOrCreateIndexWithCancellation(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 1*time.Millisecond) defer cancel() - _, err = support.getOrCreateIndex(ctx, NamespacedResource{Namespace: "ns", Group: "group", Resource: "resource"}) + _, err = support.getOrCreateIndex(ctx, NamespacedResource{Namespace: "ns", Group: "group", Resource: "resource"}, "test") // Make sure we get context deadline error require.ErrorIs(t, err, context.DeadlineExceeded) @@ -380,9 +380,9 @@ type slowSearchBackend struct { wg sync.WaitGroup } -func (m *slowSearchBackend) BuildIndex(ctx context.Context, key NamespacedResource, size int64, resourceVersion int64, fields SearchableDocumentFields, builder func(index ResourceIndex) (int64, error)) (ResourceIndex, error) { +func (m *slowSearchBackend) BuildIndex(ctx context.Context, key NamespacedResource, size int64, resourceVersion int64, fields SearchableDocumentFields, reason string, builder func(index ResourceIndex) (int64, error)) (ResourceIndex, error) { m.wg.Add(1) defer m.wg.Done() time.Sleep(1 * time.Second) - return m.mockSearchBackend.BuildIndex(ctx, key, size, resourceVersion, fields, builder) + return m.mockSearchBackend.BuildIndex(ctx, key, size, resourceVersion, fields, reason, builder) } diff --git a/pkg/storage/unified/search/bleve.go b/pkg/storage/unified/search/bleve.go index 141e02b4030..2626ab36f1d 100644 --- a/pkg/storage/unified/search/bleve.go +++ b/pkg/storage/unified/search/bleve.go @@ -23,6 +23,7 @@ import ( "github.com/blevesearch/bleve/v2/search/query" bleveSearch "github.com/blevesearch/bleve/v2/search/searcher" index "github.com/blevesearch/bleve_index_api" + "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" "k8s.io/apimachinery/pkg/selection" @@ -197,11 +198,21 @@ func (b *bleveBackend) BuildIndex( size int64, resourceVersion int64, fields resource.SearchableDocumentFields, + indexBuildReason string, builder func(index resource.ResourceIndex) (int64, error), ) (resource.ResourceIndex, error) { _, span := b.tracer.Start(ctx, tracingPrexfixBleve+"BuildIndex") defer span.End() + span.SetAttributes( + attribute.String("namespace", key.Namespace), + attribute.String("group", key.Group), + attribute.String("resource", key.Resource), + attribute.Int64("size", size), + attribute.Int64("rv", resourceVersion), + attribute.String("reason", indexBuildReason), + ) + mapper, err := GetBleveMappings(fields) if err != nil { return nil, err @@ -214,7 +225,7 @@ func (b *bleveBackend) BuildIndex( return nil, err } - logWithDetails := b.log.With("namespace", key.Namespace, "group", key.Group, "resource", key.Resource, "size", size, "rv", resourceVersion) + logWithDetails := b.log.With("namespace", key.Namespace, "group", key.Group, "resource", key.Resource, "size", size, "rv", resourceVersion, "reason", indexBuildReason) // Close the newly created/opened index by default. closeIndex := true @@ -306,14 +317,29 @@ func (b *bleveBackend) BuildIndex( } if build { + if b.indexMetrics != nil { + b.indexMetrics.IndexBuilds.WithLabelValues(indexBuildReason).Inc() + } + start := time.Now() _, err = builder(idx) if err != nil { logWithDetails.Error("Failed to build index", "err", err) + if b.indexMetrics != nil { + b.indexMetrics.IndexBuildFailures.Inc() + } return nil, fmt.Errorf("failed to build index: %w", err) } elapsed := time.Since(start) logWithDetails.Info("Finished building index", "elapsed", elapsed) + if b.indexMetrics != nil { + b.indexMetrics.IndexCreationTime.WithLabelValues().Observe(elapsed.Seconds()) + } + } else { + logWithDetails.Info("Skipping index build, using existing index") + if b.indexMetrics != nil { + b.indexMetrics.IndexBuildSkipped.Inc() + } } // Set expiration after building the index. Only expire in-memory indexes. diff --git a/pkg/storage/unified/search/bleve_search_test.go b/pkg/storage/unified/search/bleve_search_test.go index a795b4c0f8d..0680026e2af 100644 --- a/pkg/storage/unified/search/bleve_search_test.go +++ b/pkg/storage/unified/search/bleve_search_test.go @@ -553,7 +553,7 @@ func newTestDashboardsIndex(t TB, threshold int64, size int64, batchSize int64, Namespace: key.Namespace, Group: key.Group, Resource: key.Resource, - }, size, rv, info.Fields, writer) + }, size, rv, info.Fields, "test", writer) require.NoError(t, err) return index, tmpdir diff --git a/pkg/storage/unified/search/bleve_test.go b/pkg/storage/unified/search/bleve_test.go index 5a4c16be2b3..821ce6de0c0 100644 --- a/pkg/storage/unified/search/bleve_test.go +++ b/pkg/storage/unified/search/bleve_test.go @@ -71,7 +71,7 @@ func TestBleveBackend(t *testing.T) { Namespace: key.Namespace, Group: key.Group, Resource: key.Resource, - }, 2, rv, info.Fields, func(index resource.ResourceIndex) (int64, error) { + }, 2, rv, info.Fields, "test", func(index resource.ResourceIndex) (int64, error) { err := index.BulkIndex(&resource.BulkIndexRequest{ Items: []*resource.BulkIndexItem{ { @@ -352,7 +352,7 @@ func TestBleveBackend(t *testing.T) { Namespace: key.Namespace, Group: key.Group, Resource: key.Resource, - }, 2, rv, fields, func(index resource.ResourceIndex) (int64, error) { + }, 2, rv, fields, "test", func(index resource.ResourceIndex) (int64, error) { err := index.BulkIndex(&resource.BulkIndexRequest{ Items: []*resource.BulkIndexItem{ { @@ -766,7 +766,7 @@ func TestBleveInMemoryIndexExpiration(t *testing.T) { Resource: "resource", } - builtIndex, err := backend.BuildIndex(context.Background(), ns, 1 /* below FileThreshold */, 100, nil, indexTestDocs(ns, 1)) + builtIndex, err := backend.BuildIndex(context.Background(), ns, 1 /* below FileThreshold */, 100, nil, "test", indexTestDocs(ns, 1)) require.NoError(t, err) // Wait for index expiration, which is 1ns @@ -798,7 +798,7 @@ func TestBleveFileIndexExpiration(t *testing.T) { } // size=100 is above FileThreshold, this will be file-based index - builtIndex, err := backend.BuildIndex(context.Background(), ns, 100, 100, nil, indexTestDocs(ns, 1)) + builtIndex, err := backend.BuildIndex(context.Background(), ns, 100, 100, nil, "test", indexTestDocs(ns, 1)) require.NoError(t, err) // Wait for index expiration, which is 1ns @@ -830,7 +830,7 @@ func TestFileIndexIsReusedOnSameSizeAndRV(t *testing.T) { tmpDir := t.TempDir() backend1, reg1 := setupBleveBackend(t, 5, time.Nanosecond, tmpDir) - _, err := backend1.BuildIndex(context.Background(), ns, 10 /* file based */, 100, nil, indexTestDocs(ns, 10)) + _, err := backend1.BuildIndex(context.Background(), ns, 10 /* file based */, 100, nil, "test", indexTestDocs(ns, 10)) require.NoError(t, err) // Verify one open index. @@ -853,7 +853,7 @@ func TestFileIndexIsReusedOnSameSizeAndRV(t *testing.T) { // We open new backend using same directory, and run indexing with same size (10) and RV (100). This should reuse existing index, and skip indexing. backend2, reg2 := setupBleveBackend(t, 5, time.Nanosecond, tmpDir) - idx, err := backend2.BuildIndex(context.Background(), ns, 10 /* file based */, 100, nil, indexTestDocs(ns, 1000)) + idx, err := backend2.BuildIndex(context.Background(), ns, 10 /* file based */, 100, nil, "test", indexTestDocs(ns, 1000)) require.NoError(t, err) // Verify that we're reusing existing index and there is only 10 documents in it, not 1000. @@ -879,13 +879,13 @@ func TestFileIndexIsNotReusedOnDifferentSize(t *testing.T) { tmpDir := t.TempDir() backend1, _ := setupBleveBackend(t, 5, time.Nanosecond, tmpDir) - _, err := backend1.BuildIndex(context.Background(), ns, 10, 100, nil, indexTestDocs(ns, 10)) + _, err := backend1.BuildIndex(context.Background(), ns, 10, 100, nil, "test", indexTestDocs(ns, 10)) require.NoError(t, err) backend1.closeAllIndexes() // We open new backend using same directory, but with different size. Index should be rebuilt. backend2, _ := setupBleveBackend(t, 5, time.Nanosecond, tmpDir) - idx, err := backend2.BuildIndex(context.Background(), ns, 100, 100, nil, indexTestDocs(ns, 100)) + idx, err := backend2.BuildIndex(context.Background(), ns, 100, 100, nil, "test", indexTestDocs(ns, 100)) require.NoError(t, err) // Verify that index has updated number of documents. @@ -904,13 +904,13 @@ func TestFileIndexIsNotReusedOnDifferentRV(t *testing.T) { tmpDir := t.TempDir() backend1, _ := setupBleveBackend(t, 5, time.Nanosecond, tmpDir) - _, err := backend1.BuildIndex(context.Background(), ns, 10, 100, nil, indexTestDocs(ns, 10)) + _, err := backend1.BuildIndex(context.Background(), ns, 10, 100, nil, "test", indexTestDocs(ns, 10)) require.NoError(t, err) backend1.closeAllIndexes() // We open new backend using same directory, but with different RV. Index should be rebuilt. backend2, _ := setupBleveBackend(t, 5, time.Nanosecond, tmpDir) - idx, err := backend2.BuildIndex(context.Background(), ns, 10 /* file based */, 999999, nil, indexTestDocs(ns, 100)) + idx, err := backend2.BuildIndex(context.Background(), ns, 10 /* file based */, 999999, nil, "test", indexTestDocs(ns, 100)) require.NoError(t, err) // Verify that index has updated number of documents. @@ -942,7 +942,7 @@ func TestRebuildingIndexClosesPreviousCachedIndex(t *testing.T) { if testCase.firstInMemory { firstSize = 1 } - firstIndex, err := backend.BuildIndex(context.Background(), ns, int64(firstSize), 100, nil, indexTestDocs(ns, firstSize)) + firstIndex, err := backend.BuildIndex(context.Background(), ns, int64(firstSize), 100, nil, "test", indexTestDocs(ns, firstSize)) require.NoError(t, err) openInMemoryIndexes := 0 @@ -952,7 +952,7 @@ func TestRebuildingIndexClosesPreviousCachedIndex(t *testing.T) { secondSize = 1 openInMemoryIndexes = 1 } - secondIndex, err := backend.BuildIndex(context.Background(), ns, int64(secondSize), 100, nil, indexTestDocs(ns, secondSize)) + secondIndex, err := backend.BuildIndex(context.Background(), ns, int64(secondSize), 100, nil, "test", indexTestDocs(ns, secondSize)) require.NoError(t, err) // Verify that first and second index are different, and first one is now closed. @@ -1050,12 +1050,12 @@ func testBleveIndexWithFailures(t *testing.T, fileBased bool) { // size=100 is above FileThreshold (5), make it a file-based index. size = 100 } - _, err := backend.BuildIndex(context.Background(), ns, size, 100, nil, func(index resource.ResourceIndex) (int64, error) { + _, err := backend.BuildIndex(context.Background(), ns, size, 100, nil, "test", func(index resource.ResourceIndex) (int64, error) { return 0, fmt.Errorf("fail") }) require.Error(t, err) // Even though previous build of the index failed, new building of the index should work. - _, err = backend.BuildIndex(context.Background(), ns, size, 100, nil, indexTestDocs(ns, int(size))) + _, err = backend.BuildIndex(context.Background(), ns, size, 100, nil, "test", indexTestDocs(ns, int(size))) require.NoError(t, err) } diff --git a/pkg/storage/unified/testing/benchmark.go b/pkg/storage/unified/testing/benchmark.go index 8503b1dd2bd..dcdf113c212 100644 --- a/pkg/storage/unified/testing/benchmark.go +++ b/pkg/storage/unified/testing/benchmark.go @@ -215,7 +215,7 @@ func runSearchBackendBenchmarkWriteThroughput(ctx context.Context, backend resou // Build initial index size := int64(10000) // force the index to be on disk - index, err := backend.BuildIndex(ctx, nr, size, 0, nil, func(index resource.ResourceIndex) (int64, error) { + index, err := backend.BuildIndex(ctx, nr, size, 0, nil, "benchmark", func(index resource.ResourceIndex) (int64, error) { return 0, nil }) if err != nil { diff --git a/pkg/storage/unified/testing/search_backend.go b/pkg/storage/unified/testing/search_backend.go index 57d887682c6..52af572b01a 100644 --- a/pkg/storage/unified/testing/search_backend.go +++ b/pkg/storage/unified/testing/search_backend.go @@ -64,7 +64,7 @@ func runTestSearchBackendBuildIndex(t *testing.T, backend resource.SearchBackend require.Nil(t, index) // Build the index - index, err = backend.BuildIndex(ctx, ns, 0, 0, nil, func(index resource.ResourceIndex) (int64, error) { + index, err = backend.BuildIndex(ctx, ns, 0, 0, nil, "test", func(index resource.ResourceIndex) (int64, error) { // Write a test document err := index.BulkIndex(&resource.BulkIndexRequest{ Items: []*resource.BulkIndexItem{ @@ -111,7 +111,7 @@ func runTestResourceIndex(t *testing.T, backend resource.SearchBackend, nsPrefix } // Build initial index with some test documents - index, err := backend.BuildIndex(ctx, ns, 3, 0, nil, func(index resource.ResourceIndex) (int64, error) { + index, err := backend.BuildIndex(ctx, ns, 3, 0, nil, "test", func(index resource.ResourceIndex) (int64, error) { err := index.BulkIndex(&resource.BulkIndexRequest{ Items: []*resource.BulkIndexItem{ { @@ -235,7 +235,7 @@ func runTestResourceIndex(t *testing.T, backend resource.SearchBackend, nsPrefix t.Run("Search by LibraryPanel reference", func(t *testing.T) { // Build index with dashboards that have LibraryPanel references - index, err := backend.BuildIndex(ctx, ns, 3, 0, nil, func(index resource.ResourceIndex) (int64, error) { + index, err := backend.BuildIndex(ctx, ns, 3, 0, nil, "test", func(index resource.ResourceIndex) (int64, error) { err := index.BulkIndex(&resource.BulkIndexRequest{ Items: []*resource.BulkIndexItem{ { From 4fae330f9be2d4de1d0fbcc3fd7420c15baea0c6 Mon Sep 17 00:00:00 2001 From: Ashley Harrison Date: Wed, 30 Jul 2025 15:36:57 +0100 Subject: [PATCH 011/114] Chore: update nginx config to match hosted-grafana (#108898) update nginx config to match hosted-grafana --- devenv/frontend-service/nginx.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devenv/frontend-service/nginx.conf b/devenv/frontend-service/nginx.conf index 26d6c1f9968..1cf21295a36 100644 --- a/devenv/frontend-service/nginx.conf +++ b/devenv/frontend-service/nginx.conf @@ -60,7 +60,7 @@ server { # API calls go to the backend # Cheat with app plugin paths and route them to the backend. These should come from # the Plugin CDN - location ~ ^/(api|apis|bootdata|logout|public\/plugins\/grafana\-\w+\-app) { + location ~ ^/(api|apis|avatar|bootdata|render|logout|public\/plugins) { if ($cookie_fs_unavailable) { add_header Content-Type application/json always; return 503 '{"code":"Loading", "message": "Soon!"}'; From 9ff44d17f268f950a24f8e8be4214656975d7083 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 30 Jul 2025 15:52:56 +0100 Subject: [PATCH 012/114] Update dependency @floating-ui/react to v0.27.15 (#108922) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- packages/grafana-prometheus/package.json | 2 +- packages/grafana-ui/package.json | 2 +- yarn.lock | 44 ++++++++++++------------ 4 files changed, 25 insertions(+), 25 deletions(-) diff --git a/package.json b/package.json index 6abb79b38ea..ac882e05234 100644 --- a/package.json +++ b/package.json @@ -268,7 +268,7 @@ "@emotion/css": "11.13.5", "@emotion/react": "11.14.0", "@fingerprintjs/fingerprintjs": "^3.4.2", - "@floating-ui/react": "0.27.14", + "@floating-ui/react": "0.27.15", "@formatjs/intl-durationformat": "^0.7.0", "@glideapps/glide-data-grid": "^6.0.0", "@grafana/alerting": "workspace:*", diff --git a/packages/grafana-prometheus/package.json b/packages/grafana-prometheus/package.json index efd2b239574..0e59a3b7760 100644 --- a/packages/grafana-prometheus/package.json +++ b/packages/grafana-prometheus/package.json @@ -40,7 +40,7 @@ }, "dependencies": { "@emotion/css": "11.13.5", - "@floating-ui/react": "0.27.14", + "@floating-ui/react": "0.27.15", "@grafana/data": "12.2.0-pre", "@grafana/e2e-selectors": "12.2.0-pre", "@grafana/i18n": "12.2.0-pre", diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json index e1ef60e75b3..ff855abfffb 100644 --- a/packages/grafana-ui/package.json +++ b/packages/grafana-ui/package.json @@ -66,7 +66,7 @@ "@emotion/css": "11.13.5", "@emotion/react": "11.14.0", "@emotion/serialize": "1.3.3", - "@floating-ui/react": "0.27.14", + "@floating-ui/react": "0.27.15", "@grafana/data": "12.2.0-pre", "@grafana/e2e-selectors": "12.2.0-pre", "@grafana/faro-web-sdk": "^1.13.2", diff --git a/yarn.lock b/yarn.lock index c98bba2096b..6ad34a673f8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2362,48 +2362,48 @@ __metadata: languageName: node linkType: hard -"@floating-ui/core@npm:^1.7.2": - version: 1.7.2 - resolution: "@floating-ui/core@npm:1.7.2" +"@floating-ui/core@npm:^1.7.3": + version: 1.7.3 + resolution: "@floating-ui/core@npm:1.7.3" dependencies: "@floating-ui/utils": "npm:^0.2.10" - checksum: 10/2885a4c824f8d148222714cf2650c29bf3b30b70f465b990734766aafb1366ba23b4a285918c4f3ee67161096e7b9e4fbe0b3ca31e0f78800338cb4e6292912d + checksum: 10/a8952ff2673ddf28f12feeb86d90c54949e45bcb1af5758b7672850ac0dadb36d4bd61aa45dad1b6a35ba40d4756d3573afac6610b90502639d7266b91e0864e languageName: node linkType: hard -"@floating-ui/dom@npm:^1.0.1, @floating-ui/dom@npm:^1.7.2": - version: 1.7.2 - resolution: "@floating-ui/dom@npm:1.7.2" +"@floating-ui/dom@npm:^1.0.1, @floating-ui/dom@npm:^1.7.3": + version: 1.7.3 + resolution: "@floating-ui/dom@npm:1.7.3" dependencies: - "@floating-ui/core": "npm:^1.7.2" + "@floating-ui/core": "npm:^1.7.3" "@floating-ui/utils": "npm:^0.2.10" - checksum: 10/2d581459973b66024c47f039cabdc059b39c72abca93a4a0a8110e0a90b79d3fb84e49099afb73e13b3e7f17096070422220d996b206a5f0120c92bd7c48b98b + checksum: 10/0e91b67df31d30247a9516216b2d610a0b52f572c11d60a9875c68e5d65db0fe8819d096b823f123b45ed3ac7ef38d7462e268bdfc8b338c511c724acf098e78 languageName: node linkType: hard -"@floating-ui/react-dom@npm:^2.1.2, @floating-ui/react-dom@npm:^2.1.4": - version: 2.1.4 - resolution: "@floating-ui/react-dom@npm:2.1.4" +"@floating-ui/react-dom@npm:^2.1.2, @floating-ui/react-dom@npm:^2.1.5": + version: 2.1.5 + resolution: "@floating-ui/react-dom@npm:2.1.5" dependencies: - "@floating-ui/dom": "npm:^1.7.2" + "@floating-ui/dom": "npm:^1.7.3" peerDependencies: react: ">=16.8.0" react-dom: ">=16.8.0" - checksum: 10/8fad23bd7a94b50bbd715109a272528978511b793b8954098c8e99dd0b63557a8641b315ce09301ef014eada560fd8638e4dea4c21cf7ce8d93f0038a59cd442 + checksum: 10/45d7bf558cce2045f0a62454f7675e1d139e3e24fe5c14cf8e4f718c911cc378c687ad428c1c6f1b3a7925a3210f1a4a924980be29f90fbd4ee4d9a7d5bb2f7f languageName: node linkType: hard -"@floating-ui/react@npm:0.27.14": - version: 0.27.14 - resolution: "@floating-ui/react@npm:0.27.14" +"@floating-ui/react@npm:0.27.15": + version: 0.27.15 + resolution: "@floating-ui/react@npm:0.27.15" dependencies: - "@floating-ui/react-dom": "npm:^2.1.4" + "@floating-ui/react-dom": "npm:^2.1.5" "@floating-ui/utils": "npm:^0.2.10" tabbable: "npm:^6.0.0" peerDependencies: react: ">=17.0.0" react-dom: ">=17.0.0" - checksum: 10/5466938bdd0db8f7939a640da459bfe8417388d95b1547c69a236f87e4b218992f4a2d7cee14642a6b64c88fbb8df7fced46a01e41b7feb12785e4181498669b + checksum: 10/1b48c0956f04eb00e8d992ef27234fbf6c204ad5c266538109332a1b469ef1360b3bf98c6ebfb23b05aa29e8284a67bd01a26ba0484a9f34af4a087d67d65fcc languageName: node linkType: hard @@ -3491,7 +3491,7 @@ __metadata: resolution: "@grafana/prometheus@workspace:packages/grafana-prometheus" dependencies: "@emotion/css": "npm:11.13.5" - "@floating-ui/react": "npm:0.27.14" + "@floating-ui/react": "npm:0.27.15" "@grafana/data": "npm:12.2.0-pre" "@grafana/e2e-selectors": "npm:12.2.0-pre" "@grafana/i18n": "npm:12.2.0-pre" @@ -3740,7 +3740,7 @@ __metadata: "@emotion/react": "npm:11.14.0" "@emotion/serialize": "npm:1.3.3" "@faker-js/faker": "npm:^9.0.0" - "@floating-ui/react": "npm:0.27.14" + "@floating-ui/react": "npm:0.27.15" "@grafana/data": "npm:12.2.0-pre" "@grafana/e2e-selectors": "npm:12.2.0-pre" "@grafana/faro-web-sdk": "npm:^1.13.2" @@ -18162,7 +18162,7 @@ __metadata: "@emotion/eslint-plugin": "npm:11.12.0" "@emotion/react": "npm:11.14.0" "@fingerprintjs/fingerprintjs": "npm:^3.4.2" - "@floating-ui/react": "npm:0.27.14" + "@floating-ui/react": "npm:0.27.15" "@formatjs/intl-durationformat": "npm:^0.7.0" "@glideapps/glide-data-grid": "npm:^6.0.0" "@grafana/alerting": "workspace:*" From ae15ba562e71d52cfbd7879df9e43d044d580405 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 30 Jul 2025 16:47:15 +0100 Subject: [PATCH 013/114] Update dependency @types/node to v22.17.0 (#108936) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- packages/grafana-data/package.json | 2 +- packages/grafana-e2e-selectors/package.json | 2 +- packages/grafana-flamegraph/package.json | 2 +- .../grafana-o11y-ds-frontend/package.json | 2 +- packages/grafana-prometheus/package.json | 2 +- packages/grafana-sql/package.json | 2 +- packages/grafana-test-utils/package.json | 2 +- packages/grafana-ui/package.json | 2 +- .../datasource/azuremonitor/package.json | 2 +- .../datasource/cloud-monitoring/package.json | 2 +- .../package.json | 2 +- .../grafana-pyroscope-datasource/package.json | 2 +- .../grafana-testdata-datasource/package.json | 2 +- .../plugins/datasource/jaeger/package.json | 2 +- .../app/plugins/datasource/loki/package.json | 2 +- .../app/plugins/datasource/mssql/package.json | 2 +- .../app/plugins/datasource/mysql/package.json | 2 +- .../app/plugins/datasource/parca/package.json | 2 +- .../app/plugins/datasource/tempo/package.json | 2 +- .../plugins/datasource/zipkin/package.json | 2 +- yarn.lock | 50 +++++++++---------- 22 files changed, 46 insertions(+), 46 deletions(-) diff --git a/package.json b/package.json index ac882e05234..5d6fcd944e4 100644 --- a/package.json +++ b/package.json @@ -135,7 +135,7 @@ "@types/lodash": "4.17.20", "@types/logfmt": "^1.2.3", "@types/lucene": "^2", - "@types/node": "22.16.5", + "@types/node": "22.17.0", "@types/node-forge": "^1", "@types/ol-ext": "npm:@siedlerchr/types-ol-ext@3.3.0", "@types/pluralize": "^0.0.33", diff --git a/packages/grafana-data/package.json b/packages/grafana-data/package.json index 99cf6ee0f52..10a5d2e046f 100644 --- a/packages/grafana-data/package.json +++ b/packages/grafana-data/package.json @@ -88,7 +88,7 @@ "@rollup/plugin-node-resolve": "16.0.1", "@types/history": "4.7.11", "@types/lodash": "4.17.20", - "@types/node": "22.16.5", + "@types/node": "22.17.0", "@types/papaparse": "5.3.16", "@types/react": "18.3.18", "@types/react-dom": "18.3.5", diff --git a/packages/grafana-e2e-selectors/package.json b/packages/grafana-e2e-selectors/package.json index d647d0fb88d..2e5dd9f65a4 100644 --- a/packages/grafana-e2e-selectors/package.json +++ b/packages/grafana-e2e-selectors/package.json @@ -40,7 +40,7 @@ }, "devDependencies": { "@rollup/plugin-node-resolve": "16.0.1", - "@types/node": "22.16.5", + "@types/node": "22.17.0", "@types/semver": "7.7.0", "esbuild": "0.25.8", "rimraf": "6.0.1", diff --git a/packages/grafana-flamegraph/package.json b/packages/grafana-flamegraph/package.json index f8597f17c32..cfb6e77e230 100644 --- a/packages/grafana-flamegraph/package.json +++ b/packages/grafana-flamegraph/package.json @@ -68,7 +68,7 @@ "@types/d3": "^7", "@types/jest": "^29.5.4", "@types/lodash": "4.17.20", - "@types/node": "22.16.5", + "@types/node": "22.17.0", "@types/react": "18.3.18", "@types/react-virtualized-auto-sizer": "1.0.8", "@types/tinycolor2": "1.4.6", diff --git a/packages/grafana-o11y-ds-frontend/package.json b/packages/grafana-o11y-ds-frontend/package.json index 1d56eb47a0e..87b73a02a08 100644 --- a/packages/grafana-o11y-ds-frontend/package.json +++ b/packages/grafana-o11y-ds-frontend/package.json @@ -36,7 +36,7 @@ "@testing-library/react": "16.3.0", "@testing-library/user-event": "14.6.1", "@types/jest": "^29.5.4", - "@types/node": "22.16.5", + "@types/node": "22.17.0", "@types/react": "18.3.18", "@types/systemjs": "6.15.3", "jest": "^29.6.4", diff --git a/packages/grafana-prometheus/package.json b/packages/grafana-prometheus/package.json index 0e59a3b7760..b75ebc36f99 100644 --- a/packages/grafana-prometheus/package.json +++ b/packages/grafana-prometheus/package.json @@ -87,7 +87,7 @@ "@testing-library/react": "16.3.0", "@testing-library/user-event": "14.6.1", "@types/jest": "29.5.14", - "@types/node": "22.16.5", + "@types/node": "22.17.0", "@types/pluralize": "^0.0.33", "@types/prismjs": "1.26.5", "esbuild": "0.25.8", diff --git a/packages/grafana-sql/package.json b/packages/grafana-sql/package.json index 574d940a658..a3e9c784261 100644 --- a/packages/grafana-sql/package.json +++ b/packages/grafana-sql/package.json @@ -43,7 +43,7 @@ "@testing-library/user-event": "14.6.1", "@types/jest": "^29.5.4", "@types/lodash": "4.17.20", - "@types/node": "22.16.5", + "@types/node": "22.17.0", "@types/react": "18.3.18", "@types/react-dom": "18.3.5", "@types/react-virtualized-auto-sizer": "1.0.8", diff --git a/packages/grafana-test-utils/package.json b/packages/grafana-test-utils/package.json index 9a9f5998621..c20fad715d2 100644 --- a/packages/grafana-test-utils/package.json +++ b/packages/grafana-test-utils/package.json @@ -60,7 +60,7 @@ "@swc/core": "1.10.12", "@swc/jest": "^0.2.26", "@types/jest": "29.5.14", - "@types/node": "22.16.5", + "@types/node": "22.17.0", "jest": "29.7.0", "typescript": "5.8.3" } diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json index ff855abfffb..48bd1e11c66 100644 --- a/packages/grafana-ui/package.json +++ b/packages/grafana-ui/package.json @@ -166,7 +166,7 @@ "@types/is-hotkey": "0.1.10", "@types/jest": "29.5.14", "@types/mock-raf": "1.0.6", - "@types/node": "22.16.5", + "@types/node": "22.17.0", "@types/prismjs": "1.26.5", "@types/react": "18.3.18", "@types/react-color": "3.0.13", diff --git a/public/app/plugins/datasource/azuremonitor/package.json b/public/app/plugins/datasource/azuremonitor/package.json index e0828ae1816..a7b771654d3 100644 --- a/public/app/plugins/datasource/azuremonitor/package.json +++ b/public/app/plugins/datasource/azuremonitor/package.json @@ -34,7 +34,7 @@ "@testing-library/user-event": "14.6.1", "@types/jest": "29.5.14", "@types/lodash": "4.17.20", - "@types/node": "22.16.5", + "@types/node": "22.17.0", "@types/prismjs": "1.26.5", "@types/react": "18.3.18", "@types/react-dom": "18.3.5", diff --git a/public/app/plugins/datasource/cloud-monitoring/package.json b/public/app/plugins/datasource/cloud-monitoring/package.json index c27670248a3..6063cb8f0a1 100644 --- a/public/app/plugins/datasource/cloud-monitoring/package.json +++ b/public/app/plugins/datasource/cloud-monitoring/package.json @@ -35,7 +35,7 @@ "@types/debounce-promise": "3.1.9", "@types/jest": "29.5.14", "@types/lodash": "4.17.20", - "@types/node": "22.16.5", + "@types/node": "22.17.0", "@types/prismjs": "1.26.5", "@types/react": "18.3.18", "@types/react-dom": "18.3.5", diff --git a/public/app/plugins/datasource/grafana-postgresql-datasource/package.json b/public/app/plugins/datasource/grafana-postgresql-datasource/package.json index 07af56ca8d6..bb91cdc6cc6 100644 --- a/public/app/plugins/datasource/grafana-postgresql-datasource/package.json +++ b/public/app/plugins/datasource/grafana-postgresql-datasource/package.json @@ -23,7 +23,7 @@ "@testing-library/user-event": "14.6.1", "@types/jest": "29.5.14", "@types/lodash": "4.17.20", - "@types/node": "22.16.5", + "@types/node": "22.17.0", "@types/react": "18.3.18", "jest": "29.7.0", "ts-node": "10.9.2", diff --git a/public/app/plugins/datasource/grafana-pyroscope-datasource/package.json b/public/app/plugins/datasource/grafana-pyroscope-datasource/package.json index 595dbef2981..5ed1bd369a5 100644 --- a/public/app/plugins/datasource/grafana-pyroscope-datasource/package.json +++ b/public/app/plugins/datasource/grafana-pyroscope-datasource/package.json @@ -27,7 +27,7 @@ "@testing-library/user-event": "14.6.1", "@types/jest": "29.5.14", "@types/lodash": "4.17.20", - "@types/node": "22.16.5", + "@types/node": "22.17.0", "@types/prismjs": "1.26.5", "@types/react": "18.3.18", "@types/react-dom": "18.3.5", diff --git a/public/app/plugins/datasource/grafana-testdata-datasource/package.json b/public/app/plugins/datasource/grafana-testdata-datasource/package.json index 5118426ef3b..d8a2c573280 100644 --- a/public/app/plugins/datasource/grafana-testdata-datasource/package.json +++ b/public/app/plugins/datasource/grafana-testdata-datasource/package.json @@ -30,7 +30,7 @@ "@types/d3-random": "^3.0.2", "@types/jest": "29.5.14", "@types/lodash": "4.17.20", - "@types/node": "22.16.5", + "@types/node": "22.17.0", "@types/react": "18.3.18", "@types/react-dom": "18.3.5", "@types/uuid": "10.0.0", diff --git a/public/app/plugins/datasource/jaeger/package.json b/public/app/plugins/datasource/jaeger/package.json index 34998789a2f..b4c8fefdc08 100644 --- a/public/app/plugins/datasource/jaeger/package.json +++ b/public/app/plugins/datasource/jaeger/package.json @@ -31,7 +31,7 @@ "@types/jest": "29.5.14", "@types/lodash": "4.17.20", "@types/logfmt": "^1.2.3", - "@types/node": "22.16.5", + "@types/node": "22.17.0", "@types/react": "18.3.18", "@types/react-dom": "18.3.5", "@types/react-window": "1.8.8", diff --git a/public/app/plugins/datasource/loki/package.json b/public/app/plugins/datasource/loki/package.json index 096eedc45f7..69bf66c12a9 100644 --- a/public/app/plugins/datasource/loki/package.json +++ b/public/app/plugins/datasource/loki/package.json @@ -33,7 +33,7 @@ "@types/d3-random": "^3.0.2", "@types/jest": "29.5.14", "@types/lodash": "4.17.20", - "@types/node": "22.16.5", + "@types/node": "22.17.0", "@types/react": "18.3.18", "@types/react-dom": "18.3.5", "@types/uuid": "10.0.0", diff --git a/public/app/plugins/datasource/mssql/package.json b/public/app/plugins/datasource/mssql/package.json index f6701f152b0..46084541319 100644 --- a/public/app/plugins/datasource/mssql/package.json +++ b/public/app/plugins/datasource/mssql/package.json @@ -24,7 +24,7 @@ "@testing-library/user-event": "14.6.1", "@types/jest": "29.5.14", "@types/lodash": "4.17.20", - "@types/node": "22.16.5", + "@types/node": "22.17.0", "@types/react": "18.3.18", "i18next-parser": "9.3.0", "ts-node": "10.9.2", diff --git a/public/app/plugins/datasource/mysql/package.json b/public/app/plugins/datasource/mysql/package.json index a54da9e6b23..cb07deb595a 100644 --- a/public/app/plugins/datasource/mysql/package.json +++ b/public/app/plugins/datasource/mysql/package.json @@ -23,7 +23,7 @@ "@testing-library/user-event": "14.6.1", "@types/jest": "29.5.14", "@types/lodash": "4.17.20", - "@types/node": "22.16.5", + "@types/node": "22.17.0", "@types/react": "18.3.18", "jest": "29.7.0", "ts-node": "10.9.2", diff --git a/public/app/plugins/datasource/parca/package.json b/public/app/plugins/datasource/parca/package.json index d411208a322..999694c2dac 100644 --- a/public/app/plugins/datasource/parca/package.json +++ b/public/app/plugins/datasource/parca/package.json @@ -23,7 +23,7 @@ "@testing-library/react": "16.3.0", "@testing-library/user-event": "14.6.1", "@types/lodash": "4.17.20", - "@types/node": "22.16.5", + "@types/node": "22.17.0", "@types/react": "18.3.18", "@types/react-dom": "18.3.5", "jest": "29.7.0", diff --git a/public/app/plugins/datasource/tempo/package.json b/public/app/plugins/datasource/tempo/package.json index 115ba26cad4..e889d8c30df 100644 --- a/public/app/plugins/datasource/tempo/package.json +++ b/public/app/plugins/datasource/tempo/package.json @@ -45,7 +45,7 @@ "@testing-library/user-event": "14.6.1", "@types/jest": "29.5.14", "@types/lodash": "4.17.20", - "@types/node": "22.16.5", + "@types/node": "22.17.0", "@types/prismjs": "1.26.5", "@types/react": "18.3.18", "@types/react-dom": "18.3.5", diff --git a/public/app/plugins/datasource/zipkin/package.json b/public/app/plugins/datasource/zipkin/package.json index 126bcc8d923..4e1a7530854 100644 --- a/public/app/plugins/datasource/zipkin/package.json +++ b/public/app/plugins/datasource/zipkin/package.json @@ -26,7 +26,7 @@ "@testing-library/react": "16.3.0", "@types/jest": "29.5.14", "@types/lodash": "4.17.20", - "@types/node": "22.16.5", + "@types/node": "22.17.0", "@types/react": "18.3.18", "@types/react-dom": "18.3.5", "jest": "29.7.0", diff --git a/yarn.lock b/yarn.lock index 6ad34a673f8..a4a40fb40e5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2562,7 +2562,7 @@ __metadata: "@testing-library/user-event": "npm:14.6.1" "@types/jest": "npm:29.5.14" "@types/lodash": "npm:4.17.20" - "@types/node": "npm:22.16.5" + "@types/node": "npm:22.17.0" "@types/prismjs": "npm:1.26.5" "@types/react": "npm:18.3.18" "@types/react-dom": "npm:18.3.5" @@ -2606,7 +2606,7 @@ __metadata: "@testing-library/user-event": "npm:14.6.1" "@types/jest": "npm:29.5.14" "@types/lodash": "npm:4.17.20" - "@types/node": "npm:22.16.5" + "@types/node": "npm:22.17.0" "@types/react": "npm:18.3.18" jest: "npm:29.7.0" lodash: "npm:4.17.21" @@ -2637,7 +2637,7 @@ __metadata: "@testing-library/user-event": "npm:14.6.1" "@types/jest": "npm:29.5.14" "@types/lodash": "npm:4.17.20" - "@types/node": "npm:22.16.5" + "@types/node": "npm:22.17.0" "@types/prismjs": "npm:1.26.5" "@types/react": "npm:18.3.18" "@types/react-dom": "npm:18.3.5" @@ -2679,7 +2679,7 @@ __metadata: "@types/d3-random": "npm:^3.0.2" "@types/jest": "npm:29.5.14" "@types/lodash": "npm:4.17.20" - "@types/node": "npm:22.16.5" + "@types/node": "npm:22.17.0" "@types/react": "npm:18.3.18" "@types/react-dom": "npm:18.3.5" "@types/uuid": "npm:10.0.0" @@ -2721,7 +2721,7 @@ __metadata: "@types/jest": "npm:29.5.14" "@types/lodash": "npm:4.17.20" "@types/logfmt": "npm:^1.2.3" - "@types/node": "npm:22.16.5" + "@types/node": "npm:22.17.0" "@types/react": "npm:18.3.18" "@types/react-dom": "npm:18.3.5" "@types/react-window": "npm:1.8.8" @@ -2766,7 +2766,7 @@ __metadata: "@types/d3-random": "npm:^3.0.2" "@types/jest": "npm:29.5.14" "@types/lodash": "npm:4.17.20" - "@types/node": "npm:22.16.5" + "@types/node": "npm:22.17.0" "@types/react": "npm:18.3.18" "@types/react-dom": "npm:18.3.5" "@types/uuid": "npm:10.0.0" @@ -2806,7 +2806,7 @@ __metadata: "@testing-library/user-event": "npm:14.6.1" "@types/jest": "npm:29.5.14" "@types/lodash": "npm:4.17.20" - "@types/node": "npm:22.16.5" + "@types/node": "npm:22.17.0" "@types/react": "npm:18.3.18" i18next-parser: "npm:9.3.0" lodash: "npm:4.17.21" @@ -2838,7 +2838,7 @@ __metadata: "@testing-library/user-event": "npm:14.6.1" "@types/jest": "npm:29.5.14" "@types/lodash": "npm:4.17.20" - "@types/node": "npm:22.16.5" + "@types/node": "npm:22.17.0" "@types/react": "npm:18.3.18" jest: "npm:29.7.0" lodash: "npm:4.17.21" @@ -2867,7 +2867,7 @@ __metadata: "@testing-library/react": "npm:16.3.0" "@testing-library/user-event": "npm:14.6.1" "@types/lodash": "npm:4.17.20" - "@types/node": "npm:22.16.5" + "@types/node": "npm:22.17.0" "@types/react": "npm:18.3.18" "@types/react-dom": "npm:18.3.5" jest: "npm:29.7.0" @@ -2906,7 +2906,7 @@ __metadata: "@types/debounce-promise": "npm:3.1.9" "@types/jest": "npm:29.5.14" "@types/lodash": "npm:4.17.20" - "@types/node": "npm:22.16.5" + "@types/node": "npm:22.17.0" "@types/prismjs": "npm:1.26.5" "@types/react": "npm:18.3.18" "@types/react-dom": "npm:18.3.5" @@ -2959,7 +2959,7 @@ __metadata: "@testing-library/user-event": "npm:14.6.1" "@types/jest": "npm:29.5.14" "@types/lodash": "npm:4.17.20" - "@types/node": "npm:22.16.5" + "@types/node": "npm:22.17.0" "@types/prismjs": "npm:1.26.5" "@types/react": "npm:18.3.18" "@types/react-dom": "npm:18.3.5" @@ -3010,7 +3010,7 @@ __metadata: "@testing-library/react": "npm:16.3.0" "@types/jest": "npm:29.5.14" "@types/lodash": "npm:4.17.20" - "@types/node": "npm:22.16.5" + "@types/node": "npm:22.17.0" "@types/react": "npm:18.3.18" "@types/react-dom": "npm:18.3.5" jest: "npm:29.7.0" @@ -3121,7 +3121,7 @@ __metadata: "@types/d3-interpolate": "npm:^3.0.0" "@types/history": "npm:4.7.11" "@types/lodash": "npm:4.17.20" - "@types/node": "npm:22.16.5" + "@types/node": "npm:22.17.0" "@types/papaparse": "npm:5.3.16" "@types/react": "npm:18.3.18" "@types/react-dom": "npm:18.3.5" @@ -3168,7 +3168,7 @@ __metadata: dependencies: "@grafana/tsconfig": "npm:^2.0.0" "@rollup/plugin-node-resolve": "npm:16.0.1" - "@types/node": "npm:22.16.5" + "@types/node": "npm:22.17.0" "@types/semver": "npm:7.7.0" esbuild: "npm:0.25.8" rimraf: "npm:6.0.1" @@ -3275,7 +3275,7 @@ __metadata: "@types/d3": "npm:^7" "@types/jest": "npm:^29.5.4" "@types/lodash": "npm:4.17.20" - "@types/node": "npm:22.16.5" + "@types/node": "npm:22.17.0" "@types/react": "npm:18.3.18" "@types/react-virtualized-auto-sizer": "npm:1.0.8" "@types/tinycolor2": "npm:1.4.6" @@ -3397,7 +3397,7 @@ __metadata: "@testing-library/react": "npm:16.3.0" "@testing-library/user-event": "npm:14.6.1" "@types/jest": "npm:^29.5.4" - "@types/node": "npm:22.16.5" + "@types/node": "npm:22.17.0" "@types/react": "npm:18.3.18" "@types/systemjs": "npm:6.15.3" jest: "npm:^29.6.4" @@ -3517,7 +3517,7 @@ __metadata: "@types/debounce-promise": "npm:3.1.9" "@types/jest": "npm:29.5.14" "@types/lodash": "npm:4.17.20" - "@types/node": "npm:22.16.5" + "@types/node": "npm:22.17.0" "@types/pluralize": "npm:^0.0.33" "@types/prismjs": "npm:1.26.5" "@types/react": "npm:18.3.18" @@ -3682,7 +3682,7 @@ __metadata: "@testing-library/user-event": "npm:14.6.1" "@types/jest": "npm:^29.5.4" "@types/lodash": "npm:4.17.20" - "@types/node": "npm:22.16.5" + "@types/node": "npm:22.17.0" "@types/react": "npm:18.3.18" "@types/react-dom": "npm:18.3.5" "@types/react-virtualized-auto-sizer": "npm:1.0.8" @@ -3717,7 +3717,7 @@ __metadata: "@swc/core": "npm:1.10.12" "@swc/jest": "npm:^0.2.26" "@types/jest": "npm:29.5.14" - "@types/node": "npm:22.16.5" + "@types/node": "npm:22.17.0" jest: "npm:29.7.0" msw: "npm:2.10.4" typescript: "npm:5.8.3" @@ -3786,7 +3786,7 @@ __metadata: "@types/jquery": "npm:3.5.32" "@types/lodash": "npm:4.17.20" "@types/mock-raf": "npm:1.0.6" - "@types/node": "npm:22.16.5" + "@types/node": "npm:22.17.0" "@types/prismjs": "npm:1.26.5" "@types/react": "npm:18.3.18" "@types/react-color": "npm:3.0.13" @@ -9817,12 +9817,12 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:*, @types/node@npm:22.16.5, @types/node@npm:>=10.0.0, @types/node@npm:>=13.7.0, @types/node@npm:>=13.7.4": - version: 22.16.5 - resolution: "@types/node@npm:22.16.5" +"@types/node@npm:*, @types/node@npm:22.17.0, @types/node@npm:>=10.0.0, @types/node@npm:>=13.7.0, @types/node@npm:>=13.7.4": + version: 22.17.0 + resolution: "@types/node@npm:22.17.0" dependencies: undici-types: "npm:~6.21.0" - checksum: 10/ba45b5c9113cbc5edb12960fcfe7e80db2c998af5c1931264240695b27d756570d92462150b95781bd67a03aa82111cc970ab0f4504eb99213edff8bf425354e + checksum: 10/f77b0e1c3c00e438b56c726d6b1170d4969c600cc8d4ecf2c2aa7692243a8ff455a3d530760da95e0b6aab059c4605a384b43d18f96646c745ff133c00b84875 languageName: node linkType: hard @@ -18257,7 +18257,7 @@ __metadata: "@types/lodash": "npm:4.17.20" "@types/logfmt": "npm:^1.2.3" "@types/lucene": "npm:^2" - "@types/node": "npm:22.16.5" + "@types/node": "npm:22.17.0" "@types/node-forge": "npm:^1" "@types/ol-ext": "npm:@siedlerchr/types-ol-ext@3.3.0" "@types/pluralize": "npm:^0.0.33" From af6ac6f1d61307f5de5728fc8822217f6ddd8edd Mon Sep 17 00:00:00 2001 From: Nathan Marrs Date: Wed, 30 Jul 2025 08:59:39 -0700 Subject: [PATCH 014/114] Sharing: Export dashboard as image match user's window width (#108827) --- .../dashboard-export-image.spec.ts | 52 +++++++++++++++++++ .../sharing/ExportButton/ExportMenu.tsx | 1 + .../sharing/ExportButton/utils.test.ts | 50 +++++++++++++++++- .../sharing/ExportButton/utils.ts | 4 +- 4 files changed, 104 insertions(+), 3 deletions(-) create mode 100644 e2e-playwright/dashboards-suite/dashboard-export-image.spec.ts diff --git a/e2e-playwright/dashboards-suite/dashboard-export-image.spec.ts b/e2e-playwright/dashboards-suite/dashboard-export-image.spec.ts new file mode 100644 index 00000000000..5a72d94f9d6 --- /dev/null +++ b/e2e-playwright/dashboards-suite/dashboard-export-image.spec.ts @@ -0,0 +1,52 @@ +import { test, expect } from '@grafana/plugin-e2e'; + +const DASHBOARD_UID = 'ZqZnVvFZz'; + +test.use({ + featureToggles: { + scenes: true, + newDashboardSharingComponent: true, + sharingDashboardImage: true, // Enable the export image feature + }, +}); + +test.describe( + 'Export as Image', + { + tag: ['@dashboards'], + }, + () => { + test('Show renderer not available message when plugin not installed', async ({ + gotoDashboardPage, + page, + selectors, + }) => { + // Navigate to a dashboard + const dashboardPage = await gotoDashboardPage({ + uid: DASHBOARD_UID, + }); + + // Open the export dropdown + await dashboardPage.getByGrafanaSelector(selectors.pages.Dashboard.DashNav.NewExportButton.arrowMenu).click(); + + // Click export as image option + await dashboardPage + .getByGrafanaSelector(selectors.pages.Dashboard.DashNav.NewExportButton.Menu.exportAsImage) + .click(); + + // Verify we're on the export image view + await expect(page).toHaveURL(/.*shareView=image/); + + // Verify the "renderer not available" alert is displayed + const rendererAlert = page.getByRole('status'); + await expect(rendererAlert).toBeVisible(); + await expect(rendererAlert).toContainText(/Image renderer plugin not installed/i); + await expect(rendererAlert).toContainText( + /To render an image, you must install the Grafana image renderer plugin/i + ); + + // Verify the generate button is NOT present when renderer is unavailable + await expect(page.getByRole('button', { name: /Generate image/i })).not.toBeVisible(); + }); + } +); diff --git a/public/app/features/dashboard-scene/sharing/ExportButton/ExportMenu.tsx b/public/app/features/dashboard-scene/sharing/ExportButton/ExportMenu.tsx index d5524108b07..c0099c17f2f 100644 --- a/public/app/features/dashboard-scene/sharing/ExportButton/ExportMenu.tsx +++ b/public/app/features/dashboard-scene/sharing/ExportButton/ExportMenu.tsx @@ -52,6 +52,7 @@ export default function ExportMenu({ dashboard }: { dashboard: DashboardScene }) menuItems.push({ shareId: shareDashboardType.image, + testId: newExportButtonSelector.exportAsImage, icon: 'camera', label: t('share-dashboard.menu.export-image-title', 'Export as image'), renderCondition: Boolean(config.featureToggles.sharingDashboardImage), diff --git a/public/app/features/dashboard-scene/sharing/ExportButton/utils.test.ts b/public/app/features/dashboard-scene/sharing/ExportButton/utils.test.ts index 1c4ad371d0a..fa13471055a 100644 --- a/public/app/features/dashboard-scene/sharing/ExportButton/utils.test.ts +++ b/public/app/features/dashboard-scene/sharing/ExportButton/utils.test.ts @@ -96,6 +96,13 @@ describe('Dashboard Export Image Utils', () => { const fetchMock = jest.fn().mockReturnValue(of({ ok: true, data: mockBlob })); (getBackendSrv as jest.Mock).mockReturnValue({ fetch: fetchMock }); + // Mock window.innerWidth + Object.defineProperty(window, 'innerWidth', { + writable: true, + configurable: true, + value: 1280, + }); + const dashboard = { state: { uid: 'test-uid', @@ -119,7 +126,7 @@ describe('Dashboard Export Image Utils', () => { absolute: true, updateQuery: { height: -1, - width: 1000, + width: 1280, scale: 2, kiosk: true, hideNav: true, @@ -128,5 +135,46 @@ describe('Dashboard Export Image Utils', () => { }, }); }); + + it('should fallback to config width when window.innerWidth is not available', async () => { + config.rendererAvailable = true; + config.rendererDefaultImageWidth = 1500; + const mockBlob = new Blob(['test'], { type: 'image/png' }); + const fetchMock = jest.fn().mockReturnValue(of({ ok: true, data: mockBlob })); + (getBackendSrv as jest.Mock).mockReturnValue({ fetch: fetchMock }); + + // Ensure window.innerWidth is undefined + Object.defineProperty(window, 'innerWidth', { + writable: true, + configurable: true, + value: undefined, + }); + + const dashboard = { + state: { + uid: 'test-uid', + }, + } as DashboardScene; + + const result = await generateDashboardImage({ dashboard, scale: 1 }); + + expect(result.error).toBeUndefined(); + expect(result.blob).toBe(mockBlob); + expect(getDashboardUrl).toHaveBeenCalledWith({ + uid: 'test-uid', + currentQueryParams: '', + render: true, + absolute: true, + updateQuery: { + height: -1, + width: 1500, // Should use config value + scale: 1, + kiosk: true, + hideNav: true, + orgId: '1', + fullPageImage: true, + }, + }); + }); }); }); diff --git a/public/app/features/dashboard-scene/sharing/ExportButton/utils.ts b/public/app/features/dashboard-scene/sharing/ExportButton/utils.ts index 64d84893b45..f9fb9e5f04b 100644 --- a/public/app/features/dashboard-scene/sharing/ExportButton/utils.ts +++ b/public/app/features/dashboard-scene/sharing/ExportButton/utils.ts @@ -28,7 +28,7 @@ export interface ImageGenerationResult { */ export async function generateDashboardImage({ dashboard, - scale = config.rendererDefaultImageScale || 1, + scale = config.rendererDefaultImageScale || 2, }: ImageGenerationOptions): Promise { try { // Check if renderer plugin is available @@ -46,7 +46,7 @@ export async function generateDashboardImage({ absolute: true, updateQuery: { height: -1, // image renderer will scroll through the dashboard and set the appropriate height - width: config.rendererDefaultImageWidth || 1000, + width: window.innerWidth || config.rendererDefaultImageWidth || 1000, scale, kiosk: true, hideNav: true, From c8c017a6f586e9fb98f56766b0b09b023ee284b0 Mon Sep 17 00:00:00 2001 From: Alexander Akhmetov Date: Wed, 30 Jul 2025 18:37:33 +0200 Subject: [PATCH 015/114] Chore: make update-workspace (#108942) --- apps/advisor/go.sum | 4 ++-- apps/investigations/go.sum | 4 ++-- go.mod | 2 +- go.sum | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/advisor/go.sum b/apps/advisor/go.sum index b83026226ad..b52f4e5a7ab 100644 --- a/apps/advisor/go.sum +++ b/apps/advisor/go.sum @@ -673,8 +673,8 @@ github.com/grafana/grafana-azure-sdk-go/v2 v2.2.0 h1:0TYrkzAc3u0HX+9GK86cGrLTUAc github.com/grafana/grafana-azure-sdk-go/v2 v2.2.0/go.mod h1:H9sVh9A4yg5egMGZeh0mifxT1Q/uqwKe1LBjBJU6pN8= github.com/grafana/grafana-plugin-sdk-go v0.278.0 h1:5/rIYparLi02pofdaag8wnjspMMVNCi8cZhC4cdC3Ho= 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-20250716132114-6fd75ebc5441 h1:+TSbaxCXBZrKkdROWBzdWna8uStE1f9LYd7GiqjVfz8= -github.com/grafana/grafana/apps/dashboard v0.0.0-20250716132114-6fd75ebc5441/go.mod h1:1XWiRSVuDQiayapHhQiDc4S4e9GzEZgg/3GeNCuDgn4= +github.com/grafana/grafana/apps/dashboard v0.0.0-20250730154715-ae15ba562e71 h1:7AVAgb0CalPYtUC9SJKYAkByOBGwL6r50P4QMEiJi44= +github.com/grafana/grafana/apps/dashboard v0.0.0-20250730154715-ae15ba562e71/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/secret v0.0.0-20250711114246-c9b2126c4ad5 h1:+fMhUoqwGdY8ntH0GL2icJa3uk+bTiIMicawDG2r9Uc= diff --git a/apps/investigations/go.sum b/apps/investigations/go.sum index 88dfe75aef3..65357085011 100644 --- a/apps/investigations/go.sum +++ b/apps/investigations/go.sum @@ -351,8 +351,8 @@ github.com/grafana/grafana-azure-sdk-go/v2 v2.2.0 h1:0TYrkzAc3u0HX+9GK86cGrLTUAc github.com/grafana/grafana-azure-sdk-go/v2 v2.2.0/go.mod h1:H9sVh9A4yg5egMGZeh0mifxT1Q/uqwKe1LBjBJU6pN8= github.com/grafana/grafana-plugin-sdk-go v0.278.0 h1:5/rIYparLi02pofdaag8wnjspMMVNCi8cZhC4cdC3Ho= 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-20250716132114-6fd75ebc5441 h1:+TSbaxCXBZrKkdROWBzdWna8uStE1f9LYd7GiqjVfz8= -github.com/grafana/grafana/apps/dashboard v0.0.0-20250716132114-6fd75ebc5441/go.mod h1:1XWiRSVuDQiayapHhQiDc4S4e9GzEZgg/3GeNCuDgn4= +github.com/grafana/grafana/apps/dashboard v0.0.0-20250730154715-ae15ba562e71 h1:7AVAgb0CalPYtUC9SJKYAkByOBGwL6r50P4QMEiJi44= +github.com/grafana/grafana/apps/dashboard v0.0.0-20250730154715-ae15ba562e71/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/pkg/apiserver v0.0.0-20250627191313-2f1a6ae1712b h1:QyJLJn3xwFTIXu9KPZujsrIUN0X8DdiR9b2h75L0AfI= diff --git a/go.mod b/go.mod index dc72d4b349b..6d9b4e0d65b 100644 --- a/go.mod +++ b/go.mod @@ -230,7 +230,7 @@ require ( require ( github.com/grafana/grafana/apps/advisor v0.0.0-20250627191313-2f1a6ae1712b // @grafana/plugins-platform-backend github.com/grafana/grafana/apps/alerting/notifications v0.0.0-20250627191313-2f1a6ae1712b // @grafana/alerting-backend - github.com/grafana/grafana/apps/dashboard v0.0.0-20250716132114-6fd75ebc5441 // @grafana/grafana-app-platform-squad @grafana/dashboards-squad + github.com/grafana/grafana/apps/dashboard v0.0.0-20250730154715-ae15ba562e71 // @grafana/grafana-app-platform-squad @grafana/dashboards-squad github.com/grafana/grafana/apps/folder v0.0.0-20250627191313-2f1a6ae1712b // @grafana/grafana-search-and-storage github.com/grafana/grafana/apps/iam v0.0.0-20250627191313-2f1a6ae1712b // @grafana/identity-access-team github.com/grafana/grafana/apps/investigations v0.0.0-20250627191313-2f1a6ae1712b // @fcjack @matryer diff --git a/go.sum b/go.sum index 46799064e05..2009fce4070 100644 --- a/go.sum +++ b/go.sum @@ -1620,8 +1620,8 @@ github.com/grafana/grafana/apps/advisor v0.0.0-20250627191313-2f1a6ae1712b h1:8o github.com/grafana/grafana/apps/advisor v0.0.0-20250627191313-2f1a6ae1712b/go.mod h1:q+h3HbmqU/PposW6lq8cMle1v8vuyX1LCMrGzbabHxc= github.com/grafana/grafana/apps/alerting/notifications v0.0.0-20250627191313-2f1a6ae1712b h1:jr+C3epmjhd5Yyob4P1Z/dPaW4LRTkU5UJLXsI4eaeM= github.com/grafana/grafana/apps/alerting/notifications v0.0.0-20250627191313-2f1a6ae1712b/go.mod h1:WpI7TCck4P2wKTO2WJLBRcfOWvUGvTdxYu3QqS3z7jM= -github.com/grafana/grafana/apps/dashboard v0.0.0-20250716132114-6fd75ebc5441 h1:+TSbaxCXBZrKkdROWBzdWna8uStE1f9LYd7GiqjVfz8= -github.com/grafana/grafana/apps/dashboard v0.0.0-20250716132114-6fd75ebc5441/go.mod h1:1XWiRSVuDQiayapHhQiDc4S4e9GzEZgg/3GeNCuDgn4= +github.com/grafana/grafana/apps/dashboard v0.0.0-20250730154715-ae15ba562e71 h1:7AVAgb0CalPYtUC9SJKYAkByOBGwL6r50P4QMEiJi44= +github.com/grafana/grafana/apps/dashboard v0.0.0-20250730154715-ae15ba562e71/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/iam v0.0.0-20250627191313-2f1a6ae1712b h1:NV8v9xdM/pzjjy+1cLqUseia3bYcvQGh88vZdMW/jA0= From 34019e5ec017df63124a7355a02b73cf880ad5e7 Mon Sep 17 00:00:00 2001 From: Stephanie Hingtgen Date: Wed, 30 Jul 2025 11:46:19 -0500 Subject: [PATCH 016/114] Library Panels: Remove k8s connections feature flag (#108881) --- .../src/types/featureToggles.gen.ts | 4 -- .../dashboards/service/dashboard_service.go | 56 +++++++++---------- .../service/dashboard_service_test.go | 2 +- pkg/services/featuremgmt/registry.go | 7 --- pkg/services/featuremgmt/toggles_gen.csv | 1 - pkg/services/featuremgmt/toggles_gen.go | 4 -- pkg/services/featuremgmt/toggles_gen.json | 13 ----- 7 files changed, 27 insertions(+), 60 deletions(-) diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index 138e05f187e..e45d19454a7 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -282,10 +282,6 @@ export interface FeatureToggles { */ kubernetesLibraryPanels?: boolean; /** - * Routes library panel connections requests from /api to using search - */ - kubernetesLibraryPanelConnections?: boolean; - /** * Use the kubernetes API in the frontend for dashboards */ kubernetesDashboards?: boolean; diff --git a/pkg/services/dashboards/service/dashboard_service.go b/pkg/services/dashboards/service/dashboard_service.go index 043438db828..1eb611fa670 100644 --- a/pkg/services/dashboards/service/dashboard_service.go +++ b/pkg/services/dashboards/service/dashboard_service.go @@ -474,40 +474,36 @@ func (dr *DashboardServiceImpl) Count(ctx context.Context, scopeParams *quota.Sc } func (dr *DashboardServiceImpl) GetDashboardsByLibraryPanelUID(ctx context.Context, libraryPanelUID string, orgID int64) ([]*dashboards.DashboardRef, error) { - if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesLibraryPanelConnections) { - res, err := dr.k8sclient.Search(ctx, orgID, &resourcepb.ResourceSearchRequest{ - Options: &resourcepb.ListOptions{ - Fields: []*resourcepb.Requirement{ - { - Key: search.DASHBOARD_LIBRARY_PANEL_REFERENCE, - Operator: string(selection.Equals), - Values: []string{libraryPanelUID}, - }, + res, err := dr.k8sclient.Search(ctx, orgID, &resourcepb.ResourceSearchRequest{ + Options: &resourcepb.ListOptions{ + Fields: []*resourcepb.Requirement{ + { + Key: search.DASHBOARD_LIBRARY_PANEL_REFERENCE, + Operator: string(selection.Equals), + Values: []string{libraryPanelUID}, }, }, - Limit: listAllDashboardsLimit, - }) - if err != nil { - return nil, err - } - - results, err := dashboardsearch.ParseResults(res, 0) - if err != nil { - return nil, err - } - - dashes := make([]*dashboards.DashboardRef, 0, len(results.Hits)) - for _, row := range results.Hits { - dashes = append(dashes, &dashboards.DashboardRef{ - UID: row.Name, - FolderUID: row.Folder, - ID: row.Field.GetNestedInt64(resource.SEARCH_FIELD_LEGACY_ID), // nolint:staticcheck - }) - } - return dashes, nil + }, + Limit: listAllDashboardsLimit, + }) + if err != nil { + return nil, err } - return dr.dashboardStore.GetDashboardsByLibraryPanelUID(ctx, libraryPanelUID, orgID) + results, err := dashboardsearch.ParseResults(res, 0) + if err != nil { + return nil, err + } + + dashes := make([]*dashboards.DashboardRef, 0, len(results.Hits)) + for _, row := range results.Hits { + dashes = append(dashes, &dashboards.DashboardRef{ + UID: row.Name, + FolderUID: row.Folder, + ID: row.Field.GetNestedInt64(resource.SEARCH_FIELD_LEGACY_ID), // nolint:staticcheck + }) + } + return dashes, nil } func (dr *DashboardServiceImpl) CountDashboardsInOrg(ctx context.Context, orgID int64) (int64, error) { diff --git a/pkg/services/dashboards/service/dashboard_service_test.go b/pkg/services/dashboards/service/dashboard_service_test.go index 95e5a0a9023..1cda81c5288 100644 --- a/pkg/services/dashboards/service/dashboard_service_test.go +++ b/pkg/services/dashboards/service/dashboard_service_test.go @@ -2493,7 +2493,7 @@ func TestGetDashboardsByLibraryPanelUID(t *testing.T) { dashboardStore: &fakeStore, folderService: folderSvc, ac: actest.FakeAccessControl{ExpectedEvaluate: true}, - features: featuremgmt.WithFeatures(featuremgmt.FlagKubernetesLibraryPanelConnections), + features: featuremgmt.WithFeatures(), publicDashboardService: fakePublicDashboardService, k8sclient: k8sCliMock, } diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index f50b7854ed6..07bc3cdcc6e 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -469,13 +469,6 @@ var ( Owner: grafanaAppPlatformSquad, RequiresRestart: true, // changes the API routing }, - { - Name: "kubernetesLibraryPanelConnections", - Description: "Routes library panel connections requests from /api to using search", - Stage: FeatureStageExperimental, - Owner: grafanaAppPlatformSquad, - RequiresRestart: true, // changes the API routing - }, { Name: "kubernetesDashboards", Description: "Use the kubernetes API in the frontend for dashboards", diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index 6c21f852785..67f899018d8 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -60,7 +60,6 @@ disableClassicHTTPHistogram,experimental,@grafana/grafana-backend-services-squad formatString,GA,@grafana/dataviz-squad,false,false,true kubernetesSnapshots,experimental,@grafana/grafana-app-platform-squad,false,true,false kubernetesLibraryPanels,experimental,@grafana/grafana-app-platform-squad,false,true,false -kubernetesLibraryPanelConnections,experimental,@grafana/grafana-app-platform-squad,false,true,false kubernetesDashboards,experimental,@grafana/grafana-app-platform-squad,false,false,true dashboardDisableSchemaValidationV1,experimental,@grafana/grafana-app-platform-squad,false,false,false dashboardDisableSchemaValidationV2,experimental,@grafana/grafana-app-platform-squad,false,false,false diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go index 52552a0b8d9..236516152f5 100644 --- a/pkg/services/featuremgmt/toggles_gen.go +++ b/pkg/services/featuremgmt/toggles_gen.go @@ -251,10 +251,6 @@ const ( // Routes library panel requests from /api to the /apis endpoint FlagKubernetesLibraryPanels = "kubernetesLibraryPanels" - // FlagKubernetesLibraryPanelConnections - // Routes library panel connections requests from /api to using search - FlagKubernetesLibraryPanelConnections = "kubernetesLibraryPanelConnections" - // FlagKubernetesDashboards // Use the kubernetes API in the frontend for dashboards FlagKubernetesDashboards = "kubernetesDashboards" diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json index 980f805d56d..2ee75811760 100644 --- a/pkg/services/featuremgmt/toggles_gen.json +++ b/pkg/services/featuremgmt/toggles_gen.json @@ -1756,19 +1756,6 @@ "hideFromAdminPage": true } }, - { - "metadata": { - "name": "kubernetesLibraryPanelConnections", - "resourceVersion": "1753448760331", - "creationTimestamp": "2025-07-25T13:06:00Z" - }, - "spec": { - "description": "Routes library panel connections requests from /api to using search", - "stage": "experimental", - "codeowner": "@grafana/grafana-app-platform-squad", - "requiresRestart": true - } - }, { "metadata": { "name": "kubernetesLibraryPanels", From 60ce4ad1fa62c6acbb6cda08bc7a738998bb9778 Mon Sep 17 00:00:00 2001 From: Sarah Zinger Date: Wed, 30 Jul 2025 13:46:38 -0400 Subject: [PATCH 017/114] Ds-querier: add slug to logs (#108822) --- pkg/registry/apis/query/query.go | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/pkg/registry/apis/query/query.go b/pkg/registry/apis/query/query.go index 66a1db38eae..6e73e9dd63d 100644 --- a/pkg/registry/apis/query/query.go +++ b/pkg/registry/apis/query/query.go @@ -108,6 +108,7 @@ func (r *queryREST) NewConnectOptions() (runtime.Object, bool, string) { return nil, false, "" // true means you can use the trailing path as a variable } +// called by mt query service and also when queryServiceFromUI is enabled, can be both mt and st func (r *queryREST) Connect(connectCtx context.Context, name string, _ runtime.Object, incomingResponder rest.Responder) (http.Handler, error) { // See: /pkg/services/apiserver/builder/helper.go#L34 // The name is set with a rewriter hack @@ -175,7 +176,7 @@ func (r *queryREST) Connect(connectCtx context.Context, name string, _ runtime.O return } - qdr, err := handleQuery(ctx, *raw, *b, httpreq, *responder) + qdr, err := handleQuery(ctx, *raw, *b, httpreq, *responder, connectLogger) if err != nil { b.log.Error("execute error", "http code", query.GetResponseCode(qdr), "err", err) @@ -199,7 +200,7 @@ func (r *queryREST) Connect(connectCtx context.Context, name string, _ runtime.O }), nil } -func handleQuery(ctx context.Context, raw query.QueryDataRequest, b QueryAPIBuilder, httpreq *http.Request, responder responderWrapper) (*backend.QueryDataResponse, error) { +func handleQuery(ctx context.Context, raw query.QueryDataRequest, b QueryAPIBuilder, httpreq *http.Request, responder responderWrapper, connectLogger log.Logger) (*backend.QueryDataResponse, error) { var jsonQueries = make([]*simplejson.Json, 0, len(raw.Queries)) for _, query := range raw.Queries { jsonBytes, err := json.Marshal(query) @@ -228,17 +229,19 @@ func handleQuery(ctx context.Context, raw query.QueryDataRequest, b QueryAPIBuil instanceConfig, err := b.clientSupplier.GetInstanceConfigurationSettings(ctx) if err != nil { - b.log.Error("failed to get instance configuration settings", "err", err) + connectLogger.Error("failed to get instance configuration settings", "err", err) responder.Error(err) return nil, err } + dsQuerierLoggerWithSlug := connectLogger.New("slug", instanceConfig.Options["slug"]) + mtDsClientBuilder := mtdsclient.NewMtDatasourceClientBuilderWithClientSupplier( b.clientSupplier, ctx, headers, instanceConfig, - b.log, + dsQuerierLoggerWithSlug, ) exprService := expr.ProvideService( @@ -256,7 +259,7 @@ func handleQuery(ctx context.Context, raw query.QueryDataRequest, b QueryAPIBuil mtDsClientBuilder, ) - qdr, err := service.QueryData(ctx, b.log, cache, exprService, mReq, mtDsClientBuilder, headers) + qdr, err := service.QueryData(ctx, dsQuerierLoggerWithSlug, cache, exprService, mReq, mtDsClientBuilder, headers) if err != nil { return qdr, err From 68ed7c129d23c94e6dcea86aea686c41677fcef7 Mon Sep 17 00:00:00 2001 From: Juan Cabanas Date: Wed, 30 Jul 2025 15:12:35 -0300 Subject: [PATCH 018/114] QueryLibrary: Add `CoreApp` for `QueryEditor` rendering (#108867) --- .../panel-edit/PanelDataPane/PanelDataQueriesTab.tsx | 5 ++++- .../explore/QueryLibrary/QueryLibraryContext.tsx | 9 ++++++--- public/app/features/explore/QueryLibrary/mocks.tsx | 1 + .../explore/RichHistory/RichHistoryAddToLibrary.tsx | 2 +- public/app/features/explore/SecondaryActions.tsx | 8 ++++++-- .../app/features/query/components/QueryEditorRow.tsx | 11 +++++++---- 6 files changed, 25 insertions(+), 11 deletions(-) diff --git a/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataQueriesTab.tsx b/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataQueriesTab.tsx index ce2784758be..668b8612851 100644 --- a/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataQueriesTab.tsx +++ b/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataQueriesTab.tsx @@ -376,6 +376,7 @@ export function PanelDataQueriesTabRendered({ model }: SceneComponentProps @@ -393,7 +394,9 @@ export function PanelDataQueriesTabRendered({ model }: SceneComponentProps - openQueryLibraryDrawer(getDatasourceNames(datasource, queries), onSelectQueryFromLibrary) + openQueryLibraryDrawer(getDatasourceNames(datasource, queries), onSelectQueryFromLibrary, { + context: CoreApp.PanelEditor, + }) } variant="secondary" data-testid={selectors.components.QueryTab.addQueryFromLibrary} diff --git a/public/app/features/explore/QueryLibrary/QueryLibraryContext.tsx b/public/app/features/explore/QueryLibrary/QueryLibraryContext.tsx index 90640203d48..9037182c156 100644 --- a/public/app/features/explore/QueryLibrary/QueryLibraryContext.tsx +++ b/public/app/features/explore/QueryLibrary/QueryLibraryContext.tsx @@ -1,5 +1,6 @@ import { createContext, ReactNode, useContext } from 'react'; +import { CoreApp } from '@grafana/data'; import { DataQuery } from '@grafana/schema'; import { OnSelectQueryType } from './types'; @@ -17,7 +18,7 @@ export type QueryLibraryContextType = { * @param datasourceFilters Data source names that will be used for initial filter in the library. * @param queryActionButton Action button will be shown in the library next to the query and can implement context * specific actions with the library, like running the query or updating some query in the current app. - * @param options.context Used for tracking. Should identify the context this is called from, like 'explore' or + * @param options.context Used for QueryEditor. Should identify the context this is called from, like 'explore' or * 'dashboard'. */ openDrawer: ( @@ -32,7 +33,7 @@ export type QueryLibraryContextType = { * Opens a modal for adding a query to the library. * @param query Query to be saved * @param options.onSave Callback that will be called after the query is saved. - * @param options.context Used for tracking. Should identify the context this is called from, like 'explore' or + * @param options.context Used for rendering QueryEditor. Should identify the context this is called from, like 'explore' or * 'dashboard'. * @param options.title Default title for the modal, can be overridden by the query title. */ @@ -46,8 +47,9 @@ export type QueryLibraryContextType = { * Returns a predefined small button that can be used to save a query to the library. * @param query */ - renderSaveQueryButton: (query: DataQuery) => ReactNode; + renderSaveQueryButton: (query: DataQuery, app?: CoreApp) => ReactNode; queryLibraryEnabled: boolean; + context: string; }; export const QueryLibraryContext = createContext({ @@ -63,6 +65,7 @@ export const QueryLibraryContext = createContext({ }, queryLibraryEnabled: false, + context: 'unknown', }); export function useQueryLibraryContext() { diff --git a/public/app/features/explore/QueryLibrary/mocks.tsx b/public/app/features/explore/QueryLibrary/mocks.tsx index 9126fdabe42..e59732870a9 100644 --- a/public/app/features/explore/QueryLibrary/mocks.tsx +++ b/public/app/features/explore/QueryLibrary/mocks.tsx @@ -17,6 +17,7 @@ export function QueryLibraryContextProviderMock(props: PropsWithChildren) closeAddQueryModal: jest.fn(), renderSaveQueryButton: jest.fn(), queryLibraryEnabled: Boolean(props.queryLibraryEnabled), + context: 'explore', }} > {props.children} diff --git a/public/app/features/explore/RichHistory/RichHistoryAddToLibrary.tsx b/public/app/features/explore/RichHistory/RichHistoryAddToLibrary.tsx index 6c70ac1bf89..e99c0e6cebc 100644 --- a/public/app/features/explore/RichHistory/RichHistoryAddToLibrary.tsx +++ b/public/app/features/explore/RichHistory/RichHistoryAddToLibrary.tsx @@ -22,7 +22,7 @@ export const RichHistoryAddToLibrary = ({ query }: Props) => { variant="secondary" aria-label={buttonLabel} onClick={() => { - openAddQueryModal(query, { onSave: () => setHasBeenSaved(true), context: 'richHistory' }); + openAddQueryModal(query, { onSave: () => setHasBeenSaved(true), context: 'rich-history' }); }} > {buttonLabel} diff --git a/public/app/features/explore/SecondaryActions.tsx b/public/app/features/explore/SecondaryActions.tsx index cd9d25b860b..b5b30aaae1d 100644 --- a/public/app/features/explore/SecondaryActions.tsx +++ b/public/app/features/explore/SecondaryActions.tsx @@ -1,6 +1,6 @@ import { css } from '@emotion/css'; -import { GrafanaTheme2 } from '@grafana/data'; +import { CoreApp, GrafanaTheme2 } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; import { Trans, t } from '@grafana/i18n'; import { ToolbarButton, useTheme2 } from '@grafana/ui'; @@ -76,7 +76,11 @@ export function SecondaryActions({ data-testid={selectors.pages.Explore.General.addFromQueryLibrary} aria-label={t('explore.secondary-actions.add-from-query-library', 'Add query from library')} variant="canvas" - onClick={() => openQueryLibraryDrawer(activeDatasources, onSelectQueryFromLibrary)} + onClick={() => + openQueryLibraryDrawer(activeDatasources, onSelectQueryFromLibrary, { + context: CoreApp.Explore, + }) + } icon="plus" > Add query from library diff --git a/public/app/features/query/components/QueryEditorRow.tsx b/public/app/features/query/components/QueryEditorRow.tsx index 352451481d0..b711b76d8c8 100644 --- a/public/app/features/query/components/QueryEditorRow.tsx +++ b/public/app/features/query/components/QueryEditorRow.tsx @@ -380,7 +380,7 @@ export class QueryEditorRow extends PureComponent )} {this.renderExtraActions()} - + extends PureComponent {!hideHideQueryButton ? ( { datasourceFilters: string[]; onSelectQuery: (query: DataQuery) => void; + app?: CoreApp; } function ReplaceQueryFromLibrary({ datasourceFilters, onSelectQuery, + app, }: ReplaceQueryFromLibraryProps) { const { openDrawer, queryLibraryEnabled } = useQueryLibraryContext(); const onReplaceQueryFromLibrary = () => { - openDrawer(datasourceFilters, onSelectQuery, { isReplacingQuery: true }); + openDrawer(datasourceFilters, onSelectQuery, { isReplacingQuery: true, context: app }); }; return queryLibraryEnabled ? ( From 8fb1483a9f8dd8126d129418ac87b3aba11ee6ef Mon Sep 17 00:00:00 2001 From: Adam Yeats <16296989+adamyeats@users.noreply.github.com> Date: Wed, 30 Jul 2025 19:12:59 +0100 Subject: [PATCH 019/114] Elasticsearch: Remove link to Elasticsearch Templated Dashboard from docs (#108555) Remove link to Elasticsearch Templated Dashboard from docs --- .../datasources/elasticsearch/template-variables/index.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/sources/datasources/elasticsearch/template-variables/index.md b/docs/sources/datasources/elasticsearch/template-variables/index.md index b27861f0796..66ca17a93bd 100644 --- a/docs/sources/datasources/elasticsearch/template-variables/index.md +++ b/docs/sources/datasources/elasticsearch/template-variables/index.md @@ -86,8 +86,6 @@ You can alternatively use other sorting criteria, such as **Alphabetical**, to r In the above example, a Lucene query filters documents based on the `hostname` property using a variable named `$hostname`. The example also uses a variable in the _Terms_ group by field input box, which you can use to quickly change how data is grouped. -To view an example dashboard on Grafana Play, see the [Elasticsearch Templated Dashboard](https://play.grafana.org/d/z8OZC66nk/elasticsearch-8-2-0-sample-flight-data?orgId=1). - ## Create a query Write the query using a custom JSON string, with the field mapped as a [keyword](https://www.elastic.co/guide/en/elasticsearch/reference/current/keyword.html#keyword) in the Elasticsearch index mapping. From 6bf542889afb8e5e63bb09a82175782ddbf5dabc Mon Sep 17 00:00:00 2001 From: "lean.dev" <34773040+leandro-deveikis@users.noreply.github.com> Date: Wed, 30 Jul 2025 20:00:30 +0100 Subject: [PATCH 020/114] SecretsManager: Refactor and clean metrics (#108908) --- pkg/registry/apis/secret/contracts/decrypt.go | 2 +- .../apis/secret/service/metrics/metrics.go | 128 ++++++++++++++++ .../apis/secret/service/secure_value.go | 143 ++++++++++++++++-- .../apis/secret/testutils/testutils.go | 2 +- pkg/server/wire_gen.go | 4 +- .../secret/metadata/decrypt_store_test.go | 9 +- .../secret/metadata/metrics/metrics.go | 46 +----- .../secret/metadata/secure_value_store.go | 49 ++++-- 8 files changed, 317 insertions(+), 66 deletions(-) create mode 100644 pkg/registry/apis/secret/service/metrics/metrics.go diff --git a/pkg/registry/apis/secret/contracts/decrypt.go b/pkg/registry/apis/secret/contracts/decrypt.go index 32f9f7f6c1d..2c5403c790d 100644 --- a/pkg/registry/apis/secret/contracts/decrypt.go +++ b/pkg/registry/apis/secret/contracts/decrypt.go @@ -27,7 +27,7 @@ type DecryptAuthorizer interface { Authorize(ctx context.Context, secureValueName string, secureValueDecrypters []string) (identity string, allowed bool) } -// DecryptService is the inferface for the decrypt service. +// DecryptService is the interface for the decrypt service. type DecryptService interface { Decrypt(ctx context.Context, namespace string, names ...string) (map[string]DecryptResult, error) Close() error diff --git a/pkg/registry/apis/secret/service/metrics/metrics.go b/pkg/registry/apis/secret/service/metrics/metrics.go new file mode 100644 index 00000000000..b179ead20b7 --- /dev/null +++ b/pkg/registry/apis/secret/service/metrics/metrics.go @@ -0,0 +1,128 @@ +package metrics + +import ( + "sync" + + "github.com/prometheus/client_golang/prometheus" +) + +const ( + namespace = "grafana_secrets_manager" + subsystem = "service" +) + +// SecureValueServiceMetrics is a struct that contains all the metrics for SecureValue. +type SecureValueServiceMetrics struct { + SecureValueCreateDuration *prometheus.HistogramVec + SecureValueCreateCount *prometheus.CounterVec + SecureValueUpdateDuration *prometheus.HistogramVec + SecureValueUpdateCount *prometheus.CounterVec + SecureValueReadDuration *prometheus.HistogramVec + SecureValueReadCount *prometheus.CounterVec + SecureValueListDuration *prometheus.HistogramVec + SecureValueListCount *prometheus.CounterVec + SecureValueDeleteDuration *prometheus.HistogramVec + SecureValueDeleteCount *prometheus.CounterVec +} + +func newSecureValueServiceMetrics() *SecureValueServiceMetrics { + return &SecureValueServiceMetrics{ + SecureValueCreateDuration: prometheus.NewHistogramVec(prometheus.HistogramOpts{ + Namespace: namespace, + Subsystem: subsystem, + Name: "secure_value_create_duration_seconds", + Help: "Duration of Secure Value create operations", + Buckets: prometheus.DefBuckets, + }, []string{"success"}), + SecureValueCreateCount: prometheus.NewCounterVec(prometheus.CounterOpts{ + Namespace: namespace, + Subsystem: subsystem, + Name: "secure_value_create_count", + Help: "Count of Secure Value create operations", + }, []string{"success"}), + SecureValueReadDuration: prometheus.NewHistogramVec(prometheus.HistogramOpts{ + Namespace: namespace, + Subsystem: subsystem, + Name: "secure_value_read_duration_seconds", + Help: "Duration of Secure Value read operations", + Buckets: prometheus.DefBuckets, + }, []string{"success"}), + SecureValueReadCount: prometheus.NewCounterVec(prometheus.CounterOpts{ + Namespace: namespace, + Subsystem: subsystem, + Name: "secure_value_read_count", + Help: "Count of Secure Value read operations", + }, []string{"success"}), + SecureValueUpdateDuration: prometheus.NewHistogramVec(prometheus.HistogramOpts{ + Namespace: namespace, + Subsystem: subsystem, + Name: "secure_value_update_duration_seconds", + Help: "Duration of Secure Value update operations", + Buckets: prometheus.DefBuckets, + }, []string{"success"}), + SecureValueUpdateCount: prometheus.NewCounterVec(prometheus.CounterOpts{ + Namespace: namespace, + Subsystem: subsystem, + Name: "secure_value_update_count", + Help: "Count of Secure Value update operations", + }, []string{"success"}), + SecureValueListDuration: prometheus.NewHistogramVec(prometheus.HistogramOpts{ + Namespace: namespace, + Subsystem: subsystem, + Name: "secure_value_list_duration_seconds", + Help: "Duration of Secure Value list operations", + Buckets: prometheus.DefBuckets, + }, []string{"success"}), + SecureValueListCount: prometheus.NewCounterVec(prometheus.CounterOpts{ + Namespace: namespace, + Subsystem: subsystem, + Name: "secure_value_list_count", + Help: "Count of Secure Value list operations", + }, []string{"success"}), + SecureValueDeleteDuration: prometheus.NewHistogramVec(prometheus.HistogramOpts{ + Namespace: namespace, + Subsystem: subsystem, + Name: "secure_value_delete_duration_seconds", + Help: "Duration of Secure Value delete operations", + Buckets: prometheus.DefBuckets, + }, []string{"success"}), + SecureValueDeleteCount: prometheus.NewCounterVec(prometheus.CounterOpts{ + Namespace: namespace, + Subsystem: subsystem, + Name: "secure_value_delete_count", + Help: "Count of Secure Value delete operations", + }, []string{"success"}), + } +} + +var ( + initOnce sync.Once + metricsInstance *SecureValueServiceMetrics +) + +func NewSecureValueServiceMetrics(reg prometheus.Registerer) *SecureValueServiceMetrics { + initOnce.Do(func() { + m := newSecureValueServiceMetrics() + + if reg != nil { + reg.MustRegister( + m.SecureValueCreateDuration, + m.SecureValueCreateCount, + m.SecureValueReadDuration, + m.SecureValueReadCount, + m.SecureValueUpdateDuration, + m.SecureValueUpdateCount, + m.SecureValueListDuration, + m.SecureValueListCount, + m.SecureValueDeleteDuration, + m.SecureValueDeleteCount, + ) + } + metricsInstance = m + }) + return metricsInstance +} + +func NewTestMetrics() *SecureValueServiceMetrics { + return newSecureValueServiceMetrics() +} diff --git a/pkg/registry/apis/secret/service/secure_value.go b/pkg/registry/apis/secret/service/secure_value.go index f9bafad9467..fe32208562d 100644 --- a/pkg/registry/apis/secret/service/secure_value.go +++ b/pkg/registry/apis/secret/service/secure_value.go @@ -3,6 +3,8 @@ package service import ( "context" "fmt" + "strconv" + "time" claims "github.com/grafana/authlib/types" "go.opentelemetry.io/otel/attribute" @@ -12,9 +14,14 @@ import ( secretv1beta1 "github.com/grafana/grafana/apps/secret/pkg/apis/secret/v1beta1" "github.com/grafana/grafana/pkg/apimachinery/utils" "github.com/grafana/grafana/pkg/registry/apis/secret/contracts" + "github.com/grafana/grafana/pkg/registry/apis/secret/service/metrics" "github.com/grafana/grafana/pkg/registry/apis/secret/xkube" + "github.com/prometheus/client_golang/prometheus" + "go.opentelemetry.io/otel/codes" ) +var _ contracts.SecureValueService = (*SecureValueService)(nil) + type SecureValueService struct { tracer trace.Tracer accessClient claims.AccessClient @@ -22,6 +29,7 @@ type SecureValueService struct { secureValueMetadataStorage contracts.SecureValueMetadataStorage keeperMetadataStorage contracts.KeeperMetadataStorage keeperService contracts.KeeperService + metrics *metrics.SecureValueServiceMetrics } func ProvideSecureValueService( @@ -31,6 +39,7 @@ func ProvideSecureValueService( secureValueMetadataStorage contracts.SecureValueMetadataStorage, keeperMetadataStorage contracts.KeeperMetadataStorage, keeperService contracts.KeeperService, + reg prometheus.Registerer, ) contracts.SecureValueService { return &SecureValueService{ tracer: tracer, @@ -39,27 +48,77 @@ func ProvideSecureValueService( secureValueMetadataStorage: secureValueMetadataStorage, keeperMetadataStorage: keeperMetadataStorage, keeperService: keeperService, + metrics: metrics.NewSecureValueServiceMetrics(reg), } } -func (s *SecureValueService) Create(ctx context.Context, sv *secretv1beta1.SecureValue, actorUID string) (*secretv1beta1.SecureValue, error) { +func (s *SecureValueService) Create(ctx context.Context, sv *secretv1beta1.SecureValue, actorUID string) (_ *secretv1beta1.SecureValue, createErr error) { + start := time.Now() + name, namespace := sv.GetName(), sv.GetNamespace() ctx, span := s.tracer.Start(ctx, "SecureValueService.Create", trace.WithAttributes( - attribute.String("name", sv.GetName()), - attribute.String("namespace", sv.GetNamespace()), + attribute.String("name", name), + attribute.String("namespace", namespace), attribute.String("actor", actorUID), )) defer span.End() + + defer func() { + args := []any{ + "name", name, + "namespace", namespace, + "actorUID", actorUID, + } + + success := createErr == nil + args = append(args, "success", success) + if !success { + span.SetStatus(codes.Error, "SecureValueService.Create failed") + span.RecordError(createErr) + args = append(args, "error", createErr) + } + + logging.FromContext(ctx).Info("SecureValueService.Create finished", args...) + + s.metrics.SecureValueCreateDuration.WithLabelValues(strconv.FormatBool(success)).Observe(time.Since(start).Seconds()) + s.metrics.SecureValueCreateCount.WithLabelValues(strconv.FormatBool(success)).Inc() + }() + return s.createNewVersion(ctx, sv, actorUID) } -func (s *SecureValueService) Update(ctx context.Context, newSecureValue *secretv1beta1.SecureValue, actorUID string) (*secretv1beta1.SecureValue, bool, error) { +func (s *SecureValueService) Update(ctx context.Context, newSecureValue *secretv1beta1.SecureValue, actorUID string) (_ *secretv1beta1.SecureValue, sync bool, updateErr error) { + start := time.Now() + name, namespace := newSecureValue.GetName(), newSecureValue.GetNamespace() + ctx, span := s.tracer.Start(ctx, "SecureValueService.Update", trace.WithAttributes( - attribute.String("name", newSecureValue.GetName()), - attribute.String("namespace", newSecureValue.GetNamespace()), + attribute.String("name", name), + attribute.String("namespace", namespace), attribute.String("actor", actorUID), )) defer span.End() + defer func() { + args := []any{ + "name", name, + "namespace", namespace, + "actorUID", actorUID, + "sync", sync, + } + + success := updateErr == nil + args = append(args, "success", success) + if !success { + span.SetStatus(codes.Error, "SecureValueService.Update failed") + span.RecordError(updateErr) + args = append(args, "error", updateErr) + } + + logging.FromContext(ctx).Info("SecureValueService.Update finished", args...) + + s.metrics.SecureValueUpdateDuration.WithLabelValues(strconv.FormatBool(success)).Observe(time.Since(start).Seconds()) + s.metrics.SecureValueUpdateCount.WithLabelValues(strconv.FormatBool(success)).Inc() + }() + if newSecureValue.Spec.Value == nil { currentVersion, err := s.secureValueMetadataStorage.Read(ctx, xkube.Namespace(newSecureValue.Namespace), newSecureValue.Name, contracts.ReadOpts{}) if err != nil { @@ -136,22 +195,66 @@ func (s *SecureValueService) createNewVersion(ctx context.Context, sv *secretv1b return createdSv, nil } -func (s *SecureValueService) Read(ctx context.Context, namespace xkube.Namespace, name string) (*secretv1beta1.SecureValue, error) { +func (s *SecureValueService) Read(ctx context.Context, namespace xkube.Namespace, name string) (_ *secretv1beta1.SecureValue, readErr error) { + start := time.Now() + ctx, span := s.tracer.Start(ctx, "SecureValueService.Read", trace.WithAttributes( attribute.String("name", name), attribute.String("namespace", namespace.String()), )) + + defer func() { + args := []any{ + "name", name, + "namespace", namespace, + } + + success := readErr == nil + args = append(args, "success", success) + if !success { + span.SetStatus(codes.Error, "SecureValueService.Read failed") + span.RecordError(readErr) + args = append(args, "error", readErr) + } + + logging.FromContext(ctx).Info("SecureValueService.Read finished", args...) + + s.metrics.SecureValueReadDuration.WithLabelValues(strconv.FormatBool(success)).Observe(time.Since(start).Seconds()) + s.metrics.SecureValueReadCount.WithLabelValues(strconv.FormatBool(success)).Inc() + }() + defer span.End() return s.secureValueMetadataStorage.Read(ctx, namespace, name, contracts.ReadOpts{ForUpdate: false}) } -func (s *SecureValueService) List(ctx context.Context, namespace xkube.Namespace) (*secretv1beta1.SecureValueList, error) { +func (s *SecureValueService) List(ctx context.Context, namespace xkube.Namespace) (_ *secretv1beta1.SecureValueList, listErr error) { + start := time.Now() + ctx, span := s.tracer.Start(ctx, "SecureValueService.List", trace.WithAttributes( attribute.String("namespace", namespace.String()), )) defer span.End() + defer func() { + args := []any{ + "namespace", namespace, + } + + success := listErr == nil + args = append(args, "success", success) + if !success { + span.SetStatus(codes.Error, "SecureValueService.List failed") + span.RecordError(listErr) + args = append(args, "error", listErr) + } + + logging.FromContext(ctx).Info("SecureValueService.List finished", args...) + + s.metrics.SecureValueListDuration.WithLabelValues(strconv.FormatBool(success)).Observe(time.Since(start).Seconds()) + s.metrics.SecureValueListCount.WithLabelValues(strconv.FormatBool(success)).Inc() + }() + user, ok := claims.AuthInfoFrom(ctx) if !ok { return nil, fmt.Errorf("missing auth info in context") @@ -188,13 +291,35 @@ func (s *SecureValueService) List(ctx context.Context, namespace xkube.Namespace }, nil } -func (s *SecureValueService) Delete(ctx context.Context, namespace xkube.Namespace, name string) (*secretv1beta1.SecureValue, error) { +func (s *SecureValueService) Delete(ctx context.Context, namespace xkube.Namespace, name string) (_ *secretv1beta1.SecureValue, deleteErr error) { + start := time.Now() + ctx, span := s.tracer.Start(ctx, "SecureValueService.Delete", trace.WithAttributes( attribute.String("name", name), attribute.String("namespace", namespace.String()), )) defer span.End() + defer func() { + args := []any{ + "name", name, + "namespace", namespace, + } + + success := deleteErr == nil + args = append(args, "success", success) + if !success { + span.SetStatus(codes.Error, "SecureValueService.Delete failed") + span.RecordError(deleteErr) + args = append(args, "error", deleteErr) + } + + logging.FromContext(ctx).Info("SecureValueService.Delete finished", args...) + + s.metrics.SecureValueDeleteDuration.WithLabelValues(strconv.FormatBool(success)).Observe(time.Since(start).Seconds()) + s.metrics.SecureValueDeleteCount.WithLabelValues(strconv.FormatBool(success)).Inc() + }() + // TODO: does this need to be for update? sv, err := s.secureValueMetadataStorage.Read(ctx, namespace, name, contracts.ReadOpts{ForUpdate: true}) if err != nil { diff --git a/pkg/registry/apis/secret/testutils/testutils.go b/pkg/registry/apis/secret/testutils/testutils.go index f2d24b05905..8b6765ba642 100644 --- a/pkg/registry/apis/secret/testutils/testutils.go +++ b/pkg/registry/apis/secret/testutils/testutils.go @@ -120,7 +120,7 @@ func Setup(t *testing.T, opts ...func(*SetupConfig)) Sut { keeperService = setupCfg.KeeperService } - secureValueService := service.ProvideSecureValueService(tracer, accessClient, database, secureValueMetadataStorage, keeperMetadataStorage, keeperService) + secureValueService := service.ProvideSecureValueService(tracer, accessClient, database, secureValueMetadataStorage, keeperMetadataStorage, keeperService, nil) decryptAuthorizer := decrypt.ProvideDecryptAuthorizer(tracer) diff --git a/pkg/server/wire_gen.go b/pkg/server/wire_gen.go index aeff3aa1924..3d055c451e5 100644 --- a/pkg/server/wire_gen.go +++ b/pkg/server/wire_gen.go @@ -780,7 +780,7 @@ func Initialize(cfg *setting.Cfg, opts Options, apiOpts api.ServerOptions) (*Ser if err != nil { return nil, err } - secureValueService := service12.ProvideSecureValueService(tracer, accessClient, databaseDatabase, secureValueMetadataStorage, keeperMetadataStorage, ossKeeperService) + secureValueService := service12.ProvideSecureValueService(tracer, accessClient, databaseDatabase, secureValueMetadataStorage, keeperMetadataStorage, ossKeeperService, registerer) secureValueValidator := validator3.ProvideSecureValueValidator() secureValueClient := secret.ProvideSecureValueClient(secureValueService, secureValueValidator, accessClient) decryptAuthorizer := decrypt.ProvideDecryptAuthorizer(tracer) @@ -1341,7 +1341,7 @@ func InitializeForTest(t sqlutil.ITestDB, testingT interface { if err != nil { return nil, err } - secureValueService := service12.ProvideSecureValueService(tracer, accessClient, databaseDatabase, secureValueMetadataStorage, keeperMetadataStorage, ossKeeperService) + secureValueService := service12.ProvideSecureValueService(tracer, accessClient, databaseDatabase, secureValueMetadataStorage, keeperMetadataStorage, ossKeeperService, registerer) secureValueValidator := validator3.ProvideSecureValueValidator() secureValueClient := secret.ProvideSecureValueClient(secureValueService, secureValueValidator, accessClient) decryptAuthorizer := decrypt.ProvideDecryptAuthorizer(tracer) diff --git a/pkg/storage/secret/metadata/decrypt_store_test.go b/pkg/storage/secret/metadata/decrypt_store_test.go index 03d0daace36..3c35a443794 100644 --- a/pkg/storage/secret/metadata/decrypt_store_test.go +++ b/pkg/storage/secret/metadata/decrypt_store_test.go @@ -292,8 +292,13 @@ func TestIntegrationDecrypt(t *testing.T) { require.NotEmpty(t, exposed) require.Equal(t, "value", exposed.DangerouslyExposeAndConsumeValue()) - require.Len(t, fakeLogger.InfoArgs, 1) - args := fakeLogger.InfoArgs[0] + require.Len(t, fakeLogger.InfoMsgs, 2) + require.Equal(t, fakeLogger.InfoMsgs[0], "SecureValueMetadataStorage.Read") + require.Equal(t, fakeLogger.InfoMsgs[1], "Secrets Audit Log") + + require.Len(t, fakeLogger.InfoArgs, 2) + // we only want to check the audit log args + args := fakeLogger.InfoArgs[1] require.Contains(t, args, "grafana_decrypter_identity") require.Contains(t, args, "decrypter_identity") for i, arg := range args { diff --git a/pkg/storage/secret/metadata/metrics/metrics.go b/pkg/storage/secret/metadata/metrics/metrics.go index aea3817f12c..f9baba1de64 100644 --- a/pkg/storage/secret/metadata/metrics/metrics.go +++ b/pkg/storage/secret/metadata/metrics/metrics.go @@ -25,12 +25,8 @@ type StorageMetrics struct { KeeperMetadataListCount prometheus.Counter KeeperMetadataGetKeeperConfigDuration prometheus.Histogram - SecureValueMetadataCreateDuration prometheus.Histogram - SecureValueMetadataCreateCount prometheus.Counter - SecureValueMetadataUpdateDuration prometheus.Histogram - SecureValueMetadataUpdateCount prometheus.Counter - SecureValueMetadataDeleteDuration prometheus.Histogram - SecureValueMetadataDeleteCount prometheus.Counter + SecureValueMetadataCreateDuration *prometheus.HistogramVec + SecureValueMetadataCreateCount *prometheus.CounterVec SecureValueMetadataGetDuration prometheus.Histogram SecureValueMetadataGetCount prometheus.Counter SecureValueMetadataListDuration prometheus.Histogram @@ -119,45 +115,19 @@ func newStorageMetrics() *StorageMetrics { }), // Secure value metrics - SecureValueMetadataCreateDuration: prometheus.NewHistogram(prometheus.HistogramOpts{ + SecureValueMetadataCreateDuration: prometheus.NewHistogramVec(prometheus.HistogramOpts{ Namespace: namespace, Subsystem: subsystem, Name: "secure_value_metadata_create_duration_seconds", Help: "Duration of secure value metadata create operations", Buckets: prometheus.DefBuckets, - }), - SecureValueMetadataCreateCount: prometheus.NewCounter(prometheus.CounterOpts{ + }, []string{"successful"}), + SecureValueMetadataCreateCount: prometheus.NewCounterVec(prometheus.CounterOpts{ Namespace: namespace, Subsystem: subsystem, Name: "secure_value_metadata_create_count", Help: "Count of secure value metadata create operations", - }), - SecureValueMetadataUpdateDuration: prometheus.NewHistogram(prometheus.HistogramOpts{ - Namespace: namespace, - Subsystem: subsystem, - Name: "secure_value_metadata_update_duration_seconds", - Help: "Duration of secure value metadata update operations", - Buckets: prometheus.DefBuckets, - }), - SecureValueMetadataUpdateCount: prometheus.NewCounter(prometheus.CounterOpts{ - Namespace: namespace, - Subsystem: subsystem, - Name: "secure_value_metadata_update_count", - Help: "Count of secure value metadata update operations", - }), - SecureValueMetadataDeleteDuration: prometheus.NewHistogram(prometheus.HistogramOpts{ - Namespace: namespace, - Subsystem: subsystem, - Name: "secure_value_metadata_delete_duration_seconds", - Help: "Duration of secure value metadata delete operations", - Buckets: prometheus.DefBuckets, - }), - SecureValueMetadataDeleteCount: prometheus.NewCounter(prometheus.CounterOpts{ - Namespace: namespace, - Subsystem: subsystem, - Name: "secure_value_metadata_delete_count", - Help: "Count of secure value metadata delete operations", - }), + }, []string{"successful"}), SecureValueMetadataGetDuration: prometheus.NewHistogram(prometheus.HistogramOpts{ Namespace: namespace, Subsystem: subsystem, @@ -241,10 +211,6 @@ func NewStorageMetrics(reg prometheus.Registerer) *StorageMetrics { m.KeeperMetadataGetKeeperConfigDuration, m.SecureValueMetadataCreateDuration, m.SecureValueMetadataCreateCount, - m.SecureValueMetadataUpdateDuration, - m.SecureValueMetadataUpdateCount, - m.SecureValueMetadataDeleteDuration, - m.SecureValueMetadataDeleteCount, m.SecureValueMetadataGetDuration, m.SecureValueMetadataGetCount, m.SecureValueMetadataListDuration, diff --git a/pkg/storage/secret/metadata/secure_value_store.go b/pkg/storage/secret/metadata/secure_value_store.go index 3e539b83b3e..46001baf3ae 100644 --- a/pkg/storage/secret/metadata/secure_value_store.go +++ b/pkg/storage/secret/metadata/secure_value_store.go @@ -3,18 +3,21 @@ package metadata import ( "context" "fmt" + "strconv" "time" "github.com/prometheus/client_golang/prometheus" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" + "github.com/grafana/grafana-app-sdk/logging" secretv1beta1 "github.com/grafana/grafana/apps/secret/pkg/apis/secret/v1beta1" "github.com/grafana/grafana/pkg/registry/apis/secret/contracts" "github.com/grafana/grafana/pkg/registry/apis/secret/xkube" "github.com/grafana/grafana/pkg/storage/secret/metadata/metrics" "github.com/grafana/grafana/pkg/storage/unified/sql" "github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate" + "go.opentelemetry.io/otel/codes" ) var _ contracts.SecureValueMetadataStorage = (*secureValueMetadataStorage)(nil) @@ -40,16 +43,39 @@ type secureValueMetadataStorage struct { tracer trace.Tracer } -func (s *secureValueMetadataStorage) Create(ctx context.Context, sv *secretv1beta1.SecureValue, actorUID string) (*secretv1beta1.SecureValue, error) { +func (s *secureValueMetadataStorage) Create(ctx context.Context, sv *secretv1beta1.SecureValue, actorUID string) (_ *secretv1beta1.SecureValue, svmCreateErr error) { start := time.Now() + name := sv.GetName() + namespace := sv.GetNamespace() ctx, span := s.tracer.Start(ctx, "SecureValueMetadataStorage.Create", trace.WithAttributes( - attribute.String("name", sv.GetName()), - attribute.String("namespace", sv.GetNamespace()), + attribute.String("name", name), + attribute.String("namespace", namespace), attribute.String("actorUID", actorUID), )) defer span.End() - // Set inside of the transaction callback + defer func() { + args := []any{ + "name", name, + "namespace", namespace, + "actorUID", actorUID, + } + + success := svmCreateErr == nil + args = append(args, "success", success) + if !success { + span.SetStatus(codes.Error, "SecureValueMetadataStorage.Create failed") + span.RecordError(svmCreateErr) + args = append(args, "error", svmCreateErr) + } + + logging.FromContext(ctx).Info("SecureValueMetadataStorage.Create", args...) + + s.metrics.SecureValueMetadataCreateDuration.WithLabelValues(strconv.FormatBool(success)).Observe(time.Since(start).Seconds()) + s.metrics.SecureValueMetadataCreateCount.WithLabelValues(strconv.FormatBool(success)).Inc() + }() + + // Set inside the transaction callback var row *secureValueDB err := s.db.Transaction(ctx, func(ctx context.Context) error { @@ -145,9 +171,6 @@ func (s *secureValueMetadataStorage) Create(ctx context.Context, sv *secretv1bet return nil, fmt.Errorf("convert to kubernetes object: %w", err) } - s.metrics.SecureValueMetadataCreateDuration.Observe(time.Since(start).Seconds()) - s.metrics.SecureValueMetadataCreateCount.Inc() - return createdSecureValue, nil } @@ -230,7 +253,7 @@ func (s *secureValueMetadataStorage) readActiveVersion(ctx context.Context, name return secureValue, nil } -func (s *secureValueMetadataStorage) Read(ctx context.Context, namespace xkube.Namespace, name string, opts contracts.ReadOpts) (*secretv1beta1.SecureValue, error) { +func (s *secureValueMetadataStorage) Read(ctx context.Context, namespace xkube.Namespace, name string, opts contracts.ReadOpts) (_ *secretv1beta1.SecureValue, readErr error) { start := time.Now() ctx, span := s.tracer.Start(ctx, "SecureValueMetadataStorage.Read", trace.WithAttributes( attribute.String("name", name), @@ -239,6 +262,13 @@ func (s *secureValueMetadataStorage) Read(ctx context.Context, namespace xkube.N )) defer span.End() + defer func() { + logging.FromContext(ctx).Info("SecureValueMetadataStorage.Read", "namespace", namespace, "name", name, "success", readErr != nil, "error", readErr) + + s.metrics.SecureValueMetadataGetDuration.Observe(time.Since(start).Seconds()) + s.metrics.SecureValueMetadataGetCount.Inc() + }() + secureValue, err := s.readActiveVersion(ctx, namespace, name, opts) if err != nil { return nil, err @@ -249,9 +279,6 @@ func (s *secureValueMetadataStorage) Read(ctx context.Context, namespace xkube.N return nil, fmt.Errorf("convert to kubernetes object: %w", err) } - s.metrics.SecureValueMetadataGetDuration.Observe(time.Since(start).Seconds()) - s.metrics.SecureValueMetadataGetCount.Inc() - return secureValueKub, nil } From eecd813c5938a04c3c87e866d06005e068231fb8 Mon Sep 17 00:00:00 2001 From: Ezequiel Victorero Date: Wed, 30 Jul 2025 16:02:36 -0300 Subject: [PATCH 021/114] Chore: Update test case (#108943) --- .../app/features/explore/spec/helper/testdata/testQueryList.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/features/explore/spec/helper/testdata/testQueryList.ts b/public/app/features/explore/spec/helper/testdata/testQueryList.ts index 0f5d634490f..5d78a28215d 100644 --- a/public/app/features/explore/spec/helper/testdata/testQueryList.ts +++ b/public/app/features/explore/spec/helper/testdata/testQueryList.ts @@ -57,7 +57,7 @@ export const getTestQueryList = () => ({ kind: 'QueryTemplate', apiVersion: 'querylibrary.grafana.app/v0alpha1', metadata: { - name: 'ALoki296tj', + name: 'ALoki296ta', generateName: 'ALoki', namespace: 'default', uid: '3e71de65-efa7-40e3-8f23-124212cca455', From 5b0a118f32f9586b0a8f35688d4cadd956ad5759 Mon Sep 17 00:00:00 2001 From: Adela Almasan <88068998+adela-almasan@users.noreply.github.com> Date: Wed, 30 Jul 2025 14:28:58 -0500 Subject: [PATCH 022/114] Canvas: Add support for actions variables when one-click is enabled (#108724) --- packages/grafana-ui/src/index.ts | 1 + .../app/features/canvas/runtime/element.tsx | 63 ++++++++++++++++--- 2 files changed, 55 insertions(+), 9 deletions(-) diff --git a/packages/grafana-ui/src/index.ts b/packages/grafana-ui/src/index.ts index db367a8790e..d35561e598f 100644 --- a/packages/grafana-ui/src/index.ts +++ b/packages/grafana-ui/src/index.ts @@ -358,6 +358,7 @@ export { type UPlotConfigPrepFn } from './components/uPlot/config/UPlotConfigBui export * from './components/PanelChrome/types'; export { Label as BrowserLabel } from './components/BrowserLabel/Label'; export { PanelContainer } from './components/PanelContainer/PanelContainer'; +export { VariablesInputModal } from './components/Actions/VariablesInputModal'; // ----------------------------------------------------- // Graveyard: exported, but no longer used internally diff --git a/public/app/features/canvas/runtime/element.tsx b/public/app/features/canvas/runtime/element.tsx index 047d42a5e45..4659c2c0a84 100644 --- a/public/app/features/canvas/runtime/element.tsx +++ b/public/app/features/canvas/runtime/element.tsx @@ -10,9 +10,10 @@ import { ValueLinkConfig, OneClickMode, ActionModel, + ActionVariableInput, } from '@grafana/data'; import { t } from '@grafana/i18n'; -import { ConfirmModal } from '@grafana/ui'; +import { ConfirmModal, VariablesInputModal } from '@grafana/ui'; import { LayerElement } from 'app/core/components/Layers/types'; import { notFoundItem } from 'app/features/canvas/elements/notFound'; import { DimensionContext } from 'app/features/dimensions/context'; @@ -58,7 +59,15 @@ export class ElementState implements LayerElement { // cached for tooltips/mousemove oneClickMode = OneClickMode.Off; - showConfirmation = false; + showActionConfirmation = false; + + showActionVarsModal = false; + actionVars: ActionVariableInput = {}; + + setActionVars = (vars: ActionVariableInput) => { + this.actionVars = vars; + this.forceUpdate(); + }; constructor( public item: CanvasElementItem, @@ -394,6 +403,8 @@ export class ElementState implements LayerElement { this.oneClickMode = OneClickMode.Link; } else if (this.options.actions?.some((action) => action.oneClick === true)) { this.oneClickMode = OneClickMode.Action; + } else { + this.oneClickMode = OneClickMode.Off; } if (frames) { @@ -705,8 +716,16 @@ export class ElementState implements LayerElement { window.open(primaryDataLink.href, primaryDataLink.target ?? '_self'); } } else if (this.oneClickMode === OneClickMode.Action) { - this.showConfirmation = true; - this.forceUpdate(); + const primaryAction = this.getPrimaryAction(); + const actionHasVariables = primaryAction?.variables && primaryAction.variables.length > 0; + + if (actionHasVariables) { + this.showActionVarsModal = true; + this.forceUpdate(); + } else { + this.showActionConfirmation = true; + this.forceUpdate(); + } } else { this.handleTooltip(event); this.onTooltipCallback(); @@ -748,7 +767,7 @@ export class ElementState implements LayerElement { return ( <> - {this.showConfirmation && action && ( + {this.showActionConfirmation && action && ( { - this.showConfirmation = false; - action.onClick(new MouseEvent('click')); + this.showActionConfirmation = false; + action.onClick(new MouseEvent('click'), null, this.actionVars); this.forceUpdate(); }} onDismiss={() => { - this.showConfirmation = false; + this.showActionConfirmation = false; this.forceUpdate(); }} /> @@ -770,6 +789,31 @@ export class ElementState implements LayerElement { ); }; + renderVariablesInputModal = (action: ActionModel | undefined) => { + if (!action || !action.variables || action.variables.length === 0) { + return; + } + + const onModalContinue = () => { + this.showActionVarsModal = false; + this.showActionConfirmation = true; + this.forceUpdate(); + }; + + return ( + { + this.showActionVarsModal = false; + this.forceUpdate(); + }} + onShowConfirm={onModalContinue} + /> + ); + }; + render() { const { item, div } = this; const scene = this.getScene(); @@ -794,7 +838,8 @@ export class ElementState implements LayerElement { isSelected={isSelected} /> - {this.showConfirmation && this.renderActionsConfirmModal(this.getPrimaryAction())} + {this.showActionConfirmation && this.renderActionsConfirmModal(this.getPrimaryAction())} + {this.showActionVarsModal && this.renderVariablesInputModal(this.getPrimaryAction())} ); } From 9022357fb7f5462f0b34558bcd4cdb0a0530c9c1 Mon Sep 17 00:00:00 2001 From: Adela Almasan <88068998+adela-almasan@users.noreply.github.com> Date: Wed, 30 Jul 2025 15:26:36 -0500 Subject: [PATCH 023/114] Canvas: Add option to disable the tooltip (#108196) --- .../visualizations/canvas/index.md | 8 ++++ .../panelcfg/x/CanvasPanelCfg_types.gen.ts | 8 ++++ .../app/features/canvas/runtime/element.tsx | 11 ++--- public/app/features/canvas/runtime/scene.tsx | 43 +++++++++++-------- .../app/plugins/panel/canvas/CanvasPanel.tsx | 32 +++----------- .../panel/canvas/components/CanvasTooltip.tsx | 18 ++++---- public/app/plugins/panel/canvas/module.tsx | 24 ++++++++++- public/app/plugins/panel/canvas/panelcfg.cue | 5 +++ .../app/plugins/panel/canvas/panelcfg.gen.ts | 8 ++++ public/locales/en-US/grafana.json | 8 ++++ 10 files changed, 106 insertions(+), 59 deletions(-) diff --git a/docs/sources/panels-visualizations/visualizations/canvas/index.md b/docs/sources/panels-visualizations/visualizations/canvas/index.md index 4be10911af6..a5f4712c502 100644 --- a/docs/sources/panels-visualizations/visualizations/canvas/index.md +++ b/docs/sources/panels-visualizations/visualizations/canvas/index.md @@ -270,6 +270,14 @@ You can enable infinite panning in a canvas when pan and zoom is enabled. This a Infinite panning is an experimental feature that may not work as expected in all scenarios. For example, elements that are not top-left constrained may experience unexpected movement when panning. {{< /admonition >}} +### Tooltip options + +The **Tooltip mode** setting controls the display of tooltips when hovering over canvas elements that are connected to data, data links, or actions. +The options are: + +- **Enabled** - Show a tooltip when the cursor hovers over an element. +- **Disabled** - Tooltips are not shown on hover. + ### Layer options The **Layer** options let you add elements to the canvas and control its appearance: diff --git a/packages/grafana-schema/src/raw/composable/canvas/panelcfg/x/CanvasPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/canvas/panelcfg/x/CanvasPanelCfg_types.gen.ts index 983129db72b..722317c115a 100644 --- a/packages/grafana-schema/src/raw/composable/canvas/panelcfg/x/CanvasPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/canvas/panelcfg/x/CanvasPanelCfg_types.gen.ts @@ -112,6 +112,10 @@ export const defaultCanvasElementOptions: Partial = { connections: [], }; +export interface CanvasTooltip { + mode: ui.TooltipDisplayMode; +} + export interface Options { /** * Enable infinite pan @@ -147,6 +151,10 @@ export interface Options { * Show all available element types */ showAdvancedTypes: boolean; + /** + * Controls tooltip options + */ + tooltip: CanvasTooltip; } export const defaultOptions: Partial = { diff --git a/public/app/features/canvas/runtime/element.tsx b/public/app/features/canvas/runtime/element.tsx index 4659c2c0a84..d06c7d5caee 100644 --- a/public/app/features/canvas/runtime/element.tsx +++ b/public/app/features/canvas/runtime/element.tsx @@ -13,6 +13,7 @@ import { ActionVariableInput, } from '@grafana/data'; import { t } from '@grafana/i18n'; +import { TooltipDisplayMode } from '@grafana/schema'; import { ConfirmModal, VariablesInputModal } from '@grafana/ui'; import { LayerElement } from 'app/core/components/Layers/types'; import { notFoundItem } from 'app/features/canvas/elements/notFound'; @@ -619,7 +620,7 @@ export class ElementState implements LayerElement { handleMouseEnter = (event: React.MouseEvent, isSelected: boolean | undefined) => { const scene = this.getScene(); - const shouldHandleTooltip = !scene?.isEditingEnabled && !scene?.tooltip?.isOpen; + const shouldHandleTooltip = !scene?.isEditingEnabled && !scene?.tooltipPayload?.isOpen; if (shouldHandleTooltip) { this.handleTooltip(event); } else if (!isSelected) { @@ -686,7 +687,7 @@ export class ElementState implements LayerElement { handleTooltip = (event: React.MouseEvent) => { const scene = this.getScene(); - if (scene?.tooltipCallback) { + if (scene?.tooltipCallback && scene.tooltipMode !== TooltipDisplayMode.None) { const rect = this.div?.getBoundingClientRect(); scene.tooltipCallback({ anchorPoint: { x: rect?.right ?? event.pageX, y: rect?.top ?? event.pageY }, @@ -698,7 +699,7 @@ export class ElementState implements LayerElement { handleMouseLeave = (event: React.MouseEvent) => { const scene = this.getScene(); - if (scene?.tooltipCallback && !scene?.tooltip?.isOpen) { + if (scene?.tooltipCallback && !scene?.tooltipPayload?.isOpen) { scene.tooltipCallback(undefined); } @@ -744,9 +745,9 @@ export class ElementState implements LayerElement { onTooltipCallback = () => { const scene = this.getScene(); - if (scene?.tooltipCallback && scene.tooltip?.anchorPoint) { + if (scene?.tooltipCallback && scene.tooltipPayload?.anchorPoint) { scene.tooltipCallback({ - anchorPoint: { x: scene.tooltip.anchorPoint.x, y: scene.tooltip.anchorPoint.y }, + anchorPoint: { x: scene.tooltipPayload.anchorPoint.x, y: scene.tooltipPayload.anchorPoint.y }, element: this, isOpen: true, }); diff --git a/public/app/features/canvas/runtime/scene.tsx b/public/app/features/canvas/runtime/scene.tsx index 5e54afd9be8..298b232e16b 100644 --- a/public/app/features/canvas/runtime/scene.tsx +++ b/public/app/features/canvas/runtime/scene.tsx @@ -13,6 +13,7 @@ import { ScalarDimensionConfig, ScaleDimensionConfig, TextDimensionConfig, + TooltipDisplayMode, } from '@grafana/schema'; import { Portal } from '@grafana/ui'; import { config } from 'app/core/config'; @@ -27,11 +28,13 @@ import { import { CanvasContextMenu } from 'app/plugins/panel/canvas/components/CanvasContextMenu'; import { CanvasTooltip } from 'app/plugins/panel/canvas/components/CanvasTooltip'; import { Connections } from 'app/plugins/panel/canvas/components/connections/Connections'; +import { Options } from 'app/plugins/panel/canvas/panelcfg.gen'; import { AnchorPoint, CanvasTooltipPayload } from 'app/plugins/panel/canvas/types'; import { getTransformInstance } from 'app/plugins/panel/canvas/utils'; import appEvents from '../../../core/app_events'; import { CanvasPanel } from '../../../plugins/panel/canvas/CanvasPanel'; +import { getDashboardSrv } from '../../dashboard/services/DashboardSrv'; import { CanvasFrameOptions } from '../frame'; import { DEFAULT_CANVAS_ELEMENT_CONFIG } from '../registry'; @@ -71,6 +74,7 @@ export class Scene { shouldShowAdvancedTypes?: boolean; shouldPanZoom?: boolean; shouldInfinitePan?: boolean; + tooltipMode?: TooltipDisplayMode; skipNextSelectionBroadcast = false; ignoreDataUpdate = false; panel: CanvasPanel; @@ -93,7 +97,7 @@ export class Scene { setBackgroundCallback?: (anchorPoint: AnchorPoint) => void; tooltipCallback?: (tooltip: CanvasTooltipPayload | undefined) => void; - tooltip?: CanvasTooltipPayload; + tooltipPayload?: CanvasTooltipPayload; moveableActionCallback?: (moved: boolean) => void; @@ -106,15 +110,16 @@ export class Scene { transformComponentRef: RefObject | undefined; constructor( - cfg: CanvasFrameOptions, - enableEditing: boolean, - showAdvancedTypes: boolean, - panZoom: boolean, - infinitePan: boolean, + options: Options, public onSave: (cfg: CanvasFrameOptions) => void, panel: CanvasPanel ) { - this.root = this.load(cfg, enableEditing, showAdvancedTypes, panZoom, infinitePan); + // TODO: Will need to update this approach for dashboard scenes + // migration (new dashboard edit experience) + const dashboard = getDashboardSrv().getCurrent(); + const enableEditing = options.inlineEditing && dashboard?.editable; + + this.root = this.load(options, enableEditing); this.subscription = this.editModeEnabled.subscribe((open) => { if (!this.moveable || !this.isEditingEnabled) { @@ -147,15 +152,12 @@ export class Scene { return !this.byName.has(v); }; - load( - cfg: CanvasFrameOptions, - enableEditing: boolean, - showAdvancedTypes: boolean, - panZoom: boolean, - infinitePan: boolean - ) { + load(options: Options, enableEditing: boolean) { + const { root, showAdvancedTypes, panZoom, infinitePan, tooltip } = options; + const tooltipMode = tooltip?.mode ?? TooltipDisplayMode.Single; + this.root = new RootElement( - cfg ?? { + root ?? { type: 'frame', elements: [DEFAULT_CANVAS_ELEMENT_CONFIG], }, @@ -167,6 +169,7 @@ export class Scene { this.shouldShowAdvancedTypes = showAdvancedTypes; this.shouldPanZoom = panZoom; this.shouldInfinitePan = infinitePan; + this.tooltipMode = tooltipMode; setTimeout(() => { if (this.div) { @@ -283,11 +286,13 @@ export class Scene { }; render() { - const hasDataLinks = this.tooltip?.element?.getLinks && this.tooltip.element.getLinks({}).length > 0; - const hasActions = this.tooltip?.element?.options.actions && this.tooltip.element.options.actions.length > 0; + const hasDataLinks = this.tooltipPayload?.element?.getLinks && this.tooltipPayload.element.getLinks({}).length > 0; + const hasActions = + this.tooltipPayload?.element?.options.actions && this.tooltipPayload.element.options.actions.length > 0; - const isTooltipValid = hasDataLinks || hasActions || this.tooltip?.element?.data?.field; - const canShowElementTooltip = !this.isEditingEnabled && isTooltipValid; + const isTooltipValid = hasDataLinks || hasActions || this.tooltipPayload?.element?.data?.field; + const isTooltipEnabled = this.tooltipMode !== TooltipDisplayMode.None; + const canShowElementTooltip = !this.isEditingEnabled && isTooltipValid && isTooltipEnabled; const sceneDiv = (
diff --git a/public/app/plugins/panel/canvas/CanvasPanel.tsx b/public/app/plugins/panel/canvas/CanvasPanel.tsx index 10f43819839..3af277aa833 100644 --- a/public/app/plugins/panel/canvas/CanvasPanel.tsx +++ b/public/app/plugins/panel/canvas/CanvasPanel.tsx @@ -1,5 +1,5 @@ -import { Component } from 'react'; import * as React from 'react'; +import { Component } from 'react'; import { ReplaySubject, Subscription } from 'rxjs'; import { PanelProps } from '@grafana/data'; @@ -8,7 +8,6 @@ import { PanelContext, PanelContextRoot } from '@grafana/ui'; import { CanvasFrameOptions } from 'app/features/canvas/frame'; import { ElementState } from 'app/features/canvas/runtime/element'; import { Scene } from 'app/features/canvas/runtime/scene'; -import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv'; import { PanelEditEnteredEvent, PanelEditExitedEvent } from 'app/types/events'; import { SetBackground } from './components/SetBackground'; @@ -63,22 +62,9 @@ export class CanvasPanel extends Component { moveableAction: false, }; - // TODO: Will need to update this approach for dashboard scenes - // migration (new dashboard edit experience) - const dashboard = getDashboardSrv().getCurrent(); - const allowEditing = this.props.options.inlineEditing && dashboard?.editable; - // Only the initial options are ever used. // later changes are all controlled by the scene - this.scene = new Scene( - this.props.options.root, - allowEditing, - this.props.options.showAdvancedTypes, - this.props.options.panZoom, - this.props.options.infinitePan, - this.onUpdateScene, - this - ); + this.scene = new Scene(this.props.options, this.onUpdateScene, this); this.scene.updateSize(props.width, props.height); this.scene.updateData(props.data); this.scene.inlineEditingCallback = this.openInlineEdit; @@ -239,12 +225,14 @@ export class CanvasPanel extends Component { this.props.options.showAdvancedTypes !== nextProps.options.showAdvancedTypes; const panZoomSwitched = this.props.options.panZoom !== nextProps.options.panZoom; const infinitePanSwitched = this.props.options.infinitePan !== nextProps.options.infinitePan; + const tooltipModeSwitched = this.props.options.tooltip?.mode !== nextProps.options.tooltip?.mode; if ( this.needsReload || inlineEditingSwitched || shouldShowAdvancedTypesSwitched || panZoomSwitched || - infinitePanSwitched + infinitePanSwitched || + tooltipModeSwitched ) { if (inlineEditingSwitched) { // Replace scene div to prevent selecto instance leaks @@ -252,13 +240,7 @@ export class CanvasPanel extends Component { } this.needsReload = false; - this.scene.load( - nextProps.options.root, - nextProps.options.inlineEditing, - nextProps.options.showAdvancedTypes, - nextProps.options.panZoom, - nextProps.options.infinitePan - ); + this.scene.load(nextProps.options, nextProps.options.inlineEditing); this.scene.updateSize(nextProps.width, nextProps.height); this.scene.updateData(nextProps.data); changed = true; @@ -294,7 +276,7 @@ export class CanvasPanel extends Component { }; tooltipCallback = (tooltip: CanvasTooltipPayload | undefined) => { - this.scene.tooltip = tooltip; + this.scene.tooltipPayload = tooltip; this.forceUpdate(); }; diff --git a/public/app/plugins/panel/canvas/components/CanvasTooltip.tsx b/public/app/plugins/panel/canvas/components/CanvasTooltip.tsx index ceb17a8ce7d..aa0e29741c5 100644 --- a/public/app/plugins/panel/canvas/components/CanvasTooltip.tsx +++ b/public/app/plugins/panel/canvas/components/CanvasTooltip.tsx @@ -36,7 +36,7 @@ export const CanvasTooltip = ({ scene }: Props) => { const styles = useStyles2(getStyles); const onClose = () => { - if (scene?.tooltipCallback && scene.tooltip) { + if (scene?.tooltipCallback && scene.tooltipPayload) { scene.tooltipCallback(undefined); } }; @@ -45,7 +45,7 @@ export const CanvasTooltip = ({ scene }: Props) => { const { overlayProps } = useOverlay({ onClose: onClose, isDismissable: true }, ref); const { dialogProps } = useDialog({}, ref); - const element = scene.tooltip?.element; + const element = scene.tooltipPayload?.element; if (!element) { return <>; } @@ -138,18 +138,18 @@ export const CanvasTooltip = ({ scene }: Props) => { return ( <> - {scene.tooltip?.element && scene.tooltip.anchorPoint && ( + {scene.tooltipPayload?.element && scene.tooltipPayload.anchorPoint && (
- {scene.tooltip.isOpen && } - - {element.data.text && } + {scene.tooltipPayload.isOpen && } + + {element.data.text && } {(links.length > 0 || actions.length > 0) && }
diff --git a/public/app/plugins/panel/canvas/module.tsx b/public/app/plugins/panel/canvas/module.tsx index af54dbc280d..512509841ca 100644 --- a/public/app/plugins/panel/canvas/module.tsx +++ b/public/app/plugins/panel/canvas/module.tsx @@ -1,6 +1,7 @@ import { FieldConfigProperty, PanelOptionsEditorBuilder, PanelPlugin } from '@grafana/data'; import { t } from '@grafana/i18n'; import { config } from '@grafana/runtime'; +import { TooltipDisplayMode } from '@grafana/schema/dist/esm/common/common.gen'; import { FrameState } from 'app/features/canvas/runtime/frame'; import { CanvasPanel, InstanceState } from './CanvasPanel'; @@ -12,7 +13,7 @@ import { canvasMigrationHandler } from './migrations'; import { Options } from './panelcfg.gen'; export const addStandardCanvasEditorOptions = (builder: PanelOptionsEditorBuilder) => { - const category = [t('canvas.category-canvas', 'Canvas')]; + let category = [t('canvas.category-canvas', 'Canvas')]; builder.addBooleanSwitch({ path: 'inlineEditing', name: t('canvas.name-inline-editing', 'Inline editing'), @@ -56,6 +57,27 @@ export const addStandardCanvasEditorOptions = (builder: PanelOptionsEditorBuilde defaultValue: false, showIf: (opts) => config.featureToggles.canvasPanelPanZoom && opts.panZoom, }); + + category = [t('canvas.category-tooltip', 'Tooltip')]; + + builder.addRadio({ + path: 'tooltip.mode', + name: t('canvas.tooltip-options.name-tooltip-mode', 'Tooltip mode'), + category, + defaultValue: TooltipDisplayMode.Single, + settings: { + options: [ + { + value: TooltipDisplayMode.Single, + label: t('canvas.tooltip-options.tooltip-mode-options.label-enabled', 'Enabled'), + }, + { + value: TooltipDisplayMode.None, + label: t('canvas.tooltip-options.tooltip-mode-options.label-disabled', 'Disabled'), + }, + ], + }, + }); }; export const plugin = new PanelPlugin(CanvasPanel) diff --git a/public/app/plugins/panel/canvas/panelcfg.cue b/public/app/plugins/panel/canvas/panelcfg.cue index 6da7cebacc4..f32837ea1e8 100644 --- a/public/app/plugins/panel/canvas/panelcfg.cue +++ b/public/app/plugins/panel/canvas/panelcfg.cue @@ -88,6 +88,9 @@ composableKinds: PanelCfg: { border?: LineConfig connections?: [...CanvasConnection] } @cuetsy(kind="interface") + CanvasTooltip: { + mode: ui.TooltipDisplayMode + } @cuetsy(kind="interface") Options: { // Enable inline editing @@ -108,6 +111,8 @@ composableKinds: PanelCfg: { // The list of canvas elements attached to the root element elements: [...CanvasElementOptions] } @cuetsy(kind="interface") + // Controls tooltip options + tooltip: CanvasTooltip } @cuetsy(kind="interface") } }] diff --git a/public/app/plugins/panel/canvas/panelcfg.gen.ts b/public/app/plugins/panel/canvas/panelcfg.gen.ts index 9706c886f6d..be425f6c764 100644 --- a/public/app/plugins/panel/canvas/panelcfg.gen.ts +++ b/public/app/plugins/panel/canvas/panelcfg.gen.ts @@ -110,6 +110,10 @@ export const defaultCanvasElementOptions: Partial = { connections: [], }; +export interface CanvasTooltip { + mode: ui.TooltipDisplayMode; +} + export interface Options { /** * Enable infinite pan @@ -145,6 +149,10 @@ export interface Options { * Show all available element types */ showAdvancedTypes: boolean; + /** + * Controls tooltip options + */ + tooltip: CanvasTooltip; } export const defaultOptions: Partial = { diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index 5e9f8d224bc..0a72605b908 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -3738,6 +3738,7 @@ "category-selected-connection": "Selected connection", "category-selected-element": "Selected element ({{element}})", "category-size": "Size", + "category-tooltip": "Tooltip", "cloud-item": { "category-cloud": "Cloud", "label": { @@ -3982,6 +3983,13 @@ "auto": "Auto" } }, + "tooltip-options": { + "name-tooltip-mode": "Tooltip mode", + "tooltip-mode-options": { + "label-disabled": "Disabled", + "label-enabled": "Enabled" + } + }, "tree-navigation-editor": { "clear-selection": "Clear selection", "frame-selection": "Frame selection", From 0f53290ca70eb2bc00569dbba42993b1138b1d75 Mon Sep 17 00:00:00 2001 From: Adela Almasan <88068998+adela-almasan@users.noreply.github.com> Date: Wed, 30 Jul 2025 15:32:43 -0500 Subject: [PATCH 024/114] Chore: Decouple `hideFrom.viz` and `hideFrom.tooltip` (#106857) --- packages/grafana-data/src/field/fieldState.ts | 2 +- .../VizTooltip/VizTooltipColorIndicator.tsx | 20 ++++++++ .../VizTooltip/VizTooltipContent.tsx | 3 +- .../components/VizTooltip/VizTooltipRow.tsx | 9 +++- .../src/components/VizTooltip/types.ts | 1 + .../src/components/VizTooltip/utils.ts | 49 ++++++++++++++----- .../dashgrid/SeriesVisibilityConfigFactory.ts | 4 +- .../dashboard/state/DashboardMigrator.ts | 24 +++++++++ .../app/plugins/panel/piechart/PieChart.tsx | 2 +- .../panel/timeseries/TimeSeriesTooltip.tsx | 13 +---- 10 files changed, 98 insertions(+), 29 deletions(-) diff --git a/packages/grafana-data/src/field/fieldState.ts b/packages/grafana-data/src/field/fieldState.ts index 3ddaebbda63..2501444a3a0 100644 --- a/packages/grafana-data/src/field/fieldState.ts +++ b/packages/grafana-data/src/field/fieldState.ts @@ -62,7 +62,7 @@ export function cacheFieldDisplayNames(frames: DataFrame[]) { /** * * moves each field's config.custom.hideFrom to field.state.hideFrom - * and mutates orgiginal field.config.custom.hideFrom to one with explicit overrides only, (without the ad-hoc stateful __system override from legend toggle) + * and mutates original field.config.custom.hideFrom to one with explicit overrides only, (without the ad-hoc stateful __system override from legend toggle) */ export function decoupleHideFromState(frames: DataFrame[], fieldConfig: FieldConfigSource) { frames.forEach((frame) => { diff --git a/packages/grafana-ui/src/components/VizTooltip/VizTooltipColorIndicator.tsx b/packages/grafana-ui/src/components/VizTooltip/VizTooltipColorIndicator.tsx index 34c251eee13..789b50cba2a 100644 --- a/packages/grafana-ui/src/components/VizTooltip/VizTooltipColorIndicator.tsx +++ b/packages/grafana-ui/src/components/VizTooltip/VizTooltipColorIndicator.tsx @@ -19,6 +19,7 @@ interface Props { colorIndicator?: ColorIndicator; position?: ColorIndicatorPosition; lineStyle?: LineStyle; + isHollow?: boolean; } export type ColorIndicatorStyles = ReturnType; @@ -28,9 +29,22 @@ export const VizTooltipColorIndicator = ({ colorIndicator = DEFAULT_COLOR_INDICATOR, position = ColorIndicatorPosition.Leading, lineStyle, + isHollow, }: Props) => { const styles = useStyles2(getStyles); + if (isHollow) { + return ( +
+ ); + } + if (colorIndicator === ColorIndicator.series) { return ( ({ trailing: css({ marginLeft: theme.spacing(0.5), }), + series: css({ + width: '14px', + height: '4px', + borderRadius: theme.shape.radius.pill, + minWidth: '14px', + }), value: css({ width: '12px', height: '12px', diff --git a/packages/grafana-ui/src/components/VizTooltip/VizTooltipContent.tsx b/packages/grafana-ui/src/components/VizTooltip/VizTooltipContent.tsx index 40f19c37157..661581237a7 100644 --- a/packages/grafana-ui/src/components/VizTooltip/VizTooltipContent.tsx +++ b/packages/grafana-ui/src/components/VizTooltip/VizTooltipContent.tsx @@ -34,7 +34,7 @@ export const VizTooltipContent = ({ return (
- {items.map(({ label, value, color, colorIndicator, colorPlacement, isActive, lineStyle }, i) => ( + {items.map(({ label, value, color, colorIndicator, colorPlacement, isActive, lineStyle, isHiddenFromViz }, i) => ( ))} {children} diff --git a/packages/grafana-ui/src/components/VizTooltip/VizTooltipRow.tsx b/packages/grafana-ui/src/components/VizTooltip/VizTooltipRow.tsx index 19d0bed7874..1471dc5e38a 100644 --- a/packages/grafana-ui/src/components/VizTooltip/VizTooltipRow.tsx +++ b/packages/grafana-ui/src/components/VizTooltip/VizTooltipRow.tsx @@ -18,6 +18,7 @@ interface VizTooltipRowProps extends Omit { marginRight?: string; isPinned: boolean; showValueScroll?: boolean; + isHiddenFromViz?: boolean; } enum LabelValueTypes { @@ -41,6 +42,7 @@ export const VizTooltipRow = ({ isPinned, lineStyle, showValueScroll, + isHiddenFromViz, }: VizTooltipRowProps) => { const styles = useStyles2(getStyles, justify, marginRight); @@ -132,7 +134,12 @@ export const VizTooltipRow = ({ {(color || label) && (
{color && colorPlacement === ColorPlacement.first && ( - + )} {!isPinned ? (
{label}
diff --git a/packages/grafana-ui/src/components/VizTooltip/types.ts b/packages/grafana-ui/src/components/VizTooltip/types.ts index de4fbae048f..bd4f66488fa 100644 --- a/packages/grafana-ui/src/components/VizTooltip/types.ts +++ b/packages/grafana-ui/src/components/VizTooltip/types.ts @@ -27,6 +27,7 @@ export interface VizTooltipItem { colorPlacement?: ColorPlacement; isActive?: boolean; lineStyle?: LineStyle; + isHiddenFromViz?: boolean; // internal/tmp for sorting numeric?: number; diff --git a/packages/grafana-ui/src/components/VizTooltip/utils.ts b/packages/grafana-ui/src/components/VizTooltip/utils.ts index e192340974f..d14d636caa5 100644 --- a/packages/grafana-ui/src/components/VizTooltip/utils.ts +++ b/packages/grafana-ui/src/components/VizTooltip/utils.ts @@ -47,6 +47,8 @@ export const calculateTooltipPosition = ( export const getColorIndicatorClass = (colorIndicator: string, styles: ColorIndicatorStyles) => { switch (colorIndicator) { + case ColorIndicator.series: + return styles.series; case ColorIndicator.value: return styles.value; case ColorIndicator.hexagon: @@ -80,7 +82,8 @@ export const getContentItems = ( mode: TooltipDisplayMode, sortOrder: SortOrder, fieldFilter = (field: Field) => true, - hideZeros = false + hideZeros = false, + _restFields?: Field[] ): VizTooltipItem[] => { let rows: VizTooltipItem[] = []; @@ -93,8 +96,7 @@ export const getContentItems = ( field === xField || field.type === FieldType.time || !fieldFilter(field) || - field.config.custom?.hideFrom?.tooltip || - field.config.custom?.hideFrom?.viz + field.config.custom?.hideFrom?.tooltip ) { continue; } @@ -130,15 +132,7 @@ export const getContentItems = ( ? Number.MIN_SAFE_INTEGER : Number.MAX_SAFE_INTEGER; - const colorMode = getFieldColorModeForField(field); - - let colorIndicator = ColorIndicator.series; - let colorPlacement = ColorPlacement.first; - - if (colorMode.isByValue) { - colorIndicator = ColorIndicator.value; - colorPlacement = ColorPlacement.trailing; - } + const { colorIndicator, colorPlacement } = getIndicatorAndPlacement(field); rows.push({ label: field.state?.displayName ?? field.name, @@ -152,6 +146,23 @@ export const getContentItems = ( }); } + _restFields?.forEach((field) => { + if (!field.config.custom?.hideFrom?.tooltip) { + const { colorIndicator, colorPlacement } = getIndicatorAndPlacement(field); + const display = field.display!(field.values[dataIdxs[0]!]); + + rows.push({ + label: field.state?.displayName ?? field.name, + value: formattedValueToString(display), + color: FALLBACK_COLOR, + colorIndicator, + colorPlacement, + lineStyle: field.config.custom?.lineStyle, + isHiddenFromViz: true, + }); + } + }); + if (sortOrder !== SortOrder.None && rows.length > 1) { const cmp = allNumeric ? numberCmp : stringCmp; const mult = sortOrder === SortOrder.Descending ? -1 : 1; @@ -160,3 +171,17 @@ export const getContentItems = ( return rows; }; + +const getIndicatorAndPlacement = (field: Field) => { + const colorMode = getFieldColorModeForField(field); + + let colorIndicator = ColorIndicator.series; + let colorPlacement = ColorPlacement.first; + + if (colorMode.isByValue) { + colorIndicator = ColorIndicator.value; + colorPlacement = ColorPlacement.trailing; + } + + return { colorIndicator, colorPlacement }; +}; diff --git a/public/app/features/dashboard/dashgrid/SeriesVisibilityConfigFactory.ts b/public/app/features/dashboard/dashgrid/SeriesVisibilityConfigFactory.ts index a03cc64bb5a..412d37d4bfc 100644 --- a/public/app/features/dashboard/dashgrid/SeriesVisibilityConfigFactory.ts +++ b/public/app/features/dashboard/dashgrid/SeriesVisibilityConfigFactory.ts @@ -97,7 +97,7 @@ function createOverride( value: { viz: true, legend: false, - tooltip: false, + tooltip: true, }, }; @@ -118,7 +118,7 @@ function createOverride( value: { viz: true, legend: false, - tooltip: false, + tooltip: true, }, }, ], diff --git a/public/app/features/dashboard/state/DashboardMigrator.ts b/public/app/features/dashboard/state/DashboardMigrator.ts index da9a51195f1..6b82290c993 100644 --- a/public/app/features/dashboard/state/DashboardMigrator.ts +++ b/public/app/features/dashboard/state/DashboardMigrator.ts @@ -927,6 +927,10 @@ export class DashboardMigrator { } } + if (oldVersion < 42) { + panelUpgrades.push(migrateHideFromFunctionality); + } + /** * -==- Add migration here -==- * Your migration should go below the previous @@ -1480,3 +1484,23 @@ function ensureXAxisVisibility(panel: PanelModel) { return panel; } + +function migrateHideFromFunctionality(panel: PanelModel) { + // migrate overrides with hideFrom.viz = true to also set tooltip = true + // this includes the __systemRef override + if (panel.fieldConfig && panel.fieldConfig.overrides) { + panel.fieldConfig.overrides = panel.fieldConfig.overrides.map((override) => { + if (override.properties) { + override.properties = override.properties.map((property) => { + if (property.id === 'custom.hideFrom' && property.value?.viz === true) { + property.value.tooltip = true; + } + return property; + }); + } + return override; + }); + } + + return panel; +} diff --git a/public/app/plugins/panel/piechart/PieChart.tsx b/public/app/plugins/panel/piechart/PieChart.tsx index f0bcb6d7cc0..3abbe438cfb 100644 --- a/public/app/plugins/panel/piechart/PieChart.tsx +++ b/public/app/plugins/panel/piechart/PieChart.tsx @@ -325,7 +325,7 @@ function getTooltipData( } const customConfig = pa.data.field.custom; - return !customConfig?.hideFrom?.tooltip && !customConfig?.hideFrom?.viz; + return !customConfig?.hideFrom?.tooltip; }) .map((pieArc) => { return { diff --git a/public/app/plugins/panel/timeseries/TimeSeriesTooltip.tsx b/public/app/plugins/panel/timeseries/TimeSeriesTooltip.tsx index 36cf73bb58a..8c0bae437e9 100644 --- a/public/app/plugins/panel/timeseries/TimeSeriesTooltip.tsx +++ b/public/app/plugins/panel/timeseries/TimeSeriesTooltip.tsx @@ -13,7 +13,6 @@ import { } from '@grafana/ui/internal'; import { getFieldActions } from '../status-history/utils'; -import { fmt } from '../xychart/utils'; import { isTooltipScrollable } from './utils'; @@ -71,18 +70,10 @@ export const TimeSeriesTooltip = ({ mode, sortOrder, (field) => field.type === FieldType.number || field.type === FieldType.enum, - hideZeros + hideZeros, + _rest ); - _rest?.forEach((field) => { - if (!field.config.custom?.hideFrom?.tooltip) { - contentItems.push({ - label: field.state?.displayName ?? field.name, - value: fmt(field, field.values[dataIdxs[0]!]), - }); - } - }); - let footer: ReactNode; if (seriesIdx != null) { From 8729ea2dbad9d3ee8cd49cb0fbf5d060a083a661 Mon Sep 17 00:00:00 2001 From: Todd Treece <360020+toddtreece@users.noreply.github.com> Date: Wed, 30 Jul 2025 17:11:39 -0400 Subject: [PATCH 025/114] Apps: Fix status key functions (#108915) --- apps/advisor/go.mod | 4 +- apps/advisor/go.sum | 12 +-- apps/alerting/notifications/go.mod | 4 +- apps/alerting/notifications/go.sum | 8 +- apps/dashboard/go.mod | 4 +- apps/dashboard/go.sum | 8 +- apps/folder/go.mod | 4 +- apps/folder/go.sum | 8 +- apps/iam/go.mod | 4 +- apps/iam/go.sum | 8 +- apps/investigations/go.mod | 4 +- apps/investigations/go.sum | 12 +-- apps/playlist/go.mod | 4 +- apps/playlist/go.sum | 8 +- apps/sdk.mk | 2 +- apps/secret/go.mod | 4 +- apps/secret/go.sum | 8 +- go.mod | 6 +- go.sum | 12 +-- go.work.sum | 22 ++++-- pkg/apiserver/go.mod | 2 +- pkg/apiserver/go.sum | 4 +- .../apiserver/appinstaller/installer.go | 1 - pkg/services/apiserver/appinstaller/server.go | 78 +++++++++++-------- 24 files changed, 129 insertions(+), 102 deletions(-) diff --git a/apps/advisor/go.mod b/apps/advisor/go.mod index 4559d1045d9..33afd0a0ae6 100644 --- a/apps/advisor/go.mod +++ b/apps/advisor/go.mod @@ -7,8 +7,8 @@ require ( github.com/google/go-github/v70 v70.0.0 github.com/grafana/authlib/types v0.0.0-20250710201142-9542f2f28d43 github.com/grafana/grafana v0.0.0-00010101000000-000000000000 - github.com/grafana/grafana-app-sdk v0.40.1 - github.com/grafana/grafana-app-sdk/logging v0.40.0 + 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/stretchr/testify v1.10.0 diff --git a/apps/advisor/go.sum b/apps/advisor/go.sum index b52f4e5a7ab..7adf9d1eb21 100644 --- a/apps/advisor/go.sum +++ b/apps/advisor/go.sum @@ -663,18 +663,18 @@ github.com/grafana/dataplane/sdata v0.0.9 h1:AGL1LZnCUG4MnQtnWpBPbQ8ZpptaZs14w6k github.com/grafana/dataplane/sdata v0.0.9/go.mod h1:Jvs5ddpGmn6vcxT7tCTWAZ1mgi4sbcdFt9utQx5uMAU= github.com/grafana/dskit v0.0.0-20250611075409-46f51e1ce914 h1:qcSGhr691f1mmPHwg2svGyO40Ex92G02aOyHzP6XHCE= github.com/grafana/dskit v0.0.0-20250611075409-46f51e1ce914/go.mod h1:OiN4P4aC6LwLzLbEupH3Ue83VfQoNMfG48rsna8jI/E= -github.com/grafana/grafana-app-sdk v0.40.1 h1:W8d1CQSgMg/d37xf/0t4PiSa7wVD21XFE8mF6P59YSg= -github.com/grafana/grafana-app-sdk v0.40.1/go.mod h1:4P8h7VB6KcDjX9bAoBQc6IP8iNylxe6bSXLR9gA39gM= -github.com/grafana/grafana-app-sdk/logging v0.40.0 h1:3LHA0pFM9mGGFyq6qoHvJOGEe0H0OVhzzlOKd4Ehu+E= -github.com/grafana/grafana-app-sdk/logging v0.40.0/go.mod h1:otUD9XpJD7A5sCLb8mcs9hIXGdeV6lnhzVwe747g4RU= +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-aws-sdk v1.0.4 h1:D14UAehsOqpjliHmHzveRQ1p43KCsMzdmb7GovWj+SY= github.com/grafana/grafana-aws-sdk v1.0.4/go.mod h1:hO7q7yWV+t6dmiyJjMa3IbuYnYkBua+G/IAlOPVIYKE= github.com/grafana/grafana-azure-sdk-go/v2 v2.2.0 h1:0TYrkzAc3u0HX+9GK86cGrLTUAcmQfl3/LEB3tL+SOA= github.com/grafana/grafana-azure-sdk-go/v2 v2.2.0/go.mod h1:H9sVh9A4yg5egMGZeh0mifxT1Q/uqwKe1LBjBJU6pN8= github.com/grafana/grafana-plugin-sdk-go v0.278.0 h1:5/rIYparLi02pofdaag8wnjspMMVNCi8cZhC4cdC3Ho= 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-20250730154715-ae15ba562e71 h1:7AVAgb0CalPYtUC9SJKYAkByOBGwL6r50P4QMEiJi44= -github.com/grafana/grafana/apps/dashboard v0.0.0-20250730154715-ae15ba562e71/go.mod h1:/iuseD/cEpXDiy7MpL+4qBFZ3H6esnUJTYzpoJMw9dw= +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/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/secret v0.0.0-20250711114246-c9b2126c4ad5 h1:+fMhUoqwGdY8ntH0GL2icJa3uk+bTiIMicawDG2r9Uc= diff --git a/apps/alerting/notifications/go.mod b/apps/alerting/notifications/go.mod index 8fd63458f4f..5698787f545 100644 --- a/apps/alerting/notifications/go.mod +++ b/apps/alerting/notifications/go.mod @@ -3,8 +3,8 @@ module github.com/grafana/grafana/apps/alerting/notifications go 1.24.5 require ( - github.com/grafana/grafana-app-sdk v0.40.1 - github.com/grafana/grafana-app-sdk/logging v0.40.0 + github.com/grafana/grafana-app-sdk v0.40.2 + github.com/grafana/grafana-app-sdk/logging v0.40.1 k8s.io/apimachinery v0.33.3 k8s.io/apiserver v0.33.3 k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff diff --git a/apps/alerting/notifications/go.sum b/apps/alerting/notifications/go.sum index e3c83758737..2931a8f5200 100644 --- a/apps/alerting/notifications/go.sum +++ b/apps/alerting/notifications/go.sum @@ -84,10 +84,10 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= -github.com/grafana/grafana-app-sdk v0.40.1 h1:W8d1CQSgMg/d37xf/0t4PiSa7wVD21XFE8mF6P59YSg= -github.com/grafana/grafana-app-sdk v0.40.1/go.mod h1:4P8h7VB6KcDjX9bAoBQc6IP8iNylxe6bSXLR9gA39gM= -github.com/grafana/grafana-app-sdk/logging v0.40.0 h1:3LHA0pFM9mGGFyq6qoHvJOGEe0H0OVhzzlOKd4Ehu+E= -github.com/grafana/grafana-app-sdk/logging v0.40.0/go.mod h1:otUD9XpJD7A5sCLb8mcs9hIXGdeV6lnhzVwe747g4RU= +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/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340 h1:uGoIog/wiQHI9GAxXO5TJbT0wWKH3O9HhOJW1F9c3fY= diff --git a/apps/dashboard/go.mod b/apps/dashboard/go.mod index 8bbf2e9f607..0d00cb6c511 100644 --- a/apps/dashboard/go.mod +++ b/apps/dashboard/go.mod @@ -4,7 +4,7 @@ go 1.24.5 require ( cuelang.org/go v0.11.1 - github.com/grafana/grafana-app-sdk v0.40.1 + github.com/grafana/grafana-app-sdk v0.40.2 github.com/grafana/grafana-plugin-sdk-go v0.278.0 github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250514132646-acbc7b54ed9e github.com/stretchr/testify v1.10.0 @@ -44,7 +44,7 @@ require ( github.com/google/go-cmp v0.7.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/mux v1.8.1 // indirect - github.com/grafana/grafana-app-sdk/logging v0.40.0 // indirect + github.com/grafana/grafana-app-sdk/logging v0.40.1 // indirect github.com/grafana/otel-profiling-go v0.5.1 // indirect github.com/grafana/pyroscope-go/godeltaprof v0.1.8 // indirect github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0 // indirect diff --git a/apps/dashboard/go.sum b/apps/dashboard/go.sum index 4f725783309..bb90120dc2b 100644 --- a/apps/dashboard/go.sum +++ b/apps/dashboard/go.sum @@ -94,10 +94,10 @@ github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1 github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= -github.com/grafana/grafana-app-sdk v0.40.1 h1:W8d1CQSgMg/d37xf/0t4PiSa7wVD21XFE8mF6P59YSg= -github.com/grafana/grafana-app-sdk v0.40.1/go.mod h1:4P8h7VB6KcDjX9bAoBQc6IP8iNylxe6bSXLR9gA39gM= -github.com/grafana/grafana-app-sdk/logging v0.40.0 h1:3LHA0pFM9mGGFyq6qoHvJOGEe0H0OVhzzlOKd4Ehu+E= -github.com/grafana/grafana-app-sdk/logging v0.40.0/go.mod h1:otUD9XpJD7A5sCLb8mcs9hIXGdeV6lnhzVwe747g4RU= +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-plugin-sdk-go v0.278.0 h1:5/rIYparLi02pofdaag8wnjspMMVNCi8cZhC4cdC3Ho= github.com/grafana/grafana-plugin-sdk-go v0.278.0/go.mod h1:+8NXT/XUJ/89GV6FxGQ366NZ3nU+cAXDMd0OUESF9H4= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250514132646-acbc7b54ed9e h1:BTKk7LHuG1kmAkucwTA7DuMbKpKvJTKrGdBmUNO4dfQ= diff --git a/apps/folder/go.mod b/apps/folder/go.mod index 31f8f6078a3..7736f202c1c 100644 --- a/apps/folder/go.mod +++ b/apps/folder/go.mod @@ -3,7 +3,7 @@ module github.com/grafana/grafana/apps/folder go 1.24.5 require ( - github.com/grafana/grafana-app-sdk v0.40.1 + github.com/grafana/grafana-app-sdk v0.40.2 github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250514132646-acbc7b54ed9e k8s.io/apimachinery v0.33.3 k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff @@ -23,7 +23,7 @@ require ( 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/grafana/grafana-app-sdk/logging v0.40.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 diff --git a/apps/folder/go.sum b/apps/folder/go.sum index 9707dfb4d4a..3612fc451ee 100644 --- a/apps/folder/go.sum +++ b/apps/folder/go.sum @@ -32,10 +32,10 @@ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grafana/grafana-app-sdk v0.40.1 h1:W8d1CQSgMg/d37xf/0t4PiSa7wVD21XFE8mF6P59YSg= -github.com/grafana/grafana-app-sdk v0.40.1/go.mod h1:4P8h7VB6KcDjX9bAoBQc6IP8iNylxe6bSXLR9gA39gM= -github.com/grafana/grafana-app-sdk/logging v0.40.0 h1:3LHA0pFM9mGGFyq6qoHvJOGEe0H0OVhzzlOKd4Ehu+E= -github.com/grafana/grafana-app-sdk/logging v0.40.0/go.mod h1:otUD9XpJD7A5sCLb8mcs9hIXGdeV6lnhzVwe747g4RU= +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-20250514132646-acbc7b54ed9e h1:BTKk7LHuG1kmAkucwTA7DuMbKpKvJTKrGdBmUNO4dfQ= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250514132646-acbc7b54ed9e/go.mod h1:IA4SOwun8QyST9c5UNs/fN37XL6boXXDvRYFcFwbipg= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= diff --git a/apps/iam/go.mod b/apps/iam/go.mod index 4b98e9c089f..30df8cdcd1f 100644 --- a/apps/iam/go.mod +++ b/apps/iam/go.mod @@ -3,7 +3,7 @@ module github.com/grafana/grafana/apps/iam go 1.24.5 require ( - github.com/grafana/grafana-app-sdk v0.40.1 + github.com/grafana/grafana-app-sdk v0.40.2 github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250514132646-acbc7b54ed9e k8s.io/apimachinery v0.33.3 k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff @@ -23,7 +23,7 @@ require ( 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/grafana/grafana-app-sdk/logging v0.40.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 diff --git a/apps/iam/go.sum b/apps/iam/go.sum index 9707dfb4d4a..3612fc451ee 100644 --- a/apps/iam/go.sum +++ b/apps/iam/go.sum @@ -32,10 +32,10 @@ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grafana/grafana-app-sdk v0.40.1 h1:W8d1CQSgMg/d37xf/0t4PiSa7wVD21XFE8mF6P59YSg= -github.com/grafana/grafana-app-sdk v0.40.1/go.mod h1:4P8h7VB6KcDjX9bAoBQc6IP8iNylxe6bSXLR9gA39gM= -github.com/grafana/grafana-app-sdk/logging v0.40.0 h1:3LHA0pFM9mGGFyq6qoHvJOGEe0H0OVhzzlOKd4Ehu+E= -github.com/grafana/grafana-app-sdk/logging v0.40.0/go.mod h1:otUD9XpJD7A5sCLb8mcs9hIXGdeV6lnhzVwe747g4RU= +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-20250514132646-acbc7b54ed9e h1:BTKk7LHuG1kmAkucwTA7DuMbKpKvJTKrGdBmUNO4dfQ= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250514132646-acbc7b54ed9e/go.mod h1:IA4SOwun8QyST9c5UNs/fN37XL6boXXDvRYFcFwbipg= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= diff --git a/apps/investigations/go.mod b/apps/investigations/go.mod index 33eb4e290ac..f1bb896a0ff 100644 --- a/apps/investigations/go.mod +++ b/apps/investigations/go.mod @@ -4,7 +4,7 @@ go 1.24.5 require ( github.com/grafana/grafana v0.0.0-00010101000000-000000000000 - github.com/grafana/grafana-app-sdk v0.40.1 + github.com/grafana/grafana-app-sdk v0.40.2 github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250725152715-69d3b9023cec github.com/stretchr/testify v1.10.0 k8s.io/apimachinery v0.33.3 @@ -82,7 +82,7 @@ require ( github.com/grafana/authlib/types v0.0.0-20250710201142-9542f2f28d43 // indirect github.com/grafana/dataplane/sdata v0.0.9 // indirect github.com/grafana/dskit v0.0.0-20250611075409-46f51e1ce914 // indirect - github.com/grafana/grafana-app-sdk/logging v0.40.0 // indirect + github.com/grafana/grafana-app-sdk/logging v0.40.1 // 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-plugin-sdk-go v0.278.0 // indirect diff --git a/apps/investigations/go.sum b/apps/investigations/go.sum index 65357085011..b17f4ea40a5 100644 --- a/apps/investigations/go.sum +++ b/apps/investigations/go.sum @@ -341,18 +341,18 @@ github.com/grafana/dataplane/sdata v0.0.9 h1:AGL1LZnCUG4MnQtnWpBPbQ8ZpptaZs14w6k github.com/grafana/dataplane/sdata v0.0.9/go.mod h1:Jvs5ddpGmn6vcxT7tCTWAZ1mgi4sbcdFt9utQx5uMAU= github.com/grafana/dskit v0.0.0-20250611075409-46f51e1ce914 h1:qcSGhr691f1mmPHwg2svGyO40Ex92G02aOyHzP6XHCE= github.com/grafana/dskit v0.0.0-20250611075409-46f51e1ce914/go.mod h1:OiN4P4aC6LwLzLbEupH3Ue83VfQoNMfG48rsna8jI/E= -github.com/grafana/grafana-app-sdk v0.40.1 h1:W8d1CQSgMg/d37xf/0t4PiSa7wVD21XFE8mF6P59YSg= -github.com/grafana/grafana-app-sdk v0.40.1/go.mod h1:4P8h7VB6KcDjX9bAoBQc6IP8iNylxe6bSXLR9gA39gM= -github.com/grafana/grafana-app-sdk/logging v0.40.0 h1:3LHA0pFM9mGGFyq6qoHvJOGEe0H0OVhzzlOKd4Ehu+E= -github.com/grafana/grafana-app-sdk/logging v0.40.0/go.mod h1:otUD9XpJD7A5sCLb8mcs9hIXGdeV6lnhzVwe747g4RU= +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-aws-sdk v1.0.4 h1:D14UAehsOqpjliHmHzveRQ1p43KCsMzdmb7GovWj+SY= github.com/grafana/grafana-aws-sdk v1.0.4/go.mod h1:hO7q7yWV+t6dmiyJjMa3IbuYnYkBua+G/IAlOPVIYKE= github.com/grafana/grafana-azure-sdk-go/v2 v2.2.0 h1:0TYrkzAc3u0HX+9GK86cGrLTUAcmQfl3/LEB3tL+SOA= github.com/grafana/grafana-azure-sdk-go/v2 v2.2.0/go.mod h1:H9sVh9A4yg5egMGZeh0mifxT1Q/uqwKe1LBjBJU6pN8= github.com/grafana/grafana-plugin-sdk-go v0.278.0 h1:5/rIYparLi02pofdaag8wnjspMMVNCi8cZhC4cdC3Ho= 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-20250730154715-ae15ba562e71 h1:7AVAgb0CalPYtUC9SJKYAkByOBGwL6r50P4QMEiJi44= -github.com/grafana/grafana/apps/dashboard v0.0.0-20250730154715-ae15ba562e71/go.mod h1:/iuseD/cEpXDiy7MpL+4qBFZ3H6esnUJTYzpoJMw9dw= +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/pkg/apiserver v0.0.0-20250627191313-2f1a6ae1712b h1:QyJLJn3xwFTIXu9KPZujsrIUN0X8DdiR9b2h75L0AfI= diff --git a/apps/playlist/go.mod b/apps/playlist/go.mod index 2cf20613fc8..6a3f8d519fd 100644 --- a/apps/playlist/go.mod +++ b/apps/playlist/go.mod @@ -3,7 +3,7 @@ module github.com/grafana/grafana/apps/playlist go 1.24.5 require ( - github.com/grafana/grafana-app-sdk v0.40.1 + github.com/grafana/grafana-app-sdk v0.40.2 k8s.io/apimachinery v0.33.3 k8s.io/client-go v0.33.3 k8s.io/klog/v2 v2.130.1 @@ -30,7 +30,7 @@ require ( 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.0 // indirect + github.com/grafana/grafana-app-sdk/logging v0.40.1 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect diff --git a/apps/playlist/go.sum b/apps/playlist/go.sum index b061911f7b2..88512ac2ed8 100644 --- a/apps/playlist/go.sum +++ b/apps/playlist/go.sum @@ -47,10 +47,10 @@ github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grafana/grafana-app-sdk v0.40.1 h1:W8d1CQSgMg/d37xf/0t4PiSa7wVD21XFE8mF6P59YSg= -github.com/grafana/grafana-app-sdk v0.40.1/go.mod h1:4P8h7VB6KcDjX9bAoBQc6IP8iNylxe6bSXLR9gA39gM= -github.com/grafana/grafana-app-sdk/logging v0.40.0 h1:3LHA0pFM9mGGFyq6qoHvJOGEe0H0OVhzzlOKd4Ehu+E= -github.com/grafana/grafana-app-sdk/logging v0.40.0/go.mod h1:otUD9XpJD7A5sCLb8mcs9hIXGdeV6lnhzVwe747g4RU= +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/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= diff --git a/apps/sdk.mk b/apps/sdk.mk index 093075f0be4..07105fe3973 100644 --- a/apps/sdk.mk +++ b/apps/sdk.mk @@ -1,4 +1,4 @@ -APP_SDK_VERSION = v0.40.1 +APP_SDK_VERSION = v0.40.2 APP_SDK_DIR = $(shell go env GOPATH)/bin/app-sdk-$(APP_SDK_VERSION) APP_SDK_BIN = $(APP_SDK_DIR)/grafana-app-sdk diff --git a/apps/secret/go.mod b/apps/secret/go.mod index 7f0517a504a..45567c3943a 100644 --- a/apps/secret/go.mod +++ b/apps/secret/go.mod @@ -3,7 +3,7 @@ module github.com/grafana/grafana/apps/secret go 1.24.5 require ( - github.com/grafana/grafana-app-sdk v0.40.1 + github.com/grafana/grafana-app-sdk v0.40.2 github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250710134100-1f3dc0533caf github.com/stretchr/testify v1.10.0 google.golang.org/grpc v1.74.2 @@ -28,7 +28,7 @@ require ( 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/grafana/grafana-app-sdk/logging v0.40.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 diff --git a/apps/secret/go.sum b/apps/secret/go.sum index 78d210e14a9..3705f402c16 100644 --- a/apps/secret/go.sum +++ b/apps/secret/go.sum @@ -36,10 +36,10 @@ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grafana/grafana-app-sdk v0.40.1 h1:W8d1CQSgMg/d37xf/0t4PiSa7wVD21XFE8mF6P59YSg= -github.com/grafana/grafana-app-sdk v0.40.1/go.mod h1:4P8h7VB6KcDjX9bAoBQc6IP8iNylxe6bSXLR9gA39gM= -github.com/grafana/grafana-app-sdk/logging v0.40.0 h1:3LHA0pFM9mGGFyq6qoHvJOGEe0H0OVhzzlOKd4Ehu+E= -github.com/grafana/grafana-app-sdk/logging v0.40.0/go.mod h1:otUD9XpJD7A5sCLb8mcs9hIXGdeV6lnhzVwe747g4RU= +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-20250710134100-1f3dc0533caf h1:BBGDHffvVNLoYQlXEpbXcxE0vbpq7pm/8OWF5I+UDZg= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250710134100-1f3dc0533caf/go.mod h1:eAlOam2uWhrsEZlOoAr7XZ9hbBP7SyYGYn31/aQAPs8= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= diff --git a/go.mod b/go.mod index 6d9b4e0d65b..27500ca7227 100644 --- a/go.mod +++ b/go.mod @@ -95,8 +95,8 @@ require ( github.com/grafana/gofpdf v0.0.0-20250307124105-3b9c5d35577f // @grafana/sharing-squad github.com/grafana/gomemcache v0.0.0-20250318131618-74242eea118d // @grafana/grafana-operator-experience-squad github.com/grafana/grafana-api-golang-client v0.27.0 // @grafana/alerting-backend - github.com/grafana/grafana-app-sdk v0.40.1 // @grafana/grafana-app-platform-squad - github.com/grafana/grafana-app-sdk/logging v0.40.0 // @grafana/grafana-app-platform-squad + github.com/grafana/grafana-app-sdk v0.40.2 // @grafana/grafana-app-platform-squad + github.com/grafana/grafana-app-sdk/logging v0.40.1 // @grafana/grafana-app-platform-squad github.com/grafana/grafana-aws-sdk v1.0.4 // @grafana/aws-datasources github.com/grafana/grafana-azure-sdk-go/v2 v2.2.0 // @grafana/partner-datasources github.com/grafana/grafana-cloud-migration-snapshot v1.9.0 // @grafana/grafana-operator-experience-squad @@ -230,7 +230,7 @@ require ( require ( github.com/grafana/grafana/apps/advisor v0.0.0-20250627191313-2f1a6ae1712b // @grafana/plugins-platform-backend github.com/grafana/grafana/apps/alerting/notifications v0.0.0-20250627191313-2f1a6ae1712b // @grafana/alerting-backend - github.com/grafana/grafana/apps/dashboard v0.0.0-20250730154715-ae15ba562e71 // @grafana/grafana-app-platform-squad @grafana/dashboards-squad + github.com/grafana/grafana/apps/dashboard v0.0.0-20250730164619-34019e5ec017 // @grafana/grafana-app-platform-squad @grafana/dashboards-squad github.com/grafana/grafana/apps/folder v0.0.0-20250627191313-2f1a6ae1712b // @grafana/grafana-search-and-storage github.com/grafana/grafana/apps/iam v0.0.0-20250627191313-2f1a6ae1712b // @grafana/identity-access-team github.com/grafana/grafana/apps/investigations v0.0.0-20250627191313-2f1a6ae1712b // @fcjack @matryer diff --git a/go.sum b/go.sum index 2009fce4070..41353473ddc 100644 --- a/go.sum +++ b/go.sum @@ -1600,10 +1600,10 @@ github.com/grafana/gomemcache v0.0.0-20250318131618-74242eea118d h1:oXRJlb9UjVsl github.com/grafana/gomemcache v0.0.0-20250318131618-74242eea118d/go.mod h1:j/s0jkda4UXTemDs7Pgw/vMT06alWc42CHisvYac0qw= github.com/grafana/grafana-api-golang-client v0.27.0 h1:zIwMXcbCB4n588i3O2N6HfNcQogCNTd/vPkEXTr7zX8= github.com/grafana/grafana-api-golang-client v0.27.0/go.mod h1:uNLZEmgKtTjHBtCQMwNn3qsx2mpMb8zU+7T4Xv3NR9Y= -github.com/grafana/grafana-app-sdk v0.40.1 h1:W8d1CQSgMg/d37xf/0t4PiSa7wVD21XFE8mF6P59YSg= -github.com/grafana/grafana-app-sdk v0.40.1/go.mod h1:4P8h7VB6KcDjX9bAoBQc6IP8iNylxe6bSXLR9gA39gM= -github.com/grafana/grafana-app-sdk/logging v0.40.0 h1:3LHA0pFM9mGGFyq6qoHvJOGEe0H0OVhzzlOKd4Ehu+E= -github.com/grafana/grafana-app-sdk/logging v0.40.0/go.mod h1:otUD9XpJD7A5sCLb8mcs9hIXGdeV6lnhzVwe747g4RU= +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-aws-sdk v1.0.4 h1:D14UAehsOqpjliHmHzveRQ1p43KCsMzdmb7GovWj+SY= github.com/grafana/grafana-aws-sdk v1.0.4/go.mod h1:hO7q7yWV+t6dmiyJjMa3IbuYnYkBua+G/IAlOPVIYKE= github.com/grafana/grafana-azure-sdk-go/v2 v2.2.0 h1:0TYrkzAc3u0HX+9GK86cGrLTUAcmQfl3/LEB3tL+SOA= @@ -1620,8 +1620,8 @@ github.com/grafana/grafana/apps/advisor v0.0.0-20250627191313-2f1a6ae1712b h1:8o github.com/grafana/grafana/apps/advisor v0.0.0-20250627191313-2f1a6ae1712b/go.mod h1:q+h3HbmqU/PposW6lq8cMle1v8vuyX1LCMrGzbabHxc= github.com/grafana/grafana/apps/alerting/notifications v0.0.0-20250627191313-2f1a6ae1712b h1:jr+C3epmjhd5Yyob4P1Z/dPaW4LRTkU5UJLXsI4eaeM= github.com/grafana/grafana/apps/alerting/notifications v0.0.0-20250627191313-2f1a6ae1712b/go.mod h1:WpI7TCck4P2wKTO2WJLBRcfOWvUGvTdxYu3QqS3z7jM= -github.com/grafana/grafana/apps/dashboard v0.0.0-20250730154715-ae15ba562e71 h1:7AVAgb0CalPYtUC9SJKYAkByOBGwL6r50P4QMEiJi44= -github.com/grafana/grafana/apps/dashboard v0.0.0-20250730154715-ae15ba562e71/go.mod h1:/iuseD/cEpXDiy7MpL+4qBFZ3H6esnUJTYzpoJMw9dw= +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/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/iam v0.0.0-20250627191313-2f1a6ae1712b h1:NV8v9xdM/pzjjy+1cLqUseia3bYcvQGh88vZdMW/jA0= diff --git a/go.work.sum b/go.work.sum index cd47b184cb0..d129bd22b1f 100644 --- a/go.work.sum +++ b/go.work.sum @@ -16,6 +16,7 @@ cel.dev/expr v0.19.1/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= cel.dev/expr v0.19.2/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= cel.dev/expr v0.20.0/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= cel.dev/expr v0.23.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= +cel.dev/expr v0.23.1/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= cloud.google.com/go v0.112.2/go.mod h1:iEqjp//KquGIJV/m+Pk3xecgKNhV+ry+vVTsy4TbDms= cloud.google.com/go v0.116.0/go.mod h1:cEPSRWPzZEswwdr9BxE6ChEn01dWlTaF05LiC2Xs70U= cloud.google.com/go v0.118.1/go.mod h1:CFO4UPEPi8oV21xoezZCrd3d81K4fFkDTEJu4R8K+9M= @@ -516,7 +517,9 @@ github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h github.com/Azure/azure-amqp-common-go/v3 v3.2.3 h1:uDF62mbd9bypXWi19V1bN5NZEO84JqgmI5G73ibAmrk= github.com/Azure/azure-amqp-common-go/v3 v3.2.3/go.mod h1:7rPmbSfszeovxGfc5fSAXE4ehlXQZHpMja2OtxC2Tas= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.1/go.mod h1:zGqV2R4Cr/k8Uye5w+dgQ06WJtEcbQG/8J7BB6hnCr4= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0/go.mod h1:Ot/6aikWnKWi4l9QB7qVSwa8iMphQNqkWALMoNT3rzM= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.2/go.mod h1:SqINnQ9lVVdRlyC8cd1lCI0SdX4n2paeABd2K8ggfnE= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.9.0/go.mod h1:kUjrAo8bgEwLeZ/CmHqNl3Z/kPm7y6FKfxxK0izYUg4= github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.0/go.mod h1:j2chePtV91HrC22tGoRX3sGY42uF13WzmmV80/OdVAA= github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus v1.7.1 h1:o/Ws6bEqMeKZUfj1RRm3mQ51O8JGU5w+Qdg2AhHib6A= github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus v1.7.1/go.mod h1:6QAMYBAbQeeKX+REFJMZ1nFWu9XLw/PPcjYpuc9RDFs= @@ -705,6 +708,7 @@ github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe h1:QQ3GSy+MqSHxm/d8nC github.com/cncf/xds/go v0.0.0-20240723142845-024c85f92f20/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= +github.com/cncf/xds/go v0.0.0-20250326154945-ae57f3c0d45f/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/cockroachdb/cockroach-go v0.0.0-20181001143604-e0a95dfd547c h1:2zRrJWIt/f9c9HhNHAgrRgq0San5gRRUJTBXLkchal0= github.com/cockroachdb/datadriven v1.0.2 h1:H9MtNqVoVhvd9nCBwOyDjUEdZCREqbIdCJD93PBm/jA= github.com/cockroachdb/datadriven v1.0.2/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= @@ -902,6 +906,7 @@ github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/golang-jwt/jwt v3.2.1+incompatible h1:73Z+4BJcrTC+KczS6WvTPvRGOp1WmfEP4Q1lOd9Z/+c= github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= +github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= github.com/gomarkdown/markdown v0.0.0-20230716120725-531d2d74bc12 h1:uK3X/2mt4tbSGoHvbLBHUny7CKiuwUip3MArtukol4E= github.com/gomarkdown/markdown v0.0.0-20230716120725-531d2d74bc12/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA= @@ -964,12 +969,17 @@ github.com/grafana/cloudflare-go v0.0.0-20230110200409-c627cf6792f2/go.mod h1:w/ github.com/grafana/go-gelf/v2 v2.0.1 h1:BOChP0h/jLeD+7F9mL7tq10xVkDG15he3T1zHuQaWak= github.com/grafana/go-gelf/v2 v2.0.1/go.mod h1:lexHie0xzYGwCgiRGcvZ723bSNyNI8ZRD4s0CLobh90= github.com/grafana/gomemcache v0.0.0-20250228145437-da7b95fd2ac1/go.mod h1:j/s0jkda4UXTemDs7Pgw/vMT06alWc42CHisvYac0qw= +github.com/grafana/grafana-app-sdk v0.40.0/go.mod h1:fn943JEM0CR3mY/Gd3816MUcpob5xnKc8MoojnbMjYY= github.com/grafana/grafana-app-sdk/logging v0.38.0/go.mod h1:Y/bvbDhBiV/tkIle9RW49pgfSPIPSON8Q4qjx3pyqDk= github.com/grafana/grafana-app-sdk/logging v0.39.0 h1:3GgN5+dUZYqq74Q+GT9/ET+yo+V54zWQk/Q2/JsJQB4= github.com/grafana/grafana-app-sdk/logging v0.39.0/go.mod h1:WhDENSnaGHtyVVwZGVnAR7YLvh2xlLDYR3D7E6h7XVk= github.com/grafana/grafana-app-sdk/logging v0.39.1/go.mod h1:WhDENSnaGHtyVVwZGVnAR7YLvh2xlLDYR3D7E6h7XVk= +github.com/grafana/grafana-app-sdk/logging v0.39.3/go.mod h1:otUD9XpJD7A5sCLb8mcs9hIXGdeV6lnhzVwe747g4RU= github.com/grafana/grafana-aws-sdk v0.38.2 h1:TzQD0OpWsNjtldi5G5TLDlBRk8OyDf+B5ujcoAu4Dp0= github.com/grafana/grafana-aws-sdk v0.38.2/go.mod h1:j3vi+cXYHEFqjhBGrI6/lw1TNM+dl0Y3f0cSnDOPy+s= +github.com/grafana/grafana-aws-sdk v1.0.2 h1:98eBuHYFmgvH0xO9kKf4RBsEsgQRp8EOA/9yhDIpkss= +github.com/grafana/grafana-aws-sdk v1.0.2/go.mod h1:hO7q7yWV+t6dmiyJjMa3IbuYnYkBua+G/IAlOPVIYKE= +github.com/grafana/grafana-azure-sdk-go/v2 v2.1.6/go.mod h1:V7y2BmsWxS3A9Ohebwn4OiSfJJqi//4JQydQ8fHTduo= github.com/grafana/grafana-plugin-sdk-go v0.263.0/go.mod h1:U43Cnrj/9DNYyvFcNdeUWNjMXTKNB0jcTcQGpWKd2gw= github.com/grafana/grafana-plugin-sdk-go v0.267.0/go.mod h1:OuwS4c/JYgn0rr/w5zhJBpLo4gKm/vw15RsfpYAvK9Q= github.com/grafana/grafana-plugin-sdk-go v0.269.1/go.mod h1:yv2KbO4mlr9WuDK2f+2gHAMTwwLmLuqaEnrPXTRU+OI= @@ -1051,7 +1061,6 @@ github.com/jaegertracing/jaeger v1.67.0 h1:t0BiJZVW9D3Z16y3uHqKzV9bKFTusooTH1Kgr github.com/jaegertracing/jaeger v1.67.0/go.mod h1:tE/FEQfybCSdUbBgel51YaCSkc58O+Njih8oTl6j8vw= github.com/jedib0t/go-pretty/v6 v6.6.7 h1:m+LbHpm0aIAPLzLbMfn8dc3Ht8MW7lsSO4MPItz/Uuo= github.com/jedib0t/go-pretty/v6 v6.6.7/go.mod h1:YwC5CE4fJ1HFUDeivSV1r//AmANFHyqczZk+U6BDALU= -github.com/jessevdk/go-flags v1.6.1 h1:Cvu5U8UGrLay1rZfv/zP7iLpSHGUZ/Ou68T0iX1bBK4= github.com/jhump/gopoet v0.1.0 h1:gYjOPnzHd2nzB37xYQZxj4EIQNpBrBskRqQQ3q4ZgSg= github.com/jhump/goprotoc v0.5.0 h1:Y1UgUX+txUznfqcGdDef8ZOVlyQvnV0pKWZH08RmZuo= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= @@ -1285,6 +1294,7 @@ github.com/rabbitmq/amqp091-go v1.9.0 h1:qrQtyzB4H8BQgEuJwhmVQqVHB9O4+MNDJCCAcpc github.com/rabbitmq/amqp091-go v1.9.0/go.mod h1:+jPrT9iY2eLjRaMSRHUhc3z14E/l85kv/f+6luSD3pc= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/redis/go-redis/v9 v9.7.3/go.mod h1:bGUrSggJ9X9GUmZpZNEOQKaANxSGgOEBRltRTZHSvrA= github.com/relvacode/iso8601 v1.6.0 h1:eFXUhMJN3Gz8Rcq82f9DTMW0svjtAVuIEULglM7QHTU= github.com/relvacode/iso8601 v1.6.0/go.mod h1:FlNp+jz+TXpyRqgmM7tnzHHzBnz776kmAH2h3sZCn0I= github.com/richardartoul/molecule v1.0.0 h1:+LFA9cT7fn8KF39zy4dhOnwcOwRoqKiBkPqKqya+8+U= @@ -1353,8 +1363,6 @@ github.com/testcontainers/testcontainers-go/modules/mongodb v0.35.0 h1:i1Kh9fmXg github.com/testcontainers/testcontainers-go/modules/mongodb v0.35.0/go.mod h1:SD8nVMK1m7b/K2YJqYjYNzfHmZfqHtqNOlI44nfxjdg= github.com/testcontainers/testcontainers-go/modules/redis v0.35.0 h1:RBgVefU5j5IWapp3TNKqMTYX+M22OSjtuORjPd4+g08= github.com/testcontainers/testcontainers-go/modules/redis v0.35.0/go.mod h1:UgghVXQ0//D3MjC8X71Bpb/lUCChidjNCRILD+btqfU= -github.com/thejerf/slogassert v0.3.4 h1:VoTsXixRbXMrRSSxDjYTiEDCM4VWbsYPW5rB/hX24kM= -github.com/thejerf/slogassert v0.3.4/go.mod h1:0zn9ISLVKo1aPMTqcGfG1o6dWwt+Rk574GlUxHD4rs8= github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= @@ -1434,7 +1442,6 @@ github.com/ydb-platform/ydb-go-sdk/v3 v3.108.1 h1:ixAiqjj2S/dNuJqrz4AxSqgw2P5OBM github.com/ydb-platform/ydb-go-sdk/v3 v3.108.1/go.mod h1:l5sSv153E18VvYcsmr51hok9Sjc16tEC8AXGbwrk+ho= github.com/yosssi/ace v0.0.5 h1:tUkIP/BLdKqrlrPwcmH0shwEEhTRHoGnc1wFIWmaBUA= github.com/yosssi/ace v0.0.5/go.mod h1:ALfIzm2vT7t5ZE7uoIZqF3TQ7SAOyupFZnkrF5id+K0= -github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM= github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= @@ -1451,6 +1458,7 @@ go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I= go.etcd.io/gofail v0.2.0 h1:p19drv16FKK345a09a1iubchlw/vmRuksmRzgBIGjcA= go.etcd.io/gofail v0.2.0/go.mod h1:nL3ILMGfkXTekKI3clMBNazKnjUZjYLKmBHzsVAnC1o= go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c= +go.mongodb.org/mongo-driver v1.16.1/go.mod h1:oB6AhJQvFQL4LEHyXi6aJzQJtBiTQHiAd83l0GdFaiw= go.opentelemetry.io/collector v0.124.0 h1:g/dfdGFhBcQI0ggGxTmGlJnJ6Yl6T2gVxQoIj4UfXCc= go.opentelemetry.io/collector v0.124.0/go.mod h1:QzERYfmHUedawjr8Ph/CBEEkVqWS8IlxRLAZt+KHlCg= go.opentelemetry.io/collector/client v1.30.0 h1:QbvOrvwUGcnVjnIBn2zyLLubisOjgh7kMgkzDAiYpHg= @@ -1709,6 +1717,7 @@ golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFK golang.org/x/telemetry v0.0.0-20250710130107-8d8967aff50b h1:DU+gwOBXU+6bO0sEyO7o/NeMlxZxCZEvI7v+J4a1zRQ= golang.org/x/telemetry v0.0.0-20250710130107-8d8967aff50b/go.mod h1:4ZwOYna0/zsOKwuR5X/m0QFOJpSZvAxFfkQT+Erd9D4= golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= +golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= @@ -1732,6 +1741,7 @@ golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY= golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI= golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg= +golang.org/x/tools/go/expect v0.1.0-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY= golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM= golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated/go.mod h1:RVAQXBGNv1ib0J382/DPCRS/BPnsGebyM1Gj5VSDpG8= gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0 h1:OE9mWmgKkjJyEmDAAtGMPjXu+YNeGvK9VTSHY6+Qihc= @@ -1806,7 +1816,7 @@ google.golang.org/grpc v1.72.0/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3i google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= google.golang.org/grpc v1.72.2/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc= -google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4= +google.golang.org/grpc v1.74.0/go.mod h1:NZUaK8dAMUfzhK6uxZ+9511LtOrk73UGWOFoNvz7z+s= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0 h1:M1YKkFIboKNieVO5DLUEVzQfGwJD30Nv2jfUgzb5UcE= google.golang.org/grpc/examples v0.0.0-20230224211313-3775f633ce20 h1:MLBCGN1O7GzIx+cBiwfYPwtmZ41U3Mn/cotLJciaArI= google.golang.org/grpc/examples v0.0.0-20230224211313-3775f633ce20/go.mod h1:Nr5H8+MlGWr5+xX/STzdoEqJrO+YteqFbMyCsrb6mH0= @@ -1833,6 +1843,7 @@ honnef.co/go/tools v0.3.2 h1:ytYb4rOqyp1TSa2EPvNVwtPQJctSELKaMyLfqNP4+34= honnef.co/go/tools v0.3.2/go.mod h1:jzwdWgg7Jdq75wlfblQxO4neNaFFSvgc1tD5Wv8U0Yw= howett.net/plist v1.0.1 h1:37GdZ8tP09Q35o9ych3ehygcsL+HqKSwzctveSlarvM= howett.net/plist v1.0.1/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g= +k8s.io/apiextensions-apiserver v0.33.2/go.mod h1:IvVanieYsEHJImTKXGP6XCOjTwv2LUMos0YWc9O+QP8= k8s.io/code-generator v0.33.1 h1:ZLzIRdMsh3Myfnx9BaooX6iQry29UJjVfVG+BuS+UMw= k8s.io/code-generator v0.33.1/go.mod h1:HUKT7Ubp6bOgIbbaPIs9lpd2Q02uqkMCMx9/GjDrWpY= k8s.io/code-generator v0.33.2 h1:PCJ0Y6viTCxxJHMOyGqYwWEteM4q6y1Hqo2rNpl6jF4= @@ -1869,3 +1880,4 @@ sigs.k8s.io/controller-runtime v0.20.4/go.mod h1:xg2XB0K5ShQzAgsoujxuKN4LNXR2Lfw sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo= sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e h1:4Z09Hglb792X0kfOBBJUPFEyvVfQWrYT/l8h5EKA6JQ= +sigs.k8s.io/structured-merge-diff/v4 v4.6.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps= diff --git a/pkg/apiserver/go.mod b/pkg/apiserver/go.mod index 24e2c11626c..4efad4e627d 100644 --- a/pkg/apiserver/go.mod +++ b/pkg/apiserver/go.mod @@ -5,7 +5,7 @@ go 1.24.5 require ( github.com/google/go-cmp v0.7.0 github.com/grafana/authlib/types v0.0.0-20250710201142-9542f2f28d43 - github.com/grafana/grafana-app-sdk/logging v0.40.0 + github.com/grafana/grafana-app-sdk/logging v0.40.1 github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250514132646-acbc7b54ed9e github.com/prometheus/client_golang v1.22.0 github.com/stretchr/testify v1.10.0 diff --git a/pkg/apiserver/go.sum b/pkg/apiserver/go.sum index 5547406bf98..dc2e70d63a1 100644 --- a/pkg/apiserver/go.sum +++ b/pkg/apiserver/go.sum @@ -84,8 +84,8 @@ github.com/grafana/authlib/types v0.0.0-20250710201142-9542f2f28d43 h1:NlkGMnVi/ github.com/grafana/authlib/types v0.0.0-20250710201142-9542f2f28d43/go.mod h1:qeWYbnWzaYGl88JlL9+DsP1GT2Cudm58rLtx13fKZdw= github.com/grafana/dskit v0.0.0-20250611075409-46f51e1ce914 h1:qcSGhr691f1mmPHwg2svGyO40Ex92G02aOyHzP6XHCE= github.com/grafana/dskit v0.0.0-20250611075409-46f51e1ce914/go.mod h1:OiN4P4aC6LwLzLbEupH3Ue83VfQoNMfG48rsna8jI/E= -github.com/grafana/grafana-app-sdk/logging v0.40.0 h1:3LHA0pFM9mGGFyq6qoHvJOGEe0H0OVhzzlOKd4Ehu+E= -github.com/grafana/grafana-app-sdk/logging v0.40.0/go.mod h1:otUD9XpJD7A5sCLb8mcs9hIXGdeV6lnhzVwe747g4RU= +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-20250514132646-acbc7b54ed9e h1:BTKk7LHuG1kmAkucwTA7DuMbKpKvJTKrGdBmUNO4dfQ= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250514132646-acbc7b54ed9e/go.mod h1:IA4SOwun8QyST9c5UNs/fN37XL6boXXDvRYFcFwbipg= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= diff --git a/pkg/services/apiserver/appinstaller/installer.go b/pkg/services/apiserver/appinstaller/installer.go index d80e865b5f0..fb5427b761f 100644 --- a/pkg/services/apiserver/appinstaller/installer.go +++ b/pkg/services/apiserver/appinstaller/installer.go @@ -134,7 +134,6 @@ func InstallAPIs( builderMetrics *builder.BuilderMetrics, ) error { logger := logging.FromContext(ctx) - for _, installer := range appInstallers { logger.Debug("Installing APIs for app installer", "app", installer.ManifestData().AppName) wrapper := &serverWrapper{ diff --git a/pkg/services/apiserver/appinstaller/server.go b/pkg/services/apiserver/appinstaller/server.go index 723c0753605..ea9b88e031e 100644 --- a/pkg/services/apiserver/appinstaller/server.go +++ b/pkg/services/apiserver/appinstaller/server.go @@ -8,6 +8,7 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apiserver/pkg/registry/generic" genericregistry "k8s.io/apiserver/pkg/registry/generic/registry" + genericrest "k8s.io/apiserver/pkg/registry/rest" genericapiserver "k8s.io/apiserver/pkg/server" appsdkapiserver "github.com/grafana/grafana-app-sdk/k8s/apiserver" @@ -38,17 +39,9 @@ type serverWrapper struct { func (s *serverWrapper) InstallAPIGroup(apiGroupInfo *genericapiserver.APIGroupInfo) error { log := logging.FromContext(s.ctx) - legacyProvider, ok := s.installer.(LegacyStorageProvider) - if !ok { - return s.GenericAPIServer.InstallAPIGroup(apiGroupInfo) - } for v, storageMap := range apiGroupInfo.VersionedResourcesStorageMap { for storagePath, restStorage := range storageMap { - genericStorage, ok := restStorage.(*genericregistry.Store) - if !ok { - log.Error("Expected generic registry store", "storagePath", storagePath, "version", v) - continue - } + legacyProvider, dualWriteSupported := s.installer.(LegacyStorageProvider) resource, err := getResourceFromStoragePath(storagePath) if err != nil { return err @@ -57,29 +50,28 @@ func (s *serverWrapper) InstallAPIGroup(apiGroupInfo *genericapiserver.APIGroupI Group: s.installer.ManifestData().Group, Resource: resource, } - genericStorage.KeyRootFunc = grafanaregistry.KeyRootFunc(gr) - genericStorage.KeyFunc = grafanaregistry.NamespaceKeyFunc(gr) - genericStorage.UpdateStrategy = &updateStrategyWrapper{ - RESTUpdateStrategy: genericStorage.UpdateStrategy, + 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) + dw, err := NewDualWriter( + s.ctx, + gr, + s.storageOpts, + legacyProvider.GetLegacyStorage(gr.WithVersion(v)), + unifiedStorage, + s.kvStore, + s.lock, + s.namespaceMapper, + s.dualWriteService, + s.dualWriterMetrics, + s.builderMetrics, + ) + if err != nil { + return err + } + storage = dw } - - dw, err := NewDualWriter( - s.ctx, - gr, - s.storageOpts, - legacyProvider.GetLegacyStorage(gr.WithVersion(v)), - grafanarest.Storage(genericStorage), - s.kvStore, - s.lock, - s.namespaceMapper, - s.dualWriteService, - s.dualWriterMetrics, - s.builderMetrics, - ) - if err != nil { - return err - } - apiGroupInfo.VersionedResourcesStorageMap[v][storagePath] = dw + apiGroupInfo.VersionedResourcesStorageMap[v][storagePath] = storage } } @@ -93,3 +85,27 @@ func getResourceFromStoragePath(storagePath string) (string, error) { } return parts[0], nil } + +func (s *serverWrapper) configureStorage(gr schema.GroupResource, dualWriteSupported bool, storage genericrest.Storage) genericrest.Storage { + if gs, ok := storage.(*genericregistry.Store); ok { + // if dual write is supported, we need to modify the update strategy + // this is not needed for the status store + if dualWriteSupported { + gs.UpdateStrategy = &updateStrategyWrapper{ + RESTUpdateStrategy: gs.UpdateStrategy, + } + } + gs.KeyFunc = grafanaregistry.NamespaceKeyFunc(gr) + gs.KeyRootFunc = grafanaregistry.KeyRootFunc(gr) + return gs + } + + // if the storage is a status store, we need to extract the underlying generic registry store + if statusStore, ok := storage.(*appsdkapiserver.StatusREST); ok { + statusStore.Store.KeyFunc = grafanaregistry.NamespaceKeyFunc(gr) + statusStore.Store.KeyRootFunc = grafanaregistry.KeyRootFunc(gr) + return statusStore + } + + return storage +} From f1821988d9db9c04f8ae2e7a88350892f92d10e5 Mon Sep 17 00:00:00 2001 From: "grafana-pr-automation[bot]" <140550294+grafana-pr-automation[bot]@users.noreply.github.com> Date: Thu, 31 Jul 2025 00:40:54 +0000 Subject: [PATCH 026/114] I18n: Download translations from Crowdin (#108954) New Crowdin translations by GitHub Action Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- public/locales/cs-CZ/grafana.json | 45 +++++++++++++++++++---------- public/locales/de-DE/grafana.json | 45 +++++++++++++++++++---------- public/locales/es-ES/grafana.json | 45 +++++++++++++++++++---------- public/locales/fr-FR/grafana.json | 45 +++++++++++++++++++---------- public/locales/hu-HU/grafana.json | 45 +++++++++++++++++++---------- public/locales/id-ID/grafana.json | 45 +++++++++++++++++++---------- public/locales/it-IT/grafana.json | 45 +++++++++++++++++++---------- public/locales/ja-JP/grafana.json | 45 +++++++++++++++++++---------- public/locales/ko-KR/grafana.json | 45 +++++++++++++++++++---------- public/locales/nl-NL/grafana.json | 45 +++++++++++++++++++---------- public/locales/pl-PL/grafana.json | 45 +++++++++++++++++++---------- public/locales/pt-BR/grafana.json | 45 +++++++++++++++++++---------- public/locales/pt-PT/grafana.json | 45 +++++++++++++++++++---------- public/locales/ru-RU/grafana.json | 45 +++++++++++++++++++---------- public/locales/sv-SE/grafana.json | 45 +++++++++++++++++++---------- public/locales/tr-TR/grafana.json | 45 +++++++++++++++++++---------- public/locales/zh-Hans/grafana.json | 45 +++++++++++++++++++---------- public/locales/zh-Hant/grafana.json | 45 +++++++++++++++++++---------- 18 files changed, 540 insertions(+), 270 deletions(-) diff --git a/public/locales/cs-CZ/grafana.json b/public/locales/cs-CZ/grafana.json index 8bda1a81127..308c5ba4955 100644 --- a/public/locales/cs-CZ/grafana.json +++ b/public/locales/cs-CZ/grafana.json @@ -1560,8 +1560,7 @@ "group-not-editable": "Vybranou skupinu nelze upravit", "group-not-editable-description": "Tato skupina patří ke zdroji dat, který nepodporuje úpravy.", "page-title": "Upravit skupinu pravidel", - "rule-group-error": "Chyba při načítání skupiny pravidel", - "title": "Upravit hodnotící skupinu" + "rule-group-error": "Chyba při načítání skupiny pravidel" }, "group-loader": { "group-load-failed": "Nepodařilo se načíst pravidla ze skupiny {{ groupName }} v jmenném prostoru {{ namespaceName }}", @@ -1595,8 +1594,7 @@ "synthetic-monitoring-card-item-2": "Spusťte kontroly pingu, DNS, HTTP/S a TCP na každé síťové vrstvě.", "synthetic-monitoring-card-item-3": "Použijte více než 20 globálních nebo soukromých sond za firewallem.", "synthetic-monitoring-card-item-4": "Sledujte SLO pomocí vestavěných Prometheus výstrah přímo z uživatelského rozhraní.", - "synthetic-monitoring-card-title": "Syntetické monitorování", - "title-alerting": "Výstrahy" + "synthetic-monitoring-card-title": "Syntetické monitorování" }, "import-to-gma": { "action-button": "Importovat", @@ -3778,6 +3776,7 @@ "category-selected-connection": "Vybrané připojení", "category-selected-element": "Vybraný prvek ({{element}})", "category-size": "Velikost", + "category-tooltip": "", "cloud-item": { "category-cloud": "Cloud", "label": { @@ -4022,6 +4021,13 @@ "auto": "Automaticky" } }, + "tooltip-options": { + "name-tooltip-mode": "", + "tooltip-mode-options": { + "label-disabled": "", + "label-enabled": "" + } + }, "tree-navigation-editor": { "clear-selection": "Vymazat výběr", "frame-selection": "Výběr snímku", @@ -7050,6 +7056,9 @@ "pro-tip-define-sources-through-configuration-files": " Pro tip: Zdroje dat můžete definovat také prostřednictvím konfiguračních souborů. " } }, + "pane": { + "loading-placeholder": "" + }, "prev": "Předch.", "queryless-apps-extensions": { "aria-label-go-queryless": "Vyhnout se dotazům" @@ -7316,18 +7325,24 @@ "split-tooltip": "Rozdělit podokno", "split-widen": "Rozšířit podokno" }, - "trace-page-actions": { - "ariaLabel-copy-trace-id": "", - "ariaLabel-export-trace": "", - "give-feedback": "Poskytnout zpětnou vazbu", - "label-copied": "Zkopírováno!", - "label-export": "Exportovat", - "label-trace-id": "ID trasování", - "title-share-thoughts-about-tracing-grafana": "Podělte se o svůj názor na trasování v Grafaně." - }, "trace-page-header": { - "text-partial-trace": "Částečné trasování", - "tooltip-url": "{{url}} nebo {{target}} nebo {{path}}" + "duration": "", + "export-started": "", + "give-feedback": "", + "link-copied": "", + "path": "", + "route": "", + "services": "", + "share": "", + "share-copy-link": "", + "share-export-json": "", + "share-tooltip": "", + "start-time": "", + "target": "", + "title-share-thoughts-about-tracing-grafana": "", + "tooltip-url": "", + "trace-id": "", + "url": "" }, "trace-page-search-bar": { "aria-label-clear-filters": "Tlačítko Vymazat filtry", diff --git a/public/locales/de-DE/grafana.json b/public/locales/de-DE/grafana.json index f3b222b2c46..c583a7bc4f0 100644 --- a/public/locales/de-DE/grafana.json +++ b/public/locales/de-DE/grafana.json @@ -1548,8 +1548,7 @@ "group-not-editable": "Die ausgewählte Gruppe kann nicht bearbeitet werden", "group-not-editable-description": "Diese Gruppe gehört zu einer Datenquelle, die keine Bearbeitung unterstützt.", "page-title": "Regelgruppe bearbeiten", - "rule-group-error": "Fehler beim Laden der Regelgruppe", - "title": "Evaluierungsgruppe bearbeiten" + "rule-group-error": "Fehler beim Laden der Regelgruppe" }, "group-loader": { "group-load-failed": "Die Regeln der Gruppe {{ groupName }} in {{ namespaceName }} konnten nicht geladen werden", @@ -1583,8 +1582,7 @@ "synthetic-monitoring-card-item-2": "Führen Sie auf jeder Netzwerkebene Ping-, DNS-, HTTP/S- und TCP-Prüfungen durch.", "synthetic-monitoring-card-item-3": "Nutzen Sie mehr als 20 globale Tests oder private Tests hinter Ihrer Firewall.", "synthetic-monitoring-card-item-4": "Verfolgen Sie SLOs mit integrierten Warnungen im Stil von Prometheus – direkt über die Benutzeroberfläche.", - "synthetic-monitoring-card-title": "Synthetisches Monitoring", - "title-alerting": "Meldungen" + "synthetic-monitoring-card-title": "Synthetisches Monitoring" }, "import-to-gma": { "action-button": "Importieren", @@ -3740,6 +3738,7 @@ "category-selected-connection": "Ausgewählte Verbindung", "category-selected-element": "Ausgewähltes Element ({{element}})", "category-size": "Größe", + "category-tooltip": "", "cloud-item": { "category-cloud": "Cloud", "label": { @@ -3984,6 +3983,13 @@ "auto": "Auto" } }, + "tooltip-options": { + "name-tooltip-mode": "", + "tooltip-mode-options": { + "label-disabled": "", + "label-enabled": "" + } + }, "tree-navigation-editor": { "clear-selection": "Auswahl löschen", "frame-selection": "Frame-Auswahl", @@ -7006,6 +7012,9 @@ "pro-tip-define-sources-through-configuration-files": " Profi-Tipp: Sie können Datenquellen auch über Konfigurationsdateien festlegen. " } }, + "pane": { + "loading-placeholder": "" + }, "prev": "Zurück", "queryless-apps-extensions": { "aria-label-go-queryless": "Ohne Abfrage fortfahren" @@ -7272,18 +7281,24 @@ "split-tooltip": "Bereich teilen", "split-widen": "Bereich verbreitern" }, - "trace-page-actions": { - "ariaLabel-copy-trace-id": "", - "ariaLabel-export-trace": "", - "give-feedback": "Feedback geben", - "label-copied": "Kopiert!", - "label-export": "Exportieren", - "label-trace-id": "Trace-ID", - "title-share-thoughts-about-tracing-grafana": "Sagen Sie Ihre Meinung zum Tracing in Grafana." - }, "trace-page-header": { - "text-partial-trace": "Teilweises Trace", - "tooltip-url": "{{url}} oder {{target}} oder {{path}}" + "duration": "", + "export-started": "", + "give-feedback": "", + "link-copied": "", + "path": "", + "route": "", + "services": "", + "share": "", + "share-copy-link": "", + "share-export-json": "", + "share-tooltip": "", + "start-time": "", + "target": "", + "title-share-thoughts-about-tracing-grafana": "", + "tooltip-url": "", + "trace-id": "", + "url": "" }, "trace-page-search-bar": { "aria-label-clear-filters": "Schaltfläche „Filter löschen“", diff --git a/public/locales/es-ES/grafana.json b/public/locales/es-ES/grafana.json index ab32ab5c265..dacff308cb7 100644 --- a/public/locales/es-ES/grafana.json +++ b/public/locales/es-ES/grafana.json @@ -1548,8 +1548,7 @@ "group-not-editable": "El grupo seleccionado no se puede editar", "group-not-editable-description": "Este grupo pertenece a una fuente de datos que no admite la edición.", "page-title": "Editar grupo de reglas", - "rule-group-error": "Error al cargar el grupo de reglas", - "title": "Editar grupo de evaluación" + "rule-group-error": "Error al cargar el grupo de reglas" }, "group-loader": { "group-load-failed": "Error al cargar las reglas del grupo {{ groupName }} en {{ namespaceName }}", @@ -1583,8 +1582,7 @@ "synthetic-monitoring-card-item-2": "", "synthetic-monitoring-card-item-3": "", "synthetic-monitoring-card-item-4": "", - "synthetic-monitoring-card-title": "", - "title-alerting": "Alertas" + "synthetic-monitoring-card-title": "" }, "import-to-gma": { "action-button": "Importar", @@ -3740,6 +3738,7 @@ "category-selected-connection": "", "category-selected-element": "", "category-size": "", + "category-tooltip": "", "cloud-item": { "category-cloud": "", "label": { @@ -3984,6 +3983,13 @@ "auto": "Auto" } }, + "tooltip-options": { + "name-tooltip-mode": "", + "tooltip-mode-options": { + "label-disabled": "", + "label-enabled": "" + } + }, "tree-navigation-editor": { "clear-selection": "Eliminar selección", "frame-selection": "Selección de marco", @@ -7006,6 +7012,9 @@ "pro-tip-define-sources-through-configuration-files": " Un consejo: también puedes definir fuentes de datos a través de archivos de configuración. " } }, + "pane": { + "loading-placeholder": "" + }, "prev": "Ant.", "queryless-apps-extensions": { "aria-label-go-queryless": "Seguir sin consulta" @@ -7272,18 +7281,24 @@ "split-tooltip": "Dividir el panel", "split-widen": "Agrandar panel" }, - "trace-page-actions": { - "ariaLabel-copy-trace-id": "", - "ariaLabel-export-trace": "", - "give-feedback": "Enviar comentarios", - "label-copied": "¡Copiado!", - "label-export": "Exportar", - "label-trace-id": "ID de traza", - "title-share-thoughts-about-tracing-grafana": "Comparte tu opinión sobre el seguimiento en Grafana." - }, "trace-page-header": { - "text-partial-trace": "Traza parcial", - "tooltip-url": "{{url}} o {{target}} o {{path}}" + "duration": "", + "export-started": "", + "give-feedback": "", + "link-copied": "", + "path": "", + "route": "", + "services": "", + "share": "", + "share-copy-link": "", + "share-export-json": "", + "share-tooltip": "", + "start-time": "", + "target": "", + "title-share-thoughts-about-tracing-grafana": "", + "tooltip-url": "", + "trace-id": "", + "url": "" }, "trace-page-search-bar": { "aria-label-clear-filters": "Botón para borrar filtros", diff --git a/public/locales/fr-FR/grafana.json b/public/locales/fr-FR/grafana.json index 4a38e05d966..2485164635d 100644 --- a/public/locales/fr-FR/grafana.json +++ b/public/locales/fr-FR/grafana.json @@ -1548,8 +1548,7 @@ "group-not-editable": "Le groupe sélectionné ne peut pas être modifié", "group-not-editable-description": "Ce groupe appartient à une source de données qui ne prend pas en charge la modification.", "page-title": "Modifier le groupe de règles", - "rule-group-error": "Erreur lors du chargement du groupe de règles", - "title": "Modifier le groupe d’évaluation" + "rule-group-error": "Erreur lors du chargement du groupe de règles" }, "group-loader": { "group-load-failed": "Échec du chargement des règles du groupe {{ groupName }} dans {{ namespaceName }}", @@ -1583,8 +1582,7 @@ "synthetic-monitoring-card-item-2": "Effectuez des vérifications ping, DNS, HTTP/S et TCP à tous les niveaux du réseau.", "synthetic-monitoring-card-item-3": "Utilisez plus de 20 sondes mondiales ou déployez vos propres sondes privées derrière votre pare-feu.", "synthetic-monitoring-card-item-4": "Suivez vos SLO grâce à des alertes natives au format Prometheus, directement depuis l’interface utilisateur.", - "synthetic-monitoring-card-title": "Monitoring synthétique", - "title-alerting": "Alerte" + "synthetic-monitoring-card-title": "Monitoring synthétique" }, "import-to-gma": { "action-button": "Importer", @@ -3740,6 +3738,7 @@ "category-selected-connection": "Connexion sélectionnée", "category-selected-element": "Élément sélectionné ({{element}})", "category-size": "Taille", + "category-tooltip": "", "cloud-item": { "category-cloud": "Cloud", "label": { @@ -3984,6 +3983,13 @@ "auto": "Auto" } }, + "tooltip-options": { + "name-tooltip-mode": "", + "tooltip-mode-options": { + "label-disabled": "", + "label-enabled": "" + } + }, "tree-navigation-editor": { "clear-selection": "Effacer la sélection", "frame-selection": "Sélection de trame", @@ -7006,6 +7012,9 @@ "pro-tip-define-sources-through-configuration-files": " Conseil de pro : vous pouvez également définir des sources de données via des fichiers de configuration. " } }, + "pane": { + "loading-placeholder": "" + }, "prev": "Précédent", "queryless-apps-extensions": { "aria-label-go-queryless": "Passer en mode sans requête" @@ -7272,18 +7281,24 @@ "split-tooltip": "Scinder le panneau", "split-widen": "Élargir le panneau" }, - "trace-page-actions": { - "ariaLabel-copy-trace-id": "", - "ariaLabel-export-trace": "", - "give-feedback": "Publiez votre commentaire", - "label-copied": "Copié !", - "label-export": "Exporter", - "label-trace-id": "ID de trace", - "title-share-thoughts-about-tracing-grafana": "Veuillez partager vos réflexions sur les traces dans Grafana." - }, "trace-page-header": { - "text-partial-trace": "Trace partielle", - "tooltip-url": "{{url}} ou {{target}} ou {{path}}" + "duration": "", + "export-started": "", + "give-feedback": "", + "link-copied": "", + "path": "", + "route": "", + "services": "", + "share": "", + "share-copy-link": "", + "share-export-json": "", + "share-tooltip": "", + "start-time": "", + "target": "", + "title-share-thoughts-about-tracing-grafana": "", + "tooltip-url": "", + "trace-id": "", + "url": "" }, "trace-page-search-bar": { "aria-label-clear-filters": "Bouton Supprimer les filtres", diff --git a/public/locales/hu-HU/grafana.json b/public/locales/hu-HU/grafana.json index 94b862a9048..5e9f8acb6d0 100644 --- a/public/locales/hu-HU/grafana.json +++ b/public/locales/hu-HU/grafana.json @@ -1548,8 +1548,7 @@ "group-not-editable": "A kijelölt csoport nem szerkeszthető", "group-not-editable-description": "Ez a csoport olyan adatforráshoz tartozik, amely nem támogatja a szerkesztést.", "page-title": "Szabálycsoport szerkesztése", - "rule-group-error": "Hiba történt a szabálycsoport betöltésekor", - "title": "Értékelési csoport szerkesztése" + "rule-group-error": "Hiba történt a szabálycsoport betöltésekor" }, "group-loader": { "group-load-failed": "A szabályok betöltése a(z) {{ groupName }} csoportból nem sikerült a(z) {{ namespaceName }} névtérben", @@ -1583,8 +1582,7 @@ "synthetic-monitoring-card-item-2": "Futtasson pingelést, DNS-, HTTP/S- és TCP-ellenőrzéseket minden hálózati rétegben.", "synthetic-monitoring-card-item-3": "Használhat több mint 20 globális szondát vagy privát szondát a tűzfal mögött.", "synthetic-monitoring-card-item-4": "Kövesse nyomon az SLO-kat beépített Prometheus-stílusú riasztásokkal – közvetlenül a felhasználói felületről.", - "synthetic-monitoring-card-title": "Szintetikus megfigyelés", - "title-alerting": "Riasztás" + "synthetic-monitoring-card-title": "Szintetikus megfigyelés" }, "import-to-gma": { "action-button": "Importálás", @@ -3740,6 +3738,7 @@ "category-selected-connection": "Kiválasztott kapcsolat", "category-selected-element": "Kiválasztott elem ({{element}})", "category-size": "Méret", + "category-tooltip": "", "cloud-item": { "category-cloud": "Felhő", "label": { @@ -3984,6 +3983,13 @@ "auto": "Automatikus" } }, + "tooltip-options": { + "name-tooltip-mode": "", + "tooltip-mode-options": { + "label-disabled": "", + "label-enabled": "" + } + }, "tree-navigation-editor": { "clear-selection": "Kijelölés törlése", "frame-selection": "Keret kiválasztása", @@ -7006,6 +7012,9 @@ "pro-tip-define-sources-through-configuration-files": " Tipp: Adatforrásokat konfigurációs fájlokon keresztül is definiálhat. " } }, + "pane": { + "loading-placeholder": "" + }, "prev": "Előző", "queryless-apps-extensions": { "aria-label-go-queryless": "Lekérdezés nélkül" @@ -7272,18 +7281,24 @@ "split-tooltip": "Ablaktábla felosztása", "split-widen": "Ablaktábla szélesítése" }, - "trace-page-actions": { - "ariaLabel-copy-trace-id": "", - "ariaLabel-export-trace": "", - "give-feedback": "Visszajelzés küldése", - "label-copied": "Kimásolva!", - "label-export": "Exportálás", - "label-trace-id": "Nyomkövetési azonosító", - "title-share-thoughts-about-tracing-grafana": "Ossza meg gondolatait a Grafanában elérhető nyomon követésről." - }, "trace-page-header": { - "text-partial-trace": "Részleges nyomvonal", - "tooltip-url": "{{url}}, vagy {{target}}, vagy {{path}}" + "duration": "", + "export-started": "", + "give-feedback": "", + "link-copied": "", + "path": "", + "route": "", + "services": "", + "share": "", + "share-copy-link": "", + "share-export-json": "", + "share-tooltip": "", + "start-time": "", + "target": "", + "title-share-thoughts-about-tracing-grafana": "", + "tooltip-url": "", + "trace-id": "", + "url": "" }, "trace-page-search-bar": { "aria-label-clear-filters": "Szűrők törlése gomb", diff --git a/public/locales/id-ID/grafana.json b/public/locales/id-ID/grafana.json index 52e1b7bd2f0..ee7e3db48e3 100644 --- a/public/locales/id-ID/grafana.json +++ b/public/locales/id-ID/grafana.json @@ -1542,8 +1542,7 @@ "group-not-editable": "Grup yang dipilih tidak dapat diedit", "group-not-editable-description": "Grup ini termasuk dalam sumber data yang tidak mendukung pengeditan.", "page-title": "Edit grup aturan", - "rule-group-error": "Kesalahan saat memuat grup aturan", - "title": "Edit grup evaluasi" + "rule-group-error": "Kesalahan saat memuat grup aturan" }, "group-loader": { "group-load-failed": "Gagal memuat aturan dari grup {{ groupName }} di {{ namespaceName }}", @@ -1577,8 +1576,7 @@ "synthetic-monitoring-card-item-2": "Jalankan pemeriksaan ping, DNS, HTTP/S, dan TCP di setiap layer jaringan.", "synthetic-monitoring-card-item-3": "Gunakan 20+ probe global atau probe pribadi di belakang firewall Anda.", "synthetic-monitoring-card-item-4": "Lacak SLO dengan peringatan yang mirip cara kerja Prometheus bawaan—langsung dari UI.", - "synthetic-monitoring-card-title": "Pemantauan Sintetis", - "title-alerting": "Alerting" + "synthetic-monitoring-card-title": "Pemantauan Sintetis" }, "import-to-gma": { "action-button": "Impor", @@ -3721,6 +3719,7 @@ "category-selected-connection": "Koneksi yang dipilih", "category-selected-element": "Elemen yang dipilih ({{element}})", "category-size": "Ukuran", + "category-tooltip": "", "cloud-item": { "category-cloud": "Cloud", "label": { @@ -3965,6 +3964,13 @@ "auto": "Auto" } }, + "tooltip-options": { + "name-tooltip-mode": "", + "tooltip-mode-options": { + "label-disabled": "", + "label-enabled": "" + } + }, "tree-navigation-editor": { "clear-selection": "Hapus pilihan", "frame-selection": "Pilihan frame", @@ -6984,6 +6990,9 @@ "pro-tip-define-sources-through-configuration-files": " ProTip: Anda juga dapat menentukan sumber data melalui file konfigurasi. " } }, + "pane": { + "loading-placeholder": "" + }, "prev": "Sebelumnya", "queryless-apps-extensions": { "aria-label-go-queryless": "Buka tanpa kueri" @@ -7250,18 +7259,24 @@ "split-tooltip": "Pisahkan panel", "split-widen": "Lebarkan panel" }, - "trace-page-actions": { - "ariaLabel-copy-trace-id": "", - "ariaLabel-export-trace": "", - "give-feedback": "Berikan umpan balik", - "label-copied": "Disalin!", - "label-export": "Ekspor", - "label-trace-id": "ID Pelacakan", - "title-share-thoughts-about-tracing-grafana": "Bagikan pendapat Anda tentang pelacakan di Grafana." - }, "trace-page-header": { - "text-partial-trace": "Jejak parsial", - "tooltip-url": "{{url}} atau {{target}} atau {{path}}" + "duration": "", + "export-started": "", + "give-feedback": "", + "link-copied": "", + "path": "", + "route": "", + "services": "", + "share": "", + "share-copy-link": "", + "share-export-json": "", + "share-tooltip": "", + "start-time": "", + "target": "", + "title-share-thoughts-about-tracing-grafana": "", + "tooltip-url": "", + "trace-id": "", + "url": "" }, "trace-page-search-bar": { "aria-label-clear-filters": "Tombol hapus filter", diff --git a/public/locales/it-IT/grafana.json b/public/locales/it-IT/grafana.json index cb88ba60b9b..b9280ddbc9f 100644 --- a/public/locales/it-IT/grafana.json +++ b/public/locales/it-IT/grafana.json @@ -1548,8 +1548,7 @@ "group-not-editable": "Il gruppo selezionato non può essere modificato", "group-not-editable-description": "Questo gruppo appartiene a un'origine dati che non supporta la modifica.", "page-title": "Modifica gruppo di regole", - "rule-group-error": "Errore durante il caricamento del gruppo di regole", - "title": "Modifica il gruppo di valutazione" + "rule-group-error": "Errore durante il caricamento del gruppo di regole" }, "group-loader": { "group-load-failed": "Impossibile caricare le regole dal gruppo {{ groupName }} in {{ namespaceName }}", @@ -1583,8 +1582,7 @@ "synthetic-monitoring-card-item-2": "", "synthetic-monitoring-card-item-3": "", "synthetic-monitoring-card-item-4": "", - "synthetic-monitoring-card-title": "", - "title-alerting": "Avvisi" + "synthetic-monitoring-card-title": "" }, "import-to-gma": { "action-button": "Importa", @@ -3740,6 +3738,7 @@ "category-selected-connection": "", "category-selected-element": "", "category-size": "", + "category-tooltip": "", "cloud-item": { "category-cloud": "", "label": { @@ -3984,6 +3983,13 @@ "auto": "Automatico" } }, + "tooltip-options": { + "name-tooltip-mode": "", + "tooltip-mode-options": { + "label-disabled": "", + "label-enabled": "" + } + }, "tree-navigation-editor": { "clear-selection": "Cancella selezione", "frame-selection": "", @@ -7006,6 +7012,9 @@ "pro-tip-define-sources-through-configuration-files": " Suggerimento: è inoltre possibile definire le origini dei dati tramite i file di configurazione. " } }, + "pane": { + "loading-placeholder": "" + }, "prev": "Prec.", "queryless-apps-extensions": { "aria-label-go-queryless": "Passa a senza query" @@ -7272,18 +7281,24 @@ "split-tooltip": "Dividi il riquadro", "split-widen": "Allarga riquadro" }, - "trace-page-actions": { - "ariaLabel-copy-trace-id": "", - "ariaLabel-export-trace": "", - "give-feedback": "Lascia un feedback", - "label-copied": "Copiato!", - "label-export": "Esporta", - "label-trace-id": "ID traccia", - "title-share-thoughts-about-tracing-grafana": "Condividi i tuoi pensieri sul tracciamento in Grafana." - }, "trace-page-header": { - "text-partial-trace": "Traccia parziale", - "tooltip-url": "{{url}} o {{target}} o {{path}}" + "duration": "", + "export-started": "", + "give-feedback": "", + "link-copied": "", + "path": "", + "route": "", + "services": "", + "share": "", + "share-copy-link": "", + "share-export-json": "", + "share-tooltip": "", + "start-time": "", + "target": "", + "title-share-thoughts-about-tracing-grafana": "", + "tooltip-url": "", + "trace-id": "", + "url": "" }, "trace-page-search-bar": { "aria-label-clear-filters": "Tasto Cancella filtri", diff --git a/public/locales/ja-JP/grafana.json b/public/locales/ja-JP/grafana.json index c20aa149d2f..9d43cef2cfb 100644 --- a/public/locales/ja-JP/grafana.json +++ b/public/locales/ja-JP/grafana.json @@ -1542,8 +1542,7 @@ "group-not-editable": "選択したグループは編集できません", "group-not-editable-description": "このグループは、編集をサポートしていないデータソースに属しています。", "page-title": "ルールグループを編集", - "rule-group-error": "ルールグループの読み込み中にエラーが発生しました", - "title": "評価グループを編集" + "rule-group-error": "ルールグループの読み込み中にエラーが発生しました" }, "group-loader": { "group-load-failed": "{{ namespaceName }}の{{ groupName }}グループからルールを読み込めませんでした", @@ -1577,8 +1576,7 @@ "synthetic-monitoring-card-item-2": "全ネットワーク層でping、DNS、HTTP/S、TCPチェックを実行します。", "synthetic-monitoring-card-item-3": "20以上のグローバルプローブまたはファイアウォール内のプライベートプローブを使用します。", "synthetic-monitoring-card-item-4": "UIから直接、組み込みのPrometheusスタイルアラートでSLOを追跡します。", - "synthetic-monitoring-card-title": "合成モニタリング", - "title-alerting": "アラート" + "synthetic-monitoring-card-title": "合成モニタリング" }, "import-to-gma": { "action-button": "インポート", @@ -3721,6 +3719,7 @@ "category-selected-connection": "選択した接続", "category-selected-element": "選択した要素({{element}})", "category-size": "サイズ", + "category-tooltip": "", "cloud-item": { "category-cloud": "クラウド", "label": { @@ -3965,6 +3964,13 @@ "auto": "自動" } }, + "tooltip-options": { + "name-tooltip-mode": "", + "tooltip-mode-options": { + "label-disabled": "", + "label-enabled": "" + } + }, "tree-navigation-editor": { "clear-selection": "選択をクリア", "frame-selection": "フレームの選択", @@ -6984,6 +6990,9 @@ "pro-tip-define-sources-through-configuration-files": "豆知識:設定ファイルでもデータソースを定義できます。" } }, + "pane": { + "loading-placeholder": "" + }, "prev": "前へ", "queryless-apps-extensions": { "aria-label-go-queryless": "クエリなしにする" @@ -7250,18 +7259,24 @@ "split-tooltip": "ペインを分割する", "split-widen": "ペインを広げる" }, - "trace-page-actions": { - "ariaLabel-copy-trace-id": "", - "ariaLabel-export-trace": "", - "give-feedback": "フィードバックを送信", - "label-copied": "コピーしました!", - "label-export": "エクスポート", - "label-trace-id": "トレースID", - "title-share-thoughts-about-tracing-grafana": "Grafanaでのトレースについてご意見をお聞かせください。" - }, "trace-page-header": { - "text-partial-trace": "部分トレース", - "tooltip-url": "{{url}}、{{target}}または{{path}}" + "duration": "", + "export-started": "", + "give-feedback": "", + "link-copied": "", + "path": "", + "route": "", + "services": "", + "share": "", + "share-copy-link": "", + "share-export-json": "", + "share-tooltip": "", + "start-time": "", + "target": "", + "title-share-thoughts-about-tracing-grafana": "", + "tooltip-url": "", + "trace-id": "", + "url": "" }, "trace-page-search-bar": { "aria-label-clear-filters": "フィルターのクリアボタン", diff --git a/public/locales/ko-KR/grafana.json b/public/locales/ko-KR/grafana.json index f105ff1a09c..06e3e47560b 100644 --- a/public/locales/ko-KR/grafana.json +++ b/public/locales/ko-KR/grafana.json @@ -1542,8 +1542,7 @@ "group-not-editable": "선택한 그룹은 편집할 수 없습니다.", "group-not-editable-description": "이 그룹은 편집을 지원하지 않는 데이터 소스에 속해 있습니다.", "page-title": "규칙 그룹 편집", - "rule-group-error": "규칙 그룹 로딩 중 오류 발생", - "title": "평가 그룹 편집" + "rule-group-error": "규칙 그룹 로딩 중 오류 발생" }, "group-loader": { "group-load-failed": "{{ namespaceName }}의 {{ groupName }} 그룹에서 규칙 로딩 실패", @@ -1577,8 +1576,7 @@ "synthetic-monitoring-card-item-2": "", "synthetic-monitoring-card-item-3": "", "synthetic-monitoring-card-item-4": "", - "synthetic-monitoring-card-title": "", - "title-alerting": "경고" + "synthetic-monitoring-card-title": "" }, "import-to-gma": { "action-button": "가져오기", @@ -3721,6 +3719,7 @@ "category-selected-connection": "", "category-selected-element": "", "category-size": "", + "category-tooltip": "", "cloud-item": { "category-cloud": "", "label": { @@ -3965,6 +3964,13 @@ "auto": "자동" } }, + "tooltip-options": { + "name-tooltip-mode": "", + "tooltip-mode-options": { + "label-disabled": "", + "label-enabled": "" + } + }, "tree-navigation-editor": { "clear-selection": "선택 초기화", "frame-selection": "프레임 선택", @@ -6984,6 +6990,9 @@ "pro-tip-define-sources-through-configuration-files": " 전문가 팁: 구성 파일을 통해 데이터 소스를 정의할 수도 있습니다. " } }, + "pane": { + "loading-placeholder": "" + }, "prev": "이전", "queryless-apps-extensions": { "aria-label-go-queryless": "쿼리 없이 진행" @@ -7250,18 +7259,24 @@ "split-tooltip": "창 분할", "split-widen": "창 확대" }, - "trace-page-actions": { - "ariaLabel-copy-trace-id": "", - "ariaLabel-export-trace": "", - "give-feedback": "피드백 제출하기", - "label-copied": "복사됨!", - "label-export": "내보내기", - "label-trace-id": "트레이스 ID", - "title-share-thoughts-about-tracing-grafana": "Grafana에서 트레이싱에 대한 의견을 공유하세요." - }, "trace-page-header": { - "text-partial-trace": "부분 트레이스", - "tooltip-url": "{{url}} 또는 {{target}} 또는 {{path}}" + "duration": "", + "export-started": "", + "give-feedback": "", + "link-copied": "", + "path": "", + "route": "", + "services": "", + "share": "", + "share-copy-link": "", + "share-export-json": "", + "share-tooltip": "", + "start-time": "", + "target": "", + "title-share-thoughts-about-tracing-grafana": "", + "tooltip-url": "", + "trace-id": "", + "url": "" }, "trace-page-search-bar": { "aria-label-clear-filters": "필터 지우기 버튼", diff --git a/public/locales/nl-NL/grafana.json b/public/locales/nl-NL/grafana.json index 13dea31f267..c1603266c7d 100644 --- a/public/locales/nl-NL/grafana.json +++ b/public/locales/nl-NL/grafana.json @@ -1548,8 +1548,7 @@ "group-not-editable": "Geselecteerde groep kan niet worden bewerkt", "group-not-editable-description": "Deze groep behoort tot een gegevensbron die geen bewerking ondersteunt.", "page-title": "Regelgroep bewerken", - "rule-group-error": "Er is een fout opgetreden bij het laden van de regelgroep", - "title": "Evaluatiegroep bewerken" + "rule-group-error": "Er is een fout opgetreden bij het laden van de regelgroep" }, "group-loader": { "group-load-failed": "Kan regels uit groep {{ groupName }} in {{ namespaceName }} niet laden", @@ -1583,8 +1582,7 @@ "synthetic-monitoring-card-item-2": "Voer ping-, DNS-, HTTP/S- en TCP-checks uit op elk netwerklaag.", "synthetic-monitoring-card-item-3": "Gebruik meer dan 20 wereldwijde probes of eigen probes achter je firewall.", "synthetic-monitoring-card-item-4": "Volg SLO’s met ingebouwde waarschuwingen in Prometheus-stijl, direct vanuit de UI.", - "synthetic-monitoring-card-title": "Synthetische monitoring", - "title-alerting": "Alerting" + "synthetic-monitoring-card-title": "Synthetische monitoring" }, "import-to-gma": { "action-button": "Importeren", @@ -3740,6 +3738,7 @@ "category-selected-connection": "Geselecteerde verbinding", "category-selected-element": "Geselecteerd element ({{element}})", "category-size": "Grootte", + "category-tooltip": "", "cloud-item": { "category-cloud": "Cloud", "label": { @@ -3984,6 +3983,13 @@ "auto": "Automatisch" } }, + "tooltip-options": { + "name-tooltip-mode": "", + "tooltip-mode-options": { + "label-disabled": "", + "label-enabled": "" + } + }, "tree-navigation-editor": { "clear-selection": "Selectie wissen", "frame-selection": "Frameselectie", @@ -7006,6 +7012,9 @@ "pro-tip-define-sources-through-configuration-files": "ProTip: je kunt ook gegevensbronnen definiëren via configuratiebestanden. " } }, + "pane": { + "loading-placeholder": "" + }, "prev": "Vorige", "queryless-apps-extensions": { "aria-label-go-queryless": "Zonder query's" @@ -7272,18 +7281,24 @@ "split-tooltip": "Deelvenster splitsen", "split-widen": "Deelvenster verbreden" }, - "trace-page-actions": { - "ariaLabel-copy-trace-id": "", - "ariaLabel-export-trace": "", - "give-feedback": "Feedback geven", - "label-copied": "Gekopieerd.", - "label-export": "Exporteren", - "label-trace-id": "Tracering-id", - "title-share-thoughts-about-tracing-grafana": "Deel je mening over tracing in Grafana." - }, "trace-page-header": { - "text-partial-trace": "Gedeeltelijk spoor", - "tooltip-url": "{{url}} of {{target}} of {{path}}" + "duration": "", + "export-started": "", + "give-feedback": "", + "link-copied": "", + "path": "", + "route": "", + "services": "", + "share": "", + "share-copy-link": "", + "share-export-json": "", + "share-tooltip": "", + "start-time": "", + "target": "", + "title-share-thoughts-about-tracing-grafana": "", + "tooltip-url": "", + "trace-id": "", + "url": "" }, "trace-page-search-bar": { "aria-label-clear-filters": "Knop Filters wissen", diff --git a/public/locales/pl-PL/grafana.json b/public/locales/pl-PL/grafana.json index 42b4fbd5c2b..94f57f04483 100644 --- a/public/locales/pl-PL/grafana.json +++ b/public/locales/pl-PL/grafana.json @@ -1560,8 +1560,7 @@ "group-not-editable": "Nie można edytować wybranej grupy", "group-not-editable-description": "Ta grupa należy do źródła danych, które nie obsługuje edycji.", "page-title": "Edytuj grupę reguł", - "rule-group-error": "Błąd podczas ładowania grupy reguł", - "title": "Edytuj grupę ocen" + "rule-group-error": "Błąd podczas ładowania grupy reguł" }, "group-loader": { "group-load-failed": "Nie udało się wczytać reguł z grupy {{ groupName }} w przestrzeni nazw {{ namespaceName }}", @@ -1595,8 +1594,7 @@ "synthetic-monitoring-card-item-2": "", "synthetic-monitoring-card-item-3": "", "synthetic-monitoring-card-item-4": "", - "synthetic-monitoring-card-title": "", - "title-alerting": "Alerty" + "synthetic-monitoring-card-title": "" }, "import-to-gma": { "action-button": "Importuj", @@ -3778,6 +3776,7 @@ "category-selected-connection": "", "category-selected-element": "", "category-size": "", + "category-tooltip": "", "cloud-item": { "category-cloud": "", "label": { @@ -4022,6 +4021,13 @@ "auto": "Automatycznie" } }, + "tooltip-options": { + "name-tooltip-mode": "", + "tooltip-mode-options": { + "label-disabled": "", + "label-enabled": "" + } + }, "tree-navigation-editor": { "clear-selection": "Usuń zaznaczone", "frame-selection": "Wybór ramki", @@ -7050,6 +7056,9 @@ "pro-tip-define-sources-through-configuration-files": " Wskazówka: źródła danych można też definiować za pomocą plików konfiguracyjnych. " } }, + "pane": { + "loading-placeholder": "" + }, "prev": "Wstecz", "queryless-apps-extensions": { "aria-label-go-queryless": "Bez zapytań" @@ -7316,18 +7325,24 @@ "split-tooltip": "Podziel okienko", "split-widen": "Poszerz okno" }, - "trace-page-actions": { - "ariaLabel-copy-trace-id": "", - "ariaLabel-export-trace": "", - "give-feedback": "Przekaż opinię", - "label-copied": "Skopiowano", - "label-export": "Eksport", - "label-trace-id": "Identyfikator śledzenia", - "title-share-thoughts-about-tracing-grafana": "Podziel się swoimi przemyśleniami na temat śledzenia w usłudze Grafana." - }, "trace-page-header": { - "text-partial-trace": "Częściowy ślad", - "tooltip-url": "{{url}}, {{target}} lub {{path}}" + "duration": "", + "export-started": "", + "give-feedback": "", + "link-copied": "", + "path": "", + "route": "", + "services": "", + "share": "", + "share-copy-link": "", + "share-export-json": "", + "share-tooltip": "", + "start-time": "", + "target": "", + "title-share-thoughts-about-tracing-grafana": "", + "tooltip-url": "", + "trace-id": "", + "url": "" }, "trace-page-search-bar": { "aria-label-clear-filters": "Przycisk Wyczyść filtry", diff --git a/public/locales/pt-BR/grafana.json b/public/locales/pt-BR/grafana.json index 0fa35e4fbf5..4515bb35b80 100644 --- a/public/locales/pt-BR/grafana.json +++ b/public/locales/pt-BR/grafana.json @@ -1548,8 +1548,7 @@ "group-not-editable": "O grupo selecionado não pode ser editado", "group-not-editable-description": "Este grupo pertence a uma fonte de dados que não é compatível com edição.", "page-title": "Editar grupo de regras", - "rule-group-error": "Erro ao carregar o grupo de regras", - "title": "Editar grupo de avaliação" + "rule-group-error": "Erro ao carregar o grupo de regras" }, "group-loader": { "group-load-failed": "Falha ao carregar regras do grupo {{ groupName }} em {{ namespaceName }}", @@ -1583,8 +1582,7 @@ "synthetic-monitoring-card-item-2": "", "synthetic-monitoring-card-item-3": "", "synthetic-monitoring-card-item-4": "", - "synthetic-monitoring-card-title": "", - "title-alerting": "Alertas" + "synthetic-monitoring-card-title": "" }, "import-to-gma": { "action-button": "Importar", @@ -3740,6 +3738,7 @@ "category-selected-connection": "", "category-selected-element": "", "category-size": "", + "category-tooltip": "", "cloud-item": { "category-cloud": "", "label": { @@ -3984,6 +3983,13 @@ "auto": "Automático" } }, + "tooltip-options": { + "name-tooltip-mode": "", + "tooltip-mode-options": { + "label-disabled": "", + "label-enabled": "" + } + }, "tree-navigation-editor": { "clear-selection": "Limpar seleção", "frame-selection": "Seleção de quadro", @@ -7006,6 +7012,9 @@ "pro-tip-define-sources-through-configuration-files": " Dica: você também pode definir fontes de dados por meio de arquivos de configuração. " } }, + "pane": { + "loading-placeholder": "" + }, "prev": "Anterior", "queryless-apps-extensions": { "aria-label-go-queryless": "Continuar sem consulta" @@ -7272,18 +7281,24 @@ "split-tooltip": "Dividir o painel", "split-widen": "Painel amplo" }, - "trace-page-actions": { - "ariaLabel-copy-trace-id": "", - "ariaLabel-export-trace": "", - "give-feedback": "Dar feedback", - "label-copied": "Copiado!", - "label-export": "Exportar", - "label-trace-id": "ID do rastro", - "title-share-thoughts-about-tracing-grafana": "Compartilhe sua opinião sobre os traços na Grafana." - }, "trace-page-header": { - "text-partial-trace": "Rastro parcial", - "tooltip-url": "{{url}} ou {{target}} ou {{path}}" + "duration": "", + "export-started": "", + "give-feedback": "", + "link-copied": "", + "path": "", + "route": "", + "services": "", + "share": "", + "share-copy-link": "", + "share-export-json": "", + "share-tooltip": "", + "start-time": "", + "target": "", + "title-share-thoughts-about-tracing-grafana": "", + "tooltip-url": "", + "trace-id": "", + "url": "" }, "trace-page-search-bar": { "aria-label-clear-filters": "Botão para limpar filtros", diff --git a/public/locales/pt-PT/grafana.json b/public/locales/pt-PT/grafana.json index e24b5589e56..fd87d26a16b 100644 --- a/public/locales/pt-PT/grafana.json +++ b/public/locales/pt-PT/grafana.json @@ -1548,8 +1548,7 @@ "group-not-editable": "O grupo selecionado não pode ser editado", "group-not-editable-description": "Este grupo pertence a uma origem de dados que não suporta edição.", "page-title": "Editar grupo de regras", - "rule-group-error": "Erro ao carregar grupo de regras", - "title": "Editar grupo de avaliação" + "rule-group-error": "Erro ao carregar grupo de regras" }, "group-loader": { "group-load-failed": "Falha ao carregar regras do grupo {{ groupName }} em {{ namespaceName }}", @@ -1583,8 +1582,7 @@ "synthetic-monitoring-card-item-2": "", "synthetic-monitoring-card-item-3": "", "synthetic-monitoring-card-item-4": "", - "synthetic-monitoring-card-title": "", - "title-alerting": "Alerta" + "synthetic-monitoring-card-title": "" }, "import-to-gma": { "action-button": "Importar", @@ -3740,6 +3738,7 @@ "category-selected-connection": "", "category-selected-element": "", "category-size": "", + "category-tooltip": "", "cloud-item": { "category-cloud": "", "label": { @@ -3984,6 +3983,13 @@ "auto": "Automóvel" } }, + "tooltip-options": { + "name-tooltip-mode": "", + "tooltip-mode-options": { + "label-disabled": "", + "label-enabled": "" + } + }, "tree-navigation-editor": { "clear-selection": "Limpar a seleção", "frame-selection": "Seleção de moldura", @@ -7006,6 +7012,9 @@ "pro-tip-define-sources-through-configuration-files": "Dica: também pode definir origens de dados através de ficheiros de configuração. " } }, + "pane": { + "loading-placeholder": "" + }, "prev": "Anterior", "queryless-apps-extensions": { "aria-label-go-queryless": "Sem consulta" @@ -7272,18 +7281,24 @@ "split-tooltip": "Dividir o painel", "split-widen": "Alargar painel" }, - "trace-page-actions": { - "ariaLabel-copy-trace-id": "", - "ariaLabel-export-trace": "", - "give-feedback": "Dar feedback", - "label-copied": "Copiado!", - "label-export": "Exportar", - "label-trace-id": "ID trace", - "title-share-thoughts-about-tracing-grafana": "Partilhe a sua opinião sobre o rastreamento na Grafana." - }, "trace-page-header": { - "text-partial-trace": "Trace parcial", - "tooltip-url": "{{url}} ou {{target}} ou {{path}}" + "duration": "", + "export-started": "", + "give-feedback": "", + "link-copied": "", + "path": "", + "route": "", + "services": "", + "share": "", + "share-copy-link": "", + "share-export-json": "", + "share-tooltip": "", + "start-time": "", + "target": "", + "title-share-thoughts-about-tracing-grafana": "", + "tooltip-url": "", + "trace-id": "", + "url": "" }, "trace-page-search-bar": { "aria-label-clear-filters": "Botão para limpar filtros", diff --git a/public/locales/ru-RU/grafana.json b/public/locales/ru-RU/grafana.json index ca39347f187..01ef6fe7de1 100644 --- a/public/locales/ru-RU/grafana.json +++ b/public/locales/ru-RU/grafana.json @@ -1560,8 +1560,7 @@ "group-not-editable": "Выбранную группу нельзя редактировать", "group-not-editable-description": "Эта группа принадлежит к источнику данных, который не поддерживает редактирование.", "page-title": "Редактирование группы правил", - "rule-group-error": "Ошибка при загрузке группы правил", - "title": "Редактирование группы оценки" + "rule-group-error": "Ошибка при загрузке группы правил" }, "group-loader": { "group-load-failed": "Не удалось загрузить правила из группы {{ groupName }} в {{ namespaceName }}", @@ -1595,8 +1594,7 @@ "synthetic-monitoring-card-item-2": "", "synthetic-monitoring-card-item-3": "", "synthetic-monitoring-card-item-4": "", - "synthetic-monitoring-card-title": "", - "title-alerting": "Оповещение" + "synthetic-monitoring-card-title": "" }, "import-to-gma": { "action-button": "Импорт", @@ -3778,6 +3776,7 @@ "category-selected-connection": "", "category-selected-element": "", "category-size": "", + "category-tooltip": "", "cloud-item": { "category-cloud": "", "label": { @@ -4022,6 +4021,13 @@ "auto": "Авто" } }, + "tooltip-options": { + "name-tooltip-mode": "", + "tooltip-mode-options": { + "label-disabled": "", + "label-enabled": "" + } + }, "tree-navigation-editor": { "clear-selection": "Очистить выбор", "frame-selection": "Выбор фрейма", @@ -7050,6 +7056,9 @@ "pro-tip-define-sources-through-configuration-files": "Совет. Источники данных можно также задавать в файлах конфигурации. " } }, + "pane": { + "loading-placeholder": "" + }, "prev": "Назад", "queryless-apps-extensions": { "aria-label-go-queryless": "Не выполнять запрос" @@ -7316,18 +7325,24 @@ "split-tooltip": "Разделить область", "split-widen": "Расширить область" }, - "trace-page-actions": { - "ariaLabel-copy-trace-id": "", - "ariaLabel-export-trace": "", - "give-feedback": "Отправить отзыв", - "label-copied": "Скопировано", - "label-export": "Экспорт", - "label-trace-id": "Идентификатор трассировки", - "title-share-thoughts-about-tracing-grafana": "Ваше мнение о трассировке в Grafana" - }, "trace-page-header": { - "text-partial-trace": "Частичная трассировка", - "tooltip-url": "{{url}} или {{target}}, или {{path}}" + "duration": "", + "export-started": "", + "give-feedback": "", + "link-copied": "", + "path": "", + "route": "", + "services": "", + "share": "", + "share-copy-link": "", + "share-export-json": "", + "share-tooltip": "", + "start-time": "", + "target": "", + "title-share-thoughts-about-tracing-grafana": "", + "tooltip-url": "", + "trace-id": "", + "url": "" }, "trace-page-search-bar": { "aria-label-clear-filters": "Кнопка очистки фильтров", diff --git a/public/locales/sv-SE/grafana.json b/public/locales/sv-SE/grafana.json index 357d3900787..c25f9d8d19b 100644 --- a/public/locales/sv-SE/grafana.json +++ b/public/locales/sv-SE/grafana.json @@ -1548,8 +1548,7 @@ "group-not-editable": "Vald grupp kan inte redigeras", "group-not-editable-description": "Den här gruppen tillhör en datakälla som inte stöder redigering.", "page-title": "Redigera regelgrupp", - "rule-group-error": "Fel vid laddning av regelgrupp", - "title": "Redigera utvärderingsgrupp" + "rule-group-error": "Fel vid laddning av regelgrupp" }, "group-loader": { "group-load-failed": "Det gick inte att ladda reglerna från gruppen {{ groupName }} i {{ namespaceName }}", @@ -1583,8 +1582,7 @@ "synthetic-monitoring-card-item-2": "", "synthetic-monitoring-card-item-3": "", "synthetic-monitoring-card-item-4": "", - "synthetic-monitoring-card-title": "", - "title-alerting": "Varnar" + "synthetic-monitoring-card-title": "" }, "import-to-gma": { "action-button": "Importera", @@ -3740,6 +3738,7 @@ "category-selected-connection": "", "category-selected-element": "", "category-size": "", + "category-tooltip": "", "cloud-item": { "category-cloud": "", "label": { @@ -3984,6 +3983,13 @@ "auto": "Auto" } }, + "tooltip-options": { + "name-tooltip-mode": "", + "tooltip-mode-options": { + "label-disabled": "", + "label-enabled": "" + } + }, "tree-navigation-editor": { "clear-selection": "Rensa val", "frame-selection": "Ramval", @@ -7006,6 +7012,9 @@ "pro-tip-define-sources-through-configuration-files": " Tips: Du kan även definiera datakällor via konfigurationsfiler. " } }, + "pane": { + "loading-placeholder": "" + }, "prev": "Tidigare", "queryless-apps-extensions": { "aria-label-go-queryless": "Kör utan frågor" @@ -7272,18 +7281,24 @@ "split-tooltip": "Dela rutan", "split-widen": "Bredda rutan" }, - "trace-page-actions": { - "ariaLabel-copy-trace-id": "", - "ariaLabel-export-trace": "", - "give-feedback": "Ge feedback", - "label-copied": "Kopierad! ", - "label-export": "Exportera", - "label-trace-id": "Spårnings-ID", - "title-share-thoughts-about-tracing-grafana": "Dela dina tankar om att spåra i Grafana." - }, "trace-page-header": { - "text-partial-trace": "Partiell spårning", - "tooltip-url": "{{url}} eller {{target}} eller {{path}}" + "duration": "", + "export-started": "", + "give-feedback": "", + "link-copied": "", + "path": "", + "route": "", + "services": "", + "share": "", + "share-copy-link": "", + "share-export-json": "", + "share-tooltip": "", + "start-time": "", + "target": "", + "title-share-thoughts-about-tracing-grafana": "", + "tooltip-url": "", + "trace-id": "", + "url": "" }, "trace-page-search-bar": { "aria-label-clear-filters": "Knappen Rensa filter", diff --git a/public/locales/tr-TR/grafana.json b/public/locales/tr-TR/grafana.json index cbcae4254be..ae7289f7817 100644 --- a/public/locales/tr-TR/grafana.json +++ b/public/locales/tr-TR/grafana.json @@ -1548,8 +1548,7 @@ "group-not-editable": "Seçilen grup düzenlenemez", "group-not-editable-description": "Bu grup, düzenlemeyi desteklemeyen bir veri kaynağına aittir.", "page-title": "Kural grubunu düzenle", - "rule-group-error": "Kural grubu yüklenirken hata oluştu", - "title": "Değerlendirme grubunu düzenle" + "rule-group-error": "Kural grubu yüklenirken hata oluştu" }, "group-loader": { "group-load-failed": "{{ namespaceName }} alanındaki {{ groupName }} grubundan kurallar yüklenemedi", @@ -1583,8 +1582,7 @@ "synthetic-monitoring-card-item-2": "", "synthetic-monitoring-card-item-3": "", "synthetic-monitoring-card-item-4": "", - "synthetic-monitoring-card-title": "", - "title-alerting": "Uyarı sistemi" + "synthetic-monitoring-card-title": "" }, "import-to-gma": { "action-button": "İçe aktar", @@ -3740,6 +3738,7 @@ "category-selected-connection": "", "category-selected-element": "", "category-size": "", + "category-tooltip": "", "cloud-item": { "category-cloud": "", "label": { @@ -3984,6 +3983,13 @@ "auto": "Otomatik" } }, + "tooltip-options": { + "name-tooltip-mode": "", + "tooltip-mode-options": { + "label-disabled": "", + "label-enabled": "" + } + }, "tree-navigation-editor": { "clear-selection": "Seçimi temizle", "frame-selection": "Çerçeve seçimi", @@ -7006,6 +7012,9 @@ "pro-tip-define-sources-through-configuration-files": "İpucu: Veri kaynaklarını yapılandırma dosyaları aracılığıyla da tanımlayabilirsiniz. " } }, + "pane": { + "loading-placeholder": "" + }, "prev": "Önceki", "queryless-apps-extensions": { "aria-label-go-queryless": "Sorgu olmadan devam et" @@ -7272,18 +7281,24 @@ "split-tooltip": "Bölmeyi böl", "split-widen": "Bölmeyi genişlet" }, - "trace-page-actions": { - "ariaLabel-copy-trace-id": "", - "ariaLabel-export-trace": "", - "give-feedback": "Geri bildirim gönder", - "label-copied": "Kopyalandı!", - "label-export": "Dışa aktar", - "label-trace-id": "İzleme kimliği", - "title-share-thoughts-about-tracing-grafana": "Grafana'daki izleme deneyiminiz hakkındaki görüşlerinizi paylaşın." - }, "trace-page-header": { - "text-partial-trace": "Kısmi izleme", - "tooltip-url": "{{url}} veya {{target}} veya {{path}}" + "duration": "", + "export-started": "", + "give-feedback": "", + "link-copied": "", + "path": "", + "route": "", + "services": "", + "share": "", + "share-copy-link": "", + "share-export-json": "", + "share-tooltip": "", + "start-time": "", + "target": "", + "title-share-thoughts-about-tracing-grafana": "", + "tooltip-url": "", + "trace-id": "", + "url": "" }, "trace-page-search-bar": { "aria-label-clear-filters": "Filtreleri temizle düğmesi", diff --git a/public/locales/zh-Hans/grafana.json b/public/locales/zh-Hans/grafana.json index e4fa606b72d..6a0e7daee75 100644 --- a/public/locales/zh-Hans/grafana.json +++ b/public/locales/zh-Hans/grafana.json @@ -1542,8 +1542,7 @@ "group-not-editable": "无法编辑所选组", "group-not-editable-description": "此组所属的数据源不支持编辑。", "page-title": "编辑规则组", - "rule-group-error": "加载规则组时出错", - "title": "编辑评估组" + "rule-group-error": "加载规则组时出错" }, "group-loader": { "group-load-failed": "从 {{ namespaceName }} 的组 {{ groupName }} 中加载规则失败", @@ -1577,8 +1576,7 @@ "synthetic-monitoring-card-item-2": "在每个网络层运行 ping、DNS、HTTP/S 和 TCP 检查。", "synthetic-monitoring-card-item-3": "使用 20 多个全球探测点,或部署在防火墙后的私有探测点。", "synthetic-monitoring-card-item-4": "使用内置的 Prometheus 风格的警报功能,直接从用户界面跟踪 SLO。", - "synthetic-monitoring-card-title": "综合监测", - "title-alerting": "警报" + "synthetic-monitoring-card-title": "综合监测" }, "import-to-gma": { "action-button": "导入", @@ -3721,6 +3719,7 @@ "category-selected-connection": "所选连接", "category-selected-element": "所选元素 ({{element}})", "category-size": "大小", + "category-tooltip": "", "cloud-item": { "category-cloud": "Cloud", "label": { @@ -3965,6 +3964,13 @@ "auto": "自动" } }, + "tooltip-options": { + "name-tooltip-mode": "", + "tooltip-mode-options": { + "label-disabled": "", + "label-enabled": "" + } + }, "tree-navigation-editor": { "clear-selection": "清除选择", "frame-selection": "帧选择", @@ -6984,6 +6990,9 @@ "pro-tip-define-sources-through-configuration-files": "专业提示:您还可以通过配置文件定义数据源。" } }, + "pane": { + "loading-placeholder": "" + }, "prev": "上一步", "queryless-apps-extensions": { "aria-label-go-queryless": "转为无查询" @@ -7250,18 +7259,24 @@ "split-tooltip": "拆分窗格", "split-widen": "宽窗格" }, - "trace-page-actions": { - "ariaLabel-copy-trace-id": "", - "ariaLabel-export-trace": "", - "give-feedback": "提供反馈", - "label-copied": "已复制!", - "label-export": "导出", - "label-trace-id": "跟踪 ID", - "title-share-thoughts-about-tracing-grafana": "分享您对 Grafana 跟踪功能的看法。" - }, "trace-page-header": { - "text-partial-trace": "部分跟踪", - "tooltip-url": "{{url}} 或 {{target}} 或 {{path}}" + "duration": "", + "export-started": "", + "give-feedback": "", + "link-copied": "", + "path": "", + "route": "", + "services": "", + "share": "", + "share-copy-link": "", + "share-export-json": "", + "share-tooltip": "", + "start-time": "", + "target": "", + "title-share-thoughts-about-tracing-grafana": "", + "tooltip-url": "", + "trace-id": "", + "url": "" }, "trace-page-search-bar": { "aria-label-clear-filters": "清除筛选按钮", diff --git a/public/locales/zh-Hant/grafana.json b/public/locales/zh-Hant/grafana.json index a73bab83765..13ff7bc7f60 100644 --- a/public/locales/zh-Hant/grafana.json +++ b/public/locales/zh-Hant/grafana.json @@ -1542,8 +1542,7 @@ "group-not-editable": "無法編輯所選群組", "group-not-editable-description": "此群組屬於不支援編輯的資料來源。", "page-title": "編輯規則群組", - "rule-group-error": "載入規則群組時發生錯誤", - "title": "編輯評估群組" + "rule-group-error": "載入規則群組時發生錯誤" }, "group-loader": { "group-load-failed": "無法從「{{ namespaceName }}」中的群組「{{ groupName }}」載入規則", @@ -1577,8 +1576,7 @@ "synthetic-monitoring-card-item-2": "在每個網路層執行 ping、DNS、HTTP/S 和 TCP 檢查。", "synthetic-monitoring-card-item-3": "在防火牆後使用 20 多個全球探測器或私人探測器。", "synthetic-monitoring-card-item-4": "使用內建的 Prometheus 風格警報追蹤 SLO - 直接從使用者介面操作。", - "synthetic-monitoring-card-title": "綜合監控", - "title-alerting": "警報" + "synthetic-monitoring-card-title": "綜合監控" }, "import-to-gma": { "action-button": "匯入", @@ -3721,6 +3719,7 @@ "category-selected-connection": "所選連線", "category-selected-element": "所選元素 ({{element}})", "category-size": "大小", + "category-tooltip": "", "cloud-item": { "category-cloud": "雲端", "label": { @@ -3965,6 +3964,13 @@ "auto": "自動" } }, + "tooltip-options": { + "name-tooltip-mode": "", + "tooltip-mode-options": { + "label-disabled": "", + "label-enabled": "" + } + }, "tree-navigation-editor": { "clear-selection": "清空選項", "frame-selection": "框選項目", @@ -6984,6 +6990,9 @@ "pro-tip-define-sources-through-configuration-files": "專家提示:您也可以透過設定檔案來定義資料來源。" } }, + "pane": { + "loading-placeholder": "" + }, "prev": "上一頁", "queryless-apps-extensions": { "aria-label-go-queryless": "停用查詢" @@ -7250,18 +7259,24 @@ "split-tooltip": "分割窗格", "split-widen": "擴大窗格" }, - "trace-page-actions": { - "ariaLabel-copy-trace-id": "", - "ariaLabel-export-trace": "", - "give-feedback": "提供意見回饋", - "label-copied": "已複製!", - "label-export": "匯出", - "label-trace-id": "追蹤 ID", - "title-share-thoughts-about-tracing-grafana": "分享對於在 Grafana 中進行追蹤的想法。" - }, "trace-page-header": { - "text-partial-trace": "部分追蹤", - "tooltip-url": "{{url}} 或 {{target}} 或 {{path}}" + "duration": "", + "export-started": "", + "give-feedback": "", + "link-copied": "", + "path": "", + "route": "", + "services": "", + "share": "", + "share-copy-link": "", + "share-export-json": "", + "share-tooltip": "", + "start-time": "", + "target": "", + "title-share-thoughts-about-tracing-grafana": "", + "tooltip-url": "", + "trace-id": "", + "url": "" }, "trace-page-search-bar": { "aria-label-clear-filters": "清除篩選條件按鈕", From ef80129bd99a9bf4d66f796ec7ef4dea826ce6ec Mon Sep 17 00:00:00 2001 From: Konrad Lalik Date: Thu, 31 Jul 2025 09:17:42 +0200 Subject: [PATCH 027/114] Alerting: Fix contact points scoped permission check (#108851) * Fix scoped contact point permission check * Use annotations permissions for k8s API and globals permissions for external AMs * Fix scoped permission check for GrafanaReceiverForm --- .../contact-points/ContactPointHeader.tsx | 4 +- .../contact-points/ContactPoints.test.tsx | 60 +++++++++++++++---- .../contact-points/EditContactPoint.test.tsx | 3 +- .../receivers/form/GrafanaReceiverForm.tsx | 6 +- 4 files changed, 55 insertions(+), 18 deletions(-) diff --git a/public/app/features/alerting/unified/components/contact-points/ContactPointHeader.tsx b/public/app/features/alerting/unified/components/contact-points/ContactPointHeader.tsx index 40a7afeddeb..1825b5e40a0 100644 --- a/public/app/features/alerting/unified/components/contact-points/ContactPointHeader.tsx +++ b/public/app/features/alerting/unified/components/contact-points/ContactPointHeader.tsx @@ -68,14 +68,14 @@ export const ContactPointHeader = ({ contactPoint, onDelete }: ContactPointHeade */ const isReferencedByAnything = usingK8sApi ? Boolean(numberOfPolicies || numberOfRules) : policies.length > 0; /** Does the current user have permissions to edit the contact point? */ - const hasAbilityToEdit = canEditEntity(contactPoint) || editAllowed; + const hasAbilityToEdit = usingK8sApi ? canEditEntity(contactPoint) : editAllowed; /** Can the contact point actually be edited via the UI? */ const contactPointIsEditable = !provisioned; /** Given the alertmanager, the user's permissions, and the state of the contact point - can it actually be edited? */ const canEdit = editSupported && hasAbilityToEdit && contactPointIsEditable; /** Does the current user have permissions to delete the contact point? */ - const hasAbilityToDelete = canDeleteEntity(contactPoint) || deleteAllowed; + const hasAbilityToDelete = usingK8sApi ? canDeleteEntity(contactPoint) : deleteAllowed; /** Can the contact point actually be deleted, regardless of permissions? i.e. ensuring it isn't provisioned and isn't referenced elsewhere */ const contactPointIsDeleteable = !provisioned && !numberOfPoliciesPreventingDeletion && !numberOfRules; /** Given the alertmanager, the user's permissions, and the state of the contact point - can it actually be deleted? */ diff --git a/public/app/features/alerting/unified/components/contact-points/ContactPoints.test.tsx b/public/app/features/alerting/unified/components/contact-points/ContactPoints.test.tsx index 79cec523d69..db39d474957 100644 --- a/public/app/features/alerting/unified/components/contact-points/ContactPoints.test.tsx +++ b/public/app/features/alerting/unified/components/contact-points/ContactPoints.test.tsx @@ -198,12 +198,37 @@ describe('contact points', () => { const unusedBadge = screen.getAllByLabelText('unused'); expect(unusedBadge).toHaveLength(4); - const viewProvisioned = screen.getByTestId('view-action'); - expect(viewProvisioned).toBeInTheDocument(); - expect(viewProvisioned).toBeEnabled(); + // Two contact points should have view buttons: grafana-default-email (cannot be edited) and provisioned-contact-point (provisioned) + const viewButtons = screen.getAllByRole('link', { name: /^view$/i }); + expect(viewButtons).toHaveLength(2); + + // Check view buttons by their href to verify which contact points they belong to + // The url is the same but the form should be readonly + expect(viewButtons[0]).toHaveAttribute('href', '/alerting/notifications/receivers/grafana-default-email/edit'); + expect(viewButtons[1]).toHaveAttribute( + 'href', + '/alerting/notifications/receivers/provisioned-contact-point/edit' + ); + + viewButtons.forEach((button) => { + expect(button).toBeEnabled(); + }); + + // Three contact points should have edit buttons: lotsa-emails, Slack with multiple channels, OnCall Contact point + const editButtons = screen.getAllByRole('link', { name: /^edit$/i }); + expect(editButtons).toHaveLength(3); + + // Check edit buttons by their href to verify which contact points they belong to + expect(editButtons[0]).toHaveAttribute('href', '/alerting/notifications/receivers/lotsa-emails/edit'); + expect(editButtons[1]).toHaveAttribute( + 'href', + '/alerting/notifications/receivers/OnCall%20Conctact%20point/edit' + ); + expect(editButtons[2]).toHaveAttribute( + 'href', + '/alerting/notifications/receivers/Slack%20with%20multiple%20channels/edit' + ); - const editButtons = screen.getAllByTestId('edit-action'); - expect(editButtons).toHaveLength(4); editButtons.forEach((button) => { expect(button).toBeEnabled(); }); @@ -227,11 +252,11 @@ describe('contact points', () => { expect(screen.getByRole('link', { name: 'add contact point' })).toHaveAttribute('aria-disabled', 'true'); // edit permission is based on API response - we should have 3 buttons - const editButtons = await screen.findAllByTestId('edit-action'); + const editButtons = await screen.findAllByRole('link', { name: /^edit$/i }); expect(editButtons).toHaveLength(3); // there should be view buttons though - one for provisioned, and one for the un-editable contact point - const viewButtons = screen.getAllByTestId('view-action'); + const viewButtons = screen.getAllByRole('link', { name: /^view$/i }); expect(viewButtons).toHaveLength(2); // check buttons in Notification Templates @@ -329,7 +354,18 @@ describe('contact points', () => { }, ]; - const { user } = renderWithProvider(); + // Add the necessary K8s annotations to allow deletion + const contactPointWithDeletePermission: ContactPointWithMetadata = { + ...basicContactPoint, + metadata: { + annotations: { + [K8sAnnotations.AccessDelete]: 'true', + }, + }, + policies, + }; + + const { user } = renderWithProvider(); const moreActions = screen.getByRole('button', { name: /More/ }); await user.click(moreActions); @@ -387,7 +423,7 @@ describe('contact points', () => { const unusedBadge = screen.getAllByLabelText('unused'); expect(unusedBadge).toHaveLength(1); - const editButtons = screen.getAllByTestId('edit-action'); + const editButtons = screen.getAllByRole('link', { name: /^edit$/i }); expect(editButtons).toHaveLength(2); editButtons.forEach((button) => { expect(button).toBeEnabled(); @@ -431,9 +467,9 @@ describe('contact points', () => { expect(screen.queryByRole('link', { name: 'add contact point' })).not.toBeInTheDocument(); - const viewProvisioned = screen.getByTestId('view-action'); - expect(viewProvisioned).toBeInTheDocument(); - expect(viewProvisioned).toBeEnabled(); + const viewButton = screen.getByRole('link', { name: /^view$/i }); + expect(viewButton).toBeInTheDocument(); + expect(viewButton).toBeEnabled(); // check buttons in Notification Templates const notificationTemplatesTab = screen.getByRole('tab', { name: 'Notification Templates' }); diff --git a/public/app/features/alerting/unified/components/contact-points/EditContactPoint.test.tsx b/public/app/features/alerting/unified/components/contact-points/EditContactPoint.test.tsx index 04f6fcdb435..75413e20e33 100644 --- a/public/app/features/alerting/unified/components/contact-points/EditContactPoint.test.tsx +++ b/public/app/features/alerting/unified/components/contact-points/EditContactPoint.test.tsx @@ -44,7 +44,8 @@ beforeEach(() => { grantUserPermissions([AccessControlAction.AlertingNotificationsRead, AccessControlAction.AlertingNotificationsWrite]); }); -const getTemplatePreviewContent = async () => within(screen.getByTestId('template-preview')).findByTestId('mockeditor'); +const getTemplatePreviewContent = async () => + within(await screen.findByTestId('template-preview')).findByTestId('mockeditor'); const templatesSelectorTestId = 'existing-templates-selector'; diff --git a/public/app/features/alerting/unified/components/receivers/form/GrafanaReceiverForm.tsx b/public/app/features/alerting/unified/components/receivers/form/GrafanaReceiverForm.tsx index 44da5c3588b..8971c9b34d7 100644 --- a/public/app/features/alerting/unified/components/receivers/form/GrafanaReceiverForm.tsx +++ b/public/app/features/alerting/unified/components/receivers/form/GrafanaReceiverForm.tsx @@ -135,9 +135,9 @@ export const GrafanaReceiverForm = ({ contactPoint, readOnly = false, editMode } } }; - const isEditable = Boolean( - (!readOnly || (contactPoint && canEditEntity(contactPoint))) && !contactPoint?.provisioned - ); + // If there is no contact point it means we're creating a new one, so scoped permissions doesn't exist yet + const hasScopedEditPermissions = contactPoint ? canEditEntity(contactPoint) : true; + const isEditable = !readOnly && hasScopedEditPermissions && !contactPoint?.provisioned; const isTestable = !readOnly; if (isLoadingNotifiers || isLoadingOnCallIntegration) { From 91e5e8019df41924d79909bd881525d07dfd5aaa Mon Sep 17 00:00:00 2001 From: Bruno Abrantes Date: Thu, 31 Jul 2025 09:18:29 +0200 Subject: [PATCH 028/114] chore: add documentation on unified search (#108847) * chore: add documentation on unified search Signed-off-by: Bruno Abrantes * fix: add additional needed feature flags for unified search Signed-off-by: Bruno Abrantes * fix: add docs about index_min_count and index_max_count Signed-off-by: Bruno Abrantes * fix: add documentation about sortable fields and the discrepancy with search/sortable Signed-off-by: Bruno Abrantes * fix: kubernetesClientDashboardsFolders feature flag is no more, remove it from the docs Signed-off-by: Bruno Abrantes * fix: simplify request flow diagrams Signed-off-by: Bruno Abrantes --------- Signed-off-by: Bruno Abrantes --- pkg/storage/unified/README.md | 509 ++++++++++++++++++++++++++++++++++ 1 file changed, 509 insertions(+) diff --git a/pkg/storage/unified/README.md b/pkg/storage/unified/README.md index 03301cf12bc..dd53fa622b0 100644 --- a/pkg/storage/unified/README.md +++ b/pkg/storage/unified/README.md @@ -777,3 +777,512 @@ The dual writer system provides metrics for monitoring: - `dual_writer_mode_transitions_total`: Counter of mode transitions Use these metrics to monitor the health of your migration and identify any issues with the dual writer system. + +--- + +## Unified Search System + +The Unified Search system provides a scalable, distributed search capability for Grafana's Unified Storage. It uses a ring-based architecture to distribute search requests across multiple search server instances, with namespace-based sharding for optimal performance and data distribution. + +### System Architecture + +The search system provides both unified and legacy search capabilities, with routing based on dual writer mode configuration: + +```mermaid +graph TB + subgraph "Request Sources" + A[Grafana UI
User Search] + B[Dashboard Service
Resource Searches] + C[Folder Service
Resource Searches] + D[Alerting Service
Resource Searches] + E[Provisioning Service
Resource Searches] + F[API Endpoints
Search Operations] + end + + subgraph "API Gateway Layer" + G[Grafana API Server
Search Endpoint] + H[Search Client
Dual Writer Aware] + end + + subgraph "Routing Decision" + I{Dual Writer Mode
Check} + end + + subgraph "Unified Search Path (Mode 3+)" + J[Search Distributor
Ring-based Routing] + K[Ring
Consistent Hashing] + L[Search API Server 1
Namespace Sharding
+ Embedded Bleve Backend] + M[Search API Server 2
Namespace Sharding
+ Embedded Bleve Backend] + N[Search API Server 3
Namespace Sharding
+ Embedded Bleve Backend] + O[Unified Storage
K8s-style Resources] + end + + subgraph "Legacy Search Path (Mode 0-2)" + Q[Legacy Search Service
SQL-based Search] + R[Legacy Storage
Traditional Tables] + S[Shadow Traffic
Mode 1-2 + Flag Enabled] + end + + A --> G + B --> H + C --> H + D --> H + E --> H + F --> G + G --> H + H --> I + + I -->|Mode 3+| J + I -->|Mode 0-2| Q + + J --> K + K --> L + K --> M + K --> N + L -.->|Just-in-Time
Indexing| O + M -.->|Just-in-Time
Indexing| O + N -.->|Just-in-Time
Indexing| O + + Q --> R + Q -.->|Shadow Traffic
Mode 1-2 + Flag| S + S -.-> J +``` + +### Search Backend Routing + +The search client routes requests based on the dual writer mode configuration for each resource type: + +#### Dual Writer Mode → Backend Routing +- **Mode 0-2**: Route to **Legacy Search** + - Mode 1-2: Shadow traffic to Unified Search (if `unifiedStorageSearchDualReaderEnabled` is enabled) + - Mode 0: No shadow traffic +- **Mode 3+**: Route to **Unified Search** + - No shadow traffic needed (unified is primary) + +### Feature Flags + +Unified Search requires several feature flags to be enabled depending on the desired functionality: + +#### Prerequisites (Required for Unified Storage) + +| Feature Flag | Purpose | Stage | Required For | +|--------------|---------|-------|--------------| +| `grafanaAPIServerWithExperimentalAPIs` | Allow experimental API groups | Development | Access to v0alpha1 APIs (including search) | + +#### Unified Search Specific Flags + +| Feature Flag | Purpose | Stage | Required For | +|--------------|---------|-------|--------------| +| `unifiedStorageSearch` | Core search functionality | Experimental | Search API servers, indexing | +| `unifiedStorageSearchUI` | Frontend search interface | Experimental | Grafana UI search | +| `unifiedStorageSearchPermissionFiltering` | User permission filtering | GA | Access control in search results | +| `unifiedStorageSearchSprinkles` | Usage insights integration | Experimental | Dashboard usage sorting (Enterprise) | +| `unifiedStorageSearchDualReaderEnabled` | Shadow traffic to unified search | Experimental | Shadow traffic during migration | + +#### Basic Configuration +```ini +[feature_toggles] +; Prerequisites for unified storage (required) +grafanaAPIServerWithExperimentalAPIs = true + +; Core search functionality (required) +unifiedStorageSearch = true + +; Enable search UI (required for frontend) +unifiedStorageSearchUI = true + +; Enable permission filtering (recommended) +unifiedStorageSearchPermissionFiltering = true + +; Enable shadow traffic during migration (optional) +unifiedStorageSearchDualReaderEnabled = true + +; Enable usage insights sorting (Enterprise only) +unifiedStorageSearchSprinkles = true +``` + +### Request Flow Diagrams + +#### Search Request Flow with Dual Writer Mode Routing + +Search requests originate from multiple sources, and the search client routes based on dual writer mode configuration: + +```mermaid +flowchart TD + A[Search Request] --> B{Dual Writer Mode} + B -->|Mode 3+| C[Unified Search] + B -->|Mode 0-2| D[Legacy Search] + C --> E[Return Results] + D --> E +``` + +#### Search Request Flow with Shadow Traffic + +When `unifiedStorageSearchDualReaderEnabled` is enabled and resource is in dual writer Mode 1-2 (legacy primary), shadow traffic is generated: + +```mermaid +flowchart TD + A[Search Request] --> B{Shadow Traffic Enabled?} + B -->|Yes| C[Primary: Legacy Search] + B -->|No| D[Single Search Path] + C --> E[Background: Unified Search] + C --> F[Return Legacy Results] + E --> G[Log Results for Comparison] + D --> H[Return Results] +``` + +### Distributor Architecture + +The Search Distributor acts as a smart proxy that routes search requests to the appropriate search API server based on namespace hashing: + +```mermaid +flowchart TD + A[Incoming Search Request] --> B[Hash Namespace] + B --> C[Select Random Instance] + C --> D[Proxy Request] + D --> E[Return Response] +``` + +#### Key Features: +- **Namespace-based routing**: Each request is routed based on the target namespace +- **Load balancing**: Random selection among available replicas for the namespace +- **Health awareness**: Only routes to `ACTIVE` ring instances +- **Connection pooling**: Reuses gRPC connections for efficiency +- **Proxy headers**: Adds metadata for debugging and tracing + +### Ring Architecture + +The hash ring provides consistent, distributed assignment of namespaces to search API servers: + +```mermaid +flowchart TD + A[Namespace] --> B[Hash Function] + B --> C[Ring Position] + C --> D[Assigned Instance] + D --> E[Search Processing] +``` + +#### Ring Properties: +- **Consistent hashing**: Uses FNV32 hash function for namespace distribution +- **128 tokens per instance**: Provides good distribution across the ring +- **Replication factor**: Configurable redundancy (default based on cluster size) +- **State management**: Instances transition through JOINING → ACTIVE → LEAVING +- **Automatic rebalancing**: Ring adjusts when instances join/leave + +### Namespace-Based Sharding + +Unified Search uses namespace-based sharding to distribute search indexes across multiple search API servers: + +```mermaid +flowchart LR + A[Namespaces] --> B[Hash Ring] + B --> C[Search Server 1] + B --> D[Search Server 2] + B --> E[Search Server 3] + C --> F[Indexes for Assigned Namespaces] + D --> G[Indexes for Assigned Namespaces] + E --> H[Indexes for Assigned Namespaces] +``` + +#### Sharding Benefits: +1. **Horizontal scalability**: Add more search servers to handle more namespaces +2. **Resource isolation**: Each namespace's index is independent +3. **Parallel processing**: Multiple searches can run simultaneously across different servers +4. **Fault tolerance**: Namespace availability depends only on its assigned server(s) + +#### Sharding Algorithm: +```go +func getSearchServer(namespace string) string { + hash := fnv.New32a() + hash.Write([]byte(namespace)) + + // Get replication set from ring + replicationSet := ring.GetWithOptions( + hash.Sum32(), + searchRingRead, + ring.WithReplicationFactor(ring.ReplicationFactor()) + ) + + // Random load balancing within replication set + instance := replicationSet.Instances[rand.Intn(len(replicationSet.Instances))] + return instance.Id +} +``` + +### Search Index Management + +Each search API server contains an embedded Bleve search engine that manages indexes for its assigned namespaces: + +```mermaid +flowchart TD + A[Search Request] --> B{Index Ready?} + B -->|Yes| C[Query Index] + B -->|No| D[Build Index] + D --> E[Fetch Resources] + E --> F[Create Index] + F --> C + C --> G[Return Results] + + H[Resource Changes] --> I[Update Index] + I --> F +``` + +#### Index Architecture Details: + +**Embedded Bleve Backend**: Each Search API Server contains its own Bleve search engine instance, not a shared external service. + +**Just-in-Time Indexing**: When a search request arrives for a namespace that doesn't have an index (or has an outdated index): +1. The Search API Server fetches all resources for that namespace from Unified Storage +2. Builds search documents in memory +3. Creates either a memory-based or disk-based Bleve index depending on size +4. Executes the search query against the newly built index +5. Returns results to the user + +**Index Storage Strategy**: +- **Memory indexes**: For small datasets (< `index_file_threshold` documents) +- **Disk indexes**: For large datasets (≥ `index_file_threshold` documents) +- Indexes are stored per Search API Server instance, not globally shared + +**Background Updates**: In addition to just-in-time indexing, Search API Servers also maintain indexes through background watch events for incremental updates. + +#### Index Configuration: +```ini +[unified_storage] +; Path for disk-based search indexes +index_path = /var/lib/grafana/unified-search/bleve + +; Threshold for file-based vs memory indexes +index_file_threshold = 1000 + +; Maximum batch size for indexing +index_max_batch_size = 100 + +; Number of worker threads for indexing +index_workers = 4 + +; Cache TTL for indexes +index_cache_ttl = 1h + +; Periodic rebuild interval (for usage insights) +index_rebuild_interval = 24h + +; Minimum resource count required to build an index (default: 1) +; If a namespace has fewer resources than this threshold, no index will be created +index_min_count = 1 + +; Maximum resource count before creating an empty index (default: 0 = no limit) +; When exceeded, creates an empty index instead of indexing all resources for performance +index_max_count = 0 +``` + +### Search Request Sources + +Unified Search serves multiple types of consumers within the Grafana ecosystem: + +#### 1. User-Initiated Searches +- **Source**: Grafana UI search interface +- **Purpose**: Interactive dashboard and folder discovery +- **Characteristics**: Real-time, user-facing, latency-sensitive +- **Endpoint**: `/api/v1/search` (legacy search UI) or `/apis/dashboard.grafana.app/v0alpha1/namespaces/{namespace}/search` (when `unifiedStorageSearchUI` is enabled) + +#### 2. Internal Service Searches + +Internal services use different search backends depending on dual writer mode configuration: + +- **Dashboard Service**: + - Find related dashboards based on tags, folders, or content + - Discover dashboards for playlist creation + - Validate dashboard references during operations + - **Backend**: Depends on dashboard dual writer mode (Legacy for Mode 0-2, Unified for Mode 3+) + +- **Folder Service**: + - Retrieve folder contents and nested structures + - Resolve folder hierarchy relationships + - Check folder permissions and accessibility + - **Backend**: Depends on folder dual writer mode (Legacy for Mode 0-2, Unified for Mode 3+) + +- **Alerting Service**: + - Discover dashboards and panels for alert rule creation + - Find existing alert rules across namespaces + - Resolve dashboard/panel references in alert definitions + - **Backend**: Mixed - dashboard searches use dashboard dual writer mode, alert rule searches typically use legacy + +- **Provisioning Service**: + - Check for existing resources before provisioning + - Validate resource uniqueness and naming conflicts + - Discover resources for bulk operations + - **Backend**: Depends on each resource type's dual writer mode configuration + +- **API Services**: + - Backend support for various API endpoints + - Resource validation and dependency checking + - **Backend**: Routes based on resource type's dual writer mode + +#### 3. Search Operation Types + +Unified Search supports multiple types of search operations: + +##### Resource Search +- **Purpose**: Find resources (dashboards, folders, etc.) by content +- **Endpoint**: `/api/v1/search` (legacy) or `/apis/dashboard.grafana.app/v0alpha1/namespaces/{namespace}/search` (when `unifiedStorageSearchUI` is enabled) +- **Additional endpoint**: `/apis/dashboard.grafana.app/v0alpha1/namespaces/{namespace}/search/sortable` for retrieving sortable fields +- **Features**: Full-text search, filtering, sorting + +**Sortable Fields:** + +The `/search/sortable` endpoint currently returns a limited static list: +```json +{ + "fields": [ + {"field": "title", "display": "Title (A-Z)", "type": "string"}, + {"field": "-title", "display": "Title (Z-A)", "type": "string"} + ] +} +``` + +However, the search backend actually supports sorting by many more fields: + +**Standard Fields:** +- `title` - Resource display name (uses `title_phrase` for exact sorting) +- `name` - Kubernetes resource name +- `description` - Resource description +- `folder` - Parent folder name +- `created` - Creation timestamp (int64) +- `updated` - Last update timestamp (int64) +- `createdBy` - Creator user ID +- `updatedBy` - Last updater user ID +- `tags` - Resource tags (array) +- `rv` - Resource version (int64) + +**Dashboard-Specific Fields** (require `fields.` prefix): +- `fields.schema_version` - Dashboard schema version +- `fields.link_count` - Number of dashboard links +- `fields.panel_types` - Panel types used in dashboard +- `fields.ds_types` - Data source types used +- `fields.transformation` - Transformations used + +**Usage Insights Fields** (Enterprise only, require `fields.` prefix): +- `fields.views_total` - Total dashboard views +- `fields.views_last_1_days` / `fields.views_last_7_days` / `fields.views_last_30_days` - Recent views +- `fields.views_today` - Today's views +- `fields.queries_total` - Total queries executed +- `fields.queries_last_1_days` / `fields.queries_last_7_days` / `fields.queries_last_30_days` - Recent queries +- `fields.queries_today` - Today's queries +- `fields.errors_total` - Total errors +- `fields.errors_last_1_days` / `fields.errors_last_7_days` / `fields.errors_last_30_days` - Recent errors +- `fields.errors_today` - Today's errors + +**Usage Examples:** +```bash +# Sort by title (ascending) +GET /apis/dashboard.grafana.app/v0alpha1/namespaces/{namespace}/search?sortBy=title + +# Sort by creation date (descending) +GET /apis/dashboard.grafana.app/v0alpha1/namespaces/{namespace}/search?sortBy=-created + +# Sort by usage insights (Enterprise) +GET /apis/dashboard.grafana.app/v0alpha1/namespaces/{namespace}/search?sortBy=-fields.views_total +``` + +*Note: There's currently a discrepancy between the limited fields exposed by `/search/sortable` and the full range of fields actually supported by the search backend.* + +##### Federated Search +- **Purpose**: Search across multiple resource types simultaneously +- **Features**: Cross-resource queries, unified result ranking, combined sorting and faceting +- **Implementation**: Uses Bleve IndexAlias to combine multiple indexes for unified searching +- **Default behavior**: When no type is specified, automatically federates dashboards and folders + +**How Federated Search Works:** + +Federated search is implemented using Bleve's IndexAlias feature, which allows searching across multiple indexes as if they were a single unified index. This enables: + +1. **Cross-resource queries**: Search for content across dashboards, folders, and other resource types +2. **Unified sorting**: Results from different resource types are merged and sorted together +3. **Combined faceting**: Aggregate facet statistics across all federated resource types +4. **Permission filtering**: Respects user permissions for each resource type independently + +**API Usage Examples:** + +**1. Default Federation (Dashboards + Folders):** +```bash +# When no type is specified, automatically searches dashboards and folders +GET /apis/dashboard.grafana.app/v0alpha1/namespaces/{namespace}/search?query=my-search +``` + +**2. Single Resource Type Search:** +```bash +# Search only folders (despite the "dashboard" API group, type parameter controls what's searched) +GET /apis/dashboard.grafana.app/v0alpha1/namespaces/{namespace}/search?type=folders&query=my-search + +# Search only dashboards +GET /apis/dashboard.grafana.app/v0alpha1/namespaces/{namespace}/search?type=dashboards&query=my-search +``` + +**3. Explicit Two-Type Federation:** +```bash +# Search dashboards (primary) with folders federated +GET /apis/dashboard.grafana.app/v0alpha1/namespaces/{namespace}/search?type=dashboards&type=folders&query=my-search +``` + +**4. Protocol Buffer Request Structure:** +```protobuf +message ResourceSearchRequest { + ListOptions options = 1; // Primary resource type to search + repeated ResourceKey federated = 2; // Additional resource types to federate + string query = 3; // Search query applied across all types + // ... other fields +} +``` + +**Example gRPC/Protocol Buffer Usage:** +```go +searchRequest := &resourcepb.ResourceSearchRequest{ + Options: &resourcepb.ListOptions{ + Key: dashboardKey, // Primary: search dashboards + }, + Federated: []*resourcepb.ResourceKey{ + folderKey, // Also search folders + }, + Query: "monitoring", + Limit: 50, + SortBy: []*resourcepb.ResourceSearchRequest_Sort{ + {Field: "title", Desc: false}, // Sort combined results by title + }, +} +``` + +**5. Unified Results:** +Federated search returns a single result set containing resources from all specified types, with: +- **Unified ranking**: All results scored and ranked together +- **Cross-type sorting**: Resources from different types sorted by common fields (title, tags, etc.) +- **Resource type identification**: Each result includes metadata indicating its resource type +- **Permission-aware filtering**: Only returns resources the user has permission to see + +**Limitations:** +- Federation only works across resource types with **common fields** (title, tags, folder, etc.) +- All federated indexes must be of the same search backend type (currently Bleve) +- Currently supports up to 2 resource types in federation via the API endpoint +- **Architectural note**: The search endpoint is under `dashboard.grafana.app` but can search any resource type via the `type` parameter - this is a design choice where the "dashboard search" has evolved into a generic search endpoint + +##### Managed Objects +- **Purpose**: Administrative queries for resource management +- **Operations**: Count, list, statistics + +##### Stats and Monitoring +- **Purpose**: Index health and performance metrics +- **Metrics**: Document counts, index sizes, search latency + + +### Monitoring and Observability + +Key metrics for monitoring Unified Search: + +- `unified_search_requests_total`: Search request counts by type and status +- `unified_search_request_duration_seconds`: Search request latency +- `unified_search_index_size_bytes`: Size of search indexes +- `unified_search_documents_total`: Number of indexed documents +- `unified_search_indexing_duration_seconds`: Time to build/update indexes +- `unified_search_shadow_requests_total`: Shadow traffic request counts +- `unified_search_ring_members`: Number of active search server instances + + From 6db07b901cb431e009569b23dcf517017d768a72 Mon Sep 17 00:00:00 2001 From: Alexander Akhmetov Date: Thu, 31 Jul 2025 09:55:08 +0200 Subject: [PATCH 029/114] Alerting: Enable HA clustering in remote primary mode (#108930) --- pkg/services/ngalert/ngalert.go | 1 - pkg/services/ngalert/notifier/multiorg_alertmanager.go | 8 ++------ pkg/setting/setting_unified_alerting.go | 1 - 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/pkg/services/ngalert/ngalert.go b/pkg/services/ngalert/ngalert.go index 3861fa60c54..e794460fc5e 100644 --- a/pkg/services/ngalert/ngalert.go +++ b/pkg/services/ngalert/ngalert.go @@ -225,7 +225,6 @@ func (ng *AlertNG) init() error { if remotePrimary { ng.Log.Debug("Starting Grafana with remote primary mode enabled") m.Info.WithLabelValues(metrics.ModeRemotePrimary).Set(1) - ng.Cfg.UnifiedAlerting.SkipClustering = true // This function will be used by the MOA to create new Alertmanagers. override = notifier.WithAlertmanagerOverride(func(factoryFn notifier.OrgAlertmanagerFactory) notifier.OrgAlertmanagerFactory { return func(ctx context.Context, orgID int64) (notifier.Alertmanager, error) { diff --git a/pkg/services/ngalert/notifier/multiorg_alertmanager.go b/pkg/services/ngalert/notifier/multiorg_alertmanager.go index 4ce738c08a9..4aa0151d18f 100644 --- a/pkg/services/ngalert/notifier/multiorg_alertmanager.go +++ b/pkg/services/ngalert/notifier/multiorg_alertmanager.go @@ -161,12 +161,8 @@ func NewMultiOrgAlertmanager( peer: &NilPeer{}, } - if cfg.UnifiedAlerting.SkipClustering { - l.Info("Skipping setting up clustering for MOA") - } else { - if err := moa.setupClustering(cfg); err != nil { - return nil, err - } + if err := moa.setupClustering(cfg); err != nil { + return nil, err } // Set up the default per tenant Alertmanager factory. diff --git a/pkg/setting/setting_unified_alerting.go b/pkg/setting/setting_unified_alerting.go index 665c751532f..0187652cb0e 100644 --- a/pkg/setting/setting_unified_alerting.go +++ b/pkg/setting/setting_unified_alerting.go @@ -121,7 +121,6 @@ type UnifiedAlertingSettings struct { DefaultRuleEvaluationInterval time.Duration Screenshots UnifiedAlertingScreenshotSettings ReservedLabels UnifiedAlertingReservedLabelSettings - SkipClustering bool StateHistory UnifiedAlertingStateHistorySettings NotificationHistory UnifiedAlertingNotificationHistorySettings RemoteAlertmanager RemoteAlertmanagerSettings From 6809df54276d313ef7ab936bd7a119d964833228 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 31 Jul 2025 09:18:32 +0100 Subject: [PATCH 030/114] Update dependency jsdom-testing-mocks to v1.15.2 (#108940) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 386 ++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 335 insertions(+), 51 deletions(-) diff --git a/yarn.lock b/yarn.lock index a4a40fb40e5..9eb3e4e6d45 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6334,6 +6334,23 @@ __metadata: languageName: node linkType: hard +"@puppeteer/browsers@npm:2.10.6": + version: 2.10.6 + resolution: "@puppeteer/browsers@npm:2.10.6" + dependencies: + debug: "npm:^4.4.1" + extract-zip: "npm:^2.0.1" + progress: "npm:^2.0.3" + proxy-agent: "npm:^6.5.0" + semver: "npm:^7.7.2" + tar-fs: "npm:^3.1.0" + yargs: "npm:^17.7.2" + bin: + browsers: lib/cjs/main-cli.js + checksum: 10/f6af7260b9df0bc4af55be139bab64e4a92b2019553eb48e57659cf589e80cc2a2f82fd613fec2fb933eb2de5a096ede53ba8a3471c71f14d170065880155ea3 + languageName: node + linkType: hard + "@radix-ui/react-compose-refs@npm:1.0.1": version: 1.0.1 resolution: "@radix-ui/react-compose-refs@npm:1.0.1" @@ -8920,6 +8937,13 @@ __metadata: languageName: node linkType: hard +"@tootallnate/quickjs-emscripten@npm:^0.23.0": + version: 0.23.0 + resolution: "@tootallnate/quickjs-emscripten@npm:0.23.0" + checksum: 10/95cbad451d195b9d8f312103abafcc010741eb9256e98d7953e7c026d4c1ed4abb2248a14018bf49e3201c350104fc643137b23aa0bbed2744c795c39dc48a28 + languageName: node + linkType: hard + "@tree-sitter-grammars/tree-sitter-yaml@npm:=0.7.1": version: 0.7.1 resolution: "@tree-sitter-grammars/tree-sitter-yaml@npm:0.7.1" @@ -11129,12 +11153,10 @@ __metadata: languageName: node linkType: hard -"agent-base@npm:^7.0.2, agent-base@npm:^7.1.0, agent-base@npm:^7.1.1": - version: 7.1.1 - resolution: "agent-base@npm:7.1.1" - dependencies: - debug: "npm:^4.3.4" - checksum: 10/c478fec8f79953f118704d007a38f2a185458853f5c45579b9669372bd0e12602e88dc2ad0233077831504f7cd6fcc8251c383375bba5eaaf563b102938bda26 +"agent-base@npm:^7.1.0, agent-base@npm:^7.1.2": + version: 7.1.4 + resolution: "agent-base@npm:7.1.4" + checksum: 10/79bef167247789f955aaba113bae74bf64aa1e1acca4b1d6bb444bdf91d82c3e07e9451ef6a6e2e35e8f71a6f97ce33e3d855a5328eb9fad1bc3cc4cfd031ed8 languageName: node linkType: hard @@ -11692,6 +11714,15 @@ __metadata: languageName: node linkType: hard +"ast-types@npm:^0.13.4": + version: 0.13.4 + resolution: "ast-types@npm:0.13.4" + dependencies: + tslib: "npm:^2.0.1" + checksum: 10/c55b375b9aaf44713d8c0f77a08215ab6d44f368b13e44f2141c421022af3c62b615a30c8ea629457f0cbaec409c713401c0188a124552c8fe4a5ad6b17ff3c3 + languageName: node + linkType: hard + "ast-types@npm:^0.16.1": version: 0.16.1 resolution: "ast-types@npm:0.16.1" @@ -11852,6 +11883,13 @@ __metadata: languageName: node linkType: hard +"b4a@npm:^1.6.4": + version: 1.6.7 + resolution: "b4a@npm:1.6.7" + checksum: 10/1ac056e3bce378d4d3e570e57319360a9d3125ab6916a1921b95bea33d9ee646698ebc75467561fd6fcc80ff697612124c89bb9b95e80db94c6dc23fcb977705 + languageName: node + linkType: hard + "babel-jest@npm:29.7.0, babel-jest@npm:^29.7.0": version: 29.7.0 resolution: "babel-jest@npm:29.7.0" @@ -12014,6 +12052,62 @@ __metadata: languageName: node linkType: hard +"bare-events@npm:^2.2.0, bare-events@npm:^2.5.4": + version: 2.6.0 + resolution: "bare-events@npm:2.6.0" + checksum: 10/d76a344d7202618c91290bdf94556bf86b8b76c34c001906bb1345e11323e960e092d8923f241c40ba7572e890a6bad855710e5cacd94efc43818311028e2534 + languageName: node + linkType: hard + +"bare-fs@npm:^4.0.1": + version: 4.1.6 + resolution: "bare-fs@npm:4.1.6" + dependencies: + bare-events: "npm:^2.5.4" + bare-path: "npm:^3.0.0" + bare-stream: "npm:^2.6.4" + peerDependencies: + bare-buffer: "*" + peerDependenciesMeta: + bare-buffer: + optional: true + checksum: 10/b56968a3d12bd7616b9251571d4de9550f07cd263c5150547f9cb1f45028275c25862762c3f62b3b3c08a44df4a37aa156e1b8b0697ad3f129048c1cdfa0fda3 + languageName: node + linkType: hard + +"bare-os@npm:^3.0.1": + version: 3.6.1 + resolution: "bare-os@npm:3.6.1" + checksum: 10/285d95c391250166128e64da2947f4a348ae127de680afffec1f6c82445856be0d1f259672b471afe06517e4cd3831183c373a1d63ef7799ed4aaa1321b86b67 + languageName: node + linkType: hard + +"bare-path@npm:^3.0.0": + version: 3.0.0 + resolution: "bare-path@npm:3.0.0" + dependencies: + bare-os: "npm:^3.0.1" + checksum: 10/712d90e9cd8c3263cc11b0e0d386d1531a452706d7840c081ee586b34b00d72544e65df7a40013d47c1b177277495225deeede65cb2984db88a979cb65aaa2ff + languageName: node + linkType: hard + +"bare-stream@npm:^2.6.4": + version: 2.6.5 + resolution: "bare-stream@npm:2.6.5" + dependencies: + streamx: "npm:^2.21.0" + peerDependencies: + bare-buffer: "*" + bare-events: "*" + peerDependenciesMeta: + bare-buffer: + optional: true + bare-events: + optional: true + checksum: 10/0f5ca2167fbbccc118157bce7c53a933e21726268e03d751461211550d72b2d01c296b767ccf96aae8ab28e106b126407c6fe0d29f915734b844ffe6057f0a08 + languageName: node + linkType: hard + "baron@npm:3.0.3": version: 3.0.3 resolution: "baron@npm:3.0.3" @@ -12051,6 +12145,13 @@ __metadata: languageName: node linkType: hard +"basic-ftp@npm:^5.0.2": + version: 5.0.5 + resolution: "basic-ftp@npm:5.0.5" + checksum: 10/3dc56b2092b10d67e84621f5b9bbb0430469499178e857869194184d46fbdd367a9aa9fad660084388744b074b5f540e6ac8c22c0826ebba4fcc86a9d1c324e2 + languageName: node + linkType: hard + "bcrypt-pbkdf@npm:^1.0.0": version: 1.0.2 resolution: "bcrypt-pbkdf@npm:1.0.2" @@ -12893,6 +12994,18 @@ __metadata: languageName: node linkType: hard +"chromium-bidi@npm:7.2.0": + version: 7.2.0 + resolution: "chromium-bidi@npm:7.2.0" + dependencies: + mitt: "npm:^3.0.1" + zod: "npm:^3.24.1" + peerDependencies: + devtools-protocol: "*" + checksum: 10/edbf60cd24b8d59f35674b414a5d65c52b2d4807aca10dc575702fc3240b826d43a08ef1d21b6e5f6ddc1d6d6998a2b3aeffb639fe083d628d118866d8a0eab5 + languageName: node + linkType: hard + "ci-info@npm:^3.2.0": version: 3.9.0 resolution: "ci-info@npm:3.9.0" @@ -14634,6 +14747,13 @@ __metadata: languageName: node linkType: hard +"data-uri-to-buffer@npm:^6.0.2": + version: 6.0.2 + resolution: "data-uri-to-buffer@npm:6.0.2" + checksum: 10/8b6927c33f9b54037f442856be0aa20e5fd49fa6c9c8ceece408dc306445d593ad72d207d57037c529ce65f413b421da800c6827b1dbefb607b8056f17123a61 + languageName: node + linkType: hard + "data-urls@npm:^3.0.2": version: 3.0.2 resolution: "data-urls@npm:3.0.2" @@ -14896,6 +15016,17 @@ __metadata: languageName: node linkType: hard +"degenerator@npm:^5.0.0": + version: 5.0.1 + resolution: "degenerator@npm:5.0.1" + dependencies: + ast-types: "npm:^0.13.4" + escodegen: "npm:^2.1.0" + esprima: "npm:^4.0.1" + checksum: 10/a64fa39cdf6c2edd75188157d32338ee9de7193d7dbb2aeb4acb1eb30fa4a15ed80ba8dae9bd4d7b085472cf174a5baf81adb761aaa8e326771392c922084152 + languageName: node + linkType: hard + "del@npm:^7.1.0": version: 7.1.0 resolution: "del@npm:7.1.0" @@ -14993,6 +15124,13 @@ __metadata: languageName: node linkType: hard +"devtools-protocol@npm:0.0.1464554": + version: 0.0.1464554 + resolution: "devtools-protocol@npm:0.0.1464554" + checksum: 10/c179c23a27d180d6f2196199babaea3c1e59995515df1b082621930cbff7428c2ca2fc12ef0a8fc82fcb133e08dcddaf80e17a7fe841924d6042c2c54511c431 + languageName: node + linkType: hard + "devtools-protocol@npm:0.0.869402": version: 0.0.869402 resolution: "devtools-protocol@npm:0.0.869402" @@ -16011,7 +16149,7 @@ __metadata: languageName: node linkType: hard -"escodegen@npm:^2.0.0": +"escodegen@npm:^2.0.0, escodegen@npm:^2.1.0": version: 2.1.0 resolution: "escodegen@npm:2.1.0" dependencies: @@ -16700,7 +16838,7 @@ __metadata: languageName: node linkType: hard -"extract-zip@npm:2.0.1, extract-zip@npm:^2.0.0": +"extract-zip@npm:2.0.1, extract-zip@npm:^2.0.0, extract-zip@npm:^2.0.1": version: 2.0.1 resolution: "extract-zip@npm:2.0.1" dependencies: @@ -16752,7 +16890,7 @@ __metadata: languageName: node linkType: hard -"fast-fifo@npm:^1.1.0": +"fast-fifo@npm:^1.2.0, fast-fifo@npm:^1.3.2": version: 1.3.2 resolution: "fast-fifo@npm:1.3.2" checksum: 10/6bfcba3e4df5af7be3332703b69a7898a8ed7020837ec4395bb341bd96cc3a6d86c3f6071dd98da289618cf2234c70d84b2a6f09a33dd6f988b1ff60d8e54275 @@ -17738,6 +17876,17 @@ __metadata: languageName: node linkType: hard +"get-uri@npm:^6.0.1": + version: 6.0.5 + resolution: "get-uri@npm:6.0.5" + dependencies: + basic-ftp: "npm:^5.0.2" + data-uri-to-buffer: "npm:^6.0.2" + debug: "npm:^4.3.4" + checksum: 10/6daa56eb367dc030ae7bf6db4b5d36f200c9bb47ab00593c142176e4f33f22e129a294ac94329c6bcaebda19b7506080267a336742d20a915fb2bef9c400347f + languageName: node + linkType: hard + "get-user-locale@npm:^2.2.1": version: 2.3.0 resolution: "get-user-locale@npm:2.3.0" @@ -19068,7 +19217,7 @@ __metadata: languageName: node linkType: hard -"http-proxy-agent@npm:^7.0.0": +"http-proxy-agent@npm:^7.0.0, http-proxy-agent@npm:^7.0.1": version: 7.0.2 resolution: "http-proxy-agent@npm:7.0.2" dependencies: @@ -19140,13 +19289,13 @@ __metadata: languageName: node linkType: hard -"https-proxy-agent@npm:^7.0.1": - version: 7.0.4 - resolution: "https-proxy-agent@npm:7.0.4" +"https-proxy-agent@npm:^7.0.1, https-proxy-agent@npm:^7.0.6": + version: 7.0.6 + resolution: "https-proxy-agent@npm:7.0.6" dependencies: - agent-base: "npm:^7.0.2" + agent-base: "npm:^7.1.2" debug: "npm:4" - checksum: 10/405fe582bba461bfe5c7e2f8d752b384036854488b828ae6df6a587c654299cbb2c50df38c4b6ab303502c3c5e029a793fbaac965d1e86ee0be03faceb554d63 + checksum: 10/784b628cbd55b25542a9d85033bdfd03d4eda630fb8b3c9477959367f3be95dc476ed2ecbb9836c359c7c698027fc7b45723a302324433590f45d6c1706e8c13 languageName: node linkType: hard @@ -21251,12 +21400,13 @@ __metadata: linkType: hard "jsdom-testing-mocks@npm:^1.13.1": - version: 1.14.0 - resolution: "jsdom-testing-mocks@npm:1.14.0" + version: 1.15.2 + resolution: "jsdom-testing-mocks@npm:1.15.2" dependencies: bezier-easing: "npm:^2.1.0" css-mediaquery: "npm:^0.1.2" - checksum: 10/b7f89f4687345f437f9e022cacc9ed577e58c825b4283b61dd46ab61f9612d8a715b6acc12aa91bcd3f6c97455dfe61eeae86ce4694832469f3ad95d179bd671 + puppeteer: "npm:^24.15.0" + checksum: 10/be5fceccbbae9c34b5065704cb9335403de95462b84ad01e314dff6f577bec1f62c86ce72219e1c5bb1415e8a0a1a1ad90d41e4360a367a221ca7ecb967fe2fb languageName: node linkType: hard @@ -22299,6 +22449,13 @@ __metadata: languageName: node linkType: hard +"lru-cache@npm:^7.14.1": + version: 7.18.3 + resolution: "lru-cache@npm:7.18.3" + checksum: 10/6029ca5aba3aacb554e919d7ef804fffd4adfc4c83db00fac8248c7c78811fb6d4b6f70f7fd9d55032b3823446546a007edaa66ad1f2377ae833bd983fac5d98 + languageName: node + linkType: hard + "lru-memoize@npm:^1.1.0": version: 1.1.0 resolution: "lru-memoize@npm:1.1.0" @@ -23013,6 +23170,13 @@ __metadata: languageName: node linkType: hard +"mitt@npm:^3.0.1": + version: 3.0.1 + resolution: "mitt@npm:3.0.1" + checksum: 10/287c70d8e73ffc25624261a4989c783768aed95ecb60900f051d180cf83e311e3e59865bfd6e9d029cdb149dc20ba2f128a805e9429c5c4ce33b1416c65bbd14 + languageName: node + linkType: hard + "mkdirp-classic@npm:^0.5.2": version: 0.5.3 resolution: "mkdirp-classic@npm:0.5.3" @@ -23405,6 +23569,13 @@ __metadata: languageName: node linkType: hard +"netmask@npm:^2.0.2": + version: 2.0.2 + resolution: "netmask@npm:2.0.2" + checksum: 10/375cabe898a5832816958664f26206f0a1e9f3605aa1816bfce803e060ff20f9d6ce56a2377e46f1470938358c31c27b3a8086f4a5e3ef678896147884d63ffa + languageName: node + linkType: hard + "no-case@npm:^3.0.4": version: 3.0.4 resolution: "no-case@npm:3.0.4" @@ -24666,6 +24837,32 @@ __metadata: languageName: node linkType: hard +"pac-proxy-agent@npm:^7.1.0": + version: 7.2.0 + resolution: "pac-proxy-agent@npm:7.2.0" + dependencies: + "@tootallnate/quickjs-emscripten": "npm:^0.23.0" + agent-base: "npm:^7.1.2" + debug: "npm:^4.3.4" + get-uri: "npm:^6.0.1" + http-proxy-agent: "npm:^7.0.0" + https-proxy-agent: "npm:^7.0.6" + pac-resolver: "npm:^7.0.1" + socks-proxy-agent: "npm:^8.0.5" + checksum: 10/187656be62d5a6b983d90a86d64106a38b1a9ee78f591fabb27b3cf0d51e5d528456a9faaaf981c93dd54dc9c9ee8d33e35a51072b73a19ec1a8e0d0c36a2b99 + languageName: node + linkType: hard + +"pac-resolver@npm:^7.0.1": + version: 7.0.1 + resolution: "pac-resolver@npm:7.0.1" + dependencies: + degenerator: "npm:^5.0.0" + netmask: "npm:^2.0.2" + checksum: 10/839134328781b80d49f9684eae1f5c74f50a1d4482076d44c84fc2f3ca93da66fa11245a4725a057231e06b311c20c989fd0681e662a0792d17f644d8fe62a5e + languageName: node + linkType: hard + "package-hash@npm:^4.0.0": version: 4.0.0 resolution: "package-hash@npm:4.0.0" @@ -25968,7 +26165,7 @@ __metadata: languageName: node linkType: hard -"progress@npm:^2.0.1": +"progress@npm:^2.0.1, progress@npm:^2.0.3": version: 2.0.3 resolution: "progress@npm:2.0.3" checksum: 10/e6f0bcb71f716eee9dfac0fe8a2606e3704d6a64dd93baaf49fbadbc8499989a610fe14cf1bc6f61b6d6653c49408d94f4a94e124538084efd8e4cf525e0293d @@ -26106,6 +26303,22 @@ __metadata: languageName: node linkType: hard +"proxy-agent@npm:^6.5.0": + version: 6.5.0 + resolution: "proxy-agent@npm:6.5.0" + dependencies: + agent-base: "npm:^7.1.2" + debug: "npm:^4.3.4" + http-proxy-agent: "npm:^7.0.1" + https-proxy-agent: "npm:^7.0.6" + lru-cache: "npm:^7.14.1" + pac-proxy-agent: "npm:^7.1.0" + proxy-from-env: "npm:^1.1.0" + socks-proxy-agent: "npm:^8.0.5" + checksum: 10/56d5a494d96dafad94868870af776939e7b9aaca172465a5c251d2523496a8353b029c32d2a72a012bd62622cdc9a43ba3df59b5738ab7b740bc6b362e9f9477 + languageName: node + linkType: hard + "proxy-from-env@npm:1.0.0": version: 1.0.0 resolution: "proxy-from-env@npm:1.0.0" @@ -26172,6 +26385,36 @@ __metadata: languageName: node linkType: hard +"puppeteer-core@npm:24.15.0": + version: 24.15.0 + resolution: "puppeteer-core@npm:24.15.0" + dependencies: + "@puppeteer/browsers": "npm:2.10.6" + chromium-bidi: "npm:7.2.0" + debug: "npm:^4.4.1" + devtools-protocol: "npm:0.0.1464554" + typed-query-selector: "npm:^2.12.0" + ws: "npm:^8.18.3" + checksum: 10/9fe0a7a982289f7dbc42799764a8273c814d00254f61431f3598a38fbdee7b0c6b171bddbe738d8be4f5868a9b4b034ecaa6c105e407452c276ae1046ef2e3b3 + languageName: node + linkType: hard + +"puppeteer@npm:^24.15.0": + version: 24.15.0 + resolution: "puppeteer@npm:24.15.0" + dependencies: + "@puppeteer/browsers": "npm:2.10.6" + chromium-bidi: "npm:7.2.0" + cosmiconfig: "npm:^9.0.0" + devtools-protocol: "npm:0.0.1464554" + puppeteer-core: "npm:24.15.0" + typed-query-selector: "npm:^2.12.0" + bin: + puppeteer: lib/cjs/puppeteer/node/cli.js + checksum: 10/f6d976d54643045ac0c12ed72dc807716e455ed0d8203305aac813043779c329fdf66261e44838b509dd9c57372332b6be458e636577cd7626d24f0d902166d4 + languageName: node + linkType: hard + "puppeteer@npm:~9.1.1": version: 9.1.1 resolution: "puppeteer@npm:9.1.1" @@ -26231,13 +26474,6 @@ __metadata: languageName: node linkType: hard -"queue-tick@npm:^1.0.1": - version: 1.0.1 - resolution: "queue-tick@npm:1.0.1" - checksum: 10/f447926c513b64a857906f017a3b350f7d11277e3c8d2a21a42b7998fa1a613d7a829091e12d142bb668905c8f68d8103416c7197856efb0c72fa835b8e254b5 - languageName: node - linkType: hard - "queue-typescript@npm:^1.0.1": version: 1.0.1 resolution: "queue-typescript@npm:1.0.1" @@ -28634,7 +28870,7 @@ __metadata: languageName: node linkType: hard -"semver@npm:7.7.2, semver@npm:^7.0.0, semver@npm:^7.1.1, semver@npm:^7.3.2, semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:^7.3.7, semver@npm:^7.3.8, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.6.0, semver@npm:^7.6.2, semver@npm:^7.6.3, semver@npm:^7.7.0, semver@npm:^7.7.1": +"semver@npm:7.7.2, semver@npm:^7.0.0, semver@npm:^7.1.1, semver@npm:^7.3.2, semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:^7.3.7, semver@npm:^7.3.8, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.6.0, semver@npm:^7.6.2, semver@npm:^7.6.3, semver@npm:^7.7.0, semver@npm:^7.7.1, semver@npm:^7.7.2": version: 7.7.2 resolution: "semver@npm:7.7.2" bin: @@ -29295,24 +29531,24 @@ __metadata: languageName: node linkType: hard -"socks-proxy-agent@npm:^8.0.3": - version: 8.0.3 - resolution: "socks-proxy-agent@npm:8.0.3" +"socks-proxy-agent@npm:^8.0.3, socks-proxy-agent@npm:^8.0.5": + version: 8.0.5 + resolution: "socks-proxy-agent@npm:8.0.5" dependencies: - agent-base: "npm:^7.1.1" + agent-base: "npm:^7.1.2" debug: "npm:^4.3.4" - socks: "npm:^2.7.1" - checksum: 10/c2112c66d6322e497d68e913c3780f3683237fd394bfd480b9283486a86e36095d0020db96145d88f8ccd9cc73261b98165b461f9c1bf5dc17abfe75c18029ce + socks: "npm:^2.8.3" + checksum: 10/ee99e1dacab0985b52cbe5a75640be6e604135e9489ebdc3048635d186012fbaecc20fbbe04b177dee434c319ba20f09b3e7dfefb7d932466c0d707744eac05c languageName: node linkType: hard -"socks@npm:^2.6.1, socks@npm:^2.7.1": - version: 2.8.3 - resolution: "socks@npm:2.8.3" +"socks@npm:^2.6.1, socks@npm:^2.8.3": + version: 2.8.6 + resolution: "socks@npm:2.8.6" dependencies: ip-address: "npm:^9.0.5" smart-buffer: "npm:^4.2.0" - checksum: 10/ffcb622c22481dfcd7589aae71fbfd71ca34334064d181df64bf8b7feaeee19706aba4cffd1de35cc7bbaeeaa0af96be2d7f40fcbc7bc0ab69533a7ae9ffc4fb + checksum: 10/7aef197dee914a01a39d5f250a59f0c9fa0a9fd10f8135ee2cff6c42576993ae1c817503a12eb424f1bb69f1e234f8dbaf8cc48bfcfa10c51a12af8f0ea97698 languageName: node linkType: hard @@ -29724,13 +29960,17 @@ __metadata: languageName: node linkType: hard -"streamx@npm:^2.12.0, streamx@npm:^2.12.5, streamx@npm:^2.13.2, streamx@npm:^2.14.0": - version: 2.15.7 - resolution: "streamx@npm:2.15.7" +"streamx@npm:^2.12.0, streamx@npm:^2.12.5, streamx@npm:^2.13.2, streamx@npm:^2.14.0, streamx@npm:^2.15.0, streamx@npm:^2.21.0": + version: 2.22.1 + resolution: "streamx@npm:2.22.1" dependencies: - fast-fifo: "npm:^1.1.0" - queue-tick: "npm:^1.0.1" - checksum: 10/5f59f49383b41546e87b15ba54970cca262d1eb0826c5b88c4dc5329d18821fed06f92f849da68c764ad91ea05f394518510957371cb0abd3617bdabed465cdb + bare-events: "npm:^2.2.0" + fast-fifo: "npm:^1.3.2" + text-decoder: "npm:^1.1.0" + dependenciesMeta: + bare-events: + optional: true + checksum: 10/6d8576e0e5f4a67776427e3d29a877e66295bf7e17019a5b5c77d7fa026c5e8df6cdbd0cec2774999075af985179d70f07b25db7557b9226e33148fe67edd487 languageName: node linkType: hard @@ -30392,6 +30632,23 @@ __metadata: languageName: node linkType: hard +"tar-fs@npm:^3.1.0": + version: 3.1.0 + resolution: "tar-fs@npm:3.1.0" + dependencies: + bare-fs: "npm:^4.0.1" + bare-path: "npm:^3.0.0" + pump: "npm:^3.0.0" + tar-stream: "npm:^3.1.5" + dependenciesMeta: + bare-fs: + optional: true + bare-path: + optional: true + checksum: 10/272054aa93adf66f3febac1ab666c2e1ae80fb7d9b7483efa9e9216aca6f7900dfecb9ef04e83f179251f95a965f960d760c42ebbb1e04580a182da997af98da + languageName: node + linkType: hard + "tar-stream@npm:^2.1.4, tar-stream@npm:~2.2.0": version: 2.2.0 resolution: "tar-stream@npm:2.2.0" @@ -30405,6 +30662,17 @@ __metadata: languageName: node linkType: hard +"tar-stream@npm:^3.1.5": + version: 3.1.7 + resolution: "tar-stream@npm:3.1.7" + dependencies: + b4a: "npm:^1.6.4" + fast-fifo: "npm:^1.2.0" + streamx: "npm:^2.15.0" + checksum: 10/b21a82705a72792544697c410451a4846af1f744176feb0ff11a7c3dd0896961552e3def5e1c9a6bbee4f0ae298b8252a1f4c9381e9f991553b9e4847976f05c + languageName: node + linkType: hard + "tar@npm:6.2.1, tar@npm:^6.0.2, tar@npm:^6.1.11, tar@npm:^6.1.2": version: 6.2.1 resolution: "tar@npm:6.2.1" @@ -30491,6 +30759,15 @@ __metadata: languageName: node linkType: hard +"text-decoder@npm:^1.1.0": + version: 1.2.3 + resolution: "text-decoder@npm:1.2.3" + dependencies: + b4a: "npm:^1.6.4" + checksum: 10/bcdec33c0f070aeac38e46e4cafdcd567a58473ed308bdf75260bfbd8f7dc76acbc0b13226afaec4a169d0cb44cec2ab89c57b6395ccf02e941eaebbe19e124a + languageName: node + linkType: hard + "text-extensions@npm:^1.0.0": version: 1.9.0 resolution: "text-extensions@npm:1.9.0" @@ -31251,6 +31528,13 @@ __metadata: languageName: node linkType: hard +"typed-query-selector@npm:^2.12.0": + version: 2.12.0 + resolution: "typed-query-selector@npm:2.12.0" + checksum: 10/e65b646830315e63282883acb44ea48ef8da3e9a044aa69e03f3bd876d7a69baae85f71c0918456b43f7c1bc2b448f2d64a424280f9699d34be2bae582121bc9 + languageName: node + linkType: hard + "typedarray-to-buffer@npm:^3.1.5": version: 3.1.5 resolution: "typedarray-to-buffer@npm:3.1.5" @@ -32720,9 +33004,9 @@ __metadata: languageName: node linkType: hard -"ws@npm:^8.2.3, ws@npm:^8.9.0": - version: 8.18.0 - resolution: "ws@npm:8.18.0" +"ws@npm:^8.18.3, ws@npm:^8.2.3, ws@npm:^8.9.0": + version: 8.18.3 + resolution: "ws@npm:8.18.3" peerDependencies: bufferutil: ^4.0.1 utf-8-validate: ">=5.0.2" @@ -32731,7 +33015,7 @@ __metadata: optional: true utf-8-validate: optional: true - checksum: 10/70dfe53f23ff4368d46e4c0b1d4ca734db2c4149c6f68bc62cb16fc21f753c47b35fcc6e582f3bdfba0eaeb1c488cddab3c2255755a5c3eecb251431e42b3ff6 + checksum: 10/725964438d752f0ab0de582cd48d6eeada58d1511c3f613485b5598a83680bedac6187c765b0fe082e2d8cc4341fc57707c813ae780feee82d0c5efe6a4c61b6 languageName: node linkType: hard @@ -33010,10 +33294,10 @@ __metadata: languageName: node linkType: hard -"zod@npm:^3.23.8, zod@npm:^3.25.3, zod@npm:^3.25.55": - version: 3.25.67 - resolution: "zod@npm:3.25.67" - checksum: 10/0e35432dcca7f053e63f5dd491a87c78abe0d981817547252c3b6d05f0f58788695d1a69724759c6501dff3fd62929be24c9f314a3625179bee889150f7a61fa +"zod@npm:^3.23.8, zod@npm:^3.24.1, zod@npm:^3.25.3, zod@npm:^3.25.55": + version: 3.25.76 + resolution: "zod@npm:3.25.76" + checksum: 10/f0c963ec40cd96858451d1690404d603d36507c1fc9682f2dae59ab38b578687d542708a7fdbf645f77926f78c9ed558f57c3d3aa226c285f798df0c4da16995 languageName: node linkType: hard From b11509fea2cd4b34afc69d6b901163126678ec97 Mon Sep 17 00:00:00 2001 From: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com> Date: Thu, 31 Jul 2025 10:20:28 +0200 Subject: [PATCH 031/114] Loki: Decouple unit tests from core (#108917) * Loki: Decouple tests * Fix flaky tests * Add maxParallel 2 for plugin tests --- jest.config.js | 1 + package.json | 2 +- public/app/plugins/datasource/loki/LanguageProvider.test.ts | 6 ++++-- public/app/plugins/datasource/loki/jest-setup.js | 1 + public/app/plugins/datasource/loki/jest.config.js | 3 +++ public/app/plugins/datasource/loki/package.json | 5 ++++- yarn.lock | 1 + 7 files changed, 15 insertions(+), 4 deletions(-) create mode 100644 public/app/plugins/datasource/loki/jest-setup.js create mode 100644 public/app/plugins/datasource/loki/jest.config.js diff --git a/jest.config.js b/jest.config.js index 6041aaa2b20..a576f4ec0c4 100644 --- a/jest.config.js +++ b/jest.config.js @@ -83,6 +83,7 @@ module.exports = { '/public/app/plugins/datasource/grafana-pyroscope-datasource', '/public/app/plugins/datasource/grafana-testdata-datasource', '/public/app/plugins/datasource/jaeger', + '/public/app/plugins/datasource/loki', '/public/app/plugins/datasource/mysql', '/public/app/plugins/datasource/parca', '/public/app/plugins/datasource/tempo', diff --git a/package.json b/package.json index 5d6fcd944e4..cce32ec8537 100644 --- a/package.json +++ b/package.json @@ -68,7 +68,7 @@ "plugin:build": "nx run-many -t build --projects='tag:scope:plugin'", "plugin:build:commit": "nx run-many -t build:commit --projects='tag:scope:plugin'", "plugin:build:dev": "nx run-many -t dev --projects='tag:scope:plugin' --maxParallel=100", - "plugin:test:ci": "nx run-many -t test:ci --projects='tag:scope:plugin'", + "plugin:test:ci": "nx run-many -t test:ci --projects='tag:scope:plugin' --maxParallel=2", "plugin:i18n-extract": "nx run-many -t i18n-extract --projects='tag:scope:plugin'", "process-specs": "node --experimental-strip-types scripts/process-specs.ts", "generate-apis": "yarn process-specs && rtk-query-codegen-openapi ./scripts/generate-rtk-apis.ts", diff --git a/public/app/plugins/datasource/loki/LanguageProvider.test.ts b/public/app/plugins/datasource/loki/LanguageProvider.test.ts index b00b54393c0..02a365c7d9a 100644 --- a/public/app/plugins/datasource/loki/LanguageProvider.test.ts +++ b/public/app/plugins/datasource/loki/LanguageProvider.test.ts @@ -761,7 +761,8 @@ describe('Query imports', () => { maxLines: DEFAULT_MAX_LINES_SAMPLE, refId: 'data-samples', }, - languageProvider.getDefaultTimeRange() + // We are not interested in the time range here + expect.any(Object) ); }); @@ -782,7 +783,8 @@ describe('Query imports', () => { maxLines: 5, refId: 'data-samples', }, - languageProvider.getDefaultTimeRange() + // We are not interested in the time range here + expect.any(Object) ); }); diff --git a/public/app/plugins/datasource/loki/jest-setup.js b/public/app/plugins/datasource/loki/jest-setup.js new file mode 100644 index 00000000000..c85bf9d3a57 --- /dev/null +++ b/public/app/plugins/datasource/loki/jest-setup.js @@ -0,0 +1 @@ +import '@grafana/plugin-configs/jest/jest-setup'; diff --git a/public/app/plugins/datasource/loki/jest.config.js b/public/app/plugins/datasource/loki/jest.config.js new file mode 100644 index 00000000000..fabef448081 --- /dev/null +++ b/public/app/plugins/datasource/loki/jest.config.js @@ -0,0 +1,3 @@ +import defaultConfig from '@grafana/plugin-configs/jest/jest.config.js'; + +export default defaultConfig; diff --git a/public/app/plugins/datasource/loki/package.json b/public/app/plugins/datasource/loki/package.json index 69bf66c12a9..75a465d608c 100644 --- a/public/app/plugins/datasource/loki/package.json +++ b/public/app/plugins/datasource/loki/package.json @@ -37,6 +37,7 @@ "@types/react": "18.3.18", "@types/react-dom": "18.3.5", "@types/uuid": "10.0.0", + "jest": "29.7.0", "ts-node": "10.9.2", "typescript": "5.7.3", "webpack": "5.97.1" @@ -47,7 +48,9 @@ "scripts": { "build": "NODE_OPTIONS='--experimental-strip-types --no-warnings=ExperimentalWarning' webpack -c ./webpack.config.ts --env production", "build:commit": "NODE_OPTIONS='--experimental-strip-types --no-warnings=ExperimentalWarning' webpack -c ./webpack.config.ts --env production --env commit=$(git rev-parse --short HEAD)", - "dev": "NODE_OPTIONS='--experimental-strip-types --no-warnings=ExperimentalWarning' webpack -w -c ./webpack.config.ts --env development" + "dev": "NODE_OPTIONS='--experimental-strip-types --no-warnings=ExperimentalWarning' webpack -w -c ./webpack.config.ts --env development", + "test": "jest --watch --onlyChanged", + "test:ci": "jest --maxWorkers 4" }, "packageManager": "yarn@4.9.2" } diff --git a/yarn.lock b/yarn.lock index 9eb3e4e6d45..e38aa7212a7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2771,6 +2771,7 @@ __metadata: "@types/react-dom": "npm:18.3.5" "@types/uuid": "npm:10.0.0" d3-random: "npm:^3.0.1" + jest: "npm:29.7.0" lodash: "npm:4.17.21" micro-memoize: "npm:^4.1.2" react: "npm:18.3.1" From 66bebe94e064c9d6dc460254e1fc83355d5ae9cf Mon Sep 17 00:00:00 2001 From: Tobias Skarhed <1438972+tskarhed@users.noreply.github.com> Date: Thu, 31 Jul 2025 10:28:24 +0200 Subject: [PATCH 032/114] Scopes: Add autofocus, aria-label and search area to selector (#108906) * Add autofocus, aria-label and search area * Clean up search area * Clean up search area * Add test * Remove superflous memo * Undo values change --- public/app/features/scopes/selector/ScopesTree.tsx | 11 ++++++++++- .../features/scopes/selector/ScopesTreeSearch.tsx | 12 ++++++++++-- public/app/features/scopes/tests/tree.test.ts | 10 ++++++++++ public/locales/en-US/grafana.json | 2 +- 4 files changed, 31 insertions(+), 4 deletions(-) diff --git a/public/app/features/scopes/selector/ScopesTree.tsx b/public/app/features/scopes/selector/ScopesTree.tsx index 71c7a879d63..e3fbc06e203 100644 --- a/public/app/features/scopes/selector/ScopesTree.tsx +++ b/public/app/features/scopes/selector/ScopesTree.tsx @@ -63,11 +63,20 @@ export function ScopesTree({ } } + // Used as a label and placeholder for search field + const nodeTitle = scopeNodes[tree.scopeNodeId]?.spec?.title || ''; + const searchArea = tree.scopeNodeId === '' ? '' : nodeTitle; + const lastExpandedNode = !anyChildExpanded && tree.expanded; return ( <> - + {tree.scopeNodeId === '' && !anyChildExpanded && recentScopes && diff --git a/public/app/features/scopes/selector/ScopesTreeSearch.tsx b/public/app/features/scopes/selector/ScopesTreeSearch.tsx index 99a251d20bd..e84407aa00f 100644 --- a/public/app/features/scopes/selector/ScopesTreeSearch.tsx +++ b/public/app/features/scopes/selector/ScopesTreeSearch.tsx @@ -10,11 +10,12 @@ import { TreeNode } from './types'; export interface ScopesTreeSearchProps { anyChildExpanded: boolean; + searchArea: string; treeNode: TreeNode; onNodeUpdate: (scopeNodeId: string, expanded: boolean, query: string) => void; } -export function ScopesTreeSearch({ anyChildExpanded, treeNode, onNodeUpdate }: ScopesTreeSearchProps) { +export function ScopesTreeSearch({ anyChildExpanded, treeNode, onNodeUpdate, searchArea }: ScopesTreeSearchProps) { const styles = useStyles2(getStyles); const [inputState, setInputState] = useState<{ value: string; dirty: boolean }>({ @@ -42,9 +43,16 @@ export function ScopesTreeSearch({ anyChildExpanded, treeNode, onNodeUpdate }: S return null; } + const searchLabel = t('scopes.tree.search', 'Search {{parentTitle}}', { + parentTitle: searchArea, + }); + return ( { await resetScenes([fetchNodesSpy, fetchScopeSpy]); }); + it('Gives autofocus to search field when node is expanded', async () => { + await openSelector(); + expect(screen.getByRole('textbox', { name: 'Search' })).not.toHaveFocus(); + + await expandResultApplications(); + expect(screen.getByRole('textbox', { name: 'Search Applications' })).toHaveFocus(); + }); + it('Fetches scope details on select', async () => { await openSelector(); await expandResultApplications(); diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index 0a72605b908..40ccbf5b567 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -11874,7 +11874,7 @@ "recommended": "Recommended", "results": "Results" }, - "search": "Search" + "search": "Search {{parentTitle}}" } }, "search": { From 80d7892d6a3daec4794bec6c4f8a1f3d8a01b451 Mon Sep 17 00:00:00 2001 From: Matheus Macabu Date: Thu, 31 Jul 2025 10:42:19 +0200 Subject: [PATCH 033/114] Secrets: Save owner reference fields in secure value db table (#108905) * Secrets: Save owner reference fields in secure value db table * Save api group and version separately --- .../metadata/data/secure_value_create.sql | 24 +++++ .../metadata/data/secure_value_list.sql | 6 +- .../metadata/data/secure_value_read.sql | 6 +- pkg/storage/secret/metadata/query_test.go | 68 ++++++++------ .../secret/metadata/secure_value_model.go | 94 ++++++++++++++----- .../secret/metadata/secure_value_store.go | 5 +- ...l--secure_value_create-create-not-null.sql | 8 ++ .../mysql--secure_value_list-list.sql | 6 +- ...sql--secure_value_read-read-for-update.sql | 6 +- .../mysql--secure_value_read-read.sql | 6 +- ...s--secure_value_create-create-not-null.sql | 8 ++ .../postgres--secure_value_list-list.sql | 6 +- ...res--secure_value_read-read-for-update.sql | 6 +- .../postgres--secure_value_read-read.sql | 6 +- ...e--secure_value_create-create-not-null.sql | 8 ++ .../sqlite--secure_value_list-list.sql | 6 +- ...ite--secure_value_read-read-for-update.sql | 6 +- .../sqlite--secure_value_read-read.sql | 6 +- pkg/storage/secret/migrator/migrator.go | 29 ++++++ 19 files changed, 243 insertions(+), 67 deletions(-) diff --git a/pkg/storage/secret/metadata/data/secure_value_create.sql b/pkg/storage/secret/metadata/data/secure_value_create.sql index a120feaa5e5..66d404f5bd2 100644 --- a/pkg/storage/secret/metadata/data/secure_value_create.sql +++ b/pkg/storage/secret/metadata/data/secure_value_create.sql @@ -20,6 +20,18 @@ INSERT INTO {{ .Ident "secret_secure_value" }} ( {{ if .Row.Ref.Valid }} {{ .Ident "ref" }}, {{ end }} + {{ if .Row.OwnerReferenceAPIGroup.Valid }} + {{ .Ident "owner_reference_api_group" }}, + {{ end }} + {{ if .Row.OwnerReferenceAPIVersion.Valid }} + {{ .Ident "owner_reference_api_version" }}, + {{ end }} + {{ if .Row.OwnerReferenceKind.Valid }} + {{ .Ident "owner_reference_kind" }}, + {{ end }} + {{ if .Row.OwnerReferenceName.Valid }} + {{ .Ident "owner_reference_name" }}, + {{ end }} {{ .Ident "external_id" }} ) VALUES ( {{ .Arg .Row.GUID }}, @@ -43,5 +55,17 @@ INSERT INTO {{ .Ident "secret_secure_value" }} ( {{ if .Row.Ref.Valid }} {{ .Arg .Row.Ref.String }}, {{ end }} + {{ if .Row.OwnerReferenceAPIGroup.Valid }} + {{ .Arg .Row.OwnerReferenceAPIGroup.String }}, + {{ end }} + {{ if .Row.OwnerReferenceAPIVersion.Valid }} + {{ .Arg .Row.OwnerReferenceAPIVersion.String }}, + {{ end }} + {{ if .Row.OwnerReferenceKind.Valid }} + {{ .Arg .Row.OwnerReferenceKind.String }}, + {{ end }} + {{ if .Row.OwnerReferenceName.Valid }} + {{ .Arg .Row.OwnerReferenceName.String }}, + {{ end }} {{ .Arg .Row.ExternalID }} ); \ No newline at end of file diff --git a/pkg/storage/secret/metadata/data/secure_value_list.sql b/pkg/storage/secret/metadata/data/secure_value_list.sql index 9c0c604b72d..5d2ecf51e34 100644 --- a/pkg/storage/secret/metadata/data/secure_value_list.sql +++ b/pkg/storage/secret/metadata/data/secure_value_list.sql @@ -14,7 +14,11 @@ SELECT {{ .Ident "ref" }}, {{ .Ident "external_id" }}, {{ .Ident "version" }}, - {{ .Ident "active" }} + {{ .Ident "active" }}, + {{ .Ident "owner_reference_api_group" }}, + {{ .Ident "owner_reference_api_version" }}, + {{ .Ident "owner_reference_kind" }}, + {{ .Ident "owner_reference_name" }} FROM {{ .Ident "secret_secure_value" }} WHERE diff --git a/pkg/storage/secret/metadata/data/secure_value_read.sql b/pkg/storage/secret/metadata/data/secure_value_read.sql index b90d54b4a5f..4f6e0ae0707 100644 --- a/pkg/storage/secret/metadata/data/secure_value_read.sql +++ b/pkg/storage/secret/metadata/data/secure_value_read.sql @@ -14,7 +14,11 @@ SELECT {{ .Ident "ref" }}, {{ .Ident "external_id" }}, {{ .Ident "active" }}, - {{ .Ident "version" }} + {{ .Ident "version" }}, + {{ .Ident "owner_reference_api_group" }}, + {{ .Ident "owner_reference_api_version" }}, + {{ .Ident "owner_reference_kind" }}, + {{ .Ident "owner_reference_name" }} FROM {{ .Ident "secret_secure_value" }} WHERE diff --git a/pkg/storage/secret/metadata/query_test.go b/pkg/storage/secret/metadata/query_test.go index 915fd261ec2..2b0c232f2c1 100644 --- a/pkg/storage/secret/metadata/query_test.go +++ b/pkg/storage/secret/metadata/query_test.go @@ -177,21 +177,25 @@ func TestSecureValueQueries(t *testing.T) { Data: &createSecureValue{ SQLTemplate: mocks.NewTestingSQLTemplate(), Row: &secureValueDB{ - GUID: "abc", - Name: "name", - Namespace: "ns", - Annotations: `{"x":"XXXX"}`, - Labels: `{"a":"AAA", "b", "BBBB"}`, - Created: 1234, - CreatedBy: "user:ryan", - Updated: 5678, - UpdatedBy: "user:cameron", - Version: 1, - Description: "description", - Keeper: toNullString(nil), - Decrypters: toNullString(nil), - Ref: toNullString(nil), - ExternalID: "extId", + GUID: "abc", + Name: "name", + Namespace: "ns", + Annotations: `{"x":"XXXX"}`, + Labels: `{"a":"AAA", "b", "BBBB"}`, + Created: 1234, + CreatedBy: "user:ryan", + Updated: 5678, + UpdatedBy: "user:cameron", + Version: 1, + Description: "description", + Keeper: toNullString(nil), + Decrypters: toNullString(nil), + Ref: toNullString(nil), + ExternalID: "extId", + OwnerReferenceAPIGroup: toNullString(nil), + OwnerReferenceAPIVersion: toNullString(nil), + OwnerReferenceKind: toNullString(nil), + OwnerReferenceName: toNullString(nil), }, }, }, @@ -200,21 +204,25 @@ func TestSecureValueQueries(t *testing.T) { Data: &createSecureValue{ SQLTemplate: mocks.NewTestingSQLTemplate(), Row: &secureValueDB{ - GUID: "abc", - Name: "name", - Namespace: "ns", - Annotations: `{"x":"XXXX"}`, - Labels: `{"a":"AAA", "b", "BBBB"}`, - Created: 1234, - CreatedBy: "user:ryan", - Updated: 5678, - UpdatedBy: "user:cameron", - Version: 1, - Description: "description", - Keeper: toNullString(ptr.To("keeper_test")), - Decrypters: toNullString(ptr.To("decrypters_test")), - Ref: toNullString(ptr.To("ref_test")), - ExternalID: "extId", + GUID: "abc", + Name: "name", + Namespace: "ns", + Annotations: `{"x":"XXXX"}`, + Labels: `{"a":"AAA", "b", "BBBB"}`, + Created: 1234, + CreatedBy: "user:ryan", + Updated: 5678, + UpdatedBy: "user:cameron", + Version: 1, + Description: "description", + Keeper: toNullString(ptr.To("keeper_test")), + Decrypters: toNullString(ptr.To("decrypters_test")), + Ref: toNullString(ptr.To("ref_test")), + ExternalID: "extId", + OwnerReferenceAPIGroup: toNullString(ptr.To("prometheus.datasource.grafana.app")), + OwnerReferenceAPIVersion: toNullString(ptr.To("v0alpha1")), + OwnerReferenceKind: toNullString(ptr.To("DataSource")), + OwnerReferenceName: toNullString(ptr.To("prom-config")), }, }, }, diff --git a/pkg/storage/secret/metadata/secure_value_model.go b/pkg/storage/secret/metadata/secure_value_model.go index 6e5c8511784..c64411bf19b 100644 --- a/pkg/storage/secret/metadata/secure_value_model.go +++ b/pkg/storage/secret/metadata/secure_value_model.go @@ -10,22 +10,26 @@ import ( secretv1beta1 "github.com/grafana/grafana/apps/secret/pkg/apis/secret/v1beta1" "github.com/grafana/grafana/pkg/apimachinery/utils" "github.com/grafana/grafana/pkg/registry/apis/secret/xkube" - "github.com/grafana/grafana/pkg/storage/secret/migrator" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" ) type secureValueDB struct { // Kubernetes Metadata - GUID string - Name string - Namespace string - Annotations string // map[string]string - Labels string // map[string]string - Created int64 - CreatedBy string - Updated int64 - UpdatedBy string + GUID string + Name string + Namespace string + Annotations string // map[string]string + Labels string // map[string]string + Created int64 + CreatedBy string + Updated int64 + UpdatedBy string + OwnerReferenceAPIGroup sql.NullString + OwnerReferenceAPIVersion sql.NullString + OwnerReferenceKind sql.NullString + OwnerReferenceName sql.NullString // Kubernetes Status Active bool @@ -39,10 +43,6 @@ type secureValueDB struct { ExternalID string } -func (*secureValueDB) TableName() string { - return migrator.TableNameSecureValue -} - // toKubernetes maps a DB row into a Kubernetes resource (metadata + spec). func (sv *secureValueDB) toKubernetes() (*secretv1beta1.SecureValue, error) { annotations := make(map[string]string, 0) @@ -85,8 +85,6 @@ func (sv *secureValueDB) toKubernetes() (*secretv1beta1.SecureValue, error) { resource.Spec.Ref = &sv.Ref.String } - resource.Status.ExternalID = sv.ExternalID - // Set all meta fields here for consistency. meta, err := utils.MetaAccessor(resource) if err != nil { @@ -106,6 +104,20 @@ func (sv *secureValueDB) toKubernetes() (*secretv1beta1.SecureValue, error) { meta.SetUpdatedTimestamp(&updated) meta.SetResourceVersionInt64(sv.Updated) + hasOwnerReference := sv.OwnerReferenceAPIGroup.Valid && sv.OwnerReferenceAPIGroup.String != "" && + sv.OwnerReferenceAPIVersion.Valid && sv.OwnerReferenceAPIVersion.String != "" && + sv.OwnerReferenceKind.Valid && sv.OwnerReferenceKind.String != "" && + sv.OwnerReferenceName.Valid && sv.OwnerReferenceName.String != "" + if hasOwnerReference { + meta.SetOwnerReferences([]metav1.OwnerReference{ + { + APIVersion: schema.GroupVersion{Group: sv.OwnerReferenceAPIGroup.String, Version: sv.OwnerReferenceAPIVersion.String}.String(), + Kind: sv.OwnerReferenceKind.String, + Name: sv.OwnerReferenceName.String, + }, + }) + } + return resource, nil } @@ -179,16 +191,48 @@ func toRow(sv *secretv1beta1.SecureValue, externalID string) (*secureValueDB, er return nil, fmt.Errorf("failed to get resource version: %w", err) } + var ( + ownerReferenceAPIGroup sql.NullString + ownerReferenceAPIVersion sql.NullString + ownerReferenceKind sql.NullString + ownerReferenceName sql.NullString + ) + + ownerReferences := meta.GetOwnerReferences() + if len(ownerReferences) > 1 { + return nil, fmt.Errorf("only one owner reference is supported, found %d", len(ownerReferences)) + } + if len(ownerReferences) == 1 { + ownerReference := ownerReferences[0] + + gv, err := schema.ParseGroupVersion(ownerReference.APIVersion) + if err != nil { + return nil, fmt.Errorf("failed to parse owner reference API version %s: %w", ownerReference.APIVersion, err) + } + if gv.Group == "" { + return nil, fmt.Errorf("malformed api version %s requires / format", ownerReference.APIVersion) + } + + ownerReferenceAPIGroup = toNullString(&gv.Group) + ownerReferenceAPIVersion = toNullString(&gv.Version) + ownerReferenceKind = toNullString(&ownerReference.Kind) + ownerReferenceName = toNullString(&ownerReference.Name) + } + return &secureValueDB{ - GUID: string(sv.UID), - Name: sv.Name, - Namespace: sv.Namespace, - Annotations: annotations, - Labels: labels, - Created: meta.GetCreationTimestamp().UnixMilli(), - CreatedBy: meta.GetCreatedBy(), - Updated: updatedTimestamp, - UpdatedBy: meta.GetUpdatedBy(), + GUID: string(sv.UID), + Name: sv.Name, + Namespace: sv.Namespace, + Annotations: annotations, + Labels: labels, + Created: meta.GetCreationTimestamp().UnixMilli(), + CreatedBy: meta.GetCreatedBy(), + Updated: updatedTimestamp, + UpdatedBy: meta.GetUpdatedBy(), + OwnerReferenceAPIGroup: ownerReferenceAPIGroup, + OwnerReferenceAPIVersion: ownerReferenceAPIVersion, + OwnerReferenceKind: ownerReferenceKind, + OwnerReferenceName: ownerReferenceName, Version: sv.Status.Version, diff --git a/pkg/storage/secret/metadata/secure_value_store.go b/pkg/storage/secret/metadata/secure_value_store.go index 46001baf3ae..a9c2d0664d6 100644 --- a/pkg/storage/secret/metadata/secure_value_store.go +++ b/pkg/storage/secret/metadata/secure_value_store.go @@ -243,7 +243,9 @@ func (s *secureValueMetadataStorage) readActiveVersion(ctx context.Context, name &secureValue.Annotations, &secureValue.Labels, &secureValue.Created, &secureValue.CreatedBy, &secureValue.Updated, &secureValue.UpdatedBy, - &secureValue.Description, &secureValue.Keeper, &secureValue.Decrypters, &secureValue.Ref, &secureValue.ExternalID, &secureValue.Active, &secureValue.Version); err != nil { + &secureValue.Description, &secureValue.Keeper, &secureValue.Decrypters, &secureValue.Ref, &secureValue.ExternalID, &secureValue.Active, &secureValue.Version, + &secureValue.OwnerReferenceAPIGroup, &secureValue.OwnerReferenceAPIVersion, &secureValue.OwnerReferenceKind, &secureValue.OwnerReferenceName, + ); err != nil { return secureValueDB{}, fmt.Errorf("failed to scan secure value row: %w", err) } @@ -320,6 +322,7 @@ func (s *secureValueMetadataStorage) List(ctx context.Context, namespace xkube.N &row.Updated, &row.UpdatedBy, &row.Description, &row.Keeper, &row.Decrypters, &row.Ref, &row.ExternalID, &row.Version, &row.Active, + &row.OwnerReferenceAPIGroup, &row.OwnerReferenceAPIVersion, &row.OwnerReferenceKind, &row.OwnerReferenceName, ) if err != nil { diff --git a/pkg/storage/secret/metadata/testdata/mysql--secure_value_create-create-not-null.sql b/pkg/storage/secret/metadata/testdata/mysql--secure_value_create-create-not-null.sql index aadea1d47a9..b629d32fc52 100755 --- a/pkg/storage/secret/metadata/testdata/mysql--secure_value_create-create-not-null.sql +++ b/pkg/storage/secret/metadata/testdata/mysql--secure_value_create-create-not-null.sql @@ -14,6 +14,10 @@ INSERT INTO `secret_secure_value` ( `keeper`, `decrypters`, `ref`, + `owner_reference_api_group`, + `owner_reference_api_version`, + `owner_reference_kind`, + `owner_reference_name`, `external_id` ) VALUES ( 'abc', @@ -31,5 +35,9 @@ INSERT INTO `secret_secure_value` ( 'keeper_test', 'decrypters_test', 'ref_test', + 'prometheus.datasource.grafana.app', + 'v0alpha1', + 'DataSource', + 'prom-config', 'extId' ); diff --git a/pkg/storage/secret/metadata/testdata/mysql--secure_value_list-list.sql b/pkg/storage/secret/metadata/testdata/mysql--secure_value_list-list.sql index d73828bfae8..5faf0cca659 100755 --- a/pkg/storage/secret/metadata/testdata/mysql--secure_value_list-list.sql +++ b/pkg/storage/secret/metadata/testdata/mysql--secure_value_list-list.sql @@ -14,7 +14,11 @@ SELECT `ref`, `external_id`, `version`, - `active` + `active`, + `owner_reference_api_group`, + `owner_reference_api_version`, + `owner_reference_kind`, + `owner_reference_name` FROM `secret_secure_value` WHERE diff --git a/pkg/storage/secret/metadata/testdata/mysql--secure_value_read-read-for-update.sql b/pkg/storage/secret/metadata/testdata/mysql--secure_value_read-read-for-update.sql index 4d8525dd0a0..f48f1a1d703 100755 --- a/pkg/storage/secret/metadata/testdata/mysql--secure_value_read-read-for-update.sql +++ b/pkg/storage/secret/metadata/testdata/mysql--secure_value_read-read-for-update.sql @@ -14,7 +14,11 @@ SELECT `ref`, `external_id`, `active`, - `version` + `version`, + `owner_reference_api_group`, + `owner_reference_api_version`, + `owner_reference_kind`, + `owner_reference_name` FROM `secret_secure_value` WHERE diff --git a/pkg/storage/secret/metadata/testdata/mysql--secure_value_read-read.sql b/pkg/storage/secret/metadata/testdata/mysql--secure_value_read-read.sql index c42fd0037c5..3dfc6f1e0b9 100755 --- a/pkg/storage/secret/metadata/testdata/mysql--secure_value_read-read.sql +++ b/pkg/storage/secret/metadata/testdata/mysql--secure_value_read-read.sql @@ -14,7 +14,11 @@ SELECT `ref`, `external_id`, `active`, - `version` + `version`, + `owner_reference_api_group`, + `owner_reference_api_version`, + `owner_reference_kind`, + `owner_reference_name` FROM `secret_secure_value` WHERE diff --git a/pkg/storage/secret/metadata/testdata/postgres--secure_value_create-create-not-null.sql b/pkg/storage/secret/metadata/testdata/postgres--secure_value_create-create-not-null.sql index cf43e0c130c..bbf94f89fcc 100755 --- a/pkg/storage/secret/metadata/testdata/postgres--secure_value_create-create-not-null.sql +++ b/pkg/storage/secret/metadata/testdata/postgres--secure_value_create-create-not-null.sql @@ -14,6 +14,10 @@ INSERT INTO "secret_secure_value" ( "keeper", "decrypters", "ref", + "owner_reference_api_group", + "owner_reference_api_version", + "owner_reference_kind", + "owner_reference_name", "external_id" ) VALUES ( 'abc', @@ -31,5 +35,9 @@ INSERT INTO "secret_secure_value" ( 'keeper_test', 'decrypters_test', 'ref_test', + 'prometheus.datasource.grafana.app', + 'v0alpha1', + 'DataSource', + 'prom-config', 'extId' ); diff --git a/pkg/storage/secret/metadata/testdata/postgres--secure_value_list-list.sql b/pkg/storage/secret/metadata/testdata/postgres--secure_value_list-list.sql index 40ee42ebcf7..0095a993c35 100755 --- a/pkg/storage/secret/metadata/testdata/postgres--secure_value_list-list.sql +++ b/pkg/storage/secret/metadata/testdata/postgres--secure_value_list-list.sql @@ -14,7 +14,11 @@ SELECT "ref", "external_id", "version", - "active" + "active", + "owner_reference_api_group", + "owner_reference_api_version", + "owner_reference_kind", + "owner_reference_name" FROM "secret_secure_value" WHERE diff --git a/pkg/storage/secret/metadata/testdata/postgres--secure_value_read-read-for-update.sql b/pkg/storage/secret/metadata/testdata/postgres--secure_value_read-read-for-update.sql index cce38e6db7a..162d97d042c 100755 --- a/pkg/storage/secret/metadata/testdata/postgres--secure_value_read-read-for-update.sql +++ b/pkg/storage/secret/metadata/testdata/postgres--secure_value_read-read-for-update.sql @@ -14,7 +14,11 @@ SELECT "ref", "external_id", "active", - "version" + "version", + "owner_reference_api_group", + "owner_reference_api_version", + "owner_reference_kind", + "owner_reference_name" FROM "secret_secure_value" WHERE diff --git a/pkg/storage/secret/metadata/testdata/postgres--secure_value_read-read.sql b/pkg/storage/secret/metadata/testdata/postgres--secure_value_read-read.sql index 2d16c211a85..86aab54919d 100755 --- a/pkg/storage/secret/metadata/testdata/postgres--secure_value_read-read.sql +++ b/pkg/storage/secret/metadata/testdata/postgres--secure_value_read-read.sql @@ -14,7 +14,11 @@ SELECT "ref", "external_id", "active", - "version" + "version", + "owner_reference_api_group", + "owner_reference_api_version", + "owner_reference_kind", + "owner_reference_name" FROM "secret_secure_value" WHERE diff --git a/pkg/storage/secret/metadata/testdata/sqlite--secure_value_create-create-not-null.sql b/pkg/storage/secret/metadata/testdata/sqlite--secure_value_create-create-not-null.sql index cf43e0c130c..bbf94f89fcc 100755 --- a/pkg/storage/secret/metadata/testdata/sqlite--secure_value_create-create-not-null.sql +++ b/pkg/storage/secret/metadata/testdata/sqlite--secure_value_create-create-not-null.sql @@ -14,6 +14,10 @@ INSERT INTO "secret_secure_value" ( "keeper", "decrypters", "ref", + "owner_reference_api_group", + "owner_reference_api_version", + "owner_reference_kind", + "owner_reference_name", "external_id" ) VALUES ( 'abc', @@ -31,5 +35,9 @@ INSERT INTO "secret_secure_value" ( 'keeper_test', 'decrypters_test', 'ref_test', + 'prometheus.datasource.grafana.app', + 'v0alpha1', + 'DataSource', + 'prom-config', 'extId' ); diff --git a/pkg/storage/secret/metadata/testdata/sqlite--secure_value_list-list.sql b/pkg/storage/secret/metadata/testdata/sqlite--secure_value_list-list.sql index 40ee42ebcf7..0095a993c35 100755 --- a/pkg/storage/secret/metadata/testdata/sqlite--secure_value_list-list.sql +++ b/pkg/storage/secret/metadata/testdata/sqlite--secure_value_list-list.sql @@ -14,7 +14,11 @@ SELECT "ref", "external_id", "version", - "active" + "active", + "owner_reference_api_group", + "owner_reference_api_version", + "owner_reference_kind", + "owner_reference_name" FROM "secret_secure_value" WHERE diff --git a/pkg/storage/secret/metadata/testdata/sqlite--secure_value_read-read-for-update.sql b/pkg/storage/secret/metadata/testdata/sqlite--secure_value_read-read-for-update.sql index 2d16c211a85..86aab54919d 100755 --- a/pkg/storage/secret/metadata/testdata/sqlite--secure_value_read-read-for-update.sql +++ b/pkg/storage/secret/metadata/testdata/sqlite--secure_value_read-read-for-update.sql @@ -14,7 +14,11 @@ SELECT "ref", "external_id", "active", - "version" + "version", + "owner_reference_api_group", + "owner_reference_api_version", + "owner_reference_kind", + "owner_reference_name" FROM "secret_secure_value" WHERE diff --git a/pkg/storage/secret/metadata/testdata/sqlite--secure_value_read-read.sql b/pkg/storage/secret/metadata/testdata/sqlite--secure_value_read-read.sql index 2d16c211a85..86aab54919d 100755 --- a/pkg/storage/secret/metadata/testdata/sqlite--secure_value_read-read.sql +++ b/pkg/storage/secret/metadata/testdata/sqlite--secure_value_read-read.sql @@ -14,7 +14,11 @@ SELECT "ref", "external_id", "active", - "version" + "version", + "owner_reference_api_group", + "owner_reference_api_version", + "owner_reference_kind", + "owner_reference_name" FROM "secret_secure_value" WHERE diff --git a/pkg/storage/secret/migrator/migrator.go b/pkg/storage/secret/migrator/migrator.go index 0b7790a875f..a208497c65a 100644 --- a/pkg/storage/secret/migrator/migrator.go +++ b/pkg/storage/secret/migrator/migrator.go @@ -152,4 +152,33 @@ func (*SecretDB) AddMigration(mg *migrator.Migrator) { Cols: []string{"namespace", "label", "active"}, Type: migrator.IndexType, })) + + // Owner Reference columns + mg.AddMigration("add owner_reference_api_group column to "+TableNameSecureValue, migrator.NewAddColumnMigration(secureValueTable, &migrator.Column{ + Name: "owner_reference_api_group", + Type: migrator.DB_NVarchar, + Length: 253, // Limit enforced by K8s. + Nullable: true, + })) + + mg.AddMigration("add owner_reference_api_version column to "+TableNameSecureValue, migrator.NewAddColumnMigration(secureValueTable, &migrator.Column{ + Name: "owner_reference_api_version", + Type: migrator.DB_NVarchar, + Length: 253, // Limit enforced by K8s. + Nullable: true, + })) + + mg.AddMigration("add owner_reference_kind column to "+TableNameSecureValue, migrator.NewAddColumnMigration(secureValueTable, &migrator.Column{ + Name: "owner_reference_kind", + Type: migrator.DB_NVarchar, + Length: 253, // Limit enforced by K8s. + Nullable: true, + })) + + mg.AddMigration("add owner_reference_name column to "+TableNameSecureValue, migrator.NewAddColumnMigration(secureValueTable, &migrator.Column{ + Name: "owner_reference_name", + Type: migrator.DB_NVarchar, + Length: 253, // Limit enforced by K8s. + Nullable: true, + })) } From 8b600f40c13e279fb4489566b99cc5b86c995b75 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 31 Jul 2025 08:50:01 +0000 Subject: [PATCH 034/114] Update dependency eslint-plugin-no-barrel-files to v1.2.2 (#108961) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index e38aa7212a7..b89f00a6687 100644 --- a/yarn.lock +++ b/yarn.lock @@ -16323,11 +16323,11 @@ __metadata: linkType: hard "eslint-plugin-no-barrel-files@npm:^1.1.1": - version: 1.2.1 - resolution: "eslint-plugin-no-barrel-files@npm:1.2.1" + version: 1.2.2 + resolution: "eslint-plugin-no-barrel-files@npm:1.2.2" dependencies: "@typescript-eslint/utils": "npm:^8.13.0" - checksum: 10/22a888dd3b49527bed0f92758f4c7f34e78397c9197688db1936d2bdad8369f9ded2a1231a6b85e54791f059784f1d7bcdbeeb21cf0b453399044a1957a153c4 + checksum: 10/9ab622f211270487016849a4da1d275890340721db3301fc90d2b96af5bb72d49934114c5e0fe1c3b69a354a9e0841b67a19e8c4fa4c6e35a02e3a3bc0973b1e languageName: node linkType: hard From c9cf44c4d77aa3bc77c230a54130fe0264cb3d7f Mon Sep 17 00:00:00 2001 From: Roberto Jimenez Sanchez Date: Thu, 31 Jul 2025 10:38:31 +0200 Subject: [PATCH 035/114] Add Git Sync team as owner of the integration tests --- .github/CODEOWNERS | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index b3ab77bcd19..f26cfd876aa 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -72,9 +72,12 @@ /hack/ @grafana/grafana-app-platform-squad /.air.toml @macabu +# Git Sync / App Platform Provisioning /pkg/apis/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 +# Git Sync frontend owned by frontent team as a whole. /apps/alerting/ @grafana/alerting-backend /apps/dashboard/ @grafana/grafana-app-platform-squad @grafana/dashboards-squad From f39b878b0a681152b64f15bcb200f374d16e921b Mon Sep 17 00:00:00 2001 From: Roberto Jimenez Sanchez Date: Thu, 31 Jul 2025 10:38:53 +0200 Subject: [PATCH 036/114] Add Git Sync team as owner of the Git Sync dependencies --- go.mod | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 27500ca7227..e1f5b010a2f 100644 --- a/go.mod +++ b/go.mod @@ -78,7 +78,7 @@ require ( github.com/golang/protobuf v1.5.4 // @grafana/grafana-backend-group github.com/golang/snappy v1.0.0 // @grafana/alerting-backend github.com/google/go-cmp v0.7.0 // @grafana/grafana-backend-group - github.com/google/go-github/v70 v70.0.0 // @grafana/grafana-app-platform-squad + github.com/google/go-github/v70 v70.0.0 // @grafana/grafana-git-ui-sync-team github.com/google/go-querystring v1.1.0 // indirect; @grafana/oss-big-tent github.com/google/uuid v1.6.0 // @grafana/grafana-backend-group github.com/google/wire v0.6.0 // @grafana/grafana-backend-group @@ -104,7 +104,7 @@ require ( github.com/grafana/grafana-openapi-client-go v0.0.0-20231213163343-bd475d63fb79 // @grafana/grafana-backend-group github.com/grafana/grafana-plugin-sdk-go v0.278.0 // @grafana/plugins-platform-backend github.com/grafana/loki/v3 v3.2.1 // @grafana/observability-logs - github.com/grafana/nanogit v0.0.0-20250723104447-68f58f5ecec0 // @grafana-app-platform-squad + github.com/grafana/nanogit v0.0.0-20250723104447-68f58f5ecec0 // @grafana/grafana-git-ui-sync-team github.com/grafana/otel-profiling-go v0.5.1 // @grafana/grafana-backend-group github.com/grafana/pyroscope-go/godeltaprof v0.1.8 // @grafana/observability-traces-and-profiling github.com/grafana/pyroscope/api v1.2.1-0.20250415190842-3ff7247547ae // @grafana/observability-traces-and-profiling @@ -135,7 +135,7 @@ require ( github.com/mattn/go-sqlite3 v1.14.22 // @grafana/grafana-backend-group github.com/matttproud/golang_protobuf_extensions v1.0.4 // @grafana/alerting-backend github.com/microsoft/go-mssqldb v1.8.0 // @grafana/partner-datasources - github.com/migueleliasweb/go-github-mock v1.1.0 // @grafana/grafana-app-platform-squad + github.com/migueleliasweb/go-github-mock v1.1.0 // @grafana/grafana-git-ui-sync-team github.com/mitchellh/copystructure v1.2.0 // @grafana/grafana-operator-experience-squad github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c //@grafana/identity-access-team github.com/mocktools/go-smtp-mock/v2 v2.3.1 // @grafana/grafana-backend-group From 6b1143565a15a1434f69437a7f5c4927bffad243 Mon Sep 17 00:00:00 2001 From: Serge Zaitsev Date: Thu, 31 Jul 2025 11:25:19 +0200 Subject: [PATCH 037/114] Chore: Make cgo optional (for sqlite) (#108756) * make cgo optional for sqlite * update go.mod; check error code differently * reduce api surface even more * move test errors into sqlite package * add a comment --- apps/advisor/go.mod | 7 +++ apps/advisor/go.sum | 28 +++++++++++ apps/investigations/go.mod | 7 +++ apps/investigations/go.sum | 30 ++++++++++++ go.mod | 2 +- pkg/services/sqlstore/database_wrapper.go | 4 +- pkg/services/sqlstore/migrator/migrator.go | 5 +- .../sqlstore/migrator/sqlite_dialect.go | 23 ++-------- pkg/services/sqlstore/session_test.go | 4 +- pkg/storage/unified/sql/backend.go | 7 ++- pkg/storage/unified/sql/backend_test.go | 4 +- pkg/util/sqlite/sqlite_cgo.go | 46 +++++++++++++++++++ pkg/util/sqlite/sqlite_nocgo.go | 24 ++++++++++ pkg/util/xorm/dialect_sqlite3.go | 8 +--- pkg/util/xorm/xorm_test.go | 2 +- 15 files changed, 160 insertions(+), 41 deletions(-) create mode 100644 pkg/util/sqlite/sqlite_cgo.go create mode 100644 pkg/util/sqlite/sqlite_nocgo.go diff --git a/apps/advisor/go.mod b/apps/advisor/go.mod index 33afd0a0ae6..c232ed6d95e 100644 --- a/apps/advisor/go.mod +++ b/apps/advisor/go.mod @@ -79,6 +79,7 @@ require ( github.com/dolthub/go-mysql-server v0.19.1-0.20250410182021-5632d67cd46e // indirect github.com/dolthub/jsonpath v0.0.2-0.20240227200619-19675ab05c71 // indirect github.com/dolthub/vitess v0.0.0-20250410090211-143e6b272ad4 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect github.com/elazarl/goproxy v1.7.2 // indirect github.com/emicklei/go-restful/v3 v3.12.1 // indirect github.com/evanphx/json-patch v5.6.0+incompatible // indirect @@ -190,6 +191,7 @@ require ( github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect + github.com/ncruces/go-strftime v0.1.9 // indirect github.com/nikunjy/rules v1.5.0 // indirect github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect @@ -213,6 +215,7 @@ require ( github.com/prometheus/exporter-toolkit v0.14.0 // indirect github.com/prometheus/procfs v0.16.1 // indirect github.com/puzpuzpuz/xsync/v2 v2.5.1 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/rs/cors v1.11.1 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect @@ -284,6 +287,10 @@ require ( k8s.io/component-base v0.33.3 // indirect k8s.io/klog/v2 v2.130.1 // indirect k8s.io/utils v0.0.0-20241210054802-24370beab758 // indirect + modernc.org/libc v1.65.0 // indirect + modernc.org/mathutil v1.7.1 // indirect + modernc.org/memory v1.10.0 // indirect + modernc.org/sqlite v1.37.0 // indirect sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect sigs.k8s.io/randfill v1.0.0 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.7.0 // indirect diff --git a/apps/advisor/go.sum b/apps/advisor/go.sum index 7adf9d1eb21..bc8bd30f88b 100644 --- a/apps/advisor/go.sum +++ b/apps/advisor/go.sum @@ -944,6 +944,8 @@ github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRW github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nakagami/firebirdsql v0.0.0-20190310045651-3c02a58cfed8/go.mod h1:86wM1zFnC6/uDBfZGNwB65O+pR2OFi5q/YQaEUid1qA= +github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= +github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/nikunjy/rules v1.5.0 h1:KJDSLOsFhwt7kcXUyZqwkgrQg5YoUwj+TVu6ItCQShw= github.com/nikunjy/rules v1.5.0/go.mod h1:TlZtZdBChrkqi8Lr2AXocme8Z7EsbxtFdDoKeI6neBQ= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= @@ -1056,6 +1058,8 @@ github.com/puzpuzpuz/xsync/v2 v2.5.1/go.mod h1:gD2H2krq/w52MfPLE+Uy64TzJDVY7lP2z github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/redis/go-redis/v9 v9.8.0 h1:q3nRvjrlge/6UD7eTu/DSg2uYiU2mCL0G/uzBWqhicI= github.com/redis/go-redis/v9 v9.8.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= @@ -1910,6 +1914,30 @@ k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUy 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= +modernc.org/cc/v4 v4.26.0 h1:QMYvbVduUGH0rrO+5mqF/PSPPRZNpRtg2CLELy7vUpA= +modernc.org/cc/v4 v4.26.0/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= +modernc.org/ccgo/v4 v4.26.0 h1:gVzXaDzGeBYJ2uXTOpR8FR7OlksDOe9jxnjhIKCsiTc= +modernc.org/ccgo/v4 v4.26.0/go.mod h1:Sem8f7TFUtVXkG2fiaChQtyyfkqhJBg/zjEJBkmuAVY= +modernc.org/fileutil v1.3.1 h1:8vq5fe7jdtEvoCf3Zf9Nm0Q05sH6kGx0Op2CPx1wTC8= +modernc.org/fileutil v1.3.1/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc= +modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= +modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= +modernc.org/libc v1.65.0 h1:e183gLDnAp9VJh6gWKdTy0CThL9Pt7MfcR/0bgb7Y1Y= +modernc.org/libc v1.65.0/go.mod h1:7m9VzGq7APssBTydds2zBcxGREwvIGpuUBaKTXdm2Qs= +modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= +modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= +modernc.org/memory v1.10.0 h1:fzumd51yQ1DxcOxSO+S6X7+QTuVU+n8/Aj7swYjFfC4= +modernc.org/memory v1.10.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= +modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8= +modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= +modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= +modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= +modernc.org/sqlite v1.37.0 h1:s1TMe7T3Q3ovQiK2Ouz4Jwh7dw4ZDqbebSDTlSJdfjI= +modernc.org/sqlite v1.37.0/go.mod h1:5YiWv+YviqGMuGw4V+PNplcyaJ5v+vQd7TQOgkACoJM= +modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= +modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= +modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= +modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/apps/investigations/go.mod b/apps/investigations/go.mod index f1bb896a0ff..3a2053ac21d 100644 --- a/apps/investigations/go.mod +++ b/apps/investigations/go.mod @@ -49,6 +49,7 @@ require ( github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/diegoholiveira/jsonlogic/v3 v3.7.4 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect github.com/elazarl/goproxy v1.7.2 // indirect github.com/emicklei/go-restful/v3 v3.12.1 // indirect github.com/evanphx/json-patch v5.6.0+incompatible // indirect @@ -136,6 +137,7 @@ require ( github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect + github.com/ncruces/go-strftime v0.1.9 // indirect github.com/nikunjy/rules v1.5.0 // indirect github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect @@ -157,6 +159,7 @@ require ( github.com/prometheus/exporter-toolkit v0.14.0 // indirect github.com/prometheus/procfs v0.16.1 // indirect github.com/puzpuzpuz/xsync/v2 v2.5.1 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect @@ -213,6 +216,10 @@ require ( k8s.io/client-go v0.33.3 // indirect k8s.io/component-base v0.33.3 // indirect k8s.io/utils v0.0.0-20241210054802-24370beab758 // indirect + modernc.org/libc v1.65.0 // indirect + modernc.org/mathutil v1.7.1 // indirect + modernc.org/memory v1.10.0 // indirect + modernc.org/sqlite v1.37.0 // indirect sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect sigs.k8s.io/randfill v1.0.0 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.7.0 // indirect diff --git a/apps/investigations/go.sum b/apps/investigations/go.sum index b17f4ea40a5..3cc1f3d148a 100644 --- a/apps/investigations/go.sum +++ b/apps/investigations/go.sum @@ -170,6 +170,8 @@ github.com/dolthub/jsonpath v0.0.2-0.20240227200619-19675ab05c71 h1:bMGS25NWAGTE github.com/dolthub/jsonpath v0.0.2-0.20240227200619-19675ab05c71/go.mod h1:2/2zjLQ/JOOSbbSboojeg+cAwcRV0fDLzIiWch/lhqI= github.com/dolthub/vitess v0.0.0-20250410090211-143e6b272ad4 h1:LGTt2LtYX8vaai32d+c9L0sMcP+Dg9w1kO6+lbsxxYg= github.com/dolthub/vitess v0.0.0-20250410090211-143e6b272ad4/go.mod h1:1gQZs/byeHLMSul3Lvl3MzioMtOW1je79QYGyi2fd70= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= @@ -540,6 +542,8 @@ github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRW github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nakagami/firebirdsql v0.0.0-20190310045651-3c02a58cfed8/go.mod h1:86wM1zFnC6/uDBfZGNwB65O+pR2OFi5q/YQaEUid1qA= +github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= +github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/nikunjy/rules v1.5.0 h1:KJDSLOsFhwt7kcXUyZqwkgrQg5YoUwj+TVu6ItCQShw= github.com/nikunjy/rules v1.5.0/go.mod h1:TlZtZdBChrkqi8Lr2AXocme8Z7EsbxtFdDoKeI6neBQ= github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY= @@ -626,6 +630,8 @@ github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlT github.com/puzpuzpuz/xsync/v2 v2.5.1 h1:mVGYAvzDSu52+zaGyNjC+24Xw2bQi3kTr4QJ6N9pIIU= github.com/puzpuzpuz/xsync/v2 v2.5.1/go.mod h1:gD2H2krq/w52MfPLE+Uy64TzJDVY7lP2znR9qmR35kU= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= @@ -994,6 +1000,30 @@ k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUy 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= +modernc.org/cc/v4 v4.26.0 h1:QMYvbVduUGH0rrO+5mqF/PSPPRZNpRtg2CLELy7vUpA= +modernc.org/cc/v4 v4.26.0/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= +modernc.org/ccgo/v4 v4.26.0 h1:gVzXaDzGeBYJ2uXTOpR8FR7OlksDOe9jxnjhIKCsiTc= +modernc.org/ccgo/v4 v4.26.0/go.mod h1:Sem8f7TFUtVXkG2fiaChQtyyfkqhJBg/zjEJBkmuAVY= +modernc.org/fileutil v1.3.1 h1:8vq5fe7jdtEvoCf3Zf9Nm0Q05sH6kGx0Op2CPx1wTC8= +modernc.org/fileutil v1.3.1/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc= +modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= +modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= +modernc.org/libc v1.65.0 h1:e183gLDnAp9VJh6gWKdTy0CThL9Pt7MfcR/0bgb7Y1Y= +modernc.org/libc v1.65.0/go.mod h1:7m9VzGq7APssBTydds2zBcxGREwvIGpuUBaKTXdm2Qs= +modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= +modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= +modernc.org/memory v1.10.0 h1:fzumd51yQ1DxcOxSO+S6X7+QTuVU+n8/Aj7swYjFfC4= +modernc.org/memory v1.10.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= +modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8= +modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= +modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= +modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= +modernc.org/sqlite v1.37.0 h1:s1TMe7T3Q3ovQiK2Ouz4Jwh7dw4ZDqbebSDTlSJdfjI= +modernc.org/sqlite v1.37.0/go.mod h1:5YiWv+YviqGMuGw4V+PNplcyaJ5v+vQd7TQOgkACoJM= +modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= +modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= +modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= +modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= 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= diff --git a/go.mod b/go.mod index e1f5b010a2f..7fe6de3bf42 100644 --- a/go.mod +++ b/go.mod @@ -221,6 +221,7 @@ require ( k8s.io/kube-aggregator v0.33.3 // @grafana/grafana-app-platform-squad k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // @grafana/grafana-app-platform-squad k8s.io/utils v0.0.0-20241210054802-24370beab758 // @grafana/partner-datasources + modernc.org/sqlite v1.37.0 // @grafana/grafana-backend-group pgregory.net/rapid v1.2.0 // @grafana/grafana-operator-experience-squad sigs.k8s.io/randfill v1.0.0 // @grafana/grafana-app-platform-squad sigs.k8s.io/structured-merge-diff/v4 v4.7.0 // @grafana-app-platform-squad @@ -596,7 +597,6 @@ require ( modernc.org/libc v1.65.0 // indirect modernc.org/mathutil v1.7.1 // indirect modernc.org/memory v1.10.0 // indirect - modernc.org/sqlite v1.37.0 // indirect sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 // indirect sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect sigs.k8s.io/yaml v1.5.0 // indirect diff --git a/pkg/services/sqlstore/database_wrapper.go b/pkg/services/sqlstore/database_wrapper.go index f691cb0c5a3..6a033b3b248 100644 --- a/pkg/services/sqlstore/database_wrapper.go +++ b/pkg/services/sqlstore/database_wrapper.go @@ -10,9 +10,9 @@ import ( "github.com/gchaincl/sqlhooks" "github.com/go-sql-driver/mysql" + "github.com/grafana/grafana/pkg/util/sqlite" "github.com/grafana/grafana/pkg/util/xorm/core" "github.com/lib/pq" - "github.com/mattn/go-sqlite3" "github.com/prometheus/client_golang/prometheus" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" @@ -42,7 +42,7 @@ func init() { // database queries. It also registers the metrics. func WrapDatabaseDriverWithHooks(dbType string, tracer tracing.Tracer) string { drivers := map[string]driver.Driver{ - migrator.SQLite: &sqlite3.SQLiteDriver{}, + migrator.SQLite: &sqlite.Driver{}, migrator.MySQL: &mysql.MySQLDriver{}, migrator.Postgres: &pq.Driver{}, } diff --git a/pkg/services/sqlstore/migrator/migrator.go b/pkg/services/sqlstore/migrator/migrator.go index d8e55c4ee71..75ebf8e540b 100644 --- a/pkg/services/sqlstore/migrator/migrator.go +++ b/pkg/services/sqlstore/migrator/migrator.go @@ -2,15 +2,14 @@ package migrator import ( "context" - "errors" "fmt" "strings" "time" _ "github.com/go-sql-driver/mysql" "github.com/golang-migrate/migrate/v4/database" + "github.com/grafana/grafana/pkg/util/sqlite" _ "github.com/lib/pq" - "github.com/mattn/go-sqlite3" "github.com/prometheus/client_golang/prometheus" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" @@ -326,7 +325,7 @@ func (mg *Migrator) doMigration(ctx context.Context, m Migration) error { err := mg.exec(ctx, m, sess) // if we get an sqlite busy/locked error, sleep 100ms and try again cnt := 0 - for cnt < 3 && (errors.Is(err, sqlite3.ErrLocked) || errors.Is(err, sqlite3.ErrBusy)) { + for cnt < 3 && sqlite.IsBusyOrLocked(err) { cnt++ logger.Debug("Database locked, sleeping then retrying", "error", err, "sql", sql) span.AddEvent("Database locked, sleeping then retrying", diff --git a/pkg/services/sqlstore/migrator/sqlite_dialect.go b/pkg/services/sqlstore/migrator/sqlite_dialect.go index 6c0e77972d8..d4302c18181 100644 --- a/pkg/services/sqlstore/migrator/sqlite_dialect.go +++ b/pkg/services/sqlstore/migrator/sqlite_dialect.go @@ -1,12 +1,10 @@ package migrator import ( - "errors" "fmt" "strings" - "github.com/mattn/go-sqlite3" - + "github.com/grafana/grafana/pkg/util/sqlite" "github.com/grafana/grafana/pkg/util/xorm" ) @@ -139,27 +137,12 @@ func (db *SQLite3) TruncateDBTables(engine *xorm.Engine) error { return nil } -func (db *SQLite3) isThisError(err error, errcode int) bool { - var driverErr sqlite3.Error - if errors.As(err, &driverErr) { - if int(driverErr.ExtendedCode) == errcode { - return true - } - } - - return false -} - func (db *SQLite3) ErrorMessage(err error) string { - var driverErr sqlite3.Error - if errors.As(err, &driverErr) { - return driverErr.Error() - } - return "" + return sqlite.ErrorMessage(err) } func (db *SQLite3) IsUniqueConstraintViolation(err error) bool { - return db.isThisError(err, int(sqlite3.ErrConstraintUnique)) || db.isThisError(err, int(sqlite3.ErrConstraintPrimaryKey)) + return sqlite.IsUniqueConstraintViolation(err) } func (db *SQLite3) IsDeadlock(err error) bool { diff --git a/pkg/services/sqlstore/session_test.go b/pkg/services/sqlstore/session_test.go index a30f11e6d11..0f2d0629a47 100644 --- a/pkg/services/sqlstore/session_test.go +++ b/pkg/services/sqlstore/session_test.go @@ -6,10 +6,10 @@ import ( "fmt" "testing" - "github.com/mattn/go-sqlite3" "github.com/stretchr/testify/require" "github.com/grafana/grafana/pkg/services/sqlstore/migrator" + "github.com/grafana/grafana/pkg/util/sqlite" ) func TestIntegration_RetryingDisabled(t *testing.T) { @@ -144,7 +144,7 @@ func getRetryErrors(t *testing.T, store *SQLStore) []error { var retryErrors []error switch store.GetDialect().DriverName() { case migrator.SQLite: - retryErrors = []error{sqlite3.Error{Code: sqlite3.ErrBusy}, sqlite3.Error{Code: sqlite3.ErrLocked}} + retryErrors = []error{sqlite.TestErrBusy, sqlite.TestErrLocked} } if len(retryErrors) == 0 { diff --git a/pkg/storage/unified/sql/backend.go b/pkg/storage/unified/sql/backend.go index a23cf9469ea..7733de8ff91 100644 --- a/pkg/storage/unified/sql/backend.go +++ b/pkg/storage/unified/sql/backend.go @@ -10,9 +10,9 @@ import ( "time" "github.com/go-sql-driver/mysql" + "github.com/grafana/grafana/pkg/util/sqlite" "github.com/jackc/pgx/v5/pgconn" "github.com/lib/pq" - "github.com/mattn/go-sqlite3" "github.com/prometheus/client_golang/prometheus" "go.opentelemetry.io/otel/trace" "go.opentelemetry.io/otel/trace/noop" @@ -389,9 +389,8 @@ func (b *backend) create(ctx context.Context, event resource.WriteEvent) (int64, // IsRowAlreadyExistsError checks if the error is the result of the row inserted already existing. func IsRowAlreadyExistsError(err error) bool { - var sqlite sqlite3.Error - if errors.As(err, &sqlite) { - return sqlite.ExtendedCode == sqlite3.ErrConstraintUnique + if sqlite.IsUniqueConstraintViolation(err) { + return true } var pg *pgconn.PgError diff --git a/pkg/storage/unified/sql/backend_test.go b/pkg/storage/unified/sql/backend_test.go index 92989e8454a..534b393065e 100644 --- a/pkg/storage/unified/sql/backend_test.go +++ b/pkg/storage/unified/sql/backend_test.go @@ -8,7 +8,6 @@ import ( "testing" "github.com/DATA-DOG/go-sqlmock" - "github.com/mattn/go-sqlite3" "github.com/stretchr/testify/require" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -18,6 +17,7 @@ import ( "github.com/grafana/grafana/pkg/storage/unified/resourcepb" "github.com/grafana/grafana/pkg/storage/unified/sql/db/dbimpl" "github.com/grafana/grafana/pkg/storage/unified/sql/test" + "github.com/grafana/grafana/pkg/util/sqlite" "github.com/grafana/grafana/pkg/util/testutil" ) @@ -242,7 +242,7 @@ func TestBackend_create(t *testing.T) { ) b.SQLMock.ExpectCommit() b.SQLMock.ExpectBegin() - b.SQLMock.ExpectExec("insert resource").WillReturnError(sqlite3.Error{Code: sqlite3.ErrConstraint, ExtendedCode: sqlite3.ErrConstraintUnique}) + b.SQLMock.ExpectExec("insert resource").WillReturnError(sqlite.TestErrUniqueConstraintViolation) b.SQLMock.ExpectRollback() // First we insert the resource successfully. This is what the happy path test does as well. diff --git a/pkg/util/sqlite/sqlite_cgo.go b/pkg/util/sqlite/sqlite_cgo.go new file mode 100644 index 00000000000..de18511741d --- /dev/null +++ b/pkg/util/sqlite/sqlite_cgo.go @@ -0,0 +1,46 @@ +//go:build cgo + +package sqlite + +import ( + "errors" + + "github.com/mattn/go-sqlite3" +) + +type Driver = sqlite3.SQLiteDriver + +// The errors below are used in tests to simulate specific SQLite errors. It's a temporary solution +// until we rewrite the tests not to depend on the sqlite3 package internals directly. +var ( + TestErrUniqueConstraintViolation = sqlite3.Error{Code: sqlite3.ErrConstraint, ExtendedCode: sqlite3.ErrConstraintUnique} + TestErrBusy = sqlite3.Error{Code: sqlite3.ErrBusy} + TestErrLocked = sqlite3.Error{Code: sqlite3.ErrLocked} +) + +func IsBusyOrLocked(err error) bool { + var sqliteErr sqlite3.Error + if errors.As(err, &sqliteErr) { + return sqliteErr.Code == sqlite3.ErrLocked || sqliteErr.Code == sqlite3.ErrBusy + } + return false +} + +func IsUniqueConstraintViolation(err error) bool { + var sqliteErr sqlite3.Error + if errors.As(err, &sqliteErr) { + return sqliteErr.ExtendedCode == sqlite3.ErrConstraintUnique || sqliteErr.ExtendedCode == sqlite3.ErrConstraintPrimaryKey + } + return false +} + +func ErrorMessage(err error) string { + if err == nil { + return "" + } + var sqliteErr sqlite3.Error + if errors.As(err, &sqliteErr) { + return sqliteErr.Error() + } + return err.Error() +} diff --git a/pkg/util/sqlite/sqlite_nocgo.go b/pkg/util/sqlite/sqlite_nocgo.go new file mode 100644 index 00000000000..e3b9a0429d3 --- /dev/null +++ b/pkg/util/sqlite/sqlite_nocgo.go @@ -0,0 +1,24 @@ +//go:build !cgo + +package sqlite + +import "modernc.org/sqlite" + +// +// FIXME (@zserge) +// +// This non-CGo "implementation" is merely a stub to make Grafana compile without CGo. +// Any attempts to actually use this driver are likely to fail at runtime in the most brutal ways. +// + +type Driver = sqlite.Driver + +func IsBusyOrLocked(err error) bool { + return false // FIXME +} +func IsUniqueConstraintViolation(err error) bool { + return false // FIXME +} +func ErrorMessage(err error) string { + return "" // FIXME +} diff --git a/pkg/util/xorm/dialect_sqlite3.go b/pkg/util/xorm/dialect_sqlite3.go index 381a38bb490..4c235ee1c90 100644 --- a/pkg/util/xorm/dialect_sqlite3.go +++ b/pkg/util/xorm/dialect_sqlite3.go @@ -11,8 +11,8 @@ import ( "regexp" "strings" + "github.com/grafana/grafana/pkg/util/sqlite" "github.com/grafana/grafana/pkg/util/xorm/core" - sqlite "github.com/mattn/go-sqlite3" ) var ( @@ -476,11 +476,7 @@ func (db *sqlite3) Filters() []core.Filter { } func (db *sqlite3) RetryOnError(err error) bool { - var sqlError sqlite.Error - if errors.As(err, &sqlError) && (sqlError.Code == sqlite.ErrLocked || sqlError.Code == sqlite.ErrBusy) { - return true - } - return false + return sqlite.IsBusyOrLocked(err) } type sqlite3Driver struct { diff --git a/pkg/util/xorm/xorm_test.go b/pkg/util/xorm/xorm_test.go index f2939fbf9a8..281282a1ef8 100644 --- a/pkg/util/xorm/xorm_test.go +++ b/pkg/util/xorm/xorm_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - _ "github.com/mattn/go-sqlite3" + _ "github.com/grafana/grafana/pkg/util/sqlite" "github.com/stretchr/testify/require" ) From d71924ea7bcf4e830f4b8d9d5ef064d2088f9618 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 31 Jul 2025 10:39:29 +0100 Subject: [PATCH 038/114] Update dependency stylelint to v16.23.0 (#108963) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 154 +++++++++++++++++++++++++-------------------------- 2 files changed, 77 insertions(+), 79 deletions(-) diff --git a/package.json b/package.json index cce32ec8537..3538a0d8b92 100644 --- a/package.json +++ b/package.json @@ -243,7 +243,7 @@ "sass-loader": "16.0.5", "smtp-tester": "^2.1.0", "style-loader": "4.0.0", - "stylelint": "16.14.1", + "stylelint": "16.23.0", "stylelint-config-sass-guidelines": "12.1.0", "terser-webpack-plugin": "5.3.14", "testing-library-selector": "0.3.1", diff --git a/yarn.lock b/yarn.lock index b89f00a6687..1841e4a110e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1713,19 +1713,19 @@ __metadata: languageName: node linkType: hard -"@csstools/css-parser-algorithms@npm:^3.0.1, @csstools/css-parser-algorithms@npm:^3.0.4": - version: 3.0.4 - resolution: "@csstools/css-parser-algorithms@npm:3.0.4" +"@csstools/css-parser-algorithms@npm:^3.0.1, @csstools/css-parser-algorithms@npm:^3.0.5": + version: 3.0.5 + resolution: "@csstools/css-parser-algorithms@npm:3.0.5" peerDependencies: - "@csstools/css-tokenizer": ^3.0.3 - checksum: 10/dfb6926218d9f8ba25d8b43ea46c03863c819481f8c55e4de4925780eaab9e6bcd6bead1d56b4ef82d09fcd9d69a7db2750fa9db08eece9470fd499dc76d0edb + "@csstools/css-tokenizer": ^3.0.4 + checksum: 10/e93083b5cb36a3c1e7a47ce10cf62961d05bd1e4c608bb3ee50186ff740157ab0ec16a3956f7b86251efd10703034d849693201eea858ae904848c68d2d46ada languageName: node linkType: hard -"@csstools/css-tokenizer@npm:^3.0.1, @csstools/css-tokenizer@npm:^3.0.3": - version: 3.0.3 - resolution: "@csstools/css-tokenizer@npm:3.0.3" - checksum: 10/6baa3160e426e1f177b8f10d54ec7a4a596090f65a05f16d7e9e4da049962a404eabc5f885f4867093702c259cd4080ac92a438326e22dea015201b3e71f5bbb +"@csstools/css-tokenizer@npm:^3.0.1, @csstools/css-tokenizer@npm:^3.0.4": + version: 3.0.4 + resolution: "@csstools/css-tokenizer@npm:3.0.4" + checksum: 10/eb6c84c086312f6bb8758dfe2c85addd7475b0927333c5e39a4d59fb210b9810f8c346972046f95e60a721329cffe98895abe451e51de753ad1ca7a8c24ec65f languageName: node linkType: hard @@ -1739,13 +1739,13 @@ __metadata: languageName: node linkType: hard -"@csstools/media-query-list-parser@npm:^4.0.2": - version: 4.0.2 - resolution: "@csstools/media-query-list-parser@npm:4.0.2" +"@csstools/media-query-list-parser@npm:^4.0.3": + version: 4.0.3 + resolution: "@csstools/media-query-list-parser@npm:4.0.3" peerDependencies: - "@csstools/css-parser-algorithms": ^3.0.4 - "@csstools/css-tokenizer": ^3.0.3 - checksum: 10/8aae6337d21255d34e4f6dc6df213566e35bb769fe131006ea4200b643773f3213f8ed0ab011cd85dbe3426766c408d0fe1d04d18e821add9ae7f29cda0a8b26 + "@csstools/css-parser-algorithms": ^3.0.5 + "@csstools/css-tokenizer": ^3.0.4 + checksum: 10/ac4e34c21a1c7fc8b788274f316c29ff2f07e7a08996d27b9beb06454666591be9946362c1b7c4d342558c278c8e7d0e35655043e47a9ffd94c3fdb292fbe020 languageName: node linkType: hard @@ -4805,12 +4805,10 @@ __metadata: languageName: node linkType: hard -"@keyv/serialize@npm:^1.0.2": - version: 1.0.2 - resolution: "@keyv/serialize@npm:1.0.2" - dependencies: - buffer: "npm:^6.0.3" - checksum: 10/6a42a5778a6b4542f6903ba7e6a17c5bd116441798d75c95fba9908c76c7606db527fad710b5c54abc6175e49b1bbaaafe3b836ad4b91e1af701394134f1d504 +"@keyv/serialize@npm:^1.1.0": + version: 1.1.0 + resolution: "@keyv/serialize@npm:1.1.0" + checksum: 10/73b88b69e07847b67d443efcd4949955aa7ac0f998f68e6cfe9053307c1ea3caeabe3f6f1c6a99b1b32bb9a6698c8a5c452f96246fe77d98f1cba0c2faae4616 languageName: node linkType: hard @@ -12558,13 +12556,13 @@ __metadata: languageName: node linkType: hard -"cacheable@npm:^1.8.7": - version: 1.8.7 - resolution: "cacheable@npm:1.8.7" +"cacheable@npm:^1.10.3": + version: 1.10.3 + resolution: "cacheable@npm:1.10.3" dependencies: - hookified: "npm:^1.6.0" - keyv: "npm:^5.2.3" - checksum: 10/dce96e947c5b879a58ce024fd2d08a4c44ee328e8406cd6243da2d0e3a17579d63108c80e9272f64190d2d2f00e26a307b3e3ff65b6665abc70956714442897b + hookified: "npm:^1.10.0" + keyv: "npm:^5.4.0" + checksum: 10/1afb8633e0a9c43b0e1a3db43532e2ebff7b7fa6f358925c2ba67abf396dd57c53861e0574bfb1099d6f1a12e9159a4e14f3f993377e8b6ddc32aba3c3dedb8f languageName: node linkType: hard @@ -14843,7 +14841,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4, debug@npm:^4.3.5, debug@npm:^4.3.6, debug@npm:^4.3.7, debug@npm:^4.4.0, debug@npm:^4.4.1": +"debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4, debug@npm:^4.3.5, debug@npm:^4.3.6, debug@npm:^4.4.0, debug@npm:^4.4.1": version: 4.4.1 resolution: "debug@npm:4.4.1" dependencies: @@ -17054,12 +17052,12 @@ __metadata: languageName: node linkType: hard -"file-entry-cache@npm:^10.0.5": - version: 10.0.5 - resolution: "file-entry-cache@npm:10.0.5" +"file-entry-cache@npm:^10.1.3": + version: 10.1.3 + resolution: "file-entry-cache@npm:10.1.3" dependencies: - flat-cache: "npm:^6.1.5" - checksum: 10/57439b39635e75aa900ccdaad9167b85a500a559dd8f0902509189849784e9255d43cd3d8307f918dbd19f48b02562b5e354bcf7bfa23afe3985485504eb7a5e + flat-cache: "npm:^6.1.12" + checksum: 10/dcf7d0b7ca76ebd124e6a5b7fdcab25d18b087da17c4979a680ccc0e88baf5f6727aae0f865d47188da793863f65d6119ce6eb4301e407ea3879d70e97b5c487 languageName: node linkType: hard @@ -17321,14 +17319,14 @@ __metadata: languageName: node linkType: hard -"flat-cache@npm:^6.1.5": - version: 6.1.5 - resolution: "flat-cache@npm:6.1.5" +"flat-cache@npm:^6.1.12": + version: 6.1.12 + resolution: "flat-cache@npm:6.1.12" dependencies: - cacheable: "npm:^1.8.7" - flatted: "npm:^3.3.2" - hookified: "npm:^1.6.0" - checksum: 10/c98d7635316ceee08b74c1dd3f05eda6a5f2f073688af927de68193ca992ab09eb9f0ee7d5fac40f53280844f11cf368b90dc43f73fee358165dc84e13828674 + cacheable: "npm:^1.10.3" + flatted: "npm:^3.3.3" + hookified: "npm:^1.10.0" + checksum: 10/053976363bf2de150c77b4b4c9702b4f7fdebf4fa2f60e5b042606847d4556681da3ee489ed02e9f23cabeafe771d0b3bdf8a3f7fdc1945b55d5e42222d6ffc6 languageName: node linkType: hard @@ -17341,10 +17339,10 @@ __metadata: languageName: node linkType: hard -"flatted@npm:^3.2.9, flatted@npm:^3.3.2": - version: 3.3.2 - resolution: "flatted@npm:3.3.2" - checksum: 10/ac3c159742e01d0e860a861164bcfd35bb567ccbebb8a0dd041e61cf3c64a435b917dd1e7ed1c380c2ebca85735fb16644485ec33665bc6aafc3b316aa1eed44 +"flatted@npm:^3.2.9, flatted@npm:^3.3.3": + version: 3.3.3 + resolution: "flatted@npm:3.3.3" + checksum: 10/8c96c02fbeadcf4e8ffd0fa24983241e27698b0781295622591fc13585e2f226609d95e422bcf2ef044146ffacb6b68b1f20871454eddf75ab3caa6ee5f4a1fe languageName: node linkType: hard @@ -18617,7 +18615,7 @@ __metadata: slate-react: "npm:0.22.10" smtp-tester: "npm:^2.1.0" style-loader: "npm:4.0.0" - stylelint: "npm:16.14.1" + stylelint: "npm:16.23.0" stylelint-config-sass-guidelines: "npm:12.1.0" swagger-ui-react: "npm:5.27.0" symbol-observable: "npm:4.0.0" @@ -18947,10 +18945,10 @@ __metadata: languageName: node linkType: hard -"hookified@npm:^1.6.0": - version: 1.7.0 - resolution: "hookified@npm:1.7.0" - checksum: 10/87fb8f2ae170f28b1e0b903f2b0b40fb2a92a0364baab7c54121db1c871a2e81c589d778b48e1a35462181b8840764caf6cac3744330e5687a05a4b4d2ad729d +"hookified@npm:^1.10.0": + version: 1.11.0 + resolution: "hookified@npm:1.11.0" + checksum: 10/a9cf288dcfaed9a96aaff9a725f74ea48690df45f7a42b73bef13faf6f5d8be6b3dd10e322b10fce2044e34982422a1efb25d462fc2c0c5d438151070ffee23f languageName: node linkType: hard @@ -19494,7 +19492,7 @@ __metadata: languageName: node linkType: hard -"ignore@npm:^7.0.0, ignore@npm:^7.0.3": +"ignore@npm:^7.0.0, ignore@npm:^7.0.5": version: 7.0.5 resolution: "ignore@npm:7.0.5" checksum: 10/f134b96a4de0af419196f52c529d5c6120c4456ff8a6b5a14ceaaa399f883e15d58d2ce651c9b69b9388491d4669dda47285d307e827de9304a53a1824801bc6 @@ -21727,12 +21725,12 @@ __metadata: languageName: node linkType: hard -"keyv@npm:^5.2.3": - version: 5.2.3 - resolution: "keyv@npm:5.2.3" +"keyv@npm:^5.4.0": + version: 5.4.0 + resolution: "keyv@npm:5.4.0" dependencies: - "@keyv/serialize": "npm:^1.0.2" - checksum: 10/47b4e9deb33e6a80e5ea79f3022ed3a14bc9fe553b7527ffff0a70b10c7a6c1a5d7e49b9bcfdbd8e8b9fb4632d68baa19d09e82628bcf853103e750e56d49a9e + "@keyv/serialize": "npm:^1.1.0" + checksum: 10/2d0f54505b1650238d3efb547332dc12e5e80c6a6b8e8654c3d4630461a69e9671fef87b5c3df11dc7bfc904cebfba5a74fe56edb9bc8ad994cecfe0dc155b97 languageName: node linkType: hard @@ -21764,10 +21762,10 @@ __metadata: languageName: node linkType: hard -"known-css-properties@npm:^0.35.0": - version: 0.35.0 - resolution: "known-css-properties@npm:0.35.0" - checksum: 10/a6f3f271a94913c72b29e59bd1e96836b0b5427c5dd9969f4673026cd39f7f441b5e8d0b704b0a830c43d745a5f7ca98d41d99961dc4c008ebf756545bada85c +"known-css-properties@npm:^0.37.0": + version: 0.37.0 + resolution: "known-css-properties@npm:0.37.0" + checksum: 10/9bddf2eb980f707567e3fbf8f2daaca27f5d544f4a49eab26d696ae33f54410f82f0a3032097b083d9e889e285586696c7b4324e6393103e7d71a46ca4438bfd languageName: node linkType: hard @@ -26019,7 +26017,7 @@ __metadata: languageName: node linkType: hard -"postcss@npm:8.5.6, postcss@npm:^8.4.33, postcss@npm:^8.4.40, postcss@npm:^8.5.1": +"postcss@npm:8.5.6, postcss@npm:^8.4.33, postcss@npm:^8.4.40, postcss@npm:^8.5.6": version: 8.5.6 resolution: "postcss@npm:8.5.6" dependencies: @@ -30321,13 +30319,13 @@ __metadata: languageName: node linkType: hard -"stylelint@npm:16.14.1, stylelint@npm:^16.8.2": - version: 16.14.1 - resolution: "stylelint@npm:16.14.1" +"stylelint@npm:16.23.0, stylelint@npm:^16.8.2": + version: 16.23.0 + resolution: "stylelint@npm:16.23.0" dependencies: - "@csstools/css-parser-algorithms": "npm:^3.0.4" - "@csstools/css-tokenizer": "npm:^3.0.3" - "@csstools/media-query-list-parser": "npm:^4.0.2" + "@csstools/css-parser-algorithms": "npm:^3.0.5" + "@csstools/css-tokenizer": "npm:^3.0.4" + "@csstools/media-query-list-parser": "npm:^4.0.3" "@csstools/selector-specificity": "npm:^5.0.0" "@dual-bundle/import-meta-resolve": "npm:^4.1.0" balanced-match: "npm:^2.0.0" @@ -30335,37 +30333,37 @@ __metadata: cosmiconfig: "npm:^9.0.0" css-functions-list: "npm:^3.2.3" css-tree: "npm:^3.1.0" - debug: "npm:^4.3.7" + debug: "npm:^4.4.1" fast-glob: "npm:^3.3.3" fastest-levenshtein: "npm:^1.0.16" - file-entry-cache: "npm:^10.0.5" + file-entry-cache: "npm:^10.1.3" global-modules: "npm:^2.0.0" globby: "npm:^11.1.0" globjoin: "npm:^0.1.4" html-tags: "npm:^3.3.1" - ignore: "npm:^7.0.3" + ignore: "npm:^7.0.5" imurmurhash: "npm:^0.1.4" is-plain-object: "npm:^5.0.0" - known-css-properties: "npm:^0.35.0" + known-css-properties: "npm:^0.37.0" mathml-tag-names: "npm:^2.1.3" meow: "npm:^13.2.0" micromatch: "npm:^4.0.8" normalize-path: "npm:^3.0.0" picocolors: "npm:^1.1.1" - postcss: "npm:^8.5.1" + postcss: "npm:^8.5.6" postcss-resolve-nested-selector: "npm:^0.1.6" postcss-safe-parser: "npm:^7.0.1" - postcss-selector-parser: "npm:^7.0.0" + postcss-selector-parser: "npm:^7.1.0" postcss-value-parser: "npm:^4.2.0" resolve-from: "npm:^5.0.0" string-width: "npm:^4.2.3" - supports-hyperlinks: "npm:^3.1.0" + supports-hyperlinks: "npm:^3.2.0" svg-tags: "npm:^1.0.0" table: "npm:^6.9.0" write-file-atomic: "npm:^5.0.1" bin: stylelint: bin/stylelint.mjs - checksum: 10/2c0c95e5ee77d946efdbe58573da75ff4b12bc89c220e78b69f0c2f94251f5e5a1096f5ab583f28f56ae14f298b1335e212d52fe066719d63b8a41d6cc8083b6 + checksum: 10/5a92aaaefbc22aba032d6a08df6d0cc8d72cc36e0cd1210e2ae2b8918b349a12d05d562daa1962b8fe0883835616081a0dd76e3d1440ab0bcda1fffa38cd3312 languageName: node linkType: hard @@ -30410,13 +30408,13 @@ __metadata: languageName: node linkType: hard -"supports-hyperlinks@npm:^3.1.0": - version: 3.1.0 - resolution: "supports-hyperlinks@npm:3.1.0" +"supports-hyperlinks@npm:^3.1.0, supports-hyperlinks@npm:^3.2.0": + version: 3.2.0 + resolution: "supports-hyperlinks@npm:3.2.0" dependencies: has-flag: "npm:^4.0.0" supports-color: "npm:^7.0.0" - checksum: 10/e893fb035ecd86e42c5225dc1cd24db56eb950ed77b2e8f59c7aaf2836b8b2ef276ffd11f0df88b0b12184832aa2333f875eefcb74d3c47ed2633b6b41d4be43 + checksum: 10/f7924de6049fc30bc808f98d3561318c1a4e3d55d786f9fede5e23dc5a7b0f625485bd1143135b496d521ccd0110463f2c077eb71a4ce0cf783b8b31f7909242 languageName: node linkType: hard From e7cfe0c0237394f6c87d01ad4a5e6f29954212fb Mon Sep 17 00:00:00 2001 From: Oscar Kilhed Date: Thu, 31 Jul 2025 11:49:07 +0200 Subject: [PATCH 039/114] Dashboards: Add feature toggle to use dashboard level time macros in scenes (#108670) * Add feature toggle to use dashboard level time macros in scenes * Add test * remove unregister time macro as it was removed from scenes --- .../src/types/featureToggles.gen.ts | 4 + pkg/services/dashboards/store_mock.go | 2 - pkg/services/featuremgmt/registry.go | 7 ++ pkg/services/featuremgmt/toggles_gen.csv | 1 + pkg/services/featuremgmt/toggles_gen.go | 4 + pkg/services/featuremgmt/toggles_gen.json | 13 ++++ public/app/app.ts | 8 +- .../scene/DashboardLevelTimeMacro.test.ts | 74 +++++++++++++++++++ .../scene/DashboardLevelTimeMacro.ts | 33 +++++++++ 9 files changed, 143 insertions(+), 3 deletions(-) create mode 100644 public/app/features/dashboard-scene/scene/DashboardLevelTimeMacro.test.ts create mode 100644 public/app/features/dashboard-scene/scene/DashboardLevelTimeMacro.ts diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index e45d19454a7..f8d6a827c20 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -1074,6 +1074,10 @@ export interface FeatureToggles { */ dashboardDsAdHocFiltering?: boolean; /** + * Supports __from and __to macros that always use the dashboard level time range + */ + dashboardLevelTimeMacros?: boolean; + /** * Starts Grafana in remote secondary mode pulling the latest state from the remote Alertmanager to avoid duplicate notifications. * @default false */ diff --git a/pkg/services/dashboards/store_mock.go b/pkg/services/dashboards/store_mock.go index 9185887aec9..74259652fbf 100644 --- a/pkg/services/dashboards/store_mock.go +++ b/pkg/services/dashboards/store_mock.go @@ -90,7 +90,6 @@ func (_m *FakeDashboardStore) CountInOrg(ctx context.Context, orgID int64, isFol return r0, r1 } - // DeleteDashboard provides a mock function with given fields: ctx, cmd func (_m *FakeDashboardStore) DeleteDashboard(ctx context.Context, cmd *DeleteDashboardCommand) error { ret := _m.Called(ctx, cmd) @@ -127,7 +126,6 @@ func (_m *FakeDashboardStore) DeleteDashboardsInFolders(ctx context.Context, req return r0 } - // FindDashboards provides a mock function with given fields: ctx, query func (_m *FakeDashboardStore) FindDashboards(ctx context.Context, query *FindPersistedDashboardsQuery) ([]DashboardSearchProjection, error) { ret := _m.Called(ctx, query) diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index 07bc3cdcc6e..661313c4d30 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -1857,6 +1857,13 @@ var ( Owner: grafanaDataProSquad, FrontendOnly: true, }, + { + Name: "dashboardLevelTimeMacros", + Description: "Supports __from and __to macros that always use the dashboard level time range", + Stage: FeatureStageExperimental, + Owner: grafanaDashboardsSquad, + FrontendOnly: true, + }, { Name: "alertmanagerRemoteSecondaryWithRemoteState", Description: "Starts Grafana in remote secondary mode pulling the latest state from the remote Alertmanager to avoid duplicate notifications.", diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index 67f899018d8..0b7c3e381db 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -240,5 +240,6 @@ alertingNotificationHistory,experimental,@grafana/alerting-squad,false,false,fal pluginAssetProvider,experimental,@grafana/plugins-platform-backend,false,true,false unifiedStorageSearchDualReaderEnabled,experimental,@grafana/search-and-storage,false,false,false dashboardDsAdHocFiltering,experimental,@grafana/datapro,false,false,true +dashboardLevelTimeMacros,experimental,@grafana/dashboards-squad,false,false,true alertmanagerRemoteSecondaryWithRemoteState,experimental,@grafana/alerting-squad,false,false,false adhocFiltersInTooltips,experimental,@grafana/datapro,false,false,true diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go index 236516152f5..9aee8de5873 100644 --- a/pkg/services/featuremgmt/toggles_gen.go +++ b/pkg/services/featuremgmt/toggles_gen.go @@ -971,6 +971,10 @@ const ( // Enables adhoc filtering support for the dashboard datasource FlagDashboardDsAdHocFiltering = "dashboardDsAdHocFiltering" + // FlagDashboardLevelTimeMacros + // Supports __from and __to macros that always use the dashboard level time range + FlagDashboardLevelTimeMacros = "dashboardLevelTimeMacros" + // FlagAlertmanagerRemoteSecondaryWithRemoteState // Starts Grafana in remote secondary mode pulling the latest state from the remote Alertmanager to avoid duplicate notifications. FlagAlertmanagerRemoteSecondaryWithRemoteState = "alertmanagerRemoteSecondaryWithRemoteState" diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json index 2ee75811760..e085dda6279 100644 --- a/pkg/services/featuremgmt/toggles_gen.json +++ b/pkg/services/featuremgmt/toggles_gen.json @@ -836,6 +836,19 @@ "frontend": true } }, + { + "metadata": { + "name": "dashboardLevelTimeMacros", + "resourceVersion": "1753435849295", + "creationTimestamp": "2025-07-25T09:30:49Z" + }, + "spec": { + "description": "Supports __from and __to macros that always use the dashboard level time range", + "stage": "experimental", + "codeowner": "@grafana/dashboards-squad", + "frontend": true + } + }, { "metadata": { "name": "dashboardNewLayouts", diff --git a/public/app/app.ts b/public/app/app.ts index 0e3ce53e238..5c48558c30c 100644 --- a/public/app/app.ts +++ b/public/app/app.ts @@ -50,7 +50,7 @@ import { setPanelRenderer, setPluginPage, } from '@grafana/runtime/internal'; -import { loadResources as loadScenesResources } from '@grafana/scenes'; +import { loadResources as loadScenesResources, sceneUtils } from '@grafana/scenes'; import config, { updateConfig } from 'app/core/config'; import { getStandardTransformers } from 'app/features/transformers/standardTransformers'; @@ -82,6 +82,7 @@ import { initAlerting } from './features/alerting/unified/initAlerting'; import { initAuthConfig } from './features/auth-config'; import { getTimeSrv } from './features/dashboard/services/TimeSrv'; import { EmbeddedDashboardLazy } from './features/dashboard-scene/embedding/EmbeddedDashboardLazy'; +import { DashboardLevelTimeMacro } from './features/dashboard-scene/scene/DashboardLevelTimeMacro'; import { initGrafanaLive } from './features/live'; import { PanelDataErrorView } from './features/panel/components/PanelDataErrorView'; import { PanelRenderer } from './features/panel/components/PanelRenderer'; @@ -284,6 +285,11 @@ export class GrafanaApp { initializeCrashDetection(); } + if (config.featureToggles.dashboardLevelTimeMacros) { + sceneUtils.registerVariableMacro('__from', DashboardLevelTimeMacro, true); + sceneUtils.registerVariableMacro('__to', DashboardLevelTimeMacro, true); + } + const root = createRoot(document.getElementById('reactRoot')!); root.render( createElement(AppWrapper, { diff --git a/public/app/features/dashboard-scene/scene/DashboardLevelTimeMacro.test.ts b/public/app/features/dashboard-scene/scene/DashboardLevelTimeMacro.test.ts new file mode 100644 index 00000000000..d55c4940f52 --- /dev/null +++ b/public/app/features/dashboard-scene/scene/DashboardLevelTimeMacro.test.ts @@ -0,0 +1,74 @@ +import { getPanelPlugin } from '@grafana/data/test'; +import { setPluginImportUtils } from '@grafana/runtime'; +import { SceneTimeRange, sceneGraph, sceneUtils, VizPanel } from '@grafana/scenes'; + +import { activateFullSceneTree } from '../utils/test-utils'; + +import { DashboardLevelTimeMacro } from './DashboardLevelTimeMacro'; +import { DashboardScene } from './DashboardScene'; +import { PanelTimeRange } from './PanelTimeRange'; +import { AutoGridItem } from './layout-auto-grid/AutoGridItem'; +import { AutoGridLayout } from './layout-auto-grid/AutoGridLayout'; +import { + AutoGridLayoutManager, + getAutoRowsTemplate, + getTemplateColumnsTemplate, +} from './layout-auto-grid/AutoGridLayoutManager'; + +jest.mock('@grafana/runtime', () => ({ + ...jest.requireActual('@grafana/runtime'), + getPluginLinkExtensions: jest.fn().mockReturnValue({ extensions: [] }), +})); + +setPluginImportUtils({ + importPanelPlugin: (id: string) => Promise.resolve(getPanelPlugin({})), + getPanelPluginFromCache: (id: string) => undefined, +}); + +describe('dashboardLevelTimeMacros', () => { + it('Can use use $__from and $__to', async () => { + const panel = new VizPanel({ + $timeRange: new PanelTimeRange({ timeShift: '1h' }), + title: 'Test Panel', + key: 'panel-1', + pluginId: 'timeseries', + }); + + const scene = new DashboardScene({ + $timeRange: new SceneTimeRange({ from: '2023-05-23T06:09:57.073Z', to: '2023-05-23T07:09:57.073Z' }), + body: new AutoGridLayoutManager({ + maxColumnCount: 12, + columnWidth: 100, + rowHeight: 100, + fillScreen: true, + layout: new AutoGridLayout({ + isDraggable: true, + templateColumns: getTemplateColumnsTemplate(12, 100), + autoRows: getAutoRowsTemplate(100, true), + children: [ + new AutoGridItem({ + body: panel, + }), + ], + }), + }), + }); + + activateFullSceneTree(scene); + + // Wait for the scene to be activated + await new Promise((resolve) => setTimeout(resolve, 100)); + + expect(sceneGraph.interpolate(scene, '$__from')).toBe('1684822197073'); // Dashboard level time range + expect(sceneGraph.interpolate(scene, '$__to')).toBe('1684825797073'); // Dashboard level time range + + expect(sceneGraph.interpolate(panel, '$__from')).toBe('1684818597073'); // Time shifted by 1h + expect(sceneGraph.interpolate(panel, '$__to')).toBe('1684822197073'); // Time shifted by 1h + + sceneUtils.registerVariableMacro('__from', DashboardLevelTimeMacro, true); + sceneUtils.registerVariableMacro('__to', DashboardLevelTimeMacro, true); + + expect(sceneGraph.interpolate(panel, '$__from')).toBe('1684822197073'); // Dashboard level time range even when panel is time shifted + expect(sceneGraph.interpolate(panel, '$__to')).toBe('1684825797073'); // Dashboard level time range even when panel is time shifted + }); +}); diff --git a/public/app/features/dashboard-scene/scene/DashboardLevelTimeMacro.ts b/public/app/features/dashboard-scene/scene/DashboardLevelTimeMacro.ts new file mode 100644 index 00000000000..e6d0fc032fb --- /dev/null +++ b/public/app/features/dashboard-scene/scene/DashboardLevelTimeMacro.ts @@ -0,0 +1,33 @@ +import { dateTimeFormat } from '@grafana/data'; +import { FormatVariable, sceneGraph, SceneObject } from '@grafana/scenes'; + +/** + * This macro is used to support the old __to and __from macros that always used the dashboard level time range. + **/ +export class DashboardLevelTimeMacro implements FormatVariable { + public state: { name: string; type: string }; + private _sceneObject: SceneObject; + + public constructor(name: string, sceneObject: SceneObject) { + this.state = { name: name, type: 'time_macro' }; + this._sceneObject = sceneObject.getRoot(); + } + + public getValue() { + const timeRange = sceneGraph.getTimeRange(this._sceneObject); + if (this.state.name === '__from') { + return timeRange.state.value.from.valueOf(); + } else { + return timeRange.state.value.to.valueOf(); + } + } + + public getValueText?(): string { + const timeRange = sceneGraph.getTimeRange(this._sceneObject); + if (this.state.name === '__from') { + return dateTimeFormat(timeRange.state.value.from, { timeZone: timeRange.getTimeZone() }); + } else { + return dateTimeFormat(timeRange.state.value.to, { timeZone: timeRange.getTimeZone() }); + } + } +} From 1ad042b4e09b265e38def434d0dcdefba4f45dba Mon Sep 17 00:00:00 2001 From: Alexander Akhmetov Date: Thu, 31 Jul 2025 11:56:15 +0200 Subject: [PATCH 040/114] Alerting: Mark Prometheus to Grafana conversion API as stable (#103499) Alerting: Make Prometheus conversion API stable --- pkg/services/ngalert/api/tooling/api.json | 580 ++++++++++++++ .../definitions/convert_prometheus_api.go | 28 +- pkg/services/ngalert/api/tooling/spec.json | 42 +- public/api-merged.json | 580 ++++++++++++++ public/openapi3.json | 718 ++++++++++++++++++ 5 files changed, 1920 insertions(+), 28 deletions(-) diff --git a/pkg/services/ngalert/api/tooling/api.json b/pkg/services/ngalert/api/tooling/api.json index bf2a859fd61..7eae1a55974 100644 --- a/pkg/services/ngalert/api/tooling/api.json +++ b/pkg/services/ngalert/api/tooling/api.json @@ -5523,6 +5523,586 @@ "version": "1.1.0" }, "paths": { + "/convert/api/prom/rules": { + "get": { + "operationId": "RouteConvertPrometheusCortexGetRules", + "produces": [ + "application/yaml" + ], + "responses": { + "200": { + "description": "PrometheusNamespace", + "schema": { + "$ref": "#/definitions/PrometheusNamespace" + } + }, + "403": { + "description": "ForbiddenError", + "schema": { + "$ref": "#/definitions/ForbiddenError" + } + }, + "404": { + "description": "NotFound", + "schema": { + "$ref": "#/definitions/NotFound" + } + } + }, + "summary": "Gets all Grafana-managed alert rules that were imported from Prometheus-compatible sources, grouped by namespace.", + "tags": [ + "convert_prometheus" + ] + }, + "post": { + "consumes": [ + "application/json", + "application/yaml" + ], + "operationId": "RouteConvertPrometheusCortexPostRuleGroups", + "produces": [ + "application/json" + ], + "responses": { + "202": { + "description": "ConvertPrometheusResponse", + "schema": { + "$ref": "#/definitions/ConvertPrometheusResponse" + } + }, + "403": { + "description": "ForbiddenError", + "schema": { + "$ref": "#/definitions/ForbiddenError" + } + } + }, + "summary": "Converts the submitted rule groups into Grafana-Managed Rules.", + "tags": [ + "convert_prometheus" + ] + } + }, + "/convert/api/prom/rules/{NamespaceTitle}": { + "delete": { + "operationId": "RouteConvertPrometheusCortexDeleteNamespace", + "parameters": [ + { + "in": "path", + "name": "NamespaceTitle", + "required": true, + "type": "string" + } + ], + "produces": [ + "application/json" + ], + "responses": { + "202": { + "description": "ConvertPrometheusResponse", + "schema": { + "$ref": "#/definitions/ConvertPrometheusResponse" + } + }, + "403": { + "description": "ForbiddenError", + "schema": { + "$ref": "#/definitions/ForbiddenError" + } + } + }, + "summary": "Deletes all rule groups that were imported from Prometheus-compatible sources within the specified namespace.", + "tags": [ + "convert_prometheus" + ] + }, + "get": { + "operationId": "RouteConvertPrometheusCortexGetNamespace", + "parameters": [ + { + "in": "path", + "name": "NamespaceTitle", + "required": true, + "type": "string" + } + ], + "produces": [ + "application/yaml" + ], + "responses": { + "200": { + "description": "PrometheusNamespace", + "schema": { + "$ref": "#/definitions/PrometheusNamespace" + } + }, + "403": { + "description": "ForbiddenError", + "schema": { + "$ref": "#/definitions/ForbiddenError" + } + }, + "404": { + "description": "NotFound", + "schema": { + "$ref": "#/definitions/NotFound" + } + } + }, + "summary": "Gets Grafana-managed alert rules that were imported from Prometheus-compatible sources for a specified namespace (folder).", + "tags": [ + "convert_prometheus" + ] + }, + "post": { + "consumes": [ + "application/yaml" + ], + "description": "If the group already exists and was not imported from a Prometheus-compatible source initially,\nit will not be replaced and an error will be returned.", + "operationId": "RouteConvertPrometheusCortexPostRuleGroup", + "parameters": [ + { + "in": "path", + "name": "NamespaceTitle", + "required": true, + "type": "string" + }, + { + "in": "header", + "name": "x-grafana-alerting-datasource-uid", + "type": "string" + }, + { + "in": "header", + "name": "x-grafana-alerting-recording-rules-paused", + "type": "boolean" + }, + { + "in": "header", + "name": "x-grafana-alerting-alert-rules-paused", + "type": "boolean" + }, + { + "in": "header", + "name": "x-grafana-alerting-target-datasource-uid", + "type": "string" + }, + { + "in": "header", + "name": "x-grafana-alerting-folder-uid", + "type": "string" + }, + { + "in": "header", + "name": "x-grafana-alerting-notification-receiver", + "type": "string" + }, + { + "in": "body", + "name": "Body", + "schema": { + "$ref": "#/definitions/PrometheusRuleGroup" + } + } + ], + "produces": [ + "application/json" + ], + "responses": { + "202": { + "description": "ConvertPrometheusResponse", + "schema": { + "$ref": "#/definitions/ConvertPrometheusResponse" + } + }, + "403": { + "description": "ForbiddenError", + "schema": { + "$ref": "#/definitions/ForbiddenError" + } + } + }, + "summary": "Converts a Prometheus rule group into a Grafana rule group and creates or updates it within the specified namespace.", + "tags": [ + "convert_prometheus" + ], + "x-raw-request": "true" + } + }, + "/convert/api/prom/rules/{NamespaceTitle}/{Group}": { + "delete": { + "operationId": "RouteConvertPrometheusCortexDeleteRuleGroup", + "parameters": [ + { + "in": "path", + "name": "NamespaceTitle", + "required": true, + "type": "string" + }, + { + "in": "path", + "name": "Group", + "required": true, + "type": "string" + } + ], + "produces": [ + "application/json" + ], + "responses": { + "202": { + "description": "ConvertPrometheusResponse", + "schema": { + "$ref": "#/definitions/ConvertPrometheusResponse" + } + }, + "403": { + "description": "ForbiddenError", + "schema": { + "$ref": "#/definitions/ForbiddenError" + } + } + }, + "summary": "Deletes a specific rule group if it was imported from a Prometheus-compatible source.", + "tags": [ + "convert_prometheus" + ] + }, + "get": { + "operationId": "RouteConvertPrometheusCortexGetRuleGroup", + "parameters": [ + { + "in": "path", + "name": "NamespaceTitle", + "required": true, + "type": "string" + }, + { + "in": "path", + "name": "Group", + "required": true, + "type": "string" + } + ], + "produces": [ + "application/yaml" + ], + "responses": { + "200": { + "description": "PrometheusRuleGroup", + "schema": { + "$ref": "#/definitions/PrometheusRuleGroup" + } + }, + "403": { + "description": "ForbiddenError", + "schema": { + "$ref": "#/definitions/ForbiddenError" + } + }, + "404": { + "description": "NotFound", + "schema": { + "$ref": "#/definitions/NotFound" + } + } + }, + "summary": "Gets a single rule group in Prometheus-compatible format if it was imported from a Prometheus-compatible source.", + "tags": [ + "convert_prometheus" + ] + } + }, + "/convert/prometheus/config/v1/rules": { + "get": { + "operationId": "RouteConvertPrometheusGetRules", + "produces": [ + "application/yaml" + ], + "responses": { + "200": { + "description": "PrometheusNamespace", + "schema": { + "$ref": "#/definitions/PrometheusNamespace" + } + }, + "403": { + "description": "ForbiddenError", + "schema": { + "$ref": "#/definitions/ForbiddenError" + } + }, + "404": { + "description": "NotFound", + "schema": { + "$ref": "#/definitions/NotFound" + } + } + }, + "summary": "Gets all Grafana-managed alert rules that were imported from Prometheus-compatible sources, grouped by namespace.", + "tags": [ + "convert_prometheus" + ] + }, + "post": { + "consumes": [ + "application/json", + "application/yaml" + ], + "operationId": "RouteConvertPrometheusPostRuleGroups", + "produces": [ + "application/json" + ], + "responses": { + "202": { + "description": "ConvertPrometheusResponse", + "schema": { + "$ref": "#/definitions/ConvertPrometheusResponse" + } + }, + "403": { + "description": "ForbiddenError", + "schema": { + "$ref": "#/definitions/ForbiddenError" + } + } + }, + "summary": "Converts the submitted rule groups into Grafana-Managed Rules.", + "tags": [ + "convert_prometheus" + ] + } + }, + "/convert/prometheus/config/v1/rules/{NamespaceTitle}": { + "delete": { + "operationId": "RouteConvertPrometheusDeleteNamespace", + "parameters": [ + { + "in": "path", + "name": "NamespaceTitle", + "required": true, + "type": "string" + } + ], + "produces": [ + "application/json" + ], + "responses": { + "202": { + "description": "ConvertPrometheusResponse", + "schema": { + "$ref": "#/definitions/ConvertPrometheusResponse" + } + }, + "403": { + "description": "ForbiddenError", + "schema": { + "$ref": "#/definitions/ForbiddenError" + } + } + }, + "summary": "Deletes all rule groups that were imported from Prometheus-compatible sources within the specified namespace.", + "tags": [ + "convert_prometheus" + ] + }, + "get": { + "operationId": "RouteConvertPrometheusGetNamespace", + "parameters": [ + { + "in": "path", + "name": "NamespaceTitle", + "required": true, + "type": "string" + } + ], + "produces": [ + "application/yaml" + ], + "responses": { + "200": { + "description": "PrometheusNamespace", + "schema": { + "$ref": "#/definitions/PrometheusNamespace" + } + }, + "403": { + "description": "ForbiddenError", + "schema": { + "$ref": "#/definitions/ForbiddenError" + } + }, + "404": { + "description": "NotFound", + "schema": { + "$ref": "#/definitions/NotFound" + } + } + }, + "summary": "Gets Grafana-managed alert rules that were imported from Prometheus-compatible sources for a specified namespace (folder).", + "tags": [ + "convert_prometheus" + ] + }, + "post": { + "consumes": [ + "application/yaml" + ], + "description": "If the group already exists and was not imported from a Prometheus-compatible source initially,\nit will not be replaced and an error will be returned.", + "operationId": "RouteConvertPrometheusPostRuleGroup", + "parameters": [ + { + "in": "path", + "name": "NamespaceTitle", + "required": true, + "type": "string" + }, + { + "in": "header", + "name": "x-grafana-alerting-datasource-uid", + "type": "string" + }, + { + "in": "header", + "name": "x-grafana-alerting-recording-rules-paused", + "type": "boolean" + }, + { + "in": "header", + "name": "x-grafana-alerting-alert-rules-paused", + "type": "boolean" + }, + { + "in": "header", + "name": "x-grafana-alerting-target-datasource-uid", + "type": "string" + }, + { + "in": "header", + "name": "x-grafana-alerting-folder-uid", + "type": "string" + }, + { + "in": "header", + "name": "x-grafana-alerting-notification-receiver", + "type": "string" + }, + { + "in": "body", + "name": "Body", + "schema": { + "$ref": "#/definitions/PrometheusRuleGroup" + } + } + ], + "produces": [ + "application/json" + ], + "responses": { + "202": { + "description": "ConvertPrometheusResponse", + "schema": { + "$ref": "#/definitions/ConvertPrometheusResponse" + } + }, + "403": { + "description": "ForbiddenError", + "schema": { + "$ref": "#/definitions/ForbiddenError" + } + } + }, + "summary": "Converts a Prometheus rule group into a Grafana rule group and creates or updates it within the specified namespace.", + "tags": [ + "convert_prometheus" + ], + "x-raw-request": "true" + } + }, + "/convert/prometheus/config/v1/rules/{NamespaceTitle}/{Group}": { + "delete": { + "operationId": "RouteConvertPrometheusDeleteRuleGroup", + "parameters": [ + { + "in": "path", + "name": "NamespaceTitle", + "required": true, + "type": "string" + }, + { + "in": "path", + "name": "Group", + "required": true, + "type": "string" + } + ], + "produces": [ + "application/json" + ], + "responses": { + "202": { + "description": "ConvertPrometheusResponse", + "schema": { + "$ref": "#/definitions/ConvertPrometheusResponse" + } + }, + "403": { + "description": "ForbiddenError", + "schema": { + "$ref": "#/definitions/ForbiddenError" + } + } + }, + "summary": "Deletes a specific rule group if it was imported from a Prometheus-compatible source.", + "tags": [ + "convert_prometheus" + ] + }, + "get": { + "operationId": "RouteConvertPrometheusGetRuleGroup", + "parameters": [ + { + "in": "path", + "name": "NamespaceTitle", + "required": true, + "type": "string" + }, + { + "in": "path", + "name": "Group", + "required": true, + "type": "string" + } + ], + "produces": [ + "application/yaml" + ], + "responses": { + "200": { + "description": "PrometheusRuleGroup", + "schema": { + "$ref": "#/definitions/PrometheusRuleGroup" + } + }, + "403": { + "description": "ForbiddenError", + "schema": { + "$ref": "#/definitions/ForbiddenError" + } + }, + "404": { + "description": "NotFound", + "schema": { + "$ref": "#/definitions/NotFound" + } + } + }, + "summary": "Gets a single rule group in Prometheus-compatible format if it was imported from a Prometheus-compatible source.", + "tags": [ + "convert_prometheus" + ] + } + }, "/v1/provisioning/alert-rules": { "get": { "operationId": "RouteGetAlertRules", diff --git a/pkg/services/ngalert/api/tooling/definitions/convert_prometheus_api.go b/pkg/services/ngalert/api/tooling/definitions/convert_prometheus_api.go index 98b17d19ed3..b3c2db6fe7a 100644 --- a/pkg/services/ngalert/api/tooling/definitions/convert_prometheus_api.go +++ b/pkg/services/ngalert/api/tooling/definitions/convert_prometheus_api.go @@ -5,7 +5,7 @@ import ( ) // Route for mimirtool -// swagger:route GET /convert/prometheus/config/v1/rules convert_prometheus RouteConvertPrometheusGetRules +// swagger:route GET /convert/prometheus/config/v1/rules convert_prometheus stable RouteConvertPrometheusGetRules // // Gets all Grafana-managed alert rules that were imported from Prometheus-compatible sources, grouped by namespace. // @@ -18,7 +18,7 @@ import ( // 404: NotFound // Route for cortextool -// swagger:route GET /convert/api/prom/rules convert_prometheus RouteConvertPrometheusCortexGetRules +// swagger:route GET /convert/api/prom/rules convert_prometheus stable RouteConvertPrometheusCortexGetRules // // Gets all Grafana-managed alert rules that were imported from Prometheus-compatible sources, grouped by namespace. // @@ -31,7 +31,7 @@ import ( // 404: NotFound // Route for mimirtool -// swagger:route GET /convert/prometheus/config/v1/rules/{NamespaceTitle} convert_prometheus RouteConvertPrometheusGetNamespace +// swagger:route GET /convert/prometheus/config/v1/rules/{NamespaceTitle} convert_prometheus stable RouteConvertPrometheusGetNamespace // // Gets Grafana-managed alert rules that were imported from Prometheus-compatible sources for a specified namespace (folder). // @@ -44,7 +44,7 @@ import ( // 404: NotFound // Route for cortextool -// swagger:route GET /convert/api/prom/rules/{NamespaceTitle} convert_prometheus RouteConvertPrometheusCortexGetNamespace +// swagger:route GET /convert/api/prom/rules/{NamespaceTitle} convert_prometheus stable RouteConvertPrometheusCortexGetNamespace // // Gets Grafana-managed alert rules that were imported from Prometheus-compatible sources for a specified namespace (folder). // @@ -57,7 +57,7 @@ import ( // 404: NotFound // Route for mimirtool -// swagger:route GET /convert/prometheus/config/v1/rules/{NamespaceTitle}/{Group} convert_prometheus RouteConvertPrometheusGetRuleGroup +// swagger:route GET /convert/prometheus/config/v1/rules/{NamespaceTitle}/{Group} convert_prometheus stable RouteConvertPrometheusGetRuleGroup // // Gets a single rule group in Prometheus-compatible format if it was imported from a Prometheus-compatible source. // @@ -70,7 +70,7 @@ import ( // 404: NotFound // Route for cortextool -// swagger:route GET /convert/api/prom/rules/{NamespaceTitle}/{Group} convert_prometheus RouteConvertPrometheusCortexGetRuleGroup +// swagger:route GET /convert/api/prom/rules/{NamespaceTitle}/{Group} convert_prometheus stable RouteConvertPrometheusCortexGetRuleGroup // // Gets a single rule group in Prometheus-compatible format if it was imported from a Prometheus-compatible source. // @@ -82,7 +82,7 @@ import ( // 403: ForbiddenError // 404: NotFound -// swagger:route POST /convert/prometheus/config/v1/rules convert_prometheus RouteConvertPrometheusPostRuleGroups +// swagger:route POST /convert/prometheus/config/v1/rules convert_prometheus stable RouteConvertPrometheusPostRuleGroups // // Converts the submitted rule groups into Grafana-Managed Rules. // @@ -97,7 +97,7 @@ import ( // 202: ConvertPrometheusResponse // 403: ForbiddenError -// swagger:route POST /convert/api/prom/rules convert_prometheus RouteConvertPrometheusCortexPostRuleGroups +// swagger:route POST /convert/api/prom/rules convert_prometheus stable RouteConvertPrometheusCortexPostRuleGroups // // Converts the submitted rule groups into Grafana-Managed Rules. // @@ -113,7 +113,7 @@ import ( // 403: ForbiddenError // Route for mimirtool -// swagger:route POST /convert/prometheus/config/v1/rules/{NamespaceTitle} convert_prometheus RouteConvertPrometheusPostRuleGroup +// swagger:route POST /convert/prometheus/config/v1/rules/{NamespaceTitle} convert_prometheus stable RouteConvertPrometheusPostRuleGroup // // Converts a Prometheus rule group into a Grafana rule group and creates or updates it within the specified namespace. // If the group already exists and was not imported from a Prometheus-compatible source initially, @@ -133,7 +133,7 @@ import ( // x-raw-request: true // Route for cortextool -// swagger:route POST /convert/api/prom/rules/{NamespaceTitle} convert_prometheus RouteConvertPrometheusCortexPostRuleGroup +// swagger:route POST /convert/api/prom/rules/{NamespaceTitle} convert_prometheus stable RouteConvertPrometheusCortexPostRuleGroup // // Converts a Prometheus rule group into a Grafana rule group and creates or updates it within the specified namespace. // If the group already exists and was not imported from a Prometheus-compatible source initially, @@ -153,7 +153,7 @@ import ( // x-raw-request: true // Route for mimirtool -// swagger:route DELETE /convert/prometheus/config/v1/rules/{NamespaceTitle} convert_prometheus RouteConvertPrometheusDeleteNamespace +// swagger:route DELETE /convert/prometheus/config/v1/rules/{NamespaceTitle} convert_prometheus stable RouteConvertPrometheusDeleteNamespace // // Deletes all rule groups that were imported from Prometheus-compatible sources within the specified namespace. // @@ -165,7 +165,7 @@ import ( // 403: ForbiddenError // Route for cortextool -// swagger:route DELETE /convert/api/prom/rules/{NamespaceTitle} convert_prometheus RouteConvertPrometheusCortexDeleteNamespace +// swagger:route DELETE /convert/api/prom/rules/{NamespaceTitle} convert_prometheus stable RouteConvertPrometheusCortexDeleteNamespace // // Deletes all rule groups that were imported from Prometheus-compatible sources within the specified namespace. // @@ -177,7 +177,7 @@ import ( // 403: ForbiddenError // Route for mimirtool -// swagger:route DELETE /convert/prometheus/config/v1/rules/{NamespaceTitle}/{Group} convert_prometheus RouteConvertPrometheusDeleteRuleGroup +// swagger:route DELETE /convert/prometheus/config/v1/rules/{NamespaceTitle}/{Group} convert_prometheus stable RouteConvertPrometheusDeleteRuleGroup // // Deletes a specific rule group if it was imported from a Prometheus-compatible source. // @@ -189,7 +189,7 @@ import ( // 403: ForbiddenError // Route for cortextool -// swagger:route DELETE /convert/api/prom/rules/{NamespaceTitle}/{Group} convert_prometheus RouteConvertPrometheusCortexDeleteRuleGroup +// swagger:route DELETE /convert/api/prom/rules/{NamespaceTitle}/{Group} convert_prometheus stable RouteConvertPrometheusCortexDeleteRuleGroup // // Deletes a specific rule group if it was imported from a Prometheus-compatible source. // diff --git a/pkg/services/ngalert/api/tooling/spec.json b/pkg/services/ngalert/api/tooling/spec.json index 1b2744b976d..0617865b8d2 100644 --- a/pkg/services/ngalert/api/tooling/spec.json +++ b/pkg/services/ngalert/api/tooling/spec.json @@ -1076,7 +1076,8 @@ "application/yaml" ], "tags": [ - "convert_prometheus" + "convert_prometheus", + "stable" ], "summary": "Gets all Grafana-managed alert rules that were imported from Prometheus-compatible sources, grouped by namespace.", "operationId": "RouteConvertPrometheusCortexGetRules", @@ -1110,7 +1111,8 @@ "application/json" ], "tags": [ - "convert_prometheus" + "convert_prometheus", + "stable" ], "summary": "Converts the submitted rule groups into Grafana-Managed Rules.", "operationId": "RouteConvertPrometheusCortexPostRuleGroups", @@ -1136,7 +1138,8 @@ "application/yaml" ], "tags": [ - "convert_prometheus" + "convert_prometheus", + "stable" ], "summary": "Gets Grafana-managed alert rules that were imported from Prometheus-compatible sources for a specified namespace (folder).", "operationId": "RouteConvertPrometheusCortexGetNamespace", @@ -1178,7 +1181,8 @@ "application/json" ], "tags": [ - "convert_prometheus" + "convert_prometheus", + "stable" ], "summary": "Converts a Prometheus rule group into a Grafana rule group and creates or updates it within the specified namespace.", "operationId": "RouteConvertPrometheusCortexPostRuleGroup", @@ -1248,7 +1252,8 @@ "application/json" ], "tags": [ - "convert_prometheus" + "convert_prometheus", + "stable" ], "summary": "Deletes all rule groups that were imported from Prometheus-compatible sources within the specified namespace.", "operationId": "RouteConvertPrometheusCortexDeleteNamespace", @@ -1282,7 +1287,8 @@ "application/yaml" ], "tags": [ - "convert_prometheus" + "convert_prometheus", + "stable" ], "summary": "Gets a single rule group in Prometheus-compatible format if it was imported from a Prometheus-compatible source.", "operationId": "RouteConvertPrometheusCortexGetRuleGroup", @@ -1326,7 +1332,8 @@ "application/json" ], "tags": [ - "convert_prometheus" + "convert_prometheus", + "stable" ], "summary": "Deletes a specific rule group if it was imported from a Prometheus-compatible source.", "operationId": "RouteConvertPrometheusCortexDeleteRuleGroup", @@ -1483,7 +1490,8 @@ "application/yaml" ], "tags": [ - "convert_prometheus" + "convert_prometheus", + "stable" ], "summary": "Gets all Grafana-managed alert rules that were imported from Prometheus-compatible sources, grouped by namespace.", "operationId": "RouteConvertPrometheusGetRules", @@ -1517,7 +1525,8 @@ "application/json" ], "tags": [ - "convert_prometheus" + "convert_prometheus", + "stable" ], "summary": "Converts the submitted rule groups into Grafana-Managed Rules.", "operationId": "RouteConvertPrometheusPostRuleGroups", @@ -1543,7 +1552,8 @@ "application/yaml" ], "tags": [ - "convert_prometheus" + "convert_prometheus", + "stable" ], "summary": "Gets Grafana-managed alert rules that were imported from Prometheus-compatible sources for a specified namespace (folder).", "operationId": "RouteConvertPrometheusGetNamespace", @@ -1585,7 +1595,8 @@ "application/json" ], "tags": [ - "convert_prometheus" + "convert_prometheus", + "stable" ], "summary": "Converts a Prometheus rule group into a Grafana rule group and creates or updates it within the specified namespace.", "operationId": "RouteConvertPrometheusPostRuleGroup", @@ -1655,7 +1666,8 @@ "application/json" ], "tags": [ - "convert_prometheus" + "convert_prometheus", + "stable" ], "summary": "Deletes all rule groups that were imported from Prometheus-compatible sources within the specified namespace.", "operationId": "RouteConvertPrometheusDeleteNamespace", @@ -1689,7 +1701,8 @@ "application/yaml" ], "tags": [ - "convert_prometheus" + "convert_prometheus", + "stable" ], "summary": "Gets a single rule group in Prometheus-compatible format if it was imported from a Prometheus-compatible source.", "operationId": "RouteConvertPrometheusGetRuleGroup", @@ -1733,7 +1746,8 @@ "application/json" ], "tags": [ - "convert_prometheus" + "convert_prometheus", + "stable" ], "summary": "Deletes a specific rule group if it was imported from a Prometheus-compatible source.", "operationId": "RouteConvertPrometheusDeleteRuleGroup", diff --git a/public/api-merged.json b/public/api-merged.json index 2f2eed4fca5..07e2a9726be 100644 --- a/public/api-merged.json +++ b/public/api-merged.json @@ -2671,6 +2671,586 @@ } } }, + "/convert/api/prom/rules": { + "get": { + "produces": [ + "application/yaml" + ], + "tags": [ + "convert_prometheus" + ], + "summary": "Gets all Grafana-managed alert rules that were imported from Prometheus-compatible sources, grouped by namespace.", + "operationId": "RouteConvertPrometheusCortexGetRules", + "responses": { + "200": { + "description": "PrometheusNamespace", + "schema": { + "$ref": "#/definitions/PrometheusNamespace" + } + }, + "403": { + "description": "ForbiddenError", + "schema": { + "$ref": "#/definitions/ForbiddenError" + } + }, + "404": { + "description": "NotFound", + "schema": { + "$ref": "#/definitions/NotFound" + } + } + } + }, + "post": { + "consumes": [ + "application/json", + "application/yaml" + ], + "produces": [ + "application/json" + ], + "tags": [ + "convert_prometheus" + ], + "summary": "Converts the submitted rule groups into Grafana-Managed Rules.", + "operationId": "RouteConvertPrometheusCortexPostRuleGroups", + "responses": { + "202": { + "description": "ConvertPrometheusResponse", + "schema": { + "$ref": "#/definitions/ConvertPrometheusResponse" + } + }, + "403": { + "description": "ForbiddenError", + "schema": { + "$ref": "#/definitions/ForbiddenError" + } + } + } + } + }, + "/convert/api/prom/rules/{NamespaceTitle}": { + "get": { + "produces": [ + "application/yaml" + ], + "tags": [ + "convert_prometheus" + ], + "summary": "Gets Grafana-managed alert rules that were imported from Prometheus-compatible sources for a specified namespace (folder).", + "operationId": "RouteConvertPrometheusCortexGetNamespace", + "parameters": [ + { + "type": "string", + "name": "NamespaceTitle", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "PrometheusNamespace", + "schema": { + "$ref": "#/definitions/PrometheusNamespace" + } + }, + "403": { + "description": "ForbiddenError", + "schema": { + "$ref": "#/definitions/ForbiddenError" + } + }, + "404": { + "description": "NotFound", + "schema": { + "$ref": "#/definitions/NotFound" + } + } + } + }, + "post": { + "description": "If the group already exists and was not imported from a Prometheus-compatible source initially,\nit will not be replaced and an error will be returned.", + "consumes": [ + "application/yaml" + ], + "produces": [ + "application/json" + ], + "tags": [ + "convert_prometheus" + ], + "summary": "Converts a Prometheus rule group into a Grafana rule group and creates or updates it within the specified namespace.", + "operationId": "RouteConvertPrometheusCortexPostRuleGroup", + "parameters": [ + { + "type": "string", + "name": "NamespaceTitle", + "in": "path", + "required": true + }, + { + "type": "string", + "name": "x-grafana-alerting-datasource-uid", + "in": "header" + }, + { + "type": "boolean", + "name": "x-grafana-alerting-recording-rules-paused", + "in": "header" + }, + { + "type": "boolean", + "name": "x-grafana-alerting-alert-rules-paused", + "in": "header" + }, + { + "type": "string", + "name": "x-grafana-alerting-target-datasource-uid", + "in": "header" + }, + { + "type": "string", + "name": "x-grafana-alerting-folder-uid", + "in": "header" + }, + { + "type": "string", + "name": "x-grafana-alerting-notification-receiver", + "in": "header" + }, + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/PrometheusRuleGroup" + } + } + ], + "responses": { + "202": { + "description": "ConvertPrometheusResponse", + "schema": { + "$ref": "#/definitions/ConvertPrometheusResponse" + } + }, + "403": { + "description": "ForbiddenError", + "schema": { + "$ref": "#/definitions/ForbiddenError" + } + } + }, + "x-raw-request": "true" + }, + "delete": { + "produces": [ + "application/json" + ], + "tags": [ + "convert_prometheus" + ], + "summary": "Deletes all rule groups that were imported from Prometheus-compatible sources within the specified namespace.", + "operationId": "RouteConvertPrometheusCortexDeleteNamespace", + "parameters": [ + { + "type": "string", + "name": "NamespaceTitle", + "in": "path", + "required": true + } + ], + "responses": { + "202": { + "description": "ConvertPrometheusResponse", + "schema": { + "$ref": "#/definitions/ConvertPrometheusResponse" + } + }, + "403": { + "description": "ForbiddenError", + "schema": { + "$ref": "#/definitions/ForbiddenError" + } + } + } + } + }, + "/convert/api/prom/rules/{NamespaceTitle}/{Group}": { + "get": { + "produces": [ + "application/yaml" + ], + "tags": [ + "convert_prometheus" + ], + "summary": "Gets a single rule group in Prometheus-compatible format if it was imported from a Prometheus-compatible source.", + "operationId": "RouteConvertPrometheusCortexGetRuleGroup", + "parameters": [ + { + "type": "string", + "name": "NamespaceTitle", + "in": "path", + "required": true + }, + { + "type": "string", + "name": "Group", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "PrometheusRuleGroup", + "schema": { + "$ref": "#/definitions/PrometheusRuleGroup" + } + }, + "403": { + "description": "ForbiddenError", + "schema": { + "$ref": "#/definitions/ForbiddenError" + } + }, + "404": { + "description": "NotFound", + "schema": { + "$ref": "#/definitions/NotFound" + } + } + } + }, + "delete": { + "produces": [ + "application/json" + ], + "tags": [ + "convert_prometheus" + ], + "summary": "Deletes a specific rule group if it was imported from a Prometheus-compatible source.", + "operationId": "RouteConvertPrometheusCortexDeleteRuleGroup", + "parameters": [ + { + "type": "string", + "name": "NamespaceTitle", + "in": "path", + "required": true + }, + { + "type": "string", + "name": "Group", + "in": "path", + "required": true + } + ], + "responses": { + "202": { + "description": "ConvertPrometheusResponse", + "schema": { + "$ref": "#/definitions/ConvertPrometheusResponse" + } + }, + "403": { + "description": "ForbiddenError", + "schema": { + "$ref": "#/definitions/ForbiddenError" + } + } + } + } + }, + "/convert/prometheus/config/v1/rules": { + "get": { + "produces": [ + "application/yaml" + ], + "tags": [ + "convert_prometheus" + ], + "summary": "Gets all Grafana-managed alert rules that were imported from Prometheus-compatible sources, grouped by namespace.", + "operationId": "RouteConvertPrometheusGetRules", + "responses": { + "200": { + "description": "PrometheusNamespace", + "schema": { + "$ref": "#/definitions/PrometheusNamespace" + } + }, + "403": { + "description": "ForbiddenError", + "schema": { + "$ref": "#/definitions/ForbiddenError" + } + }, + "404": { + "description": "NotFound", + "schema": { + "$ref": "#/definitions/NotFound" + } + } + } + }, + "post": { + "consumes": [ + "application/json", + "application/yaml" + ], + "produces": [ + "application/json" + ], + "tags": [ + "convert_prometheus" + ], + "summary": "Converts the submitted rule groups into Grafana-Managed Rules.", + "operationId": "RouteConvertPrometheusPostRuleGroups", + "responses": { + "202": { + "description": "ConvertPrometheusResponse", + "schema": { + "$ref": "#/definitions/ConvertPrometheusResponse" + } + }, + "403": { + "description": "ForbiddenError", + "schema": { + "$ref": "#/definitions/ForbiddenError" + } + } + } + } + }, + "/convert/prometheus/config/v1/rules/{NamespaceTitle}": { + "get": { + "produces": [ + "application/yaml" + ], + "tags": [ + "convert_prometheus" + ], + "summary": "Gets Grafana-managed alert rules that were imported from Prometheus-compatible sources for a specified namespace (folder).", + "operationId": "RouteConvertPrometheusGetNamespace", + "parameters": [ + { + "type": "string", + "name": "NamespaceTitle", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "PrometheusNamespace", + "schema": { + "$ref": "#/definitions/PrometheusNamespace" + } + }, + "403": { + "description": "ForbiddenError", + "schema": { + "$ref": "#/definitions/ForbiddenError" + } + }, + "404": { + "description": "NotFound", + "schema": { + "$ref": "#/definitions/NotFound" + } + } + } + }, + "post": { + "description": "If the group already exists and was not imported from a Prometheus-compatible source initially,\nit will not be replaced and an error will be returned.", + "consumes": [ + "application/yaml" + ], + "produces": [ + "application/json" + ], + "tags": [ + "convert_prometheus" + ], + "summary": "Converts a Prometheus rule group into a Grafana rule group and creates or updates it within the specified namespace.", + "operationId": "RouteConvertPrometheusPostRuleGroup", + "parameters": [ + { + "type": "string", + "name": "NamespaceTitle", + "in": "path", + "required": true + }, + { + "type": "string", + "name": "x-grafana-alerting-datasource-uid", + "in": "header" + }, + { + "type": "boolean", + "name": "x-grafana-alerting-recording-rules-paused", + "in": "header" + }, + { + "type": "boolean", + "name": "x-grafana-alerting-alert-rules-paused", + "in": "header" + }, + { + "type": "string", + "name": "x-grafana-alerting-target-datasource-uid", + "in": "header" + }, + { + "type": "string", + "name": "x-grafana-alerting-folder-uid", + "in": "header" + }, + { + "type": "string", + "name": "x-grafana-alerting-notification-receiver", + "in": "header" + }, + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/PrometheusRuleGroup" + } + } + ], + "responses": { + "202": { + "description": "ConvertPrometheusResponse", + "schema": { + "$ref": "#/definitions/ConvertPrometheusResponse" + } + }, + "403": { + "description": "ForbiddenError", + "schema": { + "$ref": "#/definitions/ForbiddenError" + } + } + }, + "x-raw-request": "true" + }, + "delete": { + "produces": [ + "application/json" + ], + "tags": [ + "convert_prometheus" + ], + "summary": "Deletes all rule groups that were imported from Prometheus-compatible sources within the specified namespace.", + "operationId": "RouteConvertPrometheusDeleteNamespace", + "parameters": [ + { + "type": "string", + "name": "NamespaceTitle", + "in": "path", + "required": true + } + ], + "responses": { + "202": { + "description": "ConvertPrometheusResponse", + "schema": { + "$ref": "#/definitions/ConvertPrometheusResponse" + } + }, + "403": { + "description": "ForbiddenError", + "schema": { + "$ref": "#/definitions/ForbiddenError" + } + } + } + } + }, + "/convert/prometheus/config/v1/rules/{NamespaceTitle}/{Group}": { + "get": { + "produces": [ + "application/yaml" + ], + "tags": [ + "convert_prometheus" + ], + "summary": "Gets a single rule group in Prometheus-compatible format if it was imported from a Prometheus-compatible source.", + "operationId": "RouteConvertPrometheusGetRuleGroup", + "parameters": [ + { + "type": "string", + "name": "NamespaceTitle", + "in": "path", + "required": true + }, + { + "type": "string", + "name": "Group", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "PrometheusRuleGroup", + "schema": { + "$ref": "#/definitions/PrometheusRuleGroup" + } + }, + "403": { + "description": "ForbiddenError", + "schema": { + "$ref": "#/definitions/ForbiddenError" + } + }, + "404": { + "description": "NotFound", + "schema": { + "$ref": "#/definitions/NotFound" + } + } + } + }, + "delete": { + "produces": [ + "application/json" + ], + "tags": [ + "convert_prometheus" + ], + "summary": "Deletes a specific rule group if it was imported from a Prometheus-compatible source.", + "operationId": "RouteConvertPrometheusDeleteRuleGroup", + "parameters": [ + { + "type": "string", + "name": "NamespaceTitle", + "in": "path", + "required": true + }, + { + "type": "string", + "name": "Group", + "in": "path", + "required": true + } + ], + "responses": { + "202": { + "description": "ConvertPrometheusResponse", + "schema": { + "$ref": "#/definitions/ConvertPrometheusResponse" + } + }, + "403": { + "description": "ForbiddenError", + "schema": { + "$ref": "#/definitions/ForbiddenError" + } + } + } + } + }, "/dashboard/snapshots": { "get": { "tags": [ diff --git a/public/openapi3.json b/public/openapi3.json index 3a1e1c93c5b..8928ac0429f 100644 --- a/public/openapi3.json +++ b/public/openapi3.json @@ -16590,6 +16590,724 @@ ] } }, + "/convert/api/prom/rules": { + "get": { + "operationId": "RouteConvertPrometheusCortexGetRules", + "responses": { + "200": { + "content": { + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/PrometheusNamespace" + } + } + }, + "description": "PrometheusNamespace" + }, + "403": { + "content": { + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + }, + "description": "ForbiddenError" + }, + "404": { + "content": { + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/NotFound" + } + } + }, + "description": "NotFound" + } + }, + "summary": "Gets all Grafana-managed alert rules that were imported from Prometheus-compatible sources, grouped by namespace.", + "tags": [ + "convert_prometheus" + ] + }, + "post": { + "operationId": "RouteConvertPrometheusCortexPostRuleGroups", + "responses": { + "202": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConvertPrometheusResponse" + } + } + }, + "description": "ConvertPrometheusResponse" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + }, + "description": "ForbiddenError" + } + }, + "summary": "Converts the submitted rule groups into Grafana-Managed Rules.", + "tags": [ + "convert_prometheus" + ] + } + }, + "/convert/api/prom/rules/{NamespaceTitle}": { + "delete": { + "operationId": "RouteConvertPrometheusCortexDeleteNamespace", + "parameters": [ + { + "in": "path", + "name": "NamespaceTitle", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "202": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConvertPrometheusResponse" + } + } + }, + "description": "ConvertPrometheusResponse" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + }, + "description": "ForbiddenError" + } + }, + "summary": "Deletes all rule groups that were imported from Prometheus-compatible sources within the specified namespace.", + "tags": [ + "convert_prometheus" + ] + }, + "get": { + "operationId": "RouteConvertPrometheusCortexGetNamespace", + "parameters": [ + { + "in": "path", + "name": "NamespaceTitle", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/PrometheusNamespace" + } + } + }, + "description": "PrometheusNamespace" + }, + "403": { + "content": { + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + }, + "description": "ForbiddenError" + }, + "404": { + "content": { + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/NotFound" + } + } + }, + "description": "NotFound" + } + }, + "summary": "Gets Grafana-managed alert rules that were imported from Prometheus-compatible sources for a specified namespace (folder).", + "tags": [ + "convert_prometheus" + ] + }, + "post": { + "description": "If the group already exists and was not imported from a Prometheus-compatible source initially,\nit will not be replaced and an error will be returned.", + "operationId": "RouteConvertPrometheusCortexPostRuleGroup", + "parameters": [ + { + "in": "path", + "name": "NamespaceTitle", + "required": true, + "schema": { + "type": "string" + } + }, + { + "in": "header", + "name": "x-grafana-alerting-datasource-uid", + "schema": { + "type": "string" + } + }, + { + "in": "header", + "name": "x-grafana-alerting-recording-rules-paused", + "schema": { + "type": "boolean" + } + }, + { + "in": "header", + "name": "x-grafana-alerting-alert-rules-paused", + "schema": { + "type": "boolean" + } + }, + { + "in": "header", + "name": "x-grafana-alerting-target-datasource-uid", + "schema": { + "type": "string" + } + }, + { + "in": "header", + "name": "x-grafana-alerting-folder-uid", + "schema": { + "type": "string" + } + }, + { + "in": "header", + "name": "x-grafana-alerting-notification-receiver", + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/PrometheusRuleGroup" + } + } + }, + "x-originalParamName": "Body" + }, + "responses": { + "202": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConvertPrometheusResponse" + } + } + }, + "description": "ConvertPrometheusResponse" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + }, + "description": "ForbiddenError" + } + }, + "summary": "Converts a Prometheus rule group into a Grafana rule group and creates or updates it within the specified namespace.", + "tags": [ + "convert_prometheus" + ], + "x-raw-request": "true" + } + }, + "/convert/api/prom/rules/{NamespaceTitle}/{Group}": { + "delete": { + "operationId": "RouteConvertPrometheusCortexDeleteRuleGroup", + "parameters": [ + { + "in": "path", + "name": "NamespaceTitle", + "required": true, + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "Group", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "202": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConvertPrometheusResponse" + } + } + }, + "description": "ConvertPrometheusResponse" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + }, + "description": "ForbiddenError" + } + }, + "summary": "Deletes a specific rule group if it was imported from a Prometheus-compatible source.", + "tags": [ + "convert_prometheus" + ] + }, + "get": { + "operationId": "RouteConvertPrometheusCortexGetRuleGroup", + "parameters": [ + { + "in": "path", + "name": "NamespaceTitle", + "required": true, + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "Group", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/PrometheusRuleGroup" + } + } + }, + "description": "PrometheusRuleGroup" + }, + "403": { + "content": { + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + }, + "description": "ForbiddenError" + }, + "404": { + "content": { + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/NotFound" + } + } + }, + "description": "NotFound" + } + }, + "summary": "Gets a single rule group in Prometheus-compatible format if it was imported from a Prometheus-compatible source.", + "tags": [ + "convert_prometheus" + ] + } + }, + "/convert/prometheus/config/v1/rules": { + "get": { + "operationId": "RouteConvertPrometheusGetRules", + "responses": { + "200": { + "content": { + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/PrometheusNamespace" + } + } + }, + "description": "PrometheusNamespace" + }, + "403": { + "content": { + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + }, + "description": "ForbiddenError" + }, + "404": { + "content": { + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/NotFound" + } + } + }, + "description": "NotFound" + } + }, + "summary": "Gets all Grafana-managed alert rules that were imported from Prometheus-compatible sources, grouped by namespace.", + "tags": [ + "convert_prometheus" + ] + }, + "post": { + "operationId": "RouteConvertPrometheusPostRuleGroups", + "responses": { + "202": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConvertPrometheusResponse" + } + } + }, + "description": "ConvertPrometheusResponse" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + }, + "description": "ForbiddenError" + } + }, + "summary": "Converts the submitted rule groups into Grafana-Managed Rules.", + "tags": [ + "convert_prometheus" + ] + } + }, + "/convert/prometheus/config/v1/rules/{NamespaceTitle}": { + "delete": { + "operationId": "RouteConvertPrometheusDeleteNamespace", + "parameters": [ + { + "in": "path", + "name": "NamespaceTitle", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "202": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConvertPrometheusResponse" + } + } + }, + "description": "ConvertPrometheusResponse" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + }, + "description": "ForbiddenError" + } + }, + "summary": "Deletes all rule groups that were imported from Prometheus-compatible sources within the specified namespace.", + "tags": [ + "convert_prometheus" + ] + }, + "get": { + "operationId": "RouteConvertPrometheusGetNamespace", + "parameters": [ + { + "in": "path", + "name": "NamespaceTitle", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/PrometheusNamespace" + } + } + }, + "description": "PrometheusNamespace" + }, + "403": { + "content": { + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + }, + "description": "ForbiddenError" + }, + "404": { + "content": { + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/NotFound" + } + } + }, + "description": "NotFound" + } + }, + "summary": "Gets Grafana-managed alert rules that were imported from Prometheus-compatible sources for a specified namespace (folder).", + "tags": [ + "convert_prometheus" + ] + }, + "post": { + "description": "If the group already exists and was not imported from a Prometheus-compatible source initially,\nit will not be replaced and an error will be returned.", + "operationId": "RouteConvertPrometheusPostRuleGroup", + "parameters": [ + { + "in": "path", + "name": "NamespaceTitle", + "required": true, + "schema": { + "type": "string" + } + }, + { + "in": "header", + "name": "x-grafana-alerting-datasource-uid", + "schema": { + "type": "string" + } + }, + { + "in": "header", + "name": "x-grafana-alerting-recording-rules-paused", + "schema": { + "type": "boolean" + } + }, + { + "in": "header", + "name": "x-grafana-alerting-alert-rules-paused", + "schema": { + "type": "boolean" + } + }, + { + "in": "header", + "name": "x-grafana-alerting-target-datasource-uid", + "schema": { + "type": "string" + } + }, + { + "in": "header", + "name": "x-grafana-alerting-folder-uid", + "schema": { + "type": "string" + } + }, + { + "in": "header", + "name": "x-grafana-alerting-notification-receiver", + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/PrometheusRuleGroup" + } + } + }, + "x-originalParamName": "Body" + }, + "responses": { + "202": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConvertPrometheusResponse" + } + } + }, + "description": "ConvertPrometheusResponse" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + }, + "description": "ForbiddenError" + } + }, + "summary": "Converts a Prometheus rule group into a Grafana rule group and creates or updates it within the specified namespace.", + "tags": [ + "convert_prometheus" + ], + "x-raw-request": "true" + } + }, + "/convert/prometheus/config/v1/rules/{NamespaceTitle}/{Group}": { + "delete": { + "operationId": "RouteConvertPrometheusDeleteRuleGroup", + "parameters": [ + { + "in": "path", + "name": "NamespaceTitle", + "required": true, + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "Group", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "202": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConvertPrometheusResponse" + } + } + }, + "description": "ConvertPrometheusResponse" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + }, + "description": "ForbiddenError" + } + }, + "summary": "Deletes a specific rule group if it was imported from a Prometheus-compatible source.", + "tags": [ + "convert_prometheus" + ] + }, + "get": { + "operationId": "RouteConvertPrometheusGetRuleGroup", + "parameters": [ + { + "in": "path", + "name": "NamespaceTitle", + "required": true, + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "Group", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/PrometheusRuleGroup" + } + } + }, + "description": "PrometheusRuleGroup" + }, + "403": { + "content": { + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + }, + "description": "ForbiddenError" + }, + "404": { + "content": { + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/NotFound" + } + } + }, + "description": "NotFound" + } + }, + "summary": "Gets a single rule group in Prometheus-compatible format if it was imported from a Prometheus-compatible source.", + "tags": [ + "convert_prometheus" + ] + } + }, "/dashboard/snapshots": { "get": { "operationId": "searchDashboardSnapshots", From a3e19209839d4bdfb0341c77b4a54583cf8c9eb5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 31 Jul 2025 11:14:02 +0100 Subject: [PATCH 041/114] Update dependency ts-jest to v29.4.0 (#108964) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- packages/grafana-flamegraph/package.json | 2 +- .../grafana-o11y-ds-frontend/package.json | 2 +- packages/grafana-sql/package.json | 2 +- yarn.lock | 56 ++++++++++++++++--- 5 files changed, 52 insertions(+), 12 deletions(-) diff --git a/package.json b/package.json index 3538a0d8b92..de65edb4836 100644 --- a/package.json +++ b/package.json @@ -248,7 +248,7 @@ "terser-webpack-plugin": "5.3.14", "testing-library-selector": "0.3.1", "tracelib": "1.0.1", - "ts-jest": "29.2.5", + "ts-jest": "29.4.0", "ts-node": "10.9.2", "typescript": "5.8.3", "webpack": "5.97.1", diff --git a/packages/grafana-flamegraph/package.json b/packages/grafana-flamegraph/package.json index cfb6e77e230..3152966e799 100644 --- a/packages/grafana-flamegraph/package.json +++ b/packages/grafana-flamegraph/package.json @@ -79,7 +79,7 @@ "rollup": "^4.22.4", "rollup-plugin-esbuild": "6.2.1", "rollup-plugin-node-externals": "^8.0.0", - "ts-jest": "29.2.5", + "ts-jest": "29.4.0", "ts-node": "10.9.2", "typescript": "5.8.3" }, diff --git a/packages/grafana-o11y-ds-frontend/package.json b/packages/grafana-o11y-ds-frontend/package.json index 87b73a02a08..11ef5bdce98 100644 --- a/packages/grafana-o11y-ds-frontend/package.json +++ b/packages/grafana-o11y-ds-frontend/package.json @@ -41,7 +41,7 @@ "@types/systemjs": "6.15.3", "jest": "^29.6.4", "react": "18.3.1", - "ts-jest": "29.2.5", + "ts-jest": "29.4.0", "ts-node": "10.9.2", "typescript": "5.8.3" }, diff --git a/packages/grafana-sql/package.json b/packages/grafana-sql/package.json index a3e9c784261..e99c2f9a737 100644 --- a/packages/grafana-sql/package.json +++ b/packages/grafana-sql/package.json @@ -51,7 +51,7 @@ "@types/uuid": "10.0.0", "i18next-parser": "9.3.0", "jest": "^29.6.4", - "ts-jest": "29.2.5", + "ts-jest": "29.4.0", "ts-node": "10.9.2", "typescript": "5.8.3" }, diff --git a/yarn.lock b/yarn.lock index 1841e4a110e..94cb5b420db 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3293,7 +3293,7 @@ __metadata: rollup-plugin-esbuild: "npm:6.2.1" rollup-plugin-node-externals: "npm:^8.0.0" tinycolor2: "npm:1.6.0" - ts-jest: "npm:29.2.5" + ts-jest: "npm:29.4.0" ts-node: "npm:10.9.2" tslib: "npm:2.8.1" typescript: "npm:5.8.3" @@ -3406,7 +3406,7 @@ __metadata: react-select: "npm:5.10.2" react-use: "npm:17.6.0" rxjs: "npm:7.8.2" - ts-jest: "npm:29.2.5" + ts-jest: "npm:29.4.0" ts-node: "npm:10.9.2" tslib: "npm:2.8.1" typescript: "npm:5.8.3" @@ -3700,7 +3700,7 @@ __metadata: react-virtualized-auto-sizer: "npm:1.0.26" rxjs: "npm:7.8.2" sql-formatter-plus: "npm:^1.3.6" - ts-jest: "npm:29.2.5" + ts-jest: "npm:29.4.0" ts-node: "npm:10.9.2" tslib: "npm:2.8.1" typescript: "npm:5.8.3" @@ -18624,7 +18624,7 @@ __metadata: testing-library-selector: "npm:0.3.1" tinycolor2: "npm:1.6.0" tracelib: "npm:1.0.1" - ts-jest: "npm:29.2.5" + ts-jest: "npm:29.4.0" ts-node: "npm:10.9.2" tslib: "npm:2.8.1" tween-functions: "npm:^1.2.0" @@ -31219,6 +31219,46 @@ __metadata: languageName: node linkType: hard +"ts-jest@npm:29.4.0": + version: 29.4.0 + resolution: "ts-jest@npm:29.4.0" + dependencies: + bs-logger: "npm:^0.2.6" + ejs: "npm:^3.1.10" + fast-json-stable-stringify: "npm:^2.1.0" + json5: "npm:^2.2.3" + lodash.memoize: "npm:^4.1.2" + make-error: "npm:^1.3.6" + semver: "npm:^7.7.2" + type-fest: "npm:^4.41.0" + yargs-parser: "npm:^21.1.1" + peerDependencies: + "@babel/core": ">=7.0.0-beta.0 <8" + "@jest/transform": ^29.0.0 || ^30.0.0 + "@jest/types": ^29.0.0 || ^30.0.0 + babel-jest: ^29.0.0 || ^30.0.0 + jest: ^29.0.0 || ^30.0.0 + jest-util: ^29.0.0 || ^30.0.0 + typescript: ">=4.3 <6" + peerDependenciesMeta: + "@babel/core": + optional: true + "@jest/transform": + optional: true + "@jest/types": + optional: true + babel-jest: + optional: true + esbuild: + optional: true + jest-util: + optional: true + bin: + ts-jest: cli.js + checksum: 10/fe501f3d9946ec52db78ae0ac6cfd72942b1c1f5d657c12db321c9d570f0f499e83eb6c7e26074cd11dfe534a6a09c676947e7a63ee08fcda552aabcdeb6c592 + languageName: node + linkType: hard + "ts-mixer@npm:^6.0.3, ts-mixer@npm:^6.0.4": version: 6.0.4 resolution: "ts-mixer@npm:6.0.4" @@ -31439,10 +31479,10 @@ __metadata: languageName: node linkType: hard -"type-fest@npm:^4.18.2, type-fest@npm:^4.26.1, type-fest@npm:^4.40.0": - version: 4.40.0 - resolution: "type-fest@npm:4.40.0" - checksum: 10/dbca20979d18c6b8c87ca28cd999d9ae6b34e0c54c3a87ac65530a32f7a178d38d3788044a589f47c9fde3f3c81422e7b021ec1455f7242b724a2d9c642ce8b8 +"type-fest@npm:^4.18.2, type-fest@npm:^4.26.1, type-fest@npm:^4.40.0, type-fest@npm:^4.41.0": + version: 4.41.0 + resolution: "type-fest@npm:4.41.0" + checksum: 10/617ace794ac0893c2986912d28b3065ad1afb484cad59297835a0807dc63286c39e8675d65f7de08fafa339afcb8fe06a36e9a188b9857756ae1e92ee8bda212 languageName: node linkType: hard From 04a563cb09388a630d22bea8d8bb1b051ca8b953 Mon Sep 17 00:00:00 2001 From: Gareth Date: Thu, 31 Jul 2025 11:30:15 +0100 Subject: [PATCH 042/114] Jaeger: remove unused code (#108612) * remove isSearchFormValid * remove getTimeRange * remove convertTagsLogfmt * remove createTableFrame * remove transformToLogfmt * remove types * remove mapJaegerDependenciesResponse * trigger workflow --- .betterer.results | 6 - .../jaeger/_importedDependencies/README.md | 3 - .../model/trace-viewer.ts | 57 ----- .../model/transform-trace-data.tsx | 201 ------------------ .../_importedDependencies/selectors/trace.ts | 65 ------ .../_importedDependencies/types/index.tsx | 15 -- .../_importedDependencies/types/trace.ts | 102 --------- .../_importedDependencies/utils/TreeNode.ts | 139 ------------ .../utils/config/default-config.ts | 40 ---- .../utils/config/get-config.tsx | 29 --- .../jaeger/components/SearchForm.tsx | 3 +- .../datasource/jaeger/datasource.test.ts | 10 - .../plugins/datasource/jaeger/datasource.ts | 21 -- .../jaeger/dependencyGraphTransform.test.ts | 106 --------- .../jaeger/dependencyGraphTransform.ts | 125 ----------- .../datasource/jaeger/responseTransform.ts | 66 +----- public/app/plugins/datasource/jaeger/types.ts | 8 +- public/app/plugins/datasource/jaeger/util.ts | 26 --- 18 files changed, 4 insertions(+), 1018 deletions(-) delete mode 100644 public/app/plugins/datasource/jaeger/_importedDependencies/README.md delete mode 100644 public/app/plugins/datasource/jaeger/_importedDependencies/model/trace-viewer.ts delete mode 100644 public/app/plugins/datasource/jaeger/_importedDependencies/model/transform-trace-data.tsx delete mode 100644 public/app/plugins/datasource/jaeger/_importedDependencies/selectors/trace.ts delete mode 100644 public/app/plugins/datasource/jaeger/_importedDependencies/types/index.tsx delete mode 100644 public/app/plugins/datasource/jaeger/_importedDependencies/types/trace.ts delete mode 100644 public/app/plugins/datasource/jaeger/_importedDependencies/utils/TreeNode.ts delete mode 100644 public/app/plugins/datasource/jaeger/_importedDependencies/utils/config/default-config.ts delete mode 100644 public/app/plugins/datasource/jaeger/_importedDependencies/utils/config/get-config.tsx delete mode 100644 public/app/plugins/datasource/jaeger/dependencyGraphTransform.test.ts delete mode 100644 public/app/plugins/datasource/jaeger/dependencyGraphTransform.ts delete mode 100644 public/app/plugins/datasource/jaeger/util.ts diff --git a/.betterer.results b/.betterer.results index 37fb0e90b15..4482c94acc5 100644 --- a/.betterer.results +++ b/.betterer.results @@ -3498,12 +3498,6 @@ exports[`better eslint`] = { "public/app/plugins/datasource/influxdb/response_parser.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], - "public/app/plugins/datasource/jaeger/_importedDependencies/model/transform-trace-data.tsx:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"] - ], - "public/app/plugins/datasource/jaeger/_importedDependencies/types/index.tsx:5381": [ - [0, 0, 0, "Do not re-export imported variable (\`./trace\`)", "0"] - ], "public/app/plugins/datasource/jaeger/datasource.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] ], diff --git a/public/app/plugins/datasource/jaeger/_importedDependencies/README.md b/public/app/plugins/datasource/jaeger/_importedDependencies/README.md deleted file mode 100644 index 6a32e35129c..00000000000 --- a/public/app/plugins/datasource/jaeger/_importedDependencies/README.md +++ /dev/null @@ -1,3 +0,0 @@ -This directory contains dependencies that we duplicated from Grafana core while working on the decoupling of Jaeger from such core. -The long-term goal is to move these files away from here by replacing them with packages. -As such, they are only temporary and meant to be used internally to this package, please avoid using them for example as dependencies (imports) in other data source plugins. diff --git a/public/app/plugins/datasource/jaeger/_importedDependencies/model/trace-viewer.ts b/public/app/plugins/datasource/jaeger/_importedDependencies/model/trace-viewer.ts deleted file mode 100644 index d3978acc247..00000000000 --- a/public/app/plugins/datasource/jaeger/_importedDependencies/model/trace-viewer.ts +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) 2020 The Jaeger Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { memoize } from 'lodash'; - -import { TraceSpan } from '../types'; - -function _getTraceNameImpl(spans: TraceSpan[]) { - // Use a span with no references to another span in given array - // prefering the span with the fewest references - // using start time as a tie breaker - let candidateSpan: TraceSpan | undefined; - const allIDs: Set = new Set(spans.map(({ spanID }) => spanID)); - - for (let i = 0; i < spans.length; i++) { - const hasInternalRef = - spans[i].references && - spans[i].references.some(({ traceID, spanID }) => traceID === spans[i].traceID && allIDs.has(spanID)); - if (hasInternalRef) { - continue; - } - - if (!candidateSpan) { - candidateSpan = spans[i]; - continue; - } - - const thisRefLength = (spans[i].references && spans[i].references.length) || 0; - const candidateRefLength = (candidateSpan.references && candidateSpan.references.length) || 0; - - if ( - thisRefLength < candidateRefLength || - (thisRefLength === candidateRefLength && spans[i].startTime < candidateSpan.startTime) - ) { - candidateSpan = spans[i]; - } - } - return candidateSpan ? `${candidateSpan.process.serviceName}: ${candidateSpan.operationName}` : ''; -} - -export const getTraceName = memoize(_getTraceNameImpl, (spans: TraceSpan[]) => { - if (!spans.length) { - return 0; - } - return spans[0].traceID; -}); diff --git a/public/app/plugins/datasource/jaeger/_importedDependencies/model/transform-trace-data.tsx b/public/app/plugins/datasource/jaeger/_importedDependencies/model/transform-trace-data.tsx deleted file mode 100644 index f095fe3d8c1..00000000000 --- a/public/app/plugins/datasource/jaeger/_importedDependencies/model/transform-trace-data.tsx +++ /dev/null @@ -1,201 +0,0 @@ -// Copyright (c) 2017 Uber Technologies, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { isEqual as _isEqual } from 'lodash'; - -import { TraceKeyValuePair } from '@grafana/data'; - -import { getTraceSpanIdsAsTree } from '../selectors/trace'; -import { TraceSpan, Trace, TraceResponse, TraceProcess } from '../types'; -import TreeNode from '../utils/TreeNode'; -import { getConfigValue } from '../utils/config/get-config'; - -import { getTraceName } from './trace-viewer'; - -function deduplicateTags(tags: TraceKeyValuePair[]) { - const warningsHash: Map = new Map(); - const dedupedTags: TraceKeyValuePair[] = tags.reduce((uniqueTags, tag) => { - if (!uniqueTags.some((t) => t.key === tag.key && t.value === tag.value)) { - uniqueTags.push(tag); - } else { - warningsHash.set(`${tag.key}:${tag.value}`, `Duplicate tag "${tag.key}:${tag.value}"`); - } - return uniqueTags; - }, []); - const warnings = Array.from(warningsHash.values()); - return { dedupedTags, warnings }; -} - -function orderTags(tags: TraceKeyValuePair[], topPrefixes?: string[]) { - const orderedTags: TraceKeyValuePair[] = tags?.slice() ?? []; - const tp = (topPrefixes || []).map((p: string) => p.toLowerCase()); - - orderedTags.sort((a, b) => { - const aKey = a.key.toLowerCase(); - const bKey = b.key.toLowerCase(); - - for (let i = 0; i < tp.length; i++) { - const p = tp[i]; - if (aKey.startsWith(p) && !bKey.startsWith(p)) { - return -1; - } - if (!aKey.startsWith(p) && bKey.startsWith(p)) { - return 1; - } - } - - if (aKey > bKey) { - return 1; - } - if (aKey < bKey) { - return -1; - } - return 0; - }); - - return orderedTags; -} - -/** - * NOTE: Mutates `data` - Transform the HTTP response data into the form the app - * generally requires. - */ -export default function transformTraceData(data: TraceResponse | undefined): Trace | null { - if (!data?.traceID) { - return null; - } - const traceID = data.traceID.toLowerCase(); - - let traceEndTime = 0; - let traceStartTime = Number.MAX_SAFE_INTEGER; - const spanIdCounts = new Map(); - const spanMap = new Map(); - // filter out spans with empty start times - // eslint-disable-next-line no-param-reassign - data.spans = data.spans.filter((span) => Boolean(span.startTime)); - - // Sort process tags - data.processes = Object.entries(data.processes).reduce>((processes, [id, process]) => { - processes[id] = { - ...process, - tags: orderTags(process.tags), - }; - return processes; - }, {}); - - const max = data.spans.length; - for (let i = 0; i < max; i++) { - const span: TraceSpan = data.spans[i] as TraceSpan; - const { startTime, duration, processID } = span; - - let spanID = span.spanID; - // check for start / end time for the trace - if (startTime < traceStartTime) { - traceStartTime = startTime; - } - if (startTime + duration > traceEndTime) { - traceEndTime = startTime + duration; - } - // make sure span IDs are unique - const idCount = spanIdCounts.get(spanID); - if (idCount != null) { - // eslint-disable-next-line no-console - console.warn(`Dupe spanID, ${idCount + 1} x ${spanID}`, span, spanMap.get(spanID)); - if (_isEqual(span, spanMap.get(spanID))) { - // eslint-disable-next-line no-console - console.warn('\t two spans with same ID have `isEqual(...) === true`'); - } - spanIdCounts.set(spanID, idCount + 1); - spanID = `${spanID}_${idCount}`; - span.spanID = spanID; - } else { - spanIdCounts.set(spanID, 1); - } - span.process = data.processes[processID]; - spanMap.set(spanID, span); - } - // tree is necessary to sort the spans, so children follow parents, and - // siblings are sorted by start time - const tree = getTraceSpanIdsAsTree(data, spanMap); - const spans: TraceSpan[] = []; - const svcCounts: Record = {}; - - tree.walk((spanID: string, node: TreeNode, depth = 0) => { - if (spanID === '__root__') { - return; - } - if (typeof spanID !== 'string') { - return; - } - const span = spanMap.get(spanID); - if (!span) { - return; - } - const { serviceName } = span.process; - svcCounts[serviceName] = (svcCounts[serviceName] || 0) + 1; - span.relativeStartTime = span.startTime - traceStartTime; - span.depth = depth - 1; - span.hasChildren = node.children.length > 0; - span.childSpanCount = node.children.length; - span.warnings = span.warnings || []; - span.tags = span.tags || []; - span.references = span.references || []; - - span.childSpanIds = node.children - .slice() - .sort((a, b) => { - const spanA = spanMap.get(a.value)!; - const spanB = spanMap.get(b.value)!; - return spanB.startTime + spanB.duration - (spanA.startTime + spanA.duration); - }) - .map((each) => each.value); - - const tagsInfo = deduplicateTags(span.tags); - span.tags = orderTags(tagsInfo.dedupedTags, getConfigValue('topTagPrefixes')); - span.warnings = span.warnings.concat(tagsInfo.warnings); - span.references.forEach((ref, index) => { - const refSpan = spanMap.get(ref.spanID); - if (refSpan) { - // eslint-disable-next-line no-param-reassign - ref.span = refSpan; - if (index > 0) { - // Don't take into account the parent, just other references. - refSpan.subsidiarilyReferencedBy = refSpan.subsidiarilyReferencedBy || []; - refSpan.subsidiarilyReferencedBy.push({ - spanID, - traceID, - span, - refType: ref.refType, - }); - } - } - }); - spans.push(span); - }); - const traceName = getTraceName(spans); - const services = Object.keys(svcCounts).map((name) => ({ name, numberOfSpans: svcCounts[name] })); - return { - services, - spans, - traceID, - traceName, - // can't use spread operator for intersection types - // repl: https://goo.gl/4Z23MJ - // issue: https://github.com/facebook/flow/issues/1511 - processes: data.processes, - duration: traceEndTime - traceStartTime, - startTime: traceStartTime, - endTime: traceEndTime, - }; -} diff --git a/public/app/plugins/datasource/jaeger/_importedDependencies/selectors/trace.ts b/public/app/plugins/datasource/jaeger/_importedDependencies/selectors/trace.ts deleted file mode 100644 index 9fb695b3321..00000000000 --- a/public/app/plugins/datasource/jaeger/_importedDependencies/selectors/trace.ts +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) 2017 Uber Technologies, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { TraceResponse, TraceSpanData } from '../types/trace'; -import TreeNode from '../utils/TreeNode'; - -const TREE_ROOT_ID = '__root__'; - -/** - * Build a tree of { value: spanID, children } items derived from the - * `span.references` information. The tree represents the grouping of parent / - * child relationships. The root-most node is nominal in that - * `.value === TREE_ROOT_ID`. This is done because a root span (the main trace - * span) is not always included with the trace data. Thus, there can be - * multiple top-level spans, and the root node acts as their common parent. - * - * The children are sorted by `span.startTime` after the tree is built. - * - * @param {Trace} trace The trace to build the tree of spanIDs. - * @return {TreeNode} A tree of spanIDs derived from the relationships - * between spans in the trace. - */ -export function getTraceSpanIdsAsTree(trace: TraceResponse, spanMap: Map | null = null) { - const nodesById = new Map(trace.spans.map((span: TraceSpanData) => [span.spanID, new TreeNode(span.spanID)])); - const spansById = spanMap ?? new Map(trace.spans.map((span: TraceSpanData) => [span.spanID, span])); - const root = new TreeNode(TREE_ROOT_ID); - trace.spans.forEach((span: TraceSpanData) => { - const node = nodesById.get(span.spanID)!; - if (Array.isArray(span.references) && span.references.length) { - const { refType, spanID: parentID } = span.references[0]; - if (refType === 'CHILD_OF' || refType === 'FOLLOWS_FROM') { - const parent = nodesById.get(parentID) || root; - parent.children?.push(node); - } else { - throw new Error(`Unrecognized ref type: ${refType}`); - } - } else { - root.children.push(node); - } - }); - const comparator = (nodeA: TreeNode, nodeB: TreeNode) => { - const a: TraceSpanData | undefined = nodeA?.value ? spansById.get(nodeA.value.toString()) : undefined; - const b: TraceSpanData | undefined = nodeB?.value ? spansById.get(nodeB.value.toString()) : undefined; - return +(a?.startTime! > b?.startTime!) || +(a?.startTime === b?.startTime) - 1; - }; - trace.spans.forEach((span: TraceSpanData) => { - const node = nodesById.get(span.spanID); - if (node!.children.length > 1) { - node?.children.sort(comparator); - } - }); - root.children.sort(comparator); - return root; -} diff --git a/public/app/plugins/datasource/jaeger/_importedDependencies/types/index.tsx b/public/app/plugins/datasource/jaeger/_importedDependencies/types/index.tsx deleted file mode 100644 index f777aee8566..00000000000 --- a/public/app/plugins/datasource/jaeger/_importedDependencies/types/index.tsx +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) 2017 Uber Technologies, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -export type { TraceSpan, TraceResponse, Trace, TraceProcess, TraceLink, CriticalPathSection } from './trace'; diff --git a/public/app/plugins/datasource/jaeger/_importedDependencies/types/trace.ts b/public/app/plugins/datasource/jaeger/_importedDependencies/types/trace.ts deleted file mode 100644 index 0c38b7ba584..00000000000 --- a/public/app/plugins/datasource/jaeger/_importedDependencies/types/trace.ts +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright (c) 2017 Uber Technologies, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { TraceKeyValuePair, TraceLog } from '@grafana/data'; - -/** - * All timestamps are in microseconds - */ - -export type TraceLink = { - url: string; - text: string; -}; - -export type TraceProcess = { - serviceName: string; - tags: TraceKeyValuePair[]; -}; - -export type TraceSpanReference = { - refType: 'CHILD_OF' | 'FOLLOWS_FROM'; - // eslint-disable-next-line no-use-before-define - span?: TraceSpan | null | undefined; - spanID: string; - traceID: string; - tags?: TraceKeyValuePair[]; -}; - -export type TraceSpanData = { - spanID: string; - traceID: string; - processID: string; - operationName: string; - // Times are in microseconds - startTime: number; - duration: number; - logs: TraceLog[]; - tags?: TraceKeyValuePair[]; - kind?: string; - statusCode?: number; - statusMessage?: string; - instrumentationLibraryName?: string; - instrumentationLibraryVersion?: string; - traceState?: string; - references?: TraceSpanReference[]; - warnings?: string[] | null; - stackTraces?: string[]; - flags: number; - errorIconColor?: string; - dataFrameRowIndex?: number; - childSpanIds?: string[]; -}; - -export type TraceSpan = TraceSpanData & { - depth: number; - hasChildren: boolean; - childSpanCount: number; - process: TraceProcess; - relativeStartTime: number; - tags: NonNullable; - references: NonNullable; - warnings: NonNullable; - childSpanIds: NonNullable; - subsidiarilyReferencedBy: TraceSpanReference[]; -}; - -export type TraceData = { - processes: Record; - traceID: string; - warnings?: string[] | null; -}; - -export type TraceResponse = TraceData & { - spans: TraceSpanData[]; -}; - -export type Trace = TraceData & { - duration: number; - endTime: number; - spans: TraceSpan[]; - startTime: number; - traceName: string; - services: Array<{ name: string; numberOfSpans: number }>; -}; - -// It is a section of span that lies on critical path -export type CriticalPathSection = { - spanId: string; - section_start: number; - section_end: number; -}; diff --git a/public/app/plugins/datasource/jaeger/_importedDependencies/utils/TreeNode.ts b/public/app/plugins/datasource/jaeger/_importedDependencies/utils/TreeNode.ts deleted file mode 100644 index f6b123f2d50..00000000000 --- a/public/app/plugins/datasource/jaeger/_importedDependencies/utils/TreeNode.ts +++ /dev/null @@ -1,139 +0,0 @@ -// Copyright (c) 2017 Uber Technologies, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License - -export default class TreeNode { - value: TValue; - children: Array>; - - static iterFunction( - fn: ((value: TValue, node: TreeNode, depth: number) => TreeNode | null) | Function, - depth = 0 - ) { - return (node: TreeNode) => fn(node.value, node, depth); - } - - static searchFunction(search: Function | TreeNode) { - if (typeof search === 'function') { - return search; - } - - return (value: TValue, node: TreeNode) => (search instanceof TreeNode ? node === search : value === search); - } - - constructor(value: TValue, children: Array> = []) { - this.value = value; - this.children = children; - } - - get depth(): number { - return this.children.reduce((depth, child) => Math.max(child.depth + 1, depth), 1); - } - - get size() { - let i = 0; - this.walk(() => i++); - return i; - } - - addChild(child: TreeNode | TValue) { - this.children.push(child instanceof TreeNode ? child : new TreeNode(child)); - return this; - } - - find(search: Function | TreeNode): TreeNode | null { - const searchFn = TreeNode.iterFunction(TreeNode.searchFunction(search)); - if (searchFn(this)) { - return this; - } - for (let i = 0; i < this.children.length; i++) { - const result = this.children[i].find(search); - if (result) { - return result; - } - } - return null; - } - - getPath(search: Function | TreeNode) { - const searchFn = TreeNode.iterFunction(TreeNode.searchFunction(search)); - - const findPath = ( - currentNode: TreeNode, - currentPath: Array> - ): Array> | null => { - // skip if we already found the result - const attempt = currentPath.concat([currentNode]); - // base case: return the array when there is a match - if (searchFn(currentNode)) { - return attempt; - } - for (let i = 0; i < currentNode.children.length; i++) { - const child = currentNode.children[i]; - const match = findPath(child, attempt); - if (match) { - return match; - } - } - return null; - }; - - return findPath(this, []); - } - - walk(fn: (spanID: TValue, node: TreeNode, depth: number) => void, startDepth = 0) { - type StackEntry = { - node: TreeNode; - depth: number; - }; - const nodeStack: StackEntry[] = []; - let actualDepth = startDepth; - nodeStack.push({ node: this, depth: actualDepth }); - while (nodeStack.length) { - const entry: StackEntry = nodeStack[nodeStack.length - 1]; - nodeStack.pop(); - const { node, depth } = entry; - fn(node.value, node, depth); - actualDepth = depth + 1; - let i = node.children.length - 1; - while (i >= 0) { - nodeStack.push({ node: node.children[i], depth: actualDepth }); - i--; - } - } - } - - paths(fn: (pathIds: TValue[]) => void) { - type StackEntry = { - node: TreeNode; - childIndex: number; - }; - const stack: StackEntry[] = []; - stack.push({ node: this, childIndex: 0 }); - const paths: TValue[] = []; - while (stack.length) { - const { node, childIndex } = stack[stack.length - 1]; - if (node.children.length >= childIndex + 1) { - stack[stack.length - 1].childIndex++; - stack.push({ node: node.children[childIndex], childIndex: 0 }); - } else { - if (node.children.length === 0) { - const path = stack.map((item) => item.node.value); - fn(path); - } - stack.pop(); - } - } - return paths; - } -} diff --git a/public/app/plugins/datasource/jaeger/_importedDependencies/utils/config/default-config.ts b/public/app/plugins/datasource/jaeger/_importedDependencies/utils/config/default-config.ts deleted file mode 100644 index 1e8215a230d..00000000000 --- a/public/app/plugins/datasource/jaeger/_importedDependencies/utils/config/default-config.ts +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) 2017 Uber Technologies, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -const FALLBACK_DAG_MAX_NUM_SERVICES = 100; - -export default Object.defineProperty( - { - archiveEnabled: false, - dependencies: { - dagMaxNumServices: FALLBACK_DAG_MAX_NUM_SERVICES, - menuEnabled: true, - }, - linkPatterns: [], - search: { - maxLookback: { - label: '2 Days', - value: '2d', - }, - maxLimit: 1500, - }, - tracking: { - gaID: null, - trackErrors: true, - }, - }, - // fields that should be individually merged vs wholesale replaced - '__mergeFields', - { value: ['dependencies', 'search', 'tracking'] } -); diff --git a/public/app/plugins/datasource/jaeger/_importedDependencies/utils/config/get-config.tsx b/public/app/plugins/datasource/jaeger/_importedDependencies/utils/config/get-config.tsx deleted file mode 100644 index 0a1ec89870c..00000000000 --- a/public/app/plugins/datasource/jaeger/_importedDependencies/utils/config/get-config.tsx +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) 2017 Uber Technologies, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { get as _get } from 'lodash'; - -import defaultConfig from './default-config'; - -/** - * Merge the embedded config from the query service (if present) with the - * default config from `../../constants/default-config`. - */ -export default function getConfig() { - return defaultConfig; -} - -export function getConfigValue(path: string) { - return _get(getConfig(), path); -} diff --git a/public/app/plugins/datasource/jaeger/components/SearchForm.tsx b/public/app/plugins/datasource/jaeger/components/SearchForm.tsx index c5840a60bab..a81c64d72a0 100644 --- a/public/app/plugins/datasource/jaeger/components/SearchForm.tsx +++ b/public/app/plugins/datasource/jaeger/components/SearchForm.tsx @@ -8,7 +8,6 @@ import { fuzzyMatch, InlineField, InlineFieldRow, Input, Select } from '@grafana import { JaegerDatasource } from '../datasource'; import { JaegerQuery } from '../types'; -import { transformToLogfmt } from '../util'; const durationPlaceholder = 'e.g. 1.2s, 100ms, 500us'; @@ -150,7 +149,7 @@ export function SearchForm({ datasource, query, onChange }: Props) { onChange({ diff --git a/public/app/plugins/datasource/jaeger/datasource.test.ts b/public/app/plugins/datasource/jaeger/datasource.test.ts index 5c0961d4ac7..2beeeb09eb8 100644 --- a/public/app/plugins/datasource/jaeger/datasource.test.ts +++ b/public/app/plugins/datasource/jaeger/datasource.test.ts @@ -136,16 +136,6 @@ describe('node graph functionality', () => { }); }); -describe('time range', () => { - it('should calculate correct time range', async () => { - const ds = new JaegerDatasource(defaultSettings); - const timeRange = ds.getTimeRange(); - const now = Date.now(); - expect(timeRange.end).toBeCloseTo(now * 1000, -4); - expect(timeRange.start).toBeCloseTo((now - 6 * 3600 * 1000) * 1000, -4); - }); -}); - function setupQueryMock(type: 'trace' | 'search') { return jest.spyOn(DataSourceWithBackend.prototype, 'query').mockImplementation(() => { if (type === 'search') { diff --git a/public/app/plugins/datasource/jaeger/datasource.ts b/public/app/plugins/datasource/jaeger/datasource.ts index 11c6a8a3f3b..ea4eb2c4d43 100644 --- a/public/app/plugins/datasource/jaeger/datasource.ts +++ b/public/app/plugins/datasource/jaeger/datasource.ts @@ -6,10 +6,7 @@ import { DataQueryResponse, DataSourceInstanceSettings, DataSourceJsonData, - dateMath, - DateTime, FieldType, - getDefaultTimeRange, MutableDataFrame, ScopedVars, toDataFrame, @@ -45,10 +42,6 @@ export class JaegerDatasource extends DataSourceWithBackend): Observable { // At this moment we expect only one target. In case we somehow change the UI to be able to show multiple // traces at one we need to change this. @@ -122,25 +115,11 @@ export class JaegerDatasource extends DataSourceWithBackend { - it('should transform Jaeger dependencies API response', () => { - const data = { - data: [ - { - parent: 'serviceA', - child: 'serviceB', - callCount: 1, - }, - { - parent: 'serviceA', - child: 'serviceC', - callCount: 2, - }, - { - parent: 'serviceB', - child: 'serviceC', - callCount: 3, - }, - ], - total: 0, - limit: 0, - offset: 0, - }; - - const res = mapJaegerDependenciesResponse({ data }); - expect(res).toMatchObject({ - data: [ - { - fields: [ - { - config: {}, - name: 'id', - type: 'string', - values: ['serviceA', 'serviceB', 'serviceC'], - }, - { - config: {}, - name: 'title', - type: 'string', - values: ['serviceA', 'serviceB', 'serviceC'], - }, - ], - meta: { preferredVisualisationType: 'nodeGraph' }, - }, - { - fields: [ - { - config: {}, - name: 'id', - type: 'string', - values: ['serviceA--serviceB', 'serviceA--serviceC', 'serviceB--serviceC'], - }, - { - config: {}, - name: 'target', - type: 'string', - values: ['serviceB', 'serviceC', 'serviceC'], - }, - { - config: {}, - name: 'source', - type: 'string', - values: ['serviceA', 'serviceA', 'serviceB'], - }, - { - config: { displayName: 'Call count' }, - name: 'mainstat', - type: 'string', - values: [1, 2, 3], - }, - ], - meta: { preferredVisualisationType: 'nodeGraph' }, - }, - ], - }); - }); - - it('should transform Jaeger API error', () => { - const data = { - total: 0, - limit: 0, - offset: 0, - errors: [ - { - code: 400, - msg: 'unable to parse param \'endTs\': strconv.ParseInt: parsing "foo": invalid syntax', - }, - ], - }; - - const res = mapJaegerDependenciesResponse({ data }); - - expect(res).toEqual({ - data: [], - errors: [ - { - message: 'unable to parse param \'endTs\': strconv.ParseInt: parsing "foo": invalid syntax', - status: 400, - }, - ], - }); - }); -}); diff --git a/public/app/plugins/datasource/jaeger/dependencyGraphTransform.ts b/public/app/plugins/datasource/jaeger/dependencyGraphTransform.ts deleted file mode 100644 index 0e8fe6dce46..00000000000 --- a/public/app/plugins/datasource/jaeger/dependencyGraphTransform.ts +++ /dev/null @@ -1,125 +0,0 @@ -import { - DataFrame, - DataQueryResponse, - FieldType, - MutableDataFrame, - NodeGraphDataFrameFieldNames as Fields, -} from '@grafana/data'; - -import { JaegerServiceDependency } from './types'; - -interface Node { - [Fields.id]: string; - [Fields.title]: string; -} - -interface Edge { - [Fields.id]: string; - [Fields.target]: string; - [Fields.source]: string; - [Fields.mainStat]: number; -} - -/** - * Error schema used by the Jaeger dependencies API. - */ -interface JaegerDependenciesResponseError { - code: number; - msg: string; -} - -interface JaegerDependenciesResponse { - data?: { - errors?: JaegerDependenciesResponseError[]; - data?: JaegerServiceDependency[]; - }; -} - -/** - * Transforms a Jaeger dependencies API response to a Grafana {@link DataQueryResponse}. - * @param response Raw response data from the API proxy. - */ -export function mapJaegerDependenciesResponse(response: JaegerDependenciesResponse): DataQueryResponse { - const errors = response?.data?.errors; - if (errors) { - return { - data: [], - errors: errors.map((e: JaegerDependenciesResponseError) => ({ message: e.msg, status: e.code })), - }; - } - const dependencies = response?.data?.data; - if (dependencies) { - return { - data: convertDependenciesToGraph(dependencies), - }; - } - - return { data: [] }; -} - -/** - * Converts a list of Jaeger service dependencies to a Grafana {@link DataFrame} array suitable for the node graph panel. - * @param dependencies List of Jaeger service dependencies as returned by the Jaeger dependencies API. - */ -function convertDependenciesToGraph(dependencies: JaegerServiceDependency[]): DataFrame[] { - const servicesByName = new Map(); - const edges: Edge[] = []; - - for (const dependency of dependencies) { - addServiceNode(dependency.parent, servicesByName); - addServiceNode(dependency.child, servicesByName); - - edges.push({ - [Fields.id]: dependency.parent + '--' + dependency.child, - [Fields.target]: dependency.child, - [Fields.source]: dependency.parent, - [Fields.mainStat]: dependency.callCount, - }); - } - - const nodesFrame = new MutableDataFrame({ - fields: [ - { name: Fields.id, type: FieldType.string }, - { name: Fields.title, type: FieldType.string }, - ], - meta: { - preferredVisualisationType: 'nodeGraph', - }, - }); - - const edgesFrame = new MutableDataFrame({ - fields: [ - { name: Fields.id, type: FieldType.string }, - { name: Fields.target, type: FieldType.string }, - { name: Fields.source, type: FieldType.string }, - { name: Fields.mainStat, type: FieldType.string, config: { displayName: 'Call count' } }, - ], - meta: { - preferredVisualisationType: 'nodeGraph', - }, - }); - - for (const node of servicesByName.values()) { - nodesFrame.add(node); - } - - for (const edge of edges) { - edgesFrame.add(edge); - } - - return [nodesFrame, edgesFrame]; -} - -/** - * Convenience function to register a service node in the dependency graph. - * @param service Name of the service to register. - * @param servicesByName Map of service nodes keyed name. - */ -function addServiceNode(service: string, servicesByName: Map) { - if (!servicesByName.has(service)) { - servicesByName.set(service, { - [Fields.id]: service, - [Fields.title]: service, - }); - } -} diff --git a/public/app/plugins/datasource/jaeger/responseTransform.ts b/public/app/plugins/datasource/jaeger/responseTransform.ts index 35446e221b8..cd56f08e61f 100644 --- a/public/app/plugins/datasource/jaeger/responseTransform.ts +++ b/public/app/plugins/datasource/jaeger/responseTransform.ts @@ -1,13 +1,5 @@ -import { - DataFrame, - DataSourceInstanceSettings, - FieldType, - MutableDataFrame, - TraceLog, - TraceSpanRow, -} from '@grafana/data'; +import { DataFrame, FieldType, MutableDataFrame, TraceLog, TraceSpanRow } from '@grafana/data'; -import transformTraceData from './_importedDependencies/model/transform-trace-data'; import { JaegerResponse, Span, TraceProcess, TraceResponse } from './types'; export function createTraceFrame(data: TraceResponse): DataFrame { @@ -68,62 +60,6 @@ function toSpanRow(span: Span, processes: Record): TraceSp }; } -export function createTableFrame(data: TraceResponse[], instanceSettings: DataSourceInstanceSettings): DataFrame { - const frame = new MutableDataFrame({ - fields: [ - { - name: 'traceID', - type: FieldType.string, - config: { - unit: 'string', - displayNameFromDS: 'Trace ID', - links: [ - { - title: 'Trace: ${__value.raw}', - url: '', - internal: { - datasourceUid: instanceSettings.uid, - datasourceName: instanceSettings.name, - query: { - query: '${__value.raw}', - }, - }, - }, - ], - }, - }, - { name: 'traceName', type: FieldType.string, config: { displayNameFromDS: 'Trace name' } }, - { name: 'startTime', type: FieldType.time, config: { displayNameFromDS: 'Start time' } }, - { name: 'duration', type: FieldType.number, config: { displayNameFromDS: 'Duration', unit: 'µs' } }, - ], - meta: { - preferredVisualisationType: 'table', - }, - }); - // Show the most recent traces - const traceData = data.map(transformToTraceData).sort((a, b) => b?.startTime! - a?.startTime!); - - for (const trace of traceData) { - frame.add(trace); - } - - return frame; -} - -function transformToTraceData(data: TraceResponse) { - const traceData = transformTraceData(data); - if (!traceData) { - return; - } - - return { - traceID: traceData.traceID, - startTime: traceData.startTime / 1000, - duration: traceData.duration, - traceName: traceData.traceName, - }; -} - export function transformToJaeger(data: MutableDataFrame): JaegerResponse { let traceResponse: TraceResponse = { traceID: '', diff --git a/public/app/plugins/datasource/jaeger/types.ts b/public/app/plugins/datasource/jaeger/types.ts index 7173be50a1f..0ddf78bc2b4 100644 --- a/public/app/plugins/datasource/jaeger/types.ts +++ b/public/app/plugins/datasource/jaeger/types.ts @@ -1,9 +1,5 @@ -import { DataQuery, TraceKeyValuePair, TraceLog } from '@grafana/data'; - -export type TraceLink = { - url: string; - text: string; -}; +import { TraceKeyValuePair, TraceLog } from '@grafana/data'; +import { DataQuery } from '@grafana/schema'; export type TraceProcess = { serviceName: string; diff --git a/public/app/plugins/datasource/jaeger/util.ts b/public/app/plugins/datasource/jaeger/util.ts deleted file mode 100644 index f025ed9df55..00000000000 --- a/public/app/plugins/datasource/jaeger/util.ts +++ /dev/null @@ -1,26 +0,0 @@ -import logfmt from 'logfmt'; - -export function convertTagsLogfmt(tags: string | undefined) { - if (!tags) { - return ''; - } - const data = logfmt.parse(tags); - Object.keys(data).forEach((key) => { - const value = data[key]; - if (typeof value !== 'string') { - data[key] = String(value); - } - }); - return JSON.stringify(data); -} - -export function transformToLogfmt(tags: string | undefined) { - if (!tags) { - return ''; - } - try { - return logfmt.stringify(JSON.parse(tags)); - } catch { - return tags; - } -} From 6cd85eb1b3d42f2401a1f98387761c6abc639a94 Mon Sep 17 00:00:00 2001 From: Bogdan Matei Date: Thu, 31 Jul 2025 13:33:03 +0300 Subject: [PATCH 043/114] Dashboard: Fix for viewing a repeated panel that is out of view (#108912) --- .../features/dashboard-scene/scene/DashboardSceneUrlSync.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/public/app/features/dashboard-scene/scene/DashboardSceneUrlSync.ts b/public/app/features/dashboard-scene/scene/DashboardSceneUrlSync.ts index 3d9acceed0b..e19a7331d5c 100644 --- a/public/app/features/dashboard-scene/scene/DashboardSceneUrlSync.ts +++ b/public/app/features/dashboard-scene/scene/DashboardSceneUrlSync.ts @@ -208,8 +208,11 @@ export class DashboardSceneUrlSync implements SceneObjectUrlSyncHandler { if (panel) { this._viewEventSub?.unsubscribe(); this._scene.setState({ viewPanelScene: new ViewPanelScene({ panelRef: panel.getRef() }) }); + this._viewEventSub = undefined; } }); + + this._scene.state.body.activateRepeaters?.(); } } From 372f115db956f02dcb36535eb290bae4321f624a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20=C5=A0tibran=C3=BD?= Date: Thu, 31 Jul 2025 13:45:55 +0200 Subject: [PATCH 044/114] Finish building index even if original context is canceled. (#108969) --- pkg/storage/unified/resource/search.go | 4 ++ pkg/storage/unified/resource/search_test.go | 49 ++++++++++++++++++--- 2 files changed, 48 insertions(+), 5 deletions(-) diff --git a/pkg/storage/unified/resource/search.go b/pkg/storage/unified/resource/search.go index 721f7105b30..4752fa35cec 100644 --- a/pkg/storage/unified/resource/search.go +++ b/pkg/storage/unified/resource/search.go @@ -644,6 +644,10 @@ func (s *searchSupport) getOrCreateIndex(ctx context.Context, key NamespacedReso } ch := s.buildIndex.DoChan(key.String(), func() (interface{}, error) { + // We want to finish building of the index even if original context is canceled. + // We reuse original context without cancel to keep the tracing spans correct. + ctx := context.WithoutCancel(ctx) + // Recheck if some other goroutine managed to build an index in the meantime. // (That is, it finished running this function and stored the index into the cache) idx, err := s.search.GetIndex(ctx, key) diff --git a/pkg/storage/unified/resource/search_test.go b/pkg/storage/unified/resource/search_test.go index a05c74c8a4b..677e24b8b8b 100644 --- a/pkg/storage/unified/resource/search_test.go +++ b/pkg/storage/unified/resource/search_test.go @@ -340,7 +340,7 @@ func TestSearchGetOrCreateIndexWithCancellation(t *testing.T) { {NamespacedResource: NamespacedResource{Namespace: "ns", Group: "group", Resource: "resource"}, Count: 50, ResourceVersion: 11111111}, }, } - search := &slowSearchBackend{ + search := &slowSearchBackendWithCache{ mockSearchBackend: mockSearchBackend{}, } supplier := &TestDocumentBuilderSupplier{ @@ -362,10 +362,12 @@ func TestSearchGetOrCreateIndexWithCancellation(t *testing.T) { require.NoError(t, err) require.NotNil(t, support) + key := NamespacedResource{Namespace: "ns", Group: "group", Resource: "resource"} + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Millisecond) defer cancel() - _, err = support.getOrCreateIndex(ctx, NamespacedResource{Namespace: "ns", Group: "group", Resource: "resource"}, "test") + _, err = support.getOrCreateIndex(ctx, key, "test") // Make sure we get context deadline error require.ErrorIs(t, err, context.DeadlineExceeded) @@ -373,16 +375,53 @@ func TestSearchGetOrCreateIndexWithCancellation(t *testing.T) { search.wg.Wait() require.NotEmpty(t, search.buildIndexCalls) + + // Wait until new index is put into cache. + require.Eventually(t, func() bool { + idx, err := support.search.GetIndex(ctx, key) + return err == nil && idx != nil + }, 1*time.Second, 100*time.Millisecond, "Indexing finishes despite context cancellation") + + // Second call to getOrCreateIndex returns index immediately, even if context is canceled, as the index is now ready and cached. + _, err = support.getOrCreateIndex(ctx, key, "test") + require.NoError(t, err) } -type slowSearchBackend struct { +type slowSearchBackendWithCache struct { mockSearchBackend wg sync.WaitGroup + + mu sync.Mutex + cache map[NamespacedResource]ResourceIndex } -func (m *slowSearchBackend) BuildIndex(ctx context.Context, key NamespacedResource, size int64, resourceVersion int64, fields SearchableDocumentFields, reason string, builder func(index ResourceIndex) (int64, error)) (ResourceIndex, error) { +func (m *slowSearchBackendWithCache) GetIndex(ctx context.Context, key NamespacedResource) (ResourceIndex, error) { + m.mu.Lock() + defer m.mu.Unlock() + return m.cache[key], nil +} + +func (m *slowSearchBackendWithCache) BuildIndex(ctx context.Context, key NamespacedResource, size int64, resourceVersion int64, fields SearchableDocumentFields, reason string, builder func(index ResourceIndex) (int64, error)) (ResourceIndex, error) { m.wg.Add(1) defer m.wg.Done() + time.Sleep(1 * time.Second) - return m.mockSearchBackend.BuildIndex(ctx, key, size, resourceVersion, fields, reason, builder) + + // Simulate erroring out when context is cancelled. + if ctx.Err() != nil { + return nil, ctx.Err() + } + idx, err := m.mockSearchBackend.BuildIndex(ctx, key, size, resourceVersion, fields, reason, builder) + if err != nil { + return nil, err + } + + m.mu.Lock() + defer m.mu.Unlock() + + if m.cache == nil { + m.cache = make(map[NamespacedResource]ResourceIndex) + } + m.cache[key] = idx + return idx, nil } From e022b6593b58f288a71e210d5161e545a3fa46b7 Mon Sep 17 00:00:00 2001 From: "lean.dev" <34773040+leandro-deveikis@users.noreply.github.com> Date: Thu, 31 Jul 2025 12:51:04 +0100 Subject: [PATCH 045/114] SecretsManager: fix log (#108970) --- pkg/storage/secret/metadata/secure_value_store.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/storage/secret/metadata/secure_value_store.go b/pkg/storage/secret/metadata/secure_value_store.go index a9c2d0664d6..5782ee9a3e4 100644 --- a/pkg/storage/secret/metadata/secure_value_store.go +++ b/pkg/storage/secret/metadata/secure_value_store.go @@ -265,7 +265,7 @@ func (s *secureValueMetadataStorage) Read(ctx context.Context, namespace xkube.N defer span.End() defer func() { - logging.FromContext(ctx).Info("SecureValueMetadataStorage.Read", "namespace", namespace, "name", name, "success", readErr != nil, "error", readErr) + logging.FromContext(ctx).Info("SecureValueMetadataStorage.Read", "namespace", namespace, "name", name, "success", readErr == nil, "error", readErr) s.metrics.SecureValueMetadataGetDuration.Observe(time.Since(start).Seconds()) s.metrics.SecureValueMetadataGetCount.Inc() From 3e752c2e35e20e30f52504aa8225cb2fc0e17dad Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 31 Jul 2025 13:11:46 +0100 Subject: [PATCH 046/114] Update dependency typescript to v5.8.3 (#108966) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .../app/plugins/datasource/loki/package.json | 2 +- yarn.lock | 22 +------------------ 2 files changed, 2 insertions(+), 22 deletions(-) diff --git a/public/app/plugins/datasource/loki/package.json b/public/app/plugins/datasource/loki/package.json index 75a465d608c..cffc2dc7dcf 100644 --- a/public/app/plugins/datasource/loki/package.json +++ b/public/app/plugins/datasource/loki/package.json @@ -39,7 +39,7 @@ "@types/uuid": "10.0.0", "jest": "29.7.0", "ts-node": "10.9.2", - "typescript": "5.7.3", + "typescript": "5.8.3", "webpack": "5.97.1" }, "peerDependencies": { diff --git a/yarn.lock b/yarn.lock index 94cb5b420db..d609c509945 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2781,7 +2781,7 @@ __metadata: rxjs: "npm:7.8.2" ts-node: "npm:10.9.2" tslib: "npm:2.8.1" - typescript: "npm:5.7.3" + typescript: "npm:5.8.3" uuid: "npm:11.1.0" webpack: "npm:5.97.1" peerDependencies: @@ -31626,16 +31626,6 @@ __metadata: languageName: node linkType: hard -"typescript@npm:5.7.3": - version: 5.7.3 - resolution: "typescript@npm:5.7.3" - bin: - tsc: bin/tsc - tsserver: bin/tsserver - checksum: 10/6a7e556de91db3d34dc51cd2600e8e91f4c312acd8e52792f243c7818dfadb27bae677175fad6947f9c81efb6c57eb6b2d0c736f196a6ee2f1f7d57b74fc92fa - languageName: node - linkType: hard - "typescript@npm:5.8.3, typescript@npm:>=2.7, typescript@npm:>=3 < 6, typescript@npm:^5.0.4, typescript@npm:^5.4.5, typescript@npm:^5.5.4": version: 5.8.3 resolution: "typescript@npm:5.8.3" @@ -31666,16 +31656,6 @@ __metadata: languageName: node linkType: hard -"typescript@patch:typescript@npm%3A5.7.3#optional!builtin": - version: 5.7.3 - resolution: "typescript@patch:typescript@npm%3A5.7.3#optional!builtin::version=5.7.3&hash=5786d5" - bin: - tsc: bin/tsc - tsserver: bin/tsserver - checksum: 10/dc58d777eb4c01973f7fbf1fd808aad49a0efdf545528dab9b07d94fdcb65b8751742804c3057e9619a4627f2d9cc85547fdd49d9f4326992ad0181b49e61d81 - languageName: node - linkType: hard - "typescript@patch:typescript@npm%3A5.8.3#optional!builtin, typescript@patch:typescript@npm%3A>=2.7#optional!builtin, typescript@patch:typescript@npm%3A>=3 < 6#optional!builtin, typescript@patch:typescript@npm%3A^5.0.4#optional!builtin, typescript@patch:typescript@npm%3A^5.4.5#optional!builtin, typescript@patch:typescript@npm%3A^5.5.4#optional!builtin": version: 5.8.3 resolution: "typescript@patch:typescript@npm%3A5.8.3#optional!builtin::version=5.8.3&hash=5786d5" From 41df2e9d26128f2948d1f6c58e73bbf6095bdc8f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 31 Jul 2025 13:27:36 +0100 Subject: [PATCH 047/114] Bump actions/setup-go from 4.2.1 to 5.5.0 (#108286) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/alerting-swagger-gen.yml | 2 +- .github/workflows/alerting-update-module.yml | 2 +- .github/workflows/backend-code-checks.yml | 2 +- .github/workflows/backend-unit-tests.yml | 4 ++-- .github/workflows/codeql-analysis.yml | 2 +- .github/workflows/core-plugins-build-and-release.yml | 2 +- .github/workflows/feature-toggles-ci.yml | 2 +- .github/workflows/go-lint.yml | 2 +- .github/workflows/pr-dependabot-update-go-workspace.yml | 2 +- .github/workflows/pr-e2e-tests.yml | 2 +- .github/workflows/pr-go-workspace-check.yml | 4 ++-- .github/workflows/pr-k8s-codegen-check.yml | 2 +- .github/workflows/pr-test-integration.yml | 6 +++--- .github/workflows/publish-kinds-next.yml | 2 +- .github/workflows/publish-kinds-release.yml | 2 +- .github/workflows/release-pr.yml | 2 +- .github/workflows/run-dashboard-search-e2e.yml | 2 +- .github/workflows/run-schema-v2-e2e.yml | 2 +- .github/workflows/swagger-gen.yml | 2 +- .github/workflows/verify-kinds.yml | 2 +- 20 files changed, 24 insertions(+), 24 deletions(-) diff --git a/.github/workflows/alerting-swagger-gen.yml b/.github/workflows/alerting-swagger-gen.yml index c06304b38a3..19476764b69 100644 --- a/.github/workflows/alerting-swagger-gen.yml +++ b/.github/workflows/alerting-swagger-gen.yml @@ -15,7 +15,7 @@ jobs: fetch-depth: 2 persist-credentials: false - name: Set go version - uses: actions/setup-go@19bb51245e9c80abacb2e91cc42b33fa478b8639 + uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 with: go-version-file: go.mod - name: Build swagger diff --git a/.github/workflows/alerting-update-module.yml b/.github/workflows/alerting-update-module.yml index dce80d065e4..0b8d5730618 100644 --- a/.github/workflows/alerting-update-module.yml +++ b/.github/workflows/alerting-update-module.yml @@ -29,7 +29,7 @@ jobs: fi - name: Setup Go - uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # 5.3.0 + uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # 5.5.0 with: "go-version-file": "go.mod" diff --git a/.github/workflows/backend-code-checks.yml b/.github/workflows/backend-code-checks.yml index b95257d99c3..bf7252a95f5 100644 --- a/.github/workflows/backend-code-checks.yml +++ b/.github/workflows/backend-code-checks.yml @@ -27,7 +27,7 @@ jobs: with: persist-credentials: false - name: Setup Go - uses: actions/setup-go@v5 + uses: actions/setup-go@v5.5.0 with: # Explicitly set Go version to 1.24.1 to ensure consistent OpenAPI spec generation # The crypto/x509 package has additional fields in Go 1.24.1 that affect the generated specs diff --git a/.github/workflows/backend-unit-tests.yml b/.github/workflows/backend-unit-tests.yml index 1c839432829..0d3c468b21f 100644 --- a/.github/workflows/backend-unit-tests.yml +++ b/.github/workflows/backend-unit-tests.yml @@ -57,7 +57,7 @@ jobs: with: persist-credentials: false - name: Setup Go - uses: actions/setup-go@v5 + uses: actions/setup-go@v5.5.0 with: go-version-file: go.mod - name: Run unit tests @@ -92,7 +92,7 @@ jobs: with: persist-credentials: false - name: Setup Go - uses: actions/setup-go@v5 + uses: actions/setup-go@v5.5.0 with: go-version-file: go.mod - name: Setup Enterprise diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index a07cb222898..eea0caa319c 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -50,7 +50,7 @@ jobs: - if: matrix.language == 'go' name: Set go version - uses: actions/setup-go@19bb51245e9c80abacb2e91cc42b33fa478b8639 + uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 with: go-version-file: go.mod diff --git a/.github/workflows/core-plugins-build-and-release.yml b/.github/workflows/core-plugins-build-and-release.yml index c3c934ea2b6..ec8b1db511f 100644 --- a/.github/workflows/core-plugins-build-and-release.yml +++ b/.github/workflows/core-plugins-build-and-release.yml @@ -101,7 +101,7 @@ jobs: echo "has_backend=false" >> "$GITHUB_OUTPUT" fi - name: Setup golang environment - uses: actions/setup-go@19bb51245e9c80abacb2e91cc42b33fa478b8639 + uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 if: steps.check_backend.outputs.has_backend == 'true' with: go-version-file: go.mod diff --git a/.github/workflows/feature-toggles-ci.yml b/.github/workflows/feature-toggles-ci.yml index ab1aa9b2dca..880810a333a 100644 --- a/.github/workflows/feature-toggles-ci.yml +++ b/.github/workflows/feature-toggles-ci.yml @@ -22,7 +22,7 @@ jobs: persist-credentials: false - name: Set up Go - uses: actions/setup-go@v5 + uses: actions/setup-go@v5.5.0 with: go-version-file: 'go.mod' cache: true diff --git a/.github/workflows/go-lint.yml b/.github/workflows/go-lint.yml index 78788f3570c..f870c16a847 100644 --- a/.github/workflows/go-lint.yml +++ b/.github/workflows/go-lint.yml @@ -19,7 +19,7 @@ jobs: - uses: actions/checkout@v4 with: persist-credentials: false - - uses: actions/setup-go@v5 + - uses: actions/setup-go@v5.5.0 with: go-version-file: ./go.mod - name: golangci-lint diff --git a/.github/workflows/pr-dependabot-update-go-workspace.yml b/.github/workflows/pr-dependabot-update-go-workspace.yml index 82169bd69ac..b0dbc68c266 100644 --- a/.github/workflows/pr-dependabot-update-go-workspace.yml +++ b/.github/workflows/pr-dependabot-update-go-workspace.yml @@ -45,7 +45,7 @@ jobs: persist-credentials: false - name: Set go version - uses: actions/setup-go@19bb51245e9c80abacb2e91cc42b33fa478b8639 + uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 with: go-version-file: go.mod diff --git a/.github/workflows/pr-e2e-tests.yml b/.github/workflows/pr-e2e-tests.yml index bbae7d86c65..6ceff2b9d45 100644 --- a/.github/workflows/pr-e2e-tests.yml +++ b/.github/workflows/pr-e2e-tests.yml @@ -113,7 +113,7 @@ jobs: with: persist-credentials: false - name: Setup Go - uses: actions/setup-go@v5 + uses: actions/setup-go@v5.5.0 with: go-version-file: go.mod cache: ${{ !github.event.pull_request.head.repo.fork }} diff --git a/.github/workflows/pr-go-workspace-check.yml b/.github/workflows/pr-go-workspace-check.yml index 91dc3037f11..0fa96a1da3f 100644 --- a/.github/workflows/pr-go-workspace-check.yml +++ b/.github/workflows/pr-go-workspace-check.yml @@ -39,7 +39,7 @@ jobs: persist-credentials: false - name: Set go version - uses: actions/setup-go@19bb51245e9c80abacb2e91cc42b33fa478b8639 + uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 with: cache: false go-version-file: go.mod @@ -76,7 +76,7 @@ jobs: persist-credentials: false - name: Set go version - uses: actions/setup-go@19bb51245e9c80abacb2e91cc42b33fa478b8639 + uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 with: cache: false go-version-file: go.mod diff --git a/.github/workflows/pr-k8s-codegen-check.yml b/.github/workflows/pr-k8s-codegen-check.yml index 6c34674e5c9..5441b4780b5 100644 --- a/.github/workflows/pr-k8s-codegen-check.yml +++ b/.github/workflows/pr-k8s-codegen-check.yml @@ -24,7 +24,7 @@ jobs: persist-credentials: false - name: Set go version - uses: actions/setup-go@19bb51245e9c80abacb2e91cc42b33fa478b8639 + uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 with: go-version-file: go.mod diff --git a/.github/workflows/pr-test-integration.yml b/.github/workflows/pr-test-integration.yml index 76f2068172c..314ca335979 100644 --- a/.github/workflows/pr-test-integration.yml +++ b/.github/workflows/pr-test-integration.yml @@ -37,7 +37,7 @@ jobs: with: persist-credentials: false - name: Setup Go - uses: actions/setup-go@v5 + uses: actions/setup-go@v5.5.0 with: go-version-file: go.mod cache: true @@ -81,7 +81,7 @@ jobs: with: persist-credentials: false - name: Setup Go - uses: actions/setup-go@v5 + uses: actions/setup-go@v5.5.0 with: go-version-file: go.mod cache: true @@ -126,7 +126,7 @@ jobs: with: persist-credentials: false - name: Setup Go - uses: actions/setup-go@v5 + uses: actions/setup-go@v5.5.0 with: go-version-file: go.mod cache: true diff --git a/.github/workflows/publish-kinds-next.yml b/.github/workflows/publish-kinds-next.yml index 495cb35abae..901d6fc3587 100644 --- a/.github/workflows/publish-kinds-next.yml +++ b/.github/workflows/publish-kinds-next.yml @@ -27,7 +27,7 @@ jobs: persist-credentials: false - name: "Setup Go" - uses: "actions/setup-go@19bb51245e9c80abacb2e91cc42b33fa478b8639" + uses: "actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5" with: go-version-file: go.mod diff --git a/.github/workflows/publish-kinds-release.yml b/.github/workflows/publish-kinds-release.yml index 03873c27641..0fd991d66f3 100644 --- a/.github/workflows/publish-kinds-release.yml +++ b/.github/workflows/publish-kinds-release.yml @@ -30,7 +30,7 @@ jobs: persist-credentials: false - name: "Setup Go" - uses: "actions/setup-go@19bb51245e9c80abacb2e91cc42b33fa478b8639" + uses: "actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5" with: go-version-file: go.mod diff --git a/.github/workflows/release-pr.yml b/.github/workflows/release-pr.yml index 2c43ae43de4..8018aaca34d 100644 --- a/.github/workflows/release-pr.yml +++ b/.github/workflows/release-pr.yml @@ -136,7 +136,7 @@ jobs: uses: actions/setup-node@v4 with: node-version-file: .nvmrc - - uses: actions/setup-go@v5 + - uses: actions/setup-go@v5.5.0 with: go-version-file: go.mod - name: Configure git user diff --git a/.github/workflows/run-dashboard-search-e2e.yml b/.github/workflows/run-dashboard-search-e2e.yml index 6eebaf9320a..3988a4390c9 100644 --- a/.github/workflows/run-dashboard-search-e2e.yml +++ b/.github/workflows/run-dashboard-search-e2e.yml @@ -29,7 +29,7 @@ jobs: with: persist-credentials: false - name: Pin Go version to mod file - uses: actions/setup-go@v5 + uses: actions/setup-go@v5.5.0 with: go-version-file: 'go.mod' cache: true diff --git a/.github/workflows/run-schema-v2-e2e.yml b/.github/workflows/run-schema-v2-e2e.yml index a13b953648b..f1ce54a24ff 100644 --- a/.github/workflows/run-schema-v2-e2e.yml +++ b/.github/workflows/run-schema-v2-e2e.yml @@ -22,7 +22,7 @@ jobs: with: persist-credentials: false - name: Pin Go version to mod file - uses: actions/setup-go@v5 + uses: actions/setup-go@v5.5.0 with: go-version-file: 'go.mod' - run: go version diff --git a/.github/workflows/swagger-gen.yml b/.github/workflows/swagger-gen.yml index ee73adc1c28..0e42e0e7441 100644 --- a/.github/workflows/swagger-gen.yml +++ b/.github/workflows/swagger-gen.yml @@ -28,7 +28,7 @@ jobs: with: persist-credentials: false - name: Setup Go - uses: actions/setup-go@v5 + uses: actions/setup-go@v5.5.0 with: go-version-file: go.mod - name: Setup Enterprise diff --git a/.github/workflows/verify-kinds.yml b/.github/workflows/verify-kinds.yml index c793dcb2895..1d6e8e80b38 100644 --- a/.github/workflows/verify-kinds.yml +++ b/.github/workflows/verify-kinds.yml @@ -17,7 +17,7 @@ jobs: persist-credentials: false - name: "Setup Go" - uses: "actions/setup-go@19bb51245e9c80abacb2e91cc42b33fa478b8639" + uses: "actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5" with: go-version-file: go.mod From 454fae17867635c37f14794f7d9cf3189155bdd6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 31 Jul 2025 12:37:33 +0000 Subject: [PATCH 048/114] Update dependency webpack to v5.101.0 (#108972) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- packages/grafana-plugin-configs/package.json | 2 +- packages/grafana-ui/package.json | 2 +- .../datasource/azuremonitor/package.json | 2 +- .../datasource/cloud-monitoring/package.json | 2 +- .../package.json | 2 +- .../grafana-pyroscope-datasource/package.json | 2 +- .../grafana-testdata-datasource/package.json | 2 +- .../plugins/datasource/jaeger/package.json | 2 +- .../app/plugins/datasource/loki/package.json | 2 +- .../app/plugins/datasource/mssql/package.json | 2 +- .../app/plugins/datasource/mysql/package.json | 2 +- .../app/plugins/datasource/parca/package.json | 2 +- .../app/plugins/datasource/tempo/package.json | 2 +- .../plugins/datasource/zipkin/package.json | 2 +- yarn.lock | 91 +++++++++++-------- 16 files changed, 66 insertions(+), 55 deletions(-) diff --git a/package.json b/package.json index de65edb4836..32091e8b962 100644 --- a/package.json +++ b/package.json @@ -251,7 +251,7 @@ "ts-jest": "29.4.0", "ts-node": "10.9.2", "typescript": "5.8.3", - "webpack": "5.97.1", + "webpack": "5.101.0", "webpack-assets-manifest": "^5.1.0", "webpack-cli": "6.0.1", "webpack-livereload-plugin": "3.0.2", diff --git a/packages/grafana-plugin-configs/package.json b/packages/grafana-plugin-configs/package.json index 3ca7029e86d..42233bea781 100644 --- a/packages/grafana-plugin-configs/package.json +++ b/packages/grafana-plugin-configs/package.json @@ -26,7 +26,7 @@ "replace-in-file-webpack-plugin": "1.0.6", "swc-loader": "0.2.6", "typescript": "5.8.3", - "webpack": "5.97.1", + "webpack": "5.101.0", "webpack-bundle-analyzer": "^4.10.2", "webpack-virtual-modules": "^0.6.2" }, diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json index 48bd1e11c66..2193c02d0e8 100644 --- a/packages/grafana-ui/package.json +++ b/packages/grafana-ui/package.json @@ -204,7 +204,7 @@ "storybook": "^8.6.2", "style-loader": "4.0.0", "typescript": "5.8.3", - "webpack": "5.97.1" + "webpack": "5.101.0" }, "peerDependencies": { "react": "^18.0.0", diff --git a/public/app/plugins/datasource/azuremonitor/package.json b/public/app/plugins/datasource/azuremonitor/package.json index a7b771654d3..056b8688f35 100644 --- a/public/app/plugins/datasource/azuremonitor/package.json +++ b/public/app/plugins/datasource/azuremonitor/package.json @@ -43,7 +43,7 @@ "react-select-event": "5.5.1", "ts-node": "10.9.2", "typescript": "5.8.3", - "webpack": "5.97.1" + "webpack": "5.101.0" }, "peerDependencies": { "@grafana/runtime": "*" diff --git a/public/app/plugins/datasource/cloud-monitoring/package.json b/public/app/plugins/datasource/cloud-monitoring/package.json index 6063cb8f0a1..7181c557951 100644 --- a/public/app/plugins/datasource/cloud-monitoring/package.json +++ b/public/app/plugins/datasource/cloud-monitoring/package.json @@ -43,7 +43,7 @@ "react-select-event": "5.5.1", "ts-node": "10.9.2", "typescript": "5.8.3", - "webpack": "5.97.1" + "webpack": "5.101.0" }, "peerDependencies": { "@grafana/runtime": "*" diff --git a/public/app/plugins/datasource/grafana-postgresql-datasource/package.json b/public/app/plugins/datasource/grafana-postgresql-datasource/package.json index bb91cdc6cc6..40ca80efa0b 100644 --- a/public/app/plugins/datasource/grafana-postgresql-datasource/package.json +++ b/public/app/plugins/datasource/grafana-postgresql-datasource/package.json @@ -28,7 +28,7 @@ "jest": "29.7.0", "ts-node": "10.9.2", "typescript": "5.8.3", - "webpack": "5.97.1" + "webpack": "5.101.0" }, "peerDependencies": { "@grafana/runtime": "*" diff --git a/public/app/plugins/datasource/grafana-pyroscope-datasource/package.json b/public/app/plugins/datasource/grafana-pyroscope-datasource/package.json index 5ed1bd369a5..612b26365b0 100644 --- a/public/app/plugins/datasource/grafana-pyroscope-datasource/package.json +++ b/public/app/plugins/datasource/grafana-pyroscope-datasource/package.json @@ -36,7 +36,7 @@ "style-loader": "4.0.0", "ts-node": "10.9.2", "typescript": "5.8.3", - "webpack": "5.97.1" + "webpack": "5.101.0" }, "peerDependencies": { "@grafana/runtime": "*" diff --git a/public/app/plugins/datasource/grafana-testdata-datasource/package.json b/public/app/plugins/datasource/grafana-testdata-datasource/package.json index d8a2c573280..aafaa01cfb5 100644 --- a/public/app/plugins/datasource/grafana-testdata-datasource/package.json +++ b/public/app/plugins/datasource/grafana-testdata-datasource/package.json @@ -37,7 +37,7 @@ "jest": "29.7.0", "ts-node": "10.9.2", "typescript": "5.8.3", - "webpack": "5.97.1" + "webpack": "5.101.0" }, "peerDependencies": { "@grafana/runtime": "*" diff --git a/public/app/plugins/datasource/jaeger/package.json b/public/app/plugins/datasource/jaeger/package.json index b4c8fefdc08..7733e2817ef 100644 --- a/public/app/plugins/datasource/jaeger/package.json +++ b/public/app/plugins/datasource/jaeger/package.json @@ -39,7 +39,7 @@ "jest": "29.7.0", "ts-node": "10.9.2", "typescript": "5.8.3", - "webpack": "5.97.1" + "webpack": "5.101.0" }, "peerDependencies": { "@grafana/runtime": "*" diff --git a/public/app/plugins/datasource/loki/package.json b/public/app/plugins/datasource/loki/package.json index cffc2dc7dcf..937fba60812 100644 --- a/public/app/plugins/datasource/loki/package.json +++ b/public/app/plugins/datasource/loki/package.json @@ -40,7 +40,7 @@ "jest": "29.7.0", "ts-node": "10.9.2", "typescript": "5.8.3", - "webpack": "5.97.1" + "webpack": "5.101.0" }, "peerDependencies": { "@grafana/runtime": "*" diff --git a/public/app/plugins/datasource/mssql/package.json b/public/app/plugins/datasource/mssql/package.json index 46084541319..48940aedbdd 100644 --- a/public/app/plugins/datasource/mssql/package.json +++ b/public/app/plugins/datasource/mssql/package.json @@ -29,7 +29,7 @@ "i18next-parser": "9.3.0", "ts-node": "10.9.2", "typescript": "5.8.3", - "webpack": "5.97.1" + "webpack": "5.101.0" }, "peerDependencies": { "@grafana/runtime": "*" diff --git a/public/app/plugins/datasource/mysql/package.json b/public/app/plugins/datasource/mysql/package.json index cb07deb595a..b8bcf1e9e14 100644 --- a/public/app/plugins/datasource/mysql/package.json +++ b/public/app/plugins/datasource/mysql/package.json @@ -28,7 +28,7 @@ "jest": "29.7.0", "ts-node": "10.9.2", "typescript": "5.8.3", - "webpack": "5.97.1" + "webpack": "5.101.0" }, "peerDependencies": { "@grafana/runtime": "*" diff --git a/public/app/plugins/datasource/parca/package.json b/public/app/plugins/datasource/parca/package.json index 999694c2dac..a3c421b7a16 100644 --- a/public/app/plugins/datasource/parca/package.json +++ b/public/app/plugins/datasource/parca/package.json @@ -29,7 +29,7 @@ "jest": "29.7.0", "ts-node": "10.9.2", "typescript": "5.8.3", - "webpack": "5.97.1" + "webpack": "5.101.0" }, "peerDependencies": { "@grafana/runtime": "*" diff --git a/public/app/plugins/datasource/tempo/package.json b/public/app/plugins/datasource/tempo/package.json index e889d8c30df..7afedbaff53 100644 --- a/public/app/plugins/datasource/tempo/package.json +++ b/public/app/plugins/datasource/tempo/package.json @@ -56,7 +56,7 @@ "react-select-event": "5.5.1", "ts-node": "10.9.2", "typescript": "5.8.3", - "webpack": "5.97.1" + "webpack": "5.101.0" }, "peerDependencies": { "@grafana/runtime": "*" diff --git a/public/app/plugins/datasource/zipkin/package.json b/public/app/plugins/datasource/zipkin/package.json index 4e1a7530854..bb336e2173d 100644 --- a/public/app/plugins/datasource/zipkin/package.json +++ b/public/app/plugins/datasource/zipkin/package.json @@ -32,7 +32,7 @@ "jest": "29.7.0", "ts-node": "10.9.2", "typescript": "5.8.3", - "webpack": "5.97.1" + "webpack": "5.101.0" }, "peerDependencies": { "@grafana/runtime": "*" diff --git a/yarn.lock b/yarn.lock index d609c509945..159ca0b7a83 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2583,7 +2583,7 @@ __metadata: ts-node: "npm:10.9.2" tslib: "npm:2.8.1" typescript: "npm:5.8.3" - webpack: "npm:5.97.1" + webpack: "npm:5.101.0" peerDependencies: "@grafana/runtime": "*" languageName: unknown @@ -2615,7 +2615,7 @@ __metadata: ts-node: "npm:10.9.2" tslib: "npm:2.8.1" typescript: "npm:5.8.3" - webpack: "npm:5.97.1" + webpack: "npm:5.101.0" peerDependencies: "@grafana/runtime": "*" languageName: unknown @@ -2655,7 +2655,7 @@ __metadata: ts-node: "npm:10.9.2" tslib: "npm:2.8.1" typescript: "npm:5.8.3" - webpack: "npm:5.97.1" + webpack: "npm:5.101.0" peerDependencies: "@grafana/runtime": "*" languageName: unknown @@ -2696,7 +2696,7 @@ __metadata: tslib: "npm:2.8.1" typescript: "npm:5.8.3" uuid: "npm:11.1.0" - webpack: "npm:5.97.1" + webpack: "npm:5.101.0" peerDependencies: "@grafana/runtime": "*" languageName: unknown @@ -2739,7 +2739,7 @@ __metadata: tslib: "npm:2.8.1" typescript: "npm:5.8.3" uuid: "npm:11.1.0" - webpack: "npm:5.97.1" + webpack: "npm:5.101.0" peerDependencies: "@grafana/runtime": "*" languageName: unknown @@ -2783,7 +2783,7 @@ __metadata: tslib: "npm:2.8.1" typescript: "npm:5.8.3" uuid: "npm:11.1.0" - webpack: "npm:5.97.1" + webpack: "npm:5.101.0" peerDependencies: "@grafana/runtime": "*" languageName: unknown @@ -2816,7 +2816,7 @@ __metadata: ts-node: "npm:10.9.2" tslib: "npm:2.8.1" typescript: "npm:5.8.3" - webpack: "npm:5.97.1" + webpack: "npm:5.101.0" peerDependencies: "@grafana/runtime": "*" languageName: unknown @@ -2848,7 +2848,7 @@ __metadata: ts-node: "npm:10.9.2" tslib: "npm:2.8.1" typescript: "npm:5.8.3" - webpack: "npm:5.97.1" + webpack: "npm:5.101.0" peerDependencies: "@grafana/runtime": "*" languageName: unknown @@ -2881,7 +2881,7 @@ __metadata: ts-node: "npm:10.9.2" tslib: "npm:2.8.1" typescript: "npm:5.8.3" - webpack: "npm:5.97.1" + webpack: "npm:5.101.0" peerDependencies: "@grafana/runtime": "*" languageName: unknown @@ -2928,7 +2928,7 @@ __metadata: ts-node: "npm:10.9.2" tslib: "npm:2.8.1" typescript: "npm:5.8.3" - webpack: "npm:5.97.1" + webpack: "npm:5.101.0" peerDependencies: "@grafana/runtime": "*" languageName: unknown @@ -2988,7 +2988,7 @@ __metadata: tslib: "npm:2.8.1" typescript: "npm:5.8.3" uuid: "npm:11.1.0" - webpack: "npm:5.97.1" + webpack: "npm:5.101.0" peerDependencies: "@grafana/runtime": "*" languageName: unknown @@ -3024,7 +3024,7 @@ __metadata: ts-node: "npm:10.9.2" tslib: "npm:2.8.1" typescript: "npm:5.8.3" - webpack: "npm:5.97.1" + webpack: "npm:5.101.0" peerDependencies: "@grafana/runtime": "*" languageName: unknown @@ -3439,7 +3439,7 @@ __metadata: swc-loader: "npm:0.2.6" tslib: "npm:2.8.1" typescript: "npm:5.8.3" - webpack: "npm:5.97.1" + webpack: "npm:5.101.0" webpack-bundle-analyzer: "npm:^4.10.2" webpack-virtual-modules: "npm:^0.6.2" languageName: unknown @@ -3875,7 +3875,7 @@ __metadata: uplot: "npm:1.6.32" uuid: "npm:11.1.0" uwrap: "npm:0.1.2" - webpack: "npm:5.97.1" + webpack: "npm:5.101.0" peerDependencies: react: ^18.0.0 react-dom: ^18.0.0 @@ -9476,7 +9476,7 @@ __metadata: languageName: node linkType: hard -"@types/estree@npm:*, @types/estree@npm:1.0.8, @types/estree@npm:^1.0.0, @types/estree@npm:^1.0.5, @types/estree@npm:^1.0.6": +"@types/estree@npm:*, @types/estree@npm:1.0.8, @types/estree@npm:^1.0.0, @types/estree@npm:^1.0.5, @types/estree@npm:^1.0.6, @types/estree@npm:^1.0.8": version: 1.0.8 resolution: "@types/estree@npm:1.0.8" checksum: 10/25a4c16a6752538ffde2826c2cc0c6491d90e69cd6187bef4a006dd2c3c45469f049e643d7e516c515f21484dc3d48fd5c870be158a5beb72f5baf3dc43e4099 @@ -11102,6 +11102,15 @@ __metadata: languageName: node linkType: hard +"acorn-import-phases@npm:^1.0.3": + version: 1.0.4 + resolution: "acorn-import-phases@npm:1.0.4" + peerDependencies: + acorn: ^8.14.0 + checksum: 10/471050ac7d9b61909c837b426de9eeef2958997f6277ad7dea88d5894fd9b3245d8ed4a225c2ca44f814dbb20688009db7a80e525e8196fc9e98c5285b66161d + languageName: node + linkType: hard + "acorn-jsx@npm:^5.3.2": version: 5.3.2 resolution: "acorn-jsx@npm:5.3.2" @@ -11120,7 +11129,7 @@ __metadata: languageName: node linkType: hard -"acorn@npm:^8.0.4, acorn@npm:^8.1.0, acorn@npm:^8.10.0, acorn@npm:^8.11.0, acorn@npm:^8.14.0, acorn@npm:^8.15.0, acorn@npm:^8.4.1, acorn@npm:^8.7.1, acorn@npm:^8.8.0, acorn@npm:^8.8.2": +"acorn@npm:^8.0.4, acorn@npm:^8.1.0, acorn@npm:^8.10.0, acorn@npm:^8.11.0, acorn@npm:^8.15.0, acorn@npm:^8.4.1, acorn@npm:^8.7.1, acorn@npm:^8.8.0, acorn@npm:^8.8.2": version: 8.15.0 resolution: "acorn@npm:8.15.0" bin: @@ -15669,13 +15678,13 @@ __metadata: languageName: node linkType: hard -"enhanced-resolve@npm:^5.17.1": - version: 5.17.1 - resolution: "enhanced-resolve@npm:5.17.1" +"enhanced-resolve@npm:^5.17.1, enhanced-resolve@npm:^5.17.2": + version: 5.18.2 + resolution: "enhanced-resolve@npm:5.18.2" dependencies: graceful-fs: "npm:^4.2.4" tapable: "npm:^2.2.0" - checksum: 10/e8e03cb7a4bf3c0250a89afbd29e5ec20e90ba5fcd026066232a0754864d7d0a393fa6fc0e5379314a6529165a1834b36731147080714459d98924520410d8f5 + checksum: 10/d1b517c908b69d1afbf87b476bbe7dd8d1daf11070127b9ec4f8553f0c6020d30f79103c938776645d569e954e4e04c326f408d2ea3820ade71e72798fb7d36f languageName: node linkType: hard @@ -18634,7 +18643,7 @@ __metadata: uuid: "npm:11.1.0" vis-data: "npm:^8.0.0" vis-network: "npm:10.0.1" - webpack: "npm:5.97.1" + webpack: "npm:5.101.0" webpack-assets-manifest: "npm:^5.1.0" webpack-cli: "npm:6.0.1" webpack-livereload-plugin: "npm:3.0.2" @@ -28789,15 +28798,15 @@ __metadata: languageName: node linkType: hard -"schema-utils@npm:>1.0.0, schema-utils@npm:^4.0.0, schema-utils@npm:^4.2.0, schema-utils@npm:^4.3.0": - version: 4.3.0 - resolution: "schema-utils@npm:4.3.0" +"schema-utils@npm:>1.0.0, schema-utils@npm:^4.0.0, schema-utils@npm:^4.2.0, schema-utils@npm:^4.3.0, schema-utils@npm:^4.3.2": + version: 4.3.2 + resolution: "schema-utils@npm:4.3.2" dependencies: "@types/json-schema": "npm:^7.0.9" ajv: "npm:^8.9.0" ajv-formats: "npm:^2.1.1" ajv-keywords: "npm:^5.1.0" - checksum: 10/86c5a7c72a275c56f140bc3cdd832d56efb11428c88ad588127db12cb9b2c83ccaa9540e115d7baa9c6175b5e360094457e29c44e6fb76787c9498c2eb6df5d6 + checksum: 10/02c32c34aae762d48468f98465a96a167fede637772871c7c7d8923671ddb9f20b2cc6f6e8448ae6bef5363e3597493c655212c8b06a4ee73aa099d9452fbd8b languageName: node linkType: hard @@ -30702,7 +30711,7 @@ __metadata: languageName: node linkType: hard -"terser-webpack-plugin@npm:5.3.14, terser-webpack-plugin@npm:^5.3.1, terser-webpack-plugin@npm:^5.3.10": +"terser-webpack-plugin@npm:5.3.14, terser-webpack-plugin@npm:^5.3.1, terser-webpack-plugin@npm:^5.3.10, terser-webpack-plugin@npm:^5.3.11": version: 5.3.14 resolution: "terser-webpack-plugin@npm:5.3.14" dependencies: @@ -32573,10 +32582,10 @@ __metadata: languageName: node linkType: hard -"webpack-sources@npm:^3.2.3": - version: 3.2.3 - resolution: "webpack-sources@npm:3.2.3" - checksum: 10/a661f41795d678b7526ae8a88cd1b3d8ce71a7d19b6503da8149b2e667fc7a12f9b899041c1665d39e38245ed3a59ab68de648ea31040c3829aa695a5a45211d +"webpack-sources@npm:^3.2.3, webpack-sources@npm:^3.3.3": + version: 3.3.3 + resolution: "webpack-sources@npm:3.3.3" + checksum: 10/ec5d72607e8068467370abccbfff855c596c098baedbe9d198a557ccf198e8546a322836a6f74241492576adba06100286592993a62b63196832cdb53c8bae91 languageName: node linkType: hard @@ -32607,19 +32616,21 @@ __metadata: languageName: node linkType: hard -"webpack@npm:5, webpack@npm:5.97.1, webpack@npm:^5": - version: 5.97.1 - resolution: "webpack@npm:5.97.1" +"webpack@npm:5, webpack@npm:5.101.0, webpack@npm:^5": + version: 5.101.0 + resolution: "webpack@npm:5.101.0" dependencies: "@types/eslint-scope": "npm:^3.7.7" - "@types/estree": "npm:^1.0.6" + "@types/estree": "npm:^1.0.8" + "@types/json-schema": "npm:^7.0.15" "@webassemblyjs/ast": "npm:^1.14.1" "@webassemblyjs/wasm-edit": "npm:^1.14.1" "@webassemblyjs/wasm-parser": "npm:^1.14.1" - acorn: "npm:^8.14.0" + acorn: "npm:^8.15.0" + acorn-import-phases: "npm:^1.0.3" browserslist: "npm:^4.24.0" chrome-trace-event: "npm:^1.0.2" - enhanced-resolve: "npm:^5.17.1" + enhanced-resolve: "npm:^5.17.2" es-module-lexer: "npm:^1.2.1" eslint-scope: "npm:5.1.1" events: "npm:^3.2.0" @@ -32629,17 +32640,17 @@ __metadata: loader-runner: "npm:^4.2.0" mime-types: "npm:^2.1.27" neo-async: "npm:^2.6.2" - schema-utils: "npm:^3.2.0" + schema-utils: "npm:^4.3.2" tapable: "npm:^2.1.1" - terser-webpack-plugin: "npm:^5.3.10" + terser-webpack-plugin: "npm:^5.3.11" watchpack: "npm:^2.4.1" - webpack-sources: "npm:^3.2.3" + webpack-sources: "npm:^3.3.3" peerDependenciesMeta: webpack-cli: optional: true bin: webpack: bin/webpack.js - checksum: 10/665bd3b8c84b20f0b1f250159865e4d3e9b76c682030313d49124d5f8e96357ccdcc799dd9fe0ebf010fdb33dbc59d9863d79676a308e868e360ac98f7c09987 + checksum: 10/edeb24a88cbe96902c601bc1292cd344d27c89c5eceb187f4e9936c8a17356c62b23a5dc441c12b16e62bb6c40804bd10d691135c638398f921760f2dba6a761 languageName: node linkType: hard From 9d2cb8ddf1e692062a03031e727b448932065972 Mon Sep 17 00:00:00 2001 From: Alexa Vargas <239999+axelavargas@users.noreply.github.com> Date: Thu, 31 Jul 2025 14:52:12 +0200 Subject: [PATCH 049/114] Query Library: Add `highlightQuery` to QueryLibraryContext (#108617) * add hightlightedQuery to QueryLibraryContext * revert go changes --- .../features/explore/QueryLibrary/QueryLibraryContext.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/public/app/features/explore/QueryLibrary/QueryLibraryContext.tsx b/public/app/features/explore/QueryLibrary/QueryLibraryContext.tsx index 9037182c156..eeb7c3d9b1d 100644 --- a/public/app/features/explore/QueryLibrary/QueryLibraryContext.tsx +++ b/public/app/features/explore/QueryLibrary/QueryLibraryContext.tsx @@ -24,7 +24,11 @@ export type QueryLibraryContextType = { openDrawer: ( datasourceFilters: string[], onSelectQuery: OnSelectQueryType, - options?: { isReplacingQuery?: boolean; context?: string } + options?: { + isReplacingQuery?: boolean; + context?: string; + highlightQuery?: string; + } ) => void; closeDrawer: () => void; isDrawerOpen: boolean; From d469a32491950e91207e2089aa8347e88d37ef61 Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Thu, 31 Jul 2025 14:55:02 +0200 Subject: [PATCH 050/114] K8s/DataQuery: Register explicit equality function (#108907) --- pkg/services/apiserver/builder/openapi.go | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/pkg/services/apiserver/builder/openapi.go b/pkg/services/apiserver/builder/openapi.go index 9489ac38b95..cde3548a136 100644 --- a/pkg/services/apiserver/builder/openapi.go +++ b/pkg/services/apiserver/builder/openapi.go @@ -1,21 +1,42 @@ package builder import ( + "bytes" + "encoding/json" "maps" "strings" + "sync" + apiequality "k8s.io/apimachinery/pkg/api/equality" "k8s.io/apimachinery/pkg/runtime/schema" openapi "k8s.io/kube-openapi/pkg/common" "k8s.io/kube-openapi/pkg/spec3" spec "k8s.io/kube-openapi/pkg/validation/spec" + "github.com/grafana/grafana-app-sdk/logging" data "github.com/grafana/grafana-plugin-sdk-go/experimental/apis/data/v0alpha1" secret "github.com/grafana/grafana/apps/secret/pkg/apis/secret/v1beta1" common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" ) +var ( + equalityInit sync.Once +) + // This should eventually live in grafana-app-sdk func GetOpenAPIDefinitions(builders []APIGroupBuilder, additionalGetters ...openapi.GetOpenAPIDefinitions) openapi.GetOpenAPIDefinitions { + equalityInit.Do(func() { + // DataQuery has private variables, so it needs an explicit equality helper + err := apiequality.Semantic.AddFunc( + func(a, b data.DataQuery) bool { + aa, _ := json.Marshal(a) + bb, _ := json.Marshal(b) + return bytes.Equal(aa, bb) + }, + ) + logging.DefaultLogger.Error("error initializing DataQuery apiequality", "err", err) + }) + return func(ref openapi.ReferenceCallback) map[string]openapi.OpenAPIDefinition { defs := common.GetOpenAPIDefinitions(ref) // common grafana apis maps.Copy(defs, data.GetOpenAPIDefinitions(ref)) From 9e0f09e6c35c0ba992c9e2110695be68a7c5efca Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 31 Jul 2025 13:03:27 +0000 Subject: [PATCH 051/114] Update dependency yaml to v2.8.0 (#108975) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 159ca0b7a83..637dd342ea9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -33189,11 +33189,11 @@ __metadata: linkType: hard "yaml@npm:^2.0.0, yaml@npm:^2.3.4, yaml@npm:^2.6.0": - version: 2.7.1 - resolution: "yaml@npm:2.7.1" + version: 2.8.0 + resolution: "yaml@npm:2.8.0" bin: yaml: bin.mjs - checksum: 10/af57658d37c5efae4bac7204589b742ae01878a278554d632f01012868cf7fa66cba09b39140f12e7f6ceecc693ae52bcfb737596c4827e6e233338cb3a9528e + checksum: 10/7d4bd9c10d0e467601f496193f2ac254140f8e36f96f5ff7f852b9ce37974168eb7354f4c36dc8837dde527a2043d004b6aff48818ec24a69ab2dd3c6b6c381c languageName: node linkType: hard From 5a67e9d6402d5a1c50531e1770813a3abc3ebb5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Farkas?= Date: Thu, 31 Jul 2025 15:26:43 +0200 Subject: [PATCH 052/114] query: add unit test (#108958) --- pkg/services/query/query_test.go | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/pkg/services/query/query_test.go b/pkg/services/query/query_test.go index 8b0d17a381c..66c4ff17f85 100644 --- a/pkg/services/query/query_test.go +++ b/pkg/services/query/query_test.go @@ -78,6 +78,38 @@ func TestIntegrationParseMetricRequest(t *testing.T) { assert.Len(t, parsedReq.getFlattenedQueries(), 2) }) + t.Run("Test a simple single datasource query with missing time range", func(t *testing.T) { + tc := setup(t, false, nil) + mr := metricRequestWithQueries(t, `{ + "refId": "A", + "datasource": { + "uid": "gIEkMvIVz", + "type": "postgres" + } + }`, `{ + "refId": "B", + "datasource": { + "uid": "gIEkMvIVz", + "type": "postgres" + } + }`) + mr.From = "" + mr.To = "" + parsedReq, err := tc.queryService.parseMetricRequest(context.Background(), tc.signedInUser, true, mr) + require.NoError(t, err) + require.NotNil(t, parsedReq) + assert.False(t, parsedReq.hasExpression) + assert.Len(t, parsedReq.parsedQueries, 1) + assert.Contains(t, parsedReq.parsedQueries, "gIEkMvIVz") + queries := parsedReq.getFlattenedQueries() + assert.Len(t, queries, 2) + + for _, q := range queries { + require.Equal(t, int64(0), q.query.TimeRange.From.UnixMilli()) + require.Equal(t, int64(0), q.query.TimeRange.To.UnixMilli()) + } + }) + t.Run("Test a single datasource query with expressions", func(t *testing.T) { tc := setup(t, false, nil) mr := metricRequestWithQueries(t, `{ From 37ccbd77a9aa17c5304b3c1725ced8f13ea8d056 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 31 Jul 2025 14:30:17 +0100 Subject: [PATCH 053/114] Update react-spectrum monorepo (#108977) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 8 ++++---- yarn.lock | 57 ++++++++++++---------------------------------------- 2 files changed, 17 insertions(+), 48 deletions(-) diff --git a/package.json b/package.json index 32091e8b962..169c2f41f6c 100644 --- a/package.json +++ b/package.json @@ -97,10 +97,10 @@ "@npmcli/package-json": "^5.2.0", "@playwright/test": "1.54.1", "@pmmmwh/react-refresh-webpack-plugin": "0.6.1", - "@react-types/button": "3.10.2", - "@react-types/menu": "3.9.14", - "@react-types/overlays": "3.8.12", - "@react-types/shared": "3.27.0", + "@react-types/button": "3.13.0", + "@react-types/menu": "3.10.3", + "@react-types/overlays": "3.9.0", + "@react-types/shared": "3.31.0", "@rsdoctor/webpack-plugin": "^0.4.6", "@rtk-query/codegen-openapi": "^2.0.0", "@rtsao/plugin-proposal-class-properties": "7.0.1-patch.1", diff --git a/yarn.lock b/yarn.lock index 637dd342ea9..c031af16035 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6660,18 +6660,7 @@ __metadata: languageName: node linkType: hard -"@react-types/button@npm:3.10.2": - version: 3.10.2 - resolution: "@react-types/button@npm:3.10.2" - dependencies: - "@react-types/shared": "npm:^3.27.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/fbc3ac3c11d6ba0f509fa8b26f7de96d77d3ef769fd474d56f1a7f430f727698dd4eb29f0f1dd75de4eb3a3cf9a805a2966e5509c60d1e5ed530f37d1a3ceb58 - languageName: node - linkType: hard - -"@react-types/button@npm:^3.13.0": +"@react-types/button@npm:3.13.0, @react-types/button@npm:^3.13.0": version: 3.13.0 resolution: "@react-types/button@npm:3.13.0" dependencies: @@ -6694,30 +6683,19 @@ __metadata: languageName: node linkType: hard -"@react-types/menu@npm:3.9.14": - version: 3.9.14 - resolution: "@react-types/menu@npm:3.9.14" +"@react-types/menu@npm:3.10.3": + version: 3.10.3 + resolution: "@react-types/menu@npm:3.10.3" dependencies: - "@react-types/overlays": "npm:^3.8.12" - "@react-types/shared": "npm:^3.27.0" + "@react-types/overlays": "npm:^3.9.0" + "@react-types/shared": "npm:^3.31.0" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/81ca0e57bd5affb4f53381334a876070c9c072119853541bd54f8ada91d584c717f6d604c01a648524c61b2b0e9cb2d51870ab5022f470961947f31ada01fd9c + checksum: 10/06944ecd950225642174fa17607560650dfbc67a3d34c68f400701229131c3c7c1f362ea6d50bf2b703a31899ca90a8d9dbf92609f3b76d4e55a9612d404dde6 languageName: node linkType: hard -"@react-types/overlays@npm:3.8.12": - version: 3.8.12 - resolution: "@react-types/overlays@npm:3.8.12" - dependencies: - "@react-types/shared": "npm:^3.27.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/3510add3f70c99a56b3dc160a51f5f71e60e958fd3753f2379eb904ff5442fe30d17841dee79ae77ae3b872a003757995074f45d701453186518dd357d204901 - languageName: node - linkType: hard - -"@react-types/overlays@npm:^3.8.12, @react-types/overlays@npm:^3.9.0": +"@react-types/overlays@npm:3.9.0, @react-types/overlays@npm:^3.9.0": version: 3.9.0 resolution: "@react-types/overlays@npm:3.9.0" dependencies: @@ -6728,16 +6706,7 @@ __metadata: languageName: node linkType: hard -"@react-types/shared@npm:3.27.0": - version: 3.27.0 - resolution: "@react-types/shared@npm:3.27.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/359be934dda6404824479d0dfdf9e694414252da4946b4e486f3cc546a080844a7d4f20507041d3f8f6f5a316c05b28785ca3f4377a7f4ea397ed1f21c2646af - languageName: node - linkType: hard - -"@react-types/shared@npm:^3.27.0, @react-types/shared@npm:^3.31.0": +"@react-types/shared@npm:3.31.0, @react-types/shared@npm:^3.31.0": version: 3.31.0 resolution: "@react-types/shared@npm:3.31.0" peerDependencies: @@ -18375,10 +18344,10 @@ __metadata: "@react-aria/overlays": "npm:3.28.0" "@react-aria/utils": "npm:3.30.0" "@react-awesome-query-builder/ui": "npm:6.6.15" - "@react-types/button": "npm:3.10.2" - "@react-types/menu": "npm:3.9.14" - "@react-types/overlays": "npm:3.8.12" - "@react-types/shared": "npm:3.27.0" + "@react-types/button": "npm:3.13.0" + "@react-types/menu": "npm:3.10.3" + "@react-types/overlays": "npm:3.9.0" + "@react-types/shared": "npm:3.31.0" "@reduxjs/toolkit": "npm:2.8.2" "@rsdoctor/webpack-plugin": "npm:^0.4.6" "@rtk-query/codegen-openapi": "npm:^2.0.0" From 9907191a7b23cac57b340c6f7e4207fe5ae7ad89 Mon Sep 17 00:00:00 2001 From: Konrad Lalik Date: Thu, 31 Jul 2025 15:32:33 +0200 Subject: [PATCH 054/114] Alerting: List V2 - Add a group link to the rule list item (#108960) * Add group link to the aler rule breadcrumbs in the new list view * Remove redundant comments --- .../rule-list/DataSourceRuleListItem.tsx | 4 ++ .../unified/rule-list/FilterView.test.tsx | 24 +++++++ .../unified/rule-list/GrafanaRuleListItem.tsx | 10 ++- .../components/AlertRuleListItem.tsx | 65 ++++++++----------- .../rule-list/components/RuleLocation.tsx | 43 ++++++++++++ 5 files changed, 108 insertions(+), 38 deletions(-) create mode 100644 public/app/features/alerting/unified/rule-list/components/RuleLocation.tsx diff --git a/public/app/features/alerting/unified/rule-list/DataSourceRuleListItem.tsx b/public/app/features/alerting/unified/rule-list/DataSourceRuleListItem.tsx index 3fc7eb8a808..3850092b40d 100644 --- a/public/app/features/alerting/unified/rule-list/DataSourceRuleListItem.tsx +++ b/public/app/features/alerting/unified/rule-list/DataSourceRuleListItem.tsx @@ -5,6 +5,7 @@ import { PromRuleType, RulerRuleDTO, RulesSourceApplication } from 'app/types/un import { createReturnTo } from '../hooks/useReturnTo'; import { Annotation } from '../utils/constants'; +import { groups } from '../utils/navigation'; import { fromRule, fromRulerRule, stringifyIdentifier } from '../utils/rule-id'; import { getRuleName, getRulePluginOrigin, rulerRuleType } from '../utils/rules'; import { createRelativeUrl } from '../utils/url'; @@ -46,11 +47,14 @@ export function DataSourceRuleListItem({ const ruleName = rulerRule ? getRuleName(rulerRule) : rule.name; const labels = rulerRule ? rulerRule.labels : rule.labels; + const groupUrl = groups.detailsPageLink(rulesSource.uid, namespace.name, groupName); + const commonProps: RuleListItemCommonProps = { name: ruleName, rulesSource: rulesSource, application: application, group: groupName, + groupUrl, namespace: namespace.name, href, health: rule.health, diff --git a/public/app/features/alerting/unified/rule-list/FilterView.test.tsx b/public/app/features/alerting/unified/rule-list/FilterView.test.tsx index 908e5978d88..3a7348048da 100644 --- a/public/app/features/alerting/unified/rule-list/FilterView.test.tsx +++ b/public/app/features/alerting/unified/rule-list/FilterView.test.tsx @@ -107,6 +107,30 @@ describe('RuleList - FilterView', () => { expect(await screen.findByText(/No matching rules found/)).toBeInTheDocument(); }); + + it('should render group names as clickable links', async () => { + render( + + ); + + await loadMoreResults(); + + const groupLink = await screen.findByRole('link', { + name: 'test-group-4501', + }); + + expect(groupLink).toBeInTheDocument(); + expect(groupLink).toHaveAttribute( + 'href', + '/alerting/mimir/namespaces/test-mimir-namespace/groups/test-group-4501/view' + ); + }); }); async function loadMoreResults() { diff --git a/public/app/features/alerting/unified/rule-list/GrafanaRuleListItem.tsx b/public/app/features/alerting/unified/rule-list/GrafanaRuleListItem.tsx index 3113c991e09..5667069082b 100644 --- a/public/app/features/alerting/unified/rule-list/GrafanaRuleListItem.tsx +++ b/public/app/features/alerting/unified/rule-list/GrafanaRuleListItem.tsx @@ -1,7 +1,8 @@ import { GrafanaRuleGroupIdentifier } from 'app/types/unified-alerting'; import { GrafanaPromRuleDTO, PromRuleType } from 'app/types/unified-alerting-dto'; -import { GrafanaRulesSource } from '../utils/datasource'; +import { GRAFANA_RULES_SOURCE_NAME, GrafanaRulesSource } from '../utils/datasource'; +import { groups } from '../utils/navigation'; import { totalFromStats } from '../utils/ruleStats'; import { prometheusRuleType } from '../utils/rules'; import { createRelativeUrl } from '../utils/url'; @@ -32,10 +33,17 @@ export function GrafanaRuleListItem({ }: GrafanaRuleListItemProps) { const { name, uid, labels, provenance } = rule; + const groupUrl = groups.detailsPageLink( + GRAFANA_RULES_SOURCE_NAME, + groupIdentifier.namespace.uid, + groupIdentifier.groupName + ); + const commonProps: RuleListItemCommonProps = { name, rulesSource: GrafanaRulesSource, group: groupIdentifier.groupName, + groupUrl, namespace: namespaceName, href: createRelativeUrl(`/alerting/grafana/${uid}/view`), health: rule?.health, 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 3f8c01831e6..5117a8516bd 100644 --- a/public/app/features/alerting/unified/rule-list/components/AlertRuleListItem.tsx +++ b/public/app/features/alerting/unified/rule-list/components/AlertRuleListItem.tsx @@ -4,7 +4,7 @@ import { ReactNode, useEffect, useId } from 'react'; import { GrafanaTheme2 } from '@grafana/data'; import { Trans, t } from '@grafana/i18n'; -import { Alert, Icon, Stack, Text, TextLink, Tooltip, useStyles2 } from '@grafana/ui'; +import { Alert, Stack, Text, TextLink, Tooltip, useStyles2 } from '@grafana/ui'; import { Rule, RuleGroupIdentifierV2, RuleHealth, RulesSourceIdentifier } from 'app/types/unified-alerting'; import { Labels, PromAlertingRuleState, RulerRuleDTO, RulesSourceApplication } from 'app/types/unified-alerting-dto'; @@ -20,8 +20,8 @@ import { createContactPointSearchLink } from '../../utils/misc'; import { RulePluginOrigin } from '../../utils/rules'; import { ListItem } from './ListItem'; -import { DataSourceIcon } from './Namespace'; import { RuleListIcon, RuleOperation } from './RuleListIcon'; +import { RuleLocation } from './RuleLocation'; import { calculateNextEvaluationEstimate } from './util'; export interface AlertRuleListItemProps { @@ -39,6 +39,7 @@ export interface AlertRuleListItemProps { instancesCount?: number; namespace?: string; group?: string; + groupUrl?: string; rulesSource?: RulesSourceIdentifier; application?: RulesSourceApplication; // used for alert rules that use simplified routing @@ -65,6 +66,7 @@ export const AlertRuleListItem = (props: AlertRuleListItemProps) => { instancesCount = 0, namespace, group, + groupUrl, rulesSource, application, contactPoint, @@ -81,7 +83,13 @@ export const AlertRuleListItem = (props: AlertRuleListItemProps) => { if (namespace && group && showLocation) { metadata.push( - + ); } @@ -160,6 +168,7 @@ export function RecordingRuleListItem({ name, namespace, group, + groupUrl, rulesSource, application, href, @@ -175,7 +184,13 @@ export function RecordingRuleListItem({ if (namespace && group && showLocation) { metadata.push( - + ); } @@ -206,6 +221,7 @@ interface RuleOperationListItemProps { name: string; namespace: string; group: string; + groupUrl?: string; rulesSource?: RulesSourceIdentifier; application?: RulesSourceApplication; operation: RuleOperation; @@ -216,6 +232,7 @@ export function RuleOperationListItem({ name, namespace, group, + groupUrl, rulesSource, application, operation, @@ -227,7 +244,13 @@ export function RuleOperationListItem({ if (namespace && group && showLocation) { metadata.push( - + ); } @@ -368,38 +391,6 @@ export const UnknownRuleListItem = ({ ruleName, groupIdentifier, ruleDefinition ); }; -interface RuleLocationProps { - namespace: string; - group: string; - rulesSource?: RulesSourceIdentifier; - application?: RulesSourceApplication; -} - -// @TODO make the datasource / namespace / group click-able to allow further filtering of the list -export const RuleLocation = ({ namespace, group, rulesSource, application }: RuleLocationProps) => { - const isGrafanaApp = application === 'grafana'; - const isDataSourceApp = !!rulesSource && !!application && !isGrafanaApp; - - return ( - - {isGrafanaApp && } - {isDataSourceApp && ( - - - - - - )} - - - {namespace} - - {group} - - - ); -}; - const getStyles = (theme: GrafanaTheme2) => ({ alertListItemContainer: css({ position: 'relative', diff --git a/public/app/features/alerting/unified/rule-list/components/RuleLocation.tsx b/public/app/features/alerting/unified/rule-list/components/RuleLocation.tsx new file mode 100644 index 00000000000..b0d4b31de5d --- /dev/null +++ b/public/app/features/alerting/unified/rule-list/components/RuleLocation.tsx @@ -0,0 +1,43 @@ +import { Icon, Stack, TextLink, Tooltip } from '@grafana/ui'; +import { RulesSourceIdentifier } from 'app/types/unified-alerting'; +import { RulesSourceApplication } from 'app/types/unified-alerting-dto'; + +import { DataSourceIcon } from './Namespace'; + +interface RuleLocationProps { + namespace: string; + group: string; + groupUrl?: string; + rulesSource?: RulesSourceIdentifier; + application?: RulesSourceApplication; +} + +export function RuleLocation({ namespace, group, groupUrl, rulesSource, application }: RuleLocationProps) { + const isGrafanaApp = application === 'grafana'; + const isDataSourceApp = !!rulesSource && !!application && !isGrafanaApp; + + return ( + + {isGrafanaApp && } + {isDataSourceApp && ( + + + + + + )} + + + {namespace} + + {groupUrl ? ( + + {group} + + ) : ( + group + )} + + + ); +} From a3cdfce25a43526fdf6688f701f025bced4559ca Mon Sep 17 00:00:00 2001 From: Dana Axinte <53751979+dana-axinte@users.noreply.github.com> Date: Thu, 31 Jul 2025 14:45:59 +0100 Subject: [PATCH 055/114] SecretsManager: Consolidation service and ability to run via cli (#108774) * list all encrypted values and count * separate interfaces * add time filter to global queries * initial secrets consolidation * Revert defaults * More verbose description of the operation * Add consolidation tests and tracing * Fix lint * Revert debug log --- pkg/cmd/grafana-cli/commands/commands.go | 12 + .../secretsconsolidation.go | 13 + .../apis/secret/contracts/encryption.go | 4 + .../apis/secret/service/consolidation.go | 87 ++++++ .../apis/secret/service/consolidation_test.go | 281 ++++++++++++++++++ .../apis/secret/testutils/testutils.go | 11 + pkg/server/runner.go | 38 +-- pkg/server/wire.go | 2 + pkg/server/wire_gen.go | 36 ++- pkg/server/wireexts_oss.go | 2 + 10 files changed, 467 insertions(+), 19 deletions(-) create mode 100644 pkg/cmd/grafana-cli/commands/secretsconsolidation/secretsconsolidation.go create mode 100644 pkg/registry/apis/secret/service/consolidation.go create mode 100644 pkg/registry/apis/secret/service/consolidation_test.go diff --git a/pkg/cmd/grafana-cli/commands/commands.go b/pkg/cmd/grafana-cli/commands/commands.go index f55209ac1d6..ae7f5f93f67 100644 --- a/pkg/cmd/grafana-cli/commands/commands.go +++ b/pkg/cmd/grafana-cli/commands/commands.go @@ -7,6 +7,7 @@ import ( "github.com/urfave/cli/v2" "github.com/grafana/grafana/pkg/cmd/grafana-cli/commands/datamigrations" + "github.com/grafana/grafana/pkg/cmd/grafana-cli/commands/secretsconsolidation" "github.com/grafana/grafana/pkg/cmd/grafana-cli/commands/secretsmigrations" "github.com/grafana/grafana/pkg/cmd/grafana-cli/logger" "github.com/grafana/grafana/pkg/cmd/grafana-cli/utils" @@ -184,6 +185,17 @@ var adminCommands = []*cli.Command{ }, }, }, + { + Name: "secrets-consolidation", + Usage: "Runs an operation that re-encrypts all encrypted values in your database with new data keys", + Subcommands: []*cli.Command{ + { + Name: "consolidate", + Usage: "Re-encrypts all encrypted values with new data keys and deletes the old deactivated data keys. Returns ok unless there is an error. Safe to execute multiple times.", + Action: runRunnerCommand(secretsconsolidation.ConsolidateSecrets), + }, + }, + }, } var Commands = []*cli.Command{ diff --git a/pkg/cmd/grafana-cli/commands/secretsconsolidation/secretsconsolidation.go b/pkg/cmd/grafana-cli/commands/secretsconsolidation/secretsconsolidation.go new file mode 100644 index 00000000000..9be8e5f5a2a --- /dev/null +++ b/pkg/cmd/grafana-cli/commands/secretsconsolidation/secretsconsolidation.go @@ -0,0 +1,13 @@ +package secretsconsolidation + +import ( + "context" + + "github.com/grafana/grafana/pkg/cmd/grafana-cli/utils" + "github.com/grafana/grafana/pkg/server" +) + +func ConsolidateSecrets(_ utils.CommandLine, runner server.Runner) error { + err := runner.SecretsConsolidationService.Consolidate(context.Background()) + return err +} diff --git a/pkg/registry/apis/secret/contracts/encryption.go b/pkg/registry/apis/secret/contracts/encryption.go index f0596de9034..f24fe73a544 100644 --- a/pkg/registry/apis/secret/contracts/encryption.go +++ b/pkg/registry/apis/secret/contracts/encryption.go @@ -38,3 +38,7 @@ type GlobalEncryptedValueStorage interface { ListAll(ctx context.Context, opts ListOpts, untilTime *int64) ([]*EncryptedValue, error) CountAll(ctx context.Context, untilTime *int64) (int64, error) } + +type ConsolidationService interface { + Consolidate(ctx context.Context) error +} diff --git a/pkg/registry/apis/secret/service/consolidation.go b/pkg/registry/apis/secret/service/consolidation.go new file mode 100644 index 00000000000..b0baea659c4 --- /dev/null +++ b/pkg/registry/apis/secret/service/consolidation.go @@ -0,0 +1,87 @@ +package service + +import ( + "context" + "fmt" + + "github.com/grafana/grafana-app-sdk/logging" + "github.com/grafana/grafana/pkg/registry/apis/secret/contracts" + otelcodes "go.opentelemetry.io/otel/codes" + "go.opentelemetry.io/otel/trace" +) + +type ConsolidationService struct { + tracer trace.Tracer + globalDataKeyStore contracts.GlobalDataKeyStorage + encryptedValueStore contracts.EncryptedValueStorage + globalEncryptedValueStore contracts.GlobalEncryptedValueStorage + encryptionManager contracts.EncryptionManager +} + +func ProvideConsolidationService( + tracer trace.Tracer, + globalDataKeyStore contracts.GlobalDataKeyStorage, + encryptedValueStore contracts.EncryptedValueStorage, + globalEncryptedValueStore contracts.GlobalEncryptedValueStorage, + encryptionManager contracts.EncryptionManager, +) contracts.ConsolidationService { + return &ConsolidationService{ + tracer: tracer, + globalDataKeyStore: globalDataKeyStore, + encryptedValueStore: encryptedValueStore, + globalEncryptedValueStore: globalEncryptedValueStore, + encryptionManager: encryptionManager, + } +} + +func (s *ConsolidationService) Consolidate(ctx context.Context) (err error) { + ctx, span := s.tracer.Start(ctx, "ConsolidationService.Consolidate") + defer span.End() + + defer func() { + if err != nil { + span.SetStatus(otelcodes.Error, err.Error()) + span.RecordError(err) + } + }() + + // Disable all active data keys. + // This will ensure that no new data can be encrypted with the old keys. + err = s.globalDataKeyStore.DisableAllDataKeys(ctx) + if err != nil { + return fmt.Errorf("disabling all data keys: %w", err) + } + + // List all encrypted values. + encryptedValues, err := s.globalEncryptedValueStore.ListAll(ctx, contracts.ListOpts{}, nil) + if err != nil { + return fmt.Errorf("listing all encrypted values: %w", err) + } + + for _, ev := range encryptedValues { + // Decrypt the value using its old data key. + decryptedValue, err := s.encryptionManager.Decrypt(ctx, ev.Namespace, ev.EncryptedData) + if err != nil { + logging.FromContext(ctx).Error("Failed to decrypt value", "namespace", ev.Namespace, "name", ev.Name, "error", err) + continue + } + + // Re-encrypt the value using a new data key. + reEncryptedValue, err := s.encryptionManager.Encrypt(ctx, ev.Namespace, decryptedValue) + if err != nil { + logging.FromContext(ctx).Error("Failed to re-encrypt value", "namespace", ev.Namespace, "name", ev.Name, "error", err) + continue + } + + // Update the encrypted value in the store. + err = s.encryptedValueStore.Update(ctx, ev.Namespace, ev.Name, ev.Version, reEncryptedValue) + if err != nil { + logging.FromContext(ctx).Error("Failed to update encrypted value", "namespace", ev.Namespace, "name", ev.Name, "error", err) + continue + } + } + + // TODO: After all values are re-encrypted, we can safely remove the old data keys. + + return nil +} diff --git a/pkg/registry/apis/secret/service/consolidation_test.go b/pkg/registry/apis/secret/service/consolidation_test.go new file mode 100644 index 00000000000..57ac94253c9 --- /dev/null +++ b/pkg/registry/apis/secret/service/consolidation_test.go @@ -0,0 +1,281 @@ +package service_test + +import ( + "context" + "testing" + + "github.com/grafana/authlib/authn" + "github.com/grafana/authlib/types" + secretv1beta1 "github.com/grafana/grafana/apps/secret/pkg/apis/secret/v1beta1" + "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" + "k8s.io/utils/ptr" +) + +// mockGlobalEncryptedValueStorage wraps the real storage and allows injecting behavior during ListAll +type mockGlobalEncryptedValueStorage struct { + real contracts.GlobalEncryptedValueStorage + sut *testutils.Sut + ctx context.Context + onListAll func() +} + +func (m *mockGlobalEncryptedValueStorage) ListAll(ctx context.Context, opts contracts.ListOpts, untilTime *int64) ([]*contracts.EncryptedValue, error) { + if m.onListAll != nil { + m.onListAll() + } + return m.real.ListAll(ctx, opts, untilTime) +} + +func (m *mockGlobalEncryptedValueStorage) CountAll(ctx context.Context, untilTime *int64) (int64, error) { + return m.real.CountAll(ctx, untilTime) +} + +func TestConsolidation(t *testing.T) { + t.Parallel() + + t.Run("consolidation re-encrypts values but preserves decrypted content", func(t *testing.T) { + t.Parallel() + sut := testutils.Setup(t) + + ctx := context.Background() + createAuthContext := func(ctx context.Context, namespace string, identityType types.IdentityType) context.Context { + return types.WithAuthInfo(ctx, &identity.StaticRequester{ + Type: identityType, + Namespace: namespace, + AccessTokenClaims: &authn.Claims[authn.AccessTokenClaims]{ + Rest: authn.AccessTokenClaims{ + Permissions: []string{"secret.grafana.app/securevalues:decrypt"}, + ServiceIdentity: "decrypter1", + }, + }, + }) + } + + // Create several secure values in different namespaces + testCases := []struct { + name string + namespace string + value string + }{ + {"test-secret-1", "namespace1", "test-value-1"}, + {"test-secret-2", "namespace1", "test-value-2"}, + {"test-secret-3", "namespace2", "test-value-3"}, + {"test-secret-4", "namespace2", "test-value-4"}, + } + + var originalDecryptedValues []string + var originalEncryptedData [][]byte + + // Create secure values and store their original decrypted values and encrypted data + for _, tc := range testCases { + sv := &secretv1beta1.SecureValue{ + ObjectMeta: metav1.ObjectMeta{ + Name: tc.name, + Namespace: tc.namespace, + }, + Spec: secretv1beta1.SecureValueSpec{ + Value: ptr.To(secretv1beta1.NewExposedSecureValue(tc.value)), + Decrypters: []string{"decrypter1"}, + }, + } + + createdSv, err := sut.CreateSv(ctx, testutils.CreateSvWithSv(sv)) + require.NoError(t, err) + require.NotNil(t, createdSv) + + // Store the original decrypted data and encrypted data + authCtx := createAuthContext(ctx, tc.namespace, types.TypeAccessPolicy) + decryptedValue, err := sut.DecryptStorage.Decrypt(authCtx, xkube.Namespace(tc.namespace), tc.name) + require.NoError(t, err) + originalDecryptedValues = append(originalDecryptedValues, decryptedValue.DangerouslyExposeAndConsumeValue()) + + encryptedValue, err := sut.EncryptedValueStorage.Get(ctx, tc.namespace, tc.name, 1) + require.NoError(t, err) + require.NotNil(t, encryptedValue) + originalEncryptedData = append(originalEncryptedData, encryptedValue.EncryptedData) + } + + // Run consolidation + err := sut.ConsolidationService.Consolidate(ctx) + require.NoError(t, err) + + for i, tc := range testCases { + // Verify that the decrypted values are still the same + authCtx := createAuthContext(ctx, tc.namespace, types.TypeAccessPolicy) + decryptedValue, err := sut.DecryptStorage.Decrypt(authCtx, xkube.Namespace(tc.namespace), tc.name) + require.NoError(t, err) + require.Equal(t, originalDecryptedValues[i], decryptedValue.DangerouslyExposeAndConsumeValue()) + + // Verify that the encrypted data has changed (indicating re-encryption) + encryptedValue, err := sut.EncryptedValueStorage.Get(ctx, tc.namespace, tc.name, 1) + require.NoError(t, err) + require.NotEqual(t, originalEncryptedData[i], encryptedValue.EncryptedData) + } + }) + + t.Run("consolidation handles secrets created during the process", func(t *testing.T) { + t.Parallel() + sut := testutils.Setup(t) + + ctx := context.Background() + createAuthContext := func(ctx context.Context, namespace string, identityType types.IdentityType) context.Context { + return types.WithAuthInfo(ctx, &identity.StaticRequester{ + Type: identityType, + Namespace: namespace, + AccessTokenClaims: &authn.Claims[authn.AccessTokenClaims]{ + Rest: authn.AccessTokenClaims{ + Permissions: []string{"secret.grafana.app/securevalues:decrypt"}, + ServiceIdentity: "decrypter1", + }, + }, + }) + } + + // Create initial secure values + initialSecrets := []struct { + name string + namespace string + value string + }{ + {"initial-secret-1", "namespace1", "initial-value-1"}, + {"initial-secret-2", "namespace2", "initial-value-2"}, + } + + var initialDecryptedValues []string + var initialEncryptedData [][]byte + + for _, tc := range initialSecrets { + sv := &secretv1beta1.SecureValue{ + ObjectMeta: metav1.ObjectMeta{ + Name: tc.name, + Namespace: tc.namespace, + }, + Spec: secretv1beta1.SecureValueSpec{ + Value: ptr.To(secretv1beta1.NewExposedSecureValue(tc.value)), + Decrypters: []string{"decrypter1"}, + }, + } + + _, err := sut.CreateSv(ctx, testutils.CreateSvWithSv(sv)) + require.NoError(t, err) + + // Store original decrypted values and encrypted data + authCtx := createAuthContext(ctx, tc.namespace, types.TypeAccessPolicy) + decryptedValue, err := sut.DecryptStorage.Decrypt(authCtx, xkube.Namespace(tc.namespace), tc.name) + require.NoError(t, err) + initialDecryptedValues = append(initialDecryptedValues, decryptedValue.DangerouslyExposeAndConsumeValue()) + + encryptedValue, err := sut.EncryptedValueStorage.Get(ctx, tc.namespace, tc.name, 1) + require.NoError(t, err) + initialEncryptedData = append(initialEncryptedData, encryptedValue.EncryptedData) + } + + // Secrets to be created during consolidation (after data keys are disabled) + var newSecretDecryptedValues []string + var newSecretEncryptedData [][]byte + + // Create a mock GlobalEncryptedValueStorage that will create new secrets when ListAll is called + mockStorage := &mockGlobalEncryptedValueStorage{ + real: sut.GlobalEncryptedValueStorage, + sut: &sut, + ctx: ctx, + onListAll: func() { + // This function is called during consolidation, after data keys are disabled + // but before the re-encryption loop begins + newSecrets := []struct { + name string + namespace string + value string + desc string + }{ + {"new-secret-1", "namespace1", "new-value-1", "New secret created during consolidation"}, + {"new-secret-2", "namespace3", "new-value-2", "Another new secret during consolidation"}, + } + + for _, tc := range newSecrets { + sv := &secretv1beta1.SecureValue{ + ObjectMeta: metav1.ObjectMeta{ + Name: tc.name, + Namespace: tc.namespace, + }, + Spec: secretv1beta1.SecureValueSpec{ + Description: tc.desc, + Value: ptr.To(secretv1beta1.NewExposedSecureValue(tc.value)), + Decrypters: []string{"decrypter1"}, + }, + } + + _, err := sut.CreateSv(ctx, testutils.CreateSvWithSv(sv)) + require.NoError(t, err) + + // Store their decrypted values and original encrypted data + authCtx := createAuthContext(ctx, tc.namespace, types.TypeAccessPolicy) + decryptedValue, err := sut.DecryptStorage.Decrypt(authCtx, xkube.Namespace(tc.namespace), tc.name) + require.NoError(t, err) + newSecretDecryptedValues = append(newSecretDecryptedValues, decryptedValue.DangerouslyExposeAndConsumeValue()) + + encryptedValue, err := sut.EncryptedValueStorage.Get(ctx, tc.namespace, tc.name, 1) + require.NoError(t, err) + newSecretEncryptedData = append(newSecretEncryptedData, encryptedValue.EncryptedData) + } + }, + } + + // Create a custom consolidation service that uses the mocked storage + tracer := noop.NewTracerProvider().Tracer("test") + customConsolidationService := service.ProvideConsolidationService( + tracer, + sut.GlobalDataKeyStore, + sut.EncryptedValueStorage, + mockStorage, + sut.EncryptionManager, + ) + + // Run consolidation + err := customConsolidationService.Consolidate(ctx) + require.NoError(t, err) + + for i, tc := range initialSecrets { + // Verify that all initial secrets still decrypt to the same values + authCtx := createAuthContext(ctx, tc.namespace, types.TypeAccessPolicy) + decryptedValue, err := sut.DecryptStorage.Decrypt(authCtx, xkube.Namespace(tc.namespace), tc.name) + require.NoError(t, err) + require.Equal(t, initialDecryptedValues[i], decryptedValue.DangerouslyExposeAndConsumeValue()) + + // Verify that the encrypted data has changed (indicating re-encryption) + encryptedValue, err := sut.EncryptedValueStorage.Get(ctx, tc.namespace, tc.name, 1) + require.NoError(t, err) + require.NotEqual(t, initialEncryptedData[i], encryptedValue.EncryptedData) + } + + // Verify that the new secrets (created during consolidation) also decrypt correctly + // These secrets should have been re-encrypted as well during the consolidation process + newSecrets := []struct { + name string + namespace string + }{ + {"new-secret-1", "namespace1"}, + {"new-secret-2", "namespace3"}, + } + + for i, tc := range newSecrets { + authCtx := createAuthContext(ctx, tc.namespace, types.TypeAccessPolicy) + decryptedValue, err := sut.DecryptStorage.Decrypt(authCtx, xkube.Namespace(tc.namespace), tc.name) + require.NoError(t, err) + require.Equal(t, newSecretDecryptedValues[i], decryptedValue.DangerouslyExposeAndConsumeValue()) + + // Verify that the encrypted data has changed from what it was when first created + // (indicating it was re-encrypted during consolidation) + encryptedValue, err := sut.EncryptedValueStorage.Get(ctx, tc.namespace, tc.name, 1) + require.NoError(t, err) + require.NotEqual(t, newSecretEncryptedData[i], encryptedValue.EncryptedData) + } + }) +} diff --git a/pkg/registry/apis/secret/testutils/testutils.go b/pkg/registry/apis/secret/testutils/testutils.go index 8b6765ba642..ebe8b109b81 100644 --- a/pkg/registry/apis/secret/testutils/testutils.go +++ b/pkg/registry/apis/secret/testutils/testutils.go @@ -87,6 +87,9 @@ func Setup(t *testing.T, opts ...func(*SetupConfig)) Sut { store, err := encryptionstorage.ProvideDataKeyStorage(database, tracer, nil) require.NoError(t, err) + globalDataKeyStore, err := encryptionstorage.ProvideGlobalDataKeyStorage(database, tracer, nil) + require.NoError(t, err) + usageStats := &usagestats.UsageStatsMock{T: t} enc, err := cipher.ProvideAESGCMCipherService(tracer, usageStats) @@ -135,6 +138,8 @@ func Setup(t *testing.T, opts ...func(*SetupConfig)) Sut { decryptService, err := decrypt.ProvideDecryptService(testCfg, tracer, decryptStorage) require.NoError(t, err) + consolidationService := service.ProvideConsolidationService(tracer, globalDataKeyStore, encryptedValueStorage, globalEncryptedValueStorage, encryptionManager) + return Sut{ SecureValueService: secureValueService, SecureValueMetadataStorage: secureValueMetadataStorage, @@ -145,6 +150,9 @@ func Setup(t *testing.T, opts ...func(*SetupConfig)) Sut { SQLKeeper: sqlKeeper, Database: database, AccessClient: accessClient, + ConsolidationService: consolidationService, + EncryptionManager: encryptionManager, + GlobalDataKeyStore: globalDataKeyStore, } } @@ -158,6 +166,9 @@ type Sut struct { SQLKeeper *sqlkeeper.SQLKeeper Database *database.Database AccessClient types.AccessClient + ConsolidationService contracts.ConsolidationService + EncryptionManager contracts.EncryptionManager + GlobalDataKeyStore contracts.GlobalDataKeyStorage } type CreateSvConfig struct { diff --git a/pkg/server/runner.go b/pkg/server/runner.go index ae9ccf28469..cc31af9711b 100644 --- a/pkg/server/runner.go +++ b/pkg/server/runner.go @@ -8,32 +8,36 @@ import ( "github.com/grafana/grafana/pkg/services/secrets/manager" "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/setting" + + "github.com/grafana/grafana/pkg/registry/apis/secret/contracts" ) type Runner struct { - Cfg *setting.Cfg - SQLStore db.DB - SettingsProvider setting.Provider - Features featuremgmt.FeatureToggles - EncryptionService encryption.Internal - SecretsService *manager.SecretsService - SecretsMigrator secrets.Migrator - UserService user.Service + Cfg *setting.Cfg + SQLStore db.DB + SettingsProvider setting.Provider + Features featuremgmt.FeatureToggles + EncryptionService encryption.Internal + SecretsService *manager.SecretsService + SecretsMigrator secrets.Migrator + UserService user.Service + SecretsConsolidationService contracts.ConsolidationService } func NewRunner(cfg *setting.Cfg, sqlStore db.DB, settingsProvider setting.Provider, encryptionService encryption.Internal, features featuremgmt.FeatureToggles, secretsService *manager.SecretsService, secretsMigrator secrets.Migrator, - userService user.Service, + userService user.Service, secretsConsolidationService contracts.ConsolidationService, ) Runner { return Runner{ - Cfg: cfg, - SQLStore: sqlStore, - SettingsProvider: settingsProvider, - EncryptionService: encryptionService, - SecretsService: secretsService, - SecretsMigrator: secretsMigrator, - Features: features, - UserService: userService, + Cfg: cfg, + SQLStore: sqlStore, + SettingsProvider: settingsProvider, + EncryptionService: encryptionService, + SecretsService: secretsService, + SecretsMigrator: secretsMigrator, + Features: features, + UserService: userService, + SecretsConsolidationService: secretsConsolidationService, } } diff --git a/pkg/server/wire.go b/pkg/server/wire.go index 741ec53ede8..a56c1bff1a6 100644 --- a/pkg/server/wire.go +++ b/pkg/server/wire.go @@ -429,7 +429,9 @@ var wireBasicSet = wire.NewSet( secretdecrypt.ProvideDecryptAuthorizer, secretdecrypt.ProvideDecryptService, secretencryption.ProvideDataKeyStorage, + secretencryption.ProvideGlobalDataKeyStorage, secretencryption.ProvideEncryptedValueStorage, + secretencryption.ProvideGlobalEncryptedValueStorage, secretsecurevalueservice.ProvideSecureValueService, secretvalidator.ProvideKeeperValidator, secretvalidator.ProvideSecureValueValidator, diff --git a/pkg/server/wire_gen.go b/pkg/server/wire_gen.go index 3d055c451e5..cf76c869512 100644 --- a/pkg/server/wire_gen.go +++ b/pkg/server/wire_gen.go @@ -1453,7 +1453,39 @@ func InitializeForCLI(cfg *setting.Cfg) (Runner, error) { if err != nil { return Runner{}, err } - runner := NewRunner(cfg, sqlStore, ossImpl, serviceService, featureToggles, secretsService, secretsMigrator, userService) + tracer := otelTracer() + databaseDatabase := database5.ProvideDatabase(sqlStore, tracer) + registerer := metrics.ProvideRegisterer() + globalDataKeyStorage, err := encryption.ProvideGlobalDataKeyStorage(databaseDatabase, tracer, registerer) + if err != nil { + return Runner{}, err + } + encryptedValueStorage, err := encryption.ProvideEncryptedValueStorage(databaseDatabase, tracer) + if err != nil { + return Runner{}, err + } + globalEncryptedValueStorage, err := encryption.ProvideGlobalEncryptedValueStorage(databaseDatabase, tracer) + if err != nil { + return Runner{}, err + } + dataKeyStorage, err := encryption.ProvideDataKeyStorage(databaseDatabase, tracer, registerer) + if err != nil { + return Runner{}, err + } + cipher, err := service11.ProvideAESGCMCipherService(tracer, usageStats) + if err != nil { + return Runner{}, err + } + providerConfig, err := kmsproviders.ProvideOSSKMSProviders(cfg, cipher) + if err != nil { + return Runner{}, err + } + encryptionManager, err := manager4.ProvideEncryptionManager(tracer, dataKeyStorage, usageStats, cipher, providerConfig) + if err != nil { + return Runner{}, err + } + consolidationService := service12.ProvideConsolidationService(tracer, globalDataKeyStorage, encryptedValueStorage, globalEncryptedValueStorage, encryptionManager) + runner := NewRunner(cfg, sqlStore, ossImpl, serviceService, featureToggles, secretsService, secretsMigrator, userService, consolidationService) return runner, nil } @@ -1540,7 +1572,7 @@ var withOTelSet = wire.NewSet( otelTracer, grpcserver.ProvideService, interceptors.ProvideAuthenticator, ) -var wireBasicSet = wire.NewSet(annotationsimpl.ProvideService, wire.Bind(new(annotations.Repository), new(*annotationsimpl.RepositoryImpl)), New, api.ProvideHTTPServer, query.ProvideService, wire.Bind(new(query.Service), new(*query.ServiceImpl)), bus.ProvideBus, wire.Bind(new(bus.Bus), new(*bus.InProcBus)), rendering.ProvideService, wire.Bind(new(rendering.Service), new(*rendering.RenderingService)), routing.ProvideRegister, wire.Bind(new(routing.RouteRegister), new(*routing.RouteRegisterImpl)), hooks.ProvideService, kvstore.ProvideService, localcache.ProvideService, bundleregistry.ProvideService, wire.Bind(new(supportbundles.Service), new(*bundleregistry.Service)), updatemanager.ProvideGrafanaService, updatemanager.ProvidePluginsService, service.ProvideService, wire.Bind(new(usagestats.Service), new(*service.UsageStats)), validator2.ProvideService, legacy.ProvideLegacyMigrator, pluginsintegration.WireSet, dashboards.ProvideFileStoreManager, wire.Bind(new(dashboards.FileStore), new(*dashboards.FileStoreManager)), cloudwatch.ProvideService, cloudmonitoring.ProvideService, azuremonitor.ProvideService, postgres.ProvideService, mysql.ProvideService, mssql.ProvideService, store.ProvideEntityEventsService, dualwrite.ProvideService, httpclientprovider.New, wire.Bind(new(httpclient.Provider), new(*httpclient2.Provider)), serverlock.ProvideService, annotationsimpl.ProvideCleanupService, wire.Bind(new(annotations.Cleaner), new(*annotationsimpl.CleanupServiceImpl)), cleanup.ProvideService, shorturlimpl.ProvideService, wire.Bind(new(shorturls.Service), new(*shorturlimpl.ShortURLService)), queryhistory.ProvideService, wire.Bind(new(queryhistory.Service), new(*queryhistory.QueryHistoryService)), correlations.ProvideService, wire.Bind(new(correlations.Service), new(*correlations.CorrelationsService)), quotaimpl.ProvideService, remotecache.ProvideService, wire.Bind(new(remotecache.CacheStorage), new(*remotecache.RemoteCache)), authinfoimpl.ProvideService, wire.Bind(new(login.AuthInfoService), new(*authinfoimpl.Service)), authinfoimpl.ProvideStore, datasourceproxy.ProvideService, sort.ProvideService, search2.ProvideService, searchV2.ProvideService, searchV2.ProvideSearchHTTPService, store.ProvideService, store.ProvideSystemUsersService, live.ProvideService, pushhttp.ProvideService, contexthandler.ProvideService, service10.ProvideService, wire.Bind(new(service10.LDAP), new(*service10.LDAPImpl)), jwt.ProvideService, wire.Bind(new(jwt.JWTService), new(*jwt.AuthService)), store2.ProvideDBStore, image.ProvideDeleteExpiredService, ngalert.ProvideService, librarypanels.ProvideService, wire.Bind(new(librarypanels.Service), new(*librarypanels.LibraryPanelService)), libraryelements.ProvideService, wire.Bind(new(libraryelements.Service), new(*libraryelements.LibraryElementService)), notifications.ProvideService, notifications.ProvideSmtpService, github.ProvideFactory, tracing.ProvideService, tracing.ProvideTracingConfig, wire.Bind(new(tracing.Tracer), new(*tracing.TracingService)), withOTelSet, testdatasource.ProvideService, api4.ProvideService, opentsdb.ProvideService, socialimpl.ProvideService, influxdb.ProvideService, wire.Bind(new(social.Service), new(*socialimpl.SocialService)), tempo.ProvideService, loki.ProvideService, graphite.ProvideService, prometheus.ProvideService, elasticsearch.ProvideService, pyroscope.ProvideService, parca.ProvideService, zipkin.ProvideService, jaeger.ProvideService, service7.ProvideCacheService, wire.Bind(new(datasources.CacheService), new(*service7.CacheServiceImpl)), service2.ProvideEncryptionService, wire.Bind(new(encryption2.Internal), new(*service2.Service)), manager.ProvideSecretsService, wire.Bind(new(secrets2.Service), new(*manager.SecretsService)), database.ProvideSecretsStore, wire.Bind(new(secrets2.Store), new(*database.SecretsStoreImpl)), grafanads.ProvideService, wire.Bind(new(dashboardsnapshots.Store), new(*database4.DashboardSnapshotStore)), database4.ProvideStore, wire.Bind(new(dashboardsnapshots.Service), new(*service8.ServiceImpl)), service8.ProvideService, service7.ProvideService, wire.Bind(new(datasources.DataSourceService), new(*service7.Service)), service7.ProvideLegacyDataSourceLookup, retriever.ProvideService, wire.Bind(new(serviceaccounts.ServiceAccountRetriever), new(*retriever.Service)), ossaccesscontrol.ProvideServiceAccountPermissions, wire.Bind(new(accesscontrol.ServiceAccountPermissionsService), new(*ossaccesscontrol.ServiceAccountPermissionsService)), manager2.ProvideServiceAccountsService, proxy.ProvideServiceAccountsProxy, wire.Bind(new(serviceaccounts.Service), new(*proxy.ServiceAccountsProxy)), mtdsclient.NewNullMTDatasourceClientBuilder, expr.ProvideService, featuremgmt.ProvideManagerService, featuremgmt.ProvideToggles, service5.ProvideDashboardServiceImpl, wire.Bind(new(dashboards2.PermissionsRegistrationService), new(*service5.DashboardServiceImpl)), service5.ProvideDashboardService, service5.ProvideDashboardProvisioningService, service5.ProvideDashboardPluginService, database2.ProvideDashboardStore, folderimpl.ProvideService, wire.Bind(new(folder.Service), new(*folderimpl.Service)), folderimpl.ProvideStore, wire.Bind(new(folder.Store), new(*folderimpl.FolderStoreImpl)), folderimpl.ProvideDashboardFolderStore, wire.Bind(new(folder.FolderStore), new(*folderimpl.DashboardFolderStoreImpl)), service9.ProvideService, wire.Bind(new(dashboardimport.Service), new(*service9.ImportDashboardService)), service6.ProvideService, wire.Bind(new(plugindashboards.Service), new(*service6.Service)), service6.ProvideDashboardUpdater, sanitizer.ProvideService, kvstore2.ProvideService, avatar.ProvideAvatarCacheServer, statscollector.ProvideService, csrf.ProvideCSRFFilter, wire.Bind(new(csrf.Service), new(*csrf.CSRF)), ossaccesscontrol.ProvideTeamPermissions, wire.Bind(new(accesscontrol.TeamPermissionsService), new(*ossaccesscontrol.TeamPermissionsService)), ossaccesscontrol.ProvideFolderPermissions, wire.Bind(new(accesscontrol.FolderPermissionsService), new(*ossaccesscontrol.FolderPermissionsService)), ossaccesscontrol.ProvideDashboardPermissions, wire.Bind(new(accesscontrol.DashboardPermissionsService), new(*ossaccesscontrol.DashboardPermissionsService)), ossaccesscontrol.ProvideReceiverPermissionsService, wire.Bind(new(accesscontrol.ReceiverPermissionsService), new(*ossaccesscontrol.ReceiverPermissionsService)), starimpl.ProvideService, playlistimpl.ProvideService, apikeyimpl.ProvideService, dashverimpl.ProvideService, service3.ProvideService, wire.Bind(new(publicdashboards.Service), new(*service3.PublicDashboardServiceImpl)), database3.ProvideStore, wire.Bind(new(publicdashboards.Store), new(*database3.PublicDashboardStoreImpl)), metric.ProvideService, api2.ProvideApi, api3.ProvideApi, userimpl.ProvideService, orgimpl.ProvideService, orgimpl.ProvideDeletionService, statsimpl.ProvideService, grpccontext.ProvideContextHandler, grpcserver.ProvideHealthService, grpcserver.ProvideReflectionService, resolver.ProvideEntityReferenceResolver, teamimpl.ProvideService, teamapi.ProvideTeamAPI, tempuserimpl.ProvideService, loginattemptimpl.ProvideService, wire.Bind(new(loginattempt.Service), new(*loginattemptimpl.Service)), migrations2.ProvideDataSourceMigrationService, migrations2.ProvideSecretMigrationProvider, wire.Bind(new(migrations2.SecretMigrationProvider), new(*migrations2.SecretMigrationProviderImpl)), resourcepermissions.NewActionSetService, wire.Bind(new(accesscontrol.ActionResolver), new(resourcepermissions.ActionSetService)), wire.Bind(new(pluginaccesscontrol.ActionSetRegistry), new(resourcepermissions.ActionSetService)), permreg.ProvidePermissionRegistry, acimpl.ProvideAccessControl, dualwrite2.ProvideZanzanaReconciler, navtreeimpl.ProvideService, wire.Bind(new(accesscontrol.AccessControl), new(*acimpl.AccessControl)), wire.Bind(new(notifications.TempUserStore), new(tempuser.Service)), tagimpl.ProvideService, wire.Bind(new(tag.Service), new(*tagimpl.Service)), authnimpl.ProvideService, authnimpl.ProvideIdentitySynchronizer, authnimpl.ProvideAuthnService, authnimpl.ProvideAuthnServiceAuthenticateOnly, authnimpl.ProvideRegistration, supportbundlesimpl.ProvideService, extsvcaccounts.ProvideExtSvcAccountsService, wire.Bind(new(serviceaccounts.ExtSvcAccountsService), new(*extsvcaccounts.ExtSvcAccountsService)), registry2.ProvideExtSvcRegistry, wire.Bind(new(extsvcauth.ExternalServiceRegistry), new(*registry2.Registry)), anonstore.ProvideAnonDBStore, wire.Bind(new(anonstore.AnonStore), new(*anonstore.AnonDBStore)), loggermw.Provide, slogadapter.Provide, signingkeysimpl.ProvideEmbeddedSigningKeysService, wire.Bind(new(signingkeys.Service), new(*signingkeysimpl.Service)), ssosettingsimpl.ProvideService, wire.Bind(new(ssosettings.Service), new(*ssosettingsimpl.Service)), idimpl.ProvideService, wire.Bind(new(auth.IDService), new(*idimpl.Service)), cloudmigrationimpl.ProvideService, userimpl.ProvideVerifier, connectors.ProvideOrgRoleMapper, wire.Bind(new(user.Verifier), new(*userimpl.Verifier)), authz.WireSet, metadata.ProvideSecureValueMetadataStorage, metadata.ProvideKeeperMetadataStorage, metadata.ProvideDecryptStorage, decrypt.ProvideDecryptAuthorizer, decrypt.ProvideDecryptService, encryption.ProvideDataKeyStorage, encryption.ProvideEncryptedValueStorage, service12.ProvideSecureValueService, validator3.ProvideKeeperValidator, validator3.ProvideSecureValueValidator, migrator2.NewWithEngine, database5.ProvideDatabase, wire.Bind(new(contracts.Database), new(*database5.Database)), manager4.ProvideEncryptionManager, service11.ProvideAESGCMCipherService, resource.ProvideStorageMetrics, resource.ProvideIndexMetrics, apiserver.WireSet, apiregistry.WireSet, appregistry.WireSet) +var wireBasicSet = wire.NewSet(annotationsimpl.ProvideService, wire.Bind(new(annotations.Repository), new(*annotationsimpl.RepositoryImpl)), New, api.ProvideHTTPServer, query.ProvideService, wire.Bind(new(query.Service), new(*query.ServiceImpl)), bus.ProvideBus, wire.Bind(new(bus.Bus), new(*bus.InProcBus)), rendering.ProvideService, wire.Bind(new(rendering.Service), new(*rendering.RenderingService)), routing.ProvideRegister, wire.Bind(new(routing.RouteRegister), new(*routing.RouteRegisterImpl)), hooks.ProvideService, kvstore.ProvideService, localcache.ProvideService, bundleregistry.ProvideService, wire.Bind(new(supportbundles.Service), new(*bundleregistry.Service)), updatemanager.ProvideGrafanaService, updatemanager.ProvidePluginsService, service.ProvideService, wire.Bind(new(usagestats.Service), new(*service.UsageStats)), validator2.ProvideService, legacy.ProvideLegacyMigrator, pluginsintegration.WireSet, dashboards.ProvideFileStoreManager, wire.Bind(new(dashboards.FileStore), new(*dashboards.FileStoreManager)), cloudwatch.ProvideService, cloudmonitoring.ProvideService, azuremonitor.ProvideService, postgres.ProvideService, mysql.ProvideService, mssql.ProvideService, store.ProvideEntityEventsService, dualwrite.ProvideService, httpclientprovider.New, wire.Bind(new(httpclient.Provider), new(*httpclient2.Provider)), serverlock.ProvideService, annotationsimpl.ProvideCleanupService, wire.Bind(new(annotations.Cleaner), new(*annotationsimpl.CleanupServiceImpl)), cleanup.ProvideService, shorturlimpl.ProvideService, wire.Bind(new(shorturls.Service), new(*shorturlimpl.ShortURLService)), queryhistory.ProvideService, wire.Bind(new(queryhistory.Service), new(*queryhistory.QueryHistoryService)), correlations.ProvideService, wire.Bind(new(correlations.Service), new(*correlations.CorrelationsService)), quotaimpl.ProvideService, remotecache.ProvideService, wire.Bind(new(remotecache.CacheStorage), new(*remotecache.RemoteCache)), authinfoimpl.ProvideService, wire.Bind(new(login.AuthInfoService), new(*authinfoimpl.Service)), authinfoimpl.ProvideStore, datasourceproxy.ProvideService, sort.ProvideService, search2.ProvideService, searchV2.ProvideService, searchV2.ProvideSearchHTTPService, store.ProvideService, store.ProvideSystemUsersService, live.ProvideService, pushhttp.ProvideService, contexthandler.ProvideService, service10.ProvideService, wire.Bind(new(service10.LDAP), new(*service10.LDAPImpl)), jwt.ProvideService, wire.Bind(new(jwt.JWTService), new(*jwt.AuthService)), store2.ProvideDBStore, image.ProvideDeleteExpiredService, ngalert.ProvideService, librarypanels.ProvideService, wire.Bind(new(librarypanels.Service), new(*librarypanels.LibraryPanelService)), libraryelements.ProvideService, wire.Bind(new(libraryelements.Service), new(*libraryelements.LibraryElementService)), notifications.ProvideService, notifications.ProvideSmtpService, github.ProvideFactory, tracing.ProvideService, tracing.ProvideTracingConfig, wire.Bind(new(tracing.Tracer), new(*tracing.TracingService)), withOTelSet, testdatasource.ProvideService, api4.ProvideService, opentsdb.ProvideService, socialimpl.ProvideService, influxdb.ProvideService, wire.Bind(new(social.Service), new(*socialimpl.SocialService)), tempo.ProvideService, loki.ProvideService, graphite.ProvideService, prometheus.ProvideService, elasticsearch.ProvideService, pyroscope.ProvideService, parca.ProvideService, zipkin.ProvideService, jaeger.ProvideService, service7.ProvideCacheService, wire.Bind(new(datasources.CacheService), new(*service7.CacheServiceImpl)), service2.ProvideEncryptionService, wire.Bind(new(encryption2.Internal), new(*service2.Service)), manager.ProvideSecretsService, wire.Bind(new(secrets2.Service), new(*manager.SecretsService)), database.ProvideSecretsStore, wire.Bind(new(secrets2.Store), new(*database.SecretsStoreImpl)), grafanads.ProvideService, wire.Bind(new(dashboardsnapshots.Store), new(*database4.DashboardSnapshotStore)), database4.ProvideStore, wire.Bind(new(dashboardsnapshots.Service), new(*service8.ServiceImpl)), service8.ProvideService, service7.ProvideService, wire.Bind(new(datasources.DataSourceService), new(*service7.Service)), service7.ProvideLegacyDataSourceLookup, retriever.ProvideService, wire.Bind(new(serviceaccounts.ServiceAccountRetriever), new(*retriever.Service)), ossaccesscontrol.ProvideServiceAccountPermissions, wire.Bind(new(accesscontrol.ServiceAccountPermissionsService), new(*ossaccesscontrol.ServiceAccountPermissionsService)), manager2.ProvideServiceAccountsService, proxy.ProvideServiceAccountsProxy, wire.Bind(new(serviceaccounts.Service), new(*proxy.ServiceAccountsProxy)), mtdsclient.NewNullMTDatasourceClientBuilder, expr.ProvideService, featuremgmt.ProvideManagerService, featuremgmt.ProvideToggles, service5.ProvideDashboardServiceImpl, wire.Bind(new(dashboards2.PermissionsRegistrationService), new(*service5.DashboardServiceImpl)), service5.ProvideDashboardService, service5.ProvideDashboardProvisioningService, service5.ProvideDashboardPluginService, database2.ProvideDashboardStore, folderimpl.ProvideService, wire.Bind(new(folder.Service), new(*folderimpl.Service)), folderimpl.ProvideStore, wire.Bind(new(folder.Store), new(*folderimpl.FolderStoreImpl)), folderimpl.ProvideDashboardFolderStore, wire.Bind(new(folder.FolderStore), new(*folderimpl.DashboardFolderStoreImpl)), service9.ProvideService, wire.Bind(new(dashboardimport.Service), new(*service9.ImportDashboardService)), service6.ProvideService, wire.Bind(new(plugindashboards.Service), new(*service6.Service)), service6.ProvideDashboardUpdater, sanitizer.ProvideService, kvstore2.ProvideService, avatar.ProvideAvatarCacheServer, statscollector.ProvideService, csrf.ProvideCSRFFilter, wire.Bind(new(csrf.Service), new(*csrf.CSRF)), ossaccesscontrol.ProvideTeamPermissions, wire.Bind(new(accesscontrol.TeamPermissionsService), new(*ossaccesscontrol.TeamPermissionsService)), ossaccesscontrol.ProvideFolderPermissions, wire.Bind(new(accesscontrol.FolderPermissionsService), new(*ossaccesscontrol.FolderPermissionsService)), ossaccesscontrol.ProvideDashboardPermissions, wire.Bind(new(accesscontrol.DashboardPermissionsService), new(*ossaccesscontrol.DashboardPermissionsService)), ossaccesscontrol.ProvideReceiverPermissionsService, wire.Bind(new(accesscontrol.ReceiverPermissionsService), new(*ossaccesscontrol.ReceiverPermissionsService)), starimpl.ProvideService, playlistimpl.ProvideService, apikeyimpl.ProvideService, dashverimpl.ProvideService, service3.ProvideService, wire.Bind(new(publicdashboards.Service), new(*service3.PublicDashboardServiceImpl)), database3.ProvideStore, wire.Bind(new(publicdashboards.Store), new(*database3.PublicDashboardStoreImpl)), metric.ProvideService, api2.ProvideApi, api3.ProvideApi, userimpl.ProvideService, orgimpl.ProvideService, orgimpl.ProvideDeletionService, statsimpl.ProvideService, grpccontext.ProvideContextHandler, grpcserver.ProvideHealthService, grpcserver.ProvideReflectionService, resolver.ProvideEntityReferenceResolver, teamimpl.ProvideService, teamapi.ProvideTeamAPI, tempuserimpl.ProvideService, loginattemptimpl.ProvideService, wire.Bind(new(loginattempt.Service), new(*loginattemptimpl.Service)), migrations2.ProvideDataSourceMigrationService, migrations2.ProvideSecretMigrationProvider, wire.Bind(new(migrations2.SecretMigrationProvider), new(*migrations2.SecretMigrationProviderImpl)), resourcepermissions.NewActionSetService, wire.Bind(new(accesscontrol.ActionResolver), new(resourcepermissions.ActionSetService)), wire.Bind(new(pluginaccesscontrol.ActionSetRegistry), new(resourcepermissions.ActionSetService)), permreg.ProvidePermissionRegistry, acimpl.ProvideAccessControl, dualwrite2.ProvideZanzanaReconciler, navtreeimpl.ProvideService, wire.Bind(new(accesscontrol.AccessControl), new(*acimpl.AccessControl)), wire.Bind(new(notifications.TempUserStore), new(tempuser.Service)), tagimpl.ProvideService, wire.Bind(new(tag.Service), new(*tagimpl.Service)), authnimpl.ProvideService, authnimpl.ProvideIdentitySynchronizer, authnimpl.ProvideAuthnService, authnimpl.ProvideAuthnServiceAuthenticateOnly, authnimpl.ProvideRegistration, supportbundlesimpl.ProvideService, extsvcaccounts.ProvideExtSvcAccountsService, wire.Bind(new(serviceaccounts.ExtSvcAccountsService), new(*extsvcaccounts.ExtSvcAccountsService)), registry2.ProvideExtSvcRegistry, wire.Bind(new(extsvcauth.ExternalServiceRegistry), new(*registry2.Registry)), anonstore.ProvideAnonDBStore, wire.Bind(new(anonstore.AnonStore), new(*anonstore.AnonDBStore)), loggermw.Provide, slogadapter.Provide, signingkeysimpl.ProvideEmbeddedSigningKeysService, wire.Bind(new(signingkeys.Service), new(*signingkeysimpl.Service)), ssosettingsimpl.ProvideService, wire.Bind(new(ssosettings.Service), new(*ssosettingsimpl.Service)), idimpl.ProvideService, wire.Bind(new(auth.IDService), new(*idimpl.Service)), cloudmigrationimpl.ProvideService, userimpl.ProvideVerifier, connectors.ProvideOrgRoleMapper, wire.Bind(new(user.Verifier), new(*userimpl.Verifier)), authz.WireSet, metadata.ProvideSecureValueMetadataStorage, metadata.ProvideKeeperMetadataStorage, metadata.ProvideDecryptStorage, decrypt.ProvideDecryptAuthorizer, decrypt.ProvideDecryptService, encryption.ProvideDataKeyStorage, encryption.ProvideGlobalDataKeyStorage, encryption.ProvideEncryptedValueStorage, encryption.ProvideGlobalEncryptedValueStorage, service12.ProvideSecureValueService, validator3.ProvideKeeperValidator, validator3.ProvideSecureValueValidator, migrator2.NewWithEngine, database5.ProvideDatabase, wire.Bind(new(contracts.Database), new(*database5.Database)), manager4.ProvideEncryptionManager, service11.ProvideAESGCMCipherService, resource.ProvideStorageMetrics, resource.ProvideIndexMetrics, apiserver.WireSet, apiregistry.WireSet, appregistry.WireSet) var wireSet = wire.NewSet( wireBasicSet, metrics.WireSet, sqlstore.ProvideService, metrics2.ProvideService, wire.Bind(new(notifications.Service), new(*notifications.NotificationService)), wire.Bind(new(notifications.WebhookSender), new(*notifications.NotificationService)), wire.Bind(new(notifications.EmailSender), new(*notifications.NotificationService)), wire.Bind(new(db.DB), new(*sqlstore.SQLStore)), prefimpl.ProvideService, oauthtoken.ProvideService, wire.Bind(new(oauthtoken.OAuthTokenService), new(*oauthtoken.Service)), wire.Bind(new(cleanup.AlertRuleService), new(*store2.DBstore)), diff --git a/pkg/server/wireexts_oss.go b/pkg/server/wireexts_oss.go index bb715ed7dea..61a05716b47 100644 --- a/pkg/server/wireexts_oss.go +++ b/pkg/server/wireexts_oss.go @@ -19,6 +19,7 @@ import ( "github.com/grafana/grafana/pkg/registry/apis/secret/contracts" gsmKMSProviders "github.com/grafana/grafana/pkg/registry/apis/secret/encryption/kmsproviders" "github.com/grafana/grafana/pkg/registry/apis/secret/secretkeeper" + secretService "github.com/grafana/grafana/pkg/registry/apis/secret/service" "github.com/grafana/grafana/pkg/registry/backgroundsvcs" "github.com/grafana/grafana/pkg/registry/usagestatssvcs" "github.com/grafana/grafana/pkg/services/accesscontrol" @@ -108,6 +109,7 @@ var wireExtsBasicSet = wire.NewSet( wire.Bind(new(kmsproviders.Service), new(osskmsproviders.Service)), secretkeeper.ProvideService, wire.Bind(new(contracts.KeeperService), new(*secretkeeper.OSSKeeperService)), + secretService.ProvideConsolidationService, ldap.ProvideGroupsService, wire.Bind(new(ldap.Groups), new(*ldap.OSSGroups)), guardian.ProvideGuardian, From f3cb8aa903250c02e703f31bbb856f27219fe3e0 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 31 Jul 2025 15:07:55 +0100 Subject: [PATCH 056/114] Update swc monorepo (#108981) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 4 +- packages/grafana-plugin-configs/package.json | 2 +- packages/grafana-test-utils/package.json | 2 +- yarn.lock | 222 +++++-------------- 4 files changed, 57 insertions(+), 173 deletions(-) diff --git a/package.json b/package.json index 169c2f41f6c..cbc51156684 100644 --- a/package.json +++ b/package.json @@ -105,8 +105,8 @@ "@rtk-query/codegen-openapi": "^2.0.0", "@rtsao/plugin-proposal-class-properties": "7.0.1-patch.1", "@stylistic/eslint-plugin-ts": "^3.0.0", - "@swc/core": "1.10.12", - "@swc/helpers": "0.5.15", + "@swc/core": "1.13.3", + "@swc/helpers": "0.5.17", "@testing-library/dom": "10.4.0", "@testing-library/jest-dom": "6.6.3", "@testing-library/react": "16.3.0", diff --git a/packages/grafana-plugin-configs/package.json b/packages/grafana-plugin-configs/package.json index 42233bea781..a2edd65dd30 100644 --- a/packages/grafana-plugin-configs/package.json +++ b/packages/grafana-plugin-configs/package.json @@ -9,7 +9,7 @@ "type": "module", "devDependencies": { "@grafana/tsconfig": "^2.0.0", - "@swc/core": "1.10.12", + "@swc/core": "1.13.3", "@swc/helpers": "^0.5.0", "@swc/jest": "^0.2.26", "@types/eslint": "9.6.1", diff --git a/packages/grafana-test-utils/package.json b/packages/grafana-test-utils/package.json index c20fad715d2..fe04697966f 100644 --- a/packages/grafana-test-utils/package.json +++ b/packages/grafana-test-utils/package.json @@ -57,7 +57,7 @@ }, "devDependencies": { "@grafana/tsconfig": "^2.0.0", - "@swc/core": "1.10.12", + "@swc/core": "1.13.3", "@swc/jest": "^0.2.26", "@types/jest": "29.5.14", "@types/node": "22.17.0", diff --git a/yarn.lock b/yarn.lock index c031af16035..c6ee7419101 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3421,7 +3421,7 @@ __metadata: resolution: "@grafana/plugin-configs@workspace:packages/grafana-plugin-configs" dependencies: "@grafana/tsconfig": "npm:^2.0.0" - "@swc/core": "npm:1.10.12" + "@swc/core": "npm:1.13.3" "@swc/helpers": "npm:^0.5.0" "@swc/jest": "npm:^0.2.26" "@types/eslint": "npm:9.6.1" @@ -3715,7 +3715,7 @@ __metadata: resolution: "@grafana/test-utils@workspace:packages/grafana-test-utils" dependencies: "@grafana/tsconfig": "npm:^2.0.0" - "@swc/core": "npm:1.10.12" + "@swc/core": "npm:1.13.3" "@swc/jest": "npm:^0.2.26" "@types/jest": "npm:29.5.14" "@types/node": "npm:22.17.0" @@ -8447,206 +8447,90 @@ __metadata: languageName: node linkType: hard -"@swc/core-darwin-arm64@npm:1.10.12": - version: 1.10.12 - resolution: "@swc/core-darwin-arm64@npm:1.10.12" +"@swc/core-darwin-arm64@npm:1.13.3": + version: 1.13.3 + resolution: "@swc/core-darwin-arm64@npm:1.13.3" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@swc/core-darwin-arm64@npm:1.13.2": - version: 1.13.2 - resolution: "@swc/core-darwin-arm64@npm:1.13.2" - conditions: os=darwin & cpu=arm64 - languageName: node - linkType: hard - -"@swc/core-darwin-x64@npm:1.10.12": - version: 1.10.12 - resolution: "@swc/core-darwin-x64@npm:1.10.12" +"@swc/core-darwin-x64@npm:1.13.3": + version: 1.13.3 + resolution: "@swc/core-darwin-x64@npm:1.13.3" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@swc/core-darwin-x64@npm:1.13.2": - version: 1.13.2 - resolution: "@swc/core-darwin-x64@npm:1.13.2" - conditions: os=darwin & cpu=x64 - languageName: node - linkType: hard - -"@swc/core-linux-arm-gnueabihf@npm:1.10.12": - version: 1.10.12 - resolution: "@swc/core-linux-arm-gnueabihf@npm:1.10.12" +"@swc/core-linux-arm-gnueabihf@npm:1.13.3": + version: 1.13.3 + resolution: "@swc/core-linux-arm-gnueabihf@npm:1.13.3" conditions: os=linux & cpu=arm languageName: node linkType: hard -"@swc/core-linux-arm-gnueabihf@npm:1.13.2": - version: 1.13.2 - resolution: "@swc/core-linux-arm-gnueabihf@npm:1.13.2" - conditions: os=linux & cpu=arm - languageName: node - linkType: hard - -"@swc/core-linux-arm64-gnu@npm:1.10.12": - version: 1.10.12 - resolution: "@swc/core-linux-arm64-gnu@npm:1.10.12" +"@swc/core-linux-arm64-gnu@npm:1.13.3": + version: 1.13.3 + resolution: "@swc/core-linux-arm64-gnu@npm:1.13.3" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@swc/core-linux-arm64-gnu@npm:1.13.2": - version: 1.13.2 - resolution: "@swc/core-linux-arm64-gnu@npm:1.13.2" - conditions: os=linux & cpu=arm64 & libc=glibc - languageName: node - linkType: hard - -"@swc/core-linux-arm64-musl@npm:1.10.12": - version: 1.10.12 - resolution: "@swc/core-linux-arm64-musl@npm:1.10.12" +"@swc/core-linux-arm64-musl@npm:1.13.3": + version: 1.13.3 + resolution: "@swc/core-linux-arm64-musl@npm:1.13.3" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@swc/core-linux-arm64-musl@npm:1.13.2": - version: 1.13.2 - resolution: "@swc/core-linux-arm64-musl@npm:1.13.2" - conditions: os=linux & cpu=arm64 & libc=musl - languageName: node - linkType: hard - -"@swc/core-linux-x64-gnu@npm:1.10.12": - version: 1.10.12 - resolution: "@swc/core-linux-x64-gnu@npm:1.10.12" +"@swc/core-linux-x64-gnu@npm:1.13.3": + version: 1.13.3 + resolution: "@swc/core-linux-x64-gnu@npm:1.13.3" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@swc/core-linux-x64-gnu@npm:1.13.2": - version: 1.13.2 - resolution: "@swc/core-linux-x64-gnu@npm:1.13.2" - conditions: os=linux & cpu=x64 & libc=glibc - languageName: node - linkType: hard - -"@swc/core-linux-x64-musl@npm:1.10.12": - version: 1.10.12 - resolution: "@swc/core-linux-x64-musl@npm:1.10.12" +"@swc/core-linux-x64-musl@npm:1.13.3": + version: 1.13.3 + resolution: "@swc/core-linux-x64-musl@npm:1.13.3" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@swc/core-linux-x64-musl@npm:1.13.2": - version: 1.13.2 - resolution: "@swc/core-linux-x64-musl@npm:1.13.2" - conditions: os=linux & cpu=x64 & libc=musl - languageName: node - linkType: hard - -"@swc/core-win32-arm64-msvc@npm:1.10.12": - version: 1.10.12 - resolution: "@swc/core-win32-arm64-msvc@npm:1.10.12" +"@swc/core-win32-arm64-msvc@npm:1.13.3": + version: 1.13.3 + resolution: "@swc/core-win32-arm64-msvc@npm:1.13.3" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@swc/core-win32-arm64-msvc@npm:1.13.2": - version: 1.13.2 - resolution: "@swc/core-win32-arm64-msvc@npm:1.13.2" - conditions: os=win32 & cpu=arm64 - languageName: node - linkType: hard - -"@swc/core-win32-ia32-msvc@npm:1.10.12": - version: 1.10.12 - resolution: "@swc/core-win32-ia32-msvc@npm:1.10.12" +"@swc/core-win32-ia32-msvc@npm:1.13.3": + version: 1.13.3 + resolution: "@swc/core-win32-ia32-msvc@npm:1.13.3" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@swc/core-win32-ia32-msvc@npm:1.13.2": - version: 1.13.2 - resolution: "@swc/core-win32-ia32-msvc@npm:1.13.2" - conditions: os=win32 & cpu=ia32 - languageName: node - linkType: hard - -"@swc/core-win32-x64-msvc@npm:1.10.12": - version: 1.10.12 - resolution: "@swc/core-win32-x64-msvc@npm:1.10.12" +"@swc/core-win32-x64-msvc@npm:1.13.3": + version: 1.13.3 + resolution: "@swc/core-win32-x64-msvc@npm:1.13.3" conditions: os=win32 & cpu=x64 languageName: node linkType: hard -"@swc/core-win32-x64-msvc@npm:1.13.2": - version: 1.13.2 - resolution: "@swc/core-win32-x64-msvc@npm:1.13.2" - conditions: os=win32 & cpu=x64 - languageName: node - linkType: hard - -"@swc/core@npm:1.10.12": - version: 1.10.12 - resolution: "@swc/core@npm:1.10.12" +"@swc/core@npm:1.13.3, @swc/core@npm:^1.10.8, @swc/core@npm:^1.5.22": + version: 1.13.3 + resolution: "@swc/core@npm:1.13.3" dependencies: - "@swc/core-darwin-arm64": "npm:1.10.12" - "@swc/core-darwin-x64": "npm:1.10.12" - "@swc/core-linux-arm-gnueabihf": "npm:1.10.12" - "@swc/core-linux-arm64-gnu": "npm:1.10.12" - "@swc/core-linux-arm64-musl": "npm:1.10.12" - "@swc/core-linux-x64-gnu": "npm:1.10.12" - "@swc/core-linux-x64-musl": "npm:1.10.12" - "@swc/core-win32-arm64-msvc": "npm:1.10.12" - "@swc/core-win32-ia32-msvc": "npm:1.10.12" - "@swc/core-win32-x64-msvc": "npm:1.10.12" - "@swc/counter": "npm:^0.1.3" - "@swc/types": "npm:^0.1.17" - peerDependencies: - "@swc/helpers": "*" - dependenciesMeta: - "@swc/core-darwin-arm64": - optional: true - "@swc/core-darwin-x64": - optional: true - "@swc/core-linux-arm-gnueabihf": - optional: true - "@swc/core-linux-arm64-gnu": - optional: true - "@swc/core-linux-arm64-musl": - optional: true - "@swc/core-linux-x64-gnu": - optional: true - "@swc/core-linux-x64-musl": - optional: true - "@swc/core-win32-arm64-msvc": - optional: true - "@swc/core-win32-ia32-msvc": - optional: true - "@swc/core-win32-x64-msvc": - optional: true - peerDependenciesMeta: - "@swc/helpers": - optional: true - checksum: 10/62b77009f267f28a486ded1de9ac250b65dc618a4c551a86ad8e438ee54d96b0d44f52332fb242eaa9a6e00db499436753ef99747e6c1a14bd6894554c10e799 - languageName: node - linkType: hard - -"@swc/core@npm:^1.10.8, @swc/core@npm:^1.5.22": - version: 1.13.2 - resolution: "@swc/core@npm:1.13.2" - dependencies: - "@swc/core-darwin-arm64": "npm:1.13.2" - "@swc/core-darwin-x64": "npm:1.13.2" - "@swc/core-linux-arm-gnueabihf": "npm:1.13.2" - "@swc/core-linux-arm64-gnu": "npm:1.13.2" - "@swc/core-linux-arm64-musl": "npm:1.13.2" - "@swc/core-linux-x64-gnu": "npm:1.13.2" - "@swc/core-linux-x64-musl": "npm:1.13.2" - "@swc/core-win32-arm64-msvc": "npm:1.13.2" - "@swc/core-win32-ia32-msvc": "npm:1.13.2" - "@swc/core-win32-x64-msvc": "npm:1.13.2" + "@swc/core-darwin-arm64": "npm:1.13.3" + "@swc/core-darwin-x64": "npm:1.13.3" + "@swc/core-linux-arm-gnueabihf": "npm:1.13.3" + "@swc/core-linux-arm64-gnu": "npm:1.13.3" + "@swc/core-linux-arm64-musl": "npm:1.13.3" + "@swc/core-linux-x64-gnu": "npm:1.13.3" + "@swc/core-linux-x64-musl": "npm:1.13.3" + "@swc/core-win32-arm64-msvc": "npm:1.13.3" + "@swc/core-win32-ia32-msvc": "npm:1.13.3" + "@swc/core-win32-x64-msvc": "npm:1.13.3" "@swc/counter": "npm:^0.1.3" "@swc/types": "npm:^0.1.23" peerDependencies: @@ -8675,7 +8559,7 @@ __metadata: peerDependenciesMeta: "@swc/helpers": optional: true - checksum: 10/ca4d05c44c3e36f7d3f68dda9b53eb1e7e1c9f07701d526c8484605bf4a744cbcb83e1cadb0b4b57723d98ba799d3a75508b2219751ad5fdb29491ca325c680d + checksum: 10/d9f933b69b6872b69a40a926eafd98a6088e545027e031f32717f7112ce39c58ba6714a44cd894b31a619a2999b94756582735eb3ec69a35ff434da2b537e868 languageName: node linkType: hard @@ -8686,12 +8570,12 @@ __metadata: languageName: node linkType: hard -"@swc/helpers@npm:0.5.15, @swc/helpers@npm:^0.5.0": - version: 0.5.15 - resolution: "@swc/helpers@npm:0.5.15" +"@swc/helpers@npm:0.5.17, @swc/helpers@npm:^0.5.0": + version: 0.5.17 + resolution: "@swc/helpers@npm:0.5.17" dependencies: tslib: "npm:^2.8.0" - checksum: 10/e3f32c6deeecfb0fa3f22edff03a7b358e7ce16d27b0f1c8b5cdc3042c5c4ce4da6eac0b781ab7cc4f54696ece657467d56734fb26883439fb00017385364c4c + checksum: 10/1fc8312a78f1f99c8ec838585445e99763eeebff2356100738cdfdb8ad47d2d38df678ee6edd93a90fe319ac52da67adc14ac00eb82b606c5fb8ebc5d06ec2a2 languageName: node linkType: hard @@ -8708,7 +8592,7 @@ __metadata: languageName: node linkType: hard -"@swc/types@npm:^0.1.17, @swc/types@npm:^0.1.23": +"@swc/types@npm:^0.1.23": version: 0.1.23 resolution: "@swc/types@npm:0.1.23" dependencies: @@ -18353,8 +18237,8 @@ __metadata: "@rtk-query/codegen-openapi": "npm:^2.0.0" "@rtsao/plugin-proposal-class-properties": "npm:7.0.1-patch.1" "@stylistic/eslint-plugin-ts": "npm:^3.0.0" - "@swc/core": "npm:1.10.12" - "@swc/helpers": "npm:0.5.15" + "@swc/core": "npm:1.13.3" + "@swc/helpers": "npm:0.5.17" "@testing-library/dom": "npm:10.4.0" "@testing-library/jest-dom": "npm:6.6.3" "@testing-library/react": "npm:16.3.0" From 7d8c2fbb82051a2b2ef10b69e29fd881e683e19e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Jamr=C3=B3z?= Date: Thu, 31 Jul 2025 16:18:53 +0200 Subject: [PATCH 057/114] TraceView: Refine UI visual hierarchy inside details section (#108929) * TraceView: Span details content redesign * Unify displaying various sections in span details * Revert i18n key change * Update tests --- .../SpanDetail/AccordianKeyValues.test.tsx | 2 +- .../SpanDetail/AccordianKeyValues.tsx | 20 +++- .../SpanDetail/AccordianLogs.test.tsx | 4 +- .../SpanDetail/AccordianLogs.tsx | 2 - .../SpanDetail/AccordianReferences.test.tsx | 4 +- .../SpanDetail/AccordianReferences.tsx | 8 +- .../SpanDetail/AccordianText.test.tsx | 49 ---------- .../SpanDetail/AccordianText.tsx | 97 ------------------- .../SpanDetail/KeyValuesTable.tsx | 36 ++++--- .../SpanDetail/TextList.tsx | 5 +- .../TraceTimelineViewer/SpanDetail/index.tsx | 65 ++++++------- .../components/TraceTimelineViewer/index.tsx | 1 - .../TraceView/components/common/CopyIcon.tsx | 1 - .../grafana-testdata-datasource/datasource.ts | 48 +++++++++ 14 files changed, 128 insertions(+), 214 deletions(-) delete mode 100644 public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/AccordianText.test.tsx delete mode 100644 public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/AccordianText.tsx diff --git a/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/AccordianKeyValues.test.tsx b/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/AccordianKeyValues.test.tsx index 597b6d125c6..908126b649b 100644 --- a/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/AccordianKeyValues.test.tsx +++ b/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/AccordianKeyValues.test.tsx @@ -96,7 +96,7 @@ describe('AccordianKeyValues test', () => { it('renders the summary instead of the table when it is not expanded', () => { setupAccordian({ isOpen: false } as AccordianKeyValuesProps); - expect(screen.getByRole('switch', { name: 'test accordian: span.kind client omg mos-def' })).toBeInTheDocument(); + expect(screen.getByRole('switch', { name: 'test accordian span.kind client omg mos-def' })).toBeInTheDocument(); expect(screen.queryByRole('table')).not.toBeInTheDocument(); expect(screen.queryAllByRole('cell')).toHaveLength(0); }); diff --git a/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/AccordianKeyValues.tsx b/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/AccordianKeyValues.tsx index d8d621cf218..93822458ac7 100644 --- a/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/AccordianKeyValues.tsx +++ b/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/AccordianKeyValues.tsx @@ -17,7 +17,7 @@ import cx from 'classnames'; import * as React from 'react'; import { GrafanaTheme2, TraceKeyValuePair } from '@grafana/data'; -import { Icon, useStyles2 } from '@grafana/ui'; +import { Counter, Icon, useStyles2 } from '@grafana/ui'; import { autoColor } from '../../Theme'; import TNil from '../../types/TNil'; @@ -43,6 +43,10 @@ export const getStyles = (theme: GrafanaTheme2) => { background: autoColor(theme, '#e8e8e8'), }, }), + headerLabel: css({ + width: '120px', + display: 'inline-block', + }), headerEmpty: css({ label: 'headerEmpty', background: 'none', @@ -87,6 +91,9 @@ export type AccordianKeyValuesProps = { logName?: string; highContrast?: boolean; interactive?: boolean; + onlyValues?: boolean; + showSummary?: boolean; + showCountBadge?: boolean; isOpen: boolean; label: string | React.ReactNode; linksGetter?: ((pairs: TraceKeyValuePair[], index: number) => KeyValuesTableLink[]) | TNil; @@ -127,6 +134,9 @@ export default function AccordianKeyValues({ isOpen, label, linksGetter, + onlyValues = false, + showSummary = true, + showCountBadge = false, onToggle = null, }: AccordianKeyValuesProps) { const isEmpty = (!Array.isArray(data) || !data.length) && !logName; @@ -148,7 +158,7 @@ export default function AccordianKeyValues({ }; } - const showDataSummaryFields = data.length > 0 && !isOpen; + const showDataSummaryFields = showSummary && data.length > 0 && !isOpen; return (
@@ -161,9 +171,9 @@ export default function AccordianKeyValues({ data-testid="AccordianKeyValues--header" > {arrow} - + {label} - {showDataSummaryFields && ':'} + {showCountBadge ? : null} {showDataSummaryFields && ( @@ -171,7 +181,7 @@ export default function AccordianKeyValues({ )}
- {isOpen && } + {isOpen && }
); } diff --git a/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/AccordianLogs.test.tsx b/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/AccordianLogs.test.tsx index 18085f3fdcb..19ff85b2dd7 100644 --- a/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/AccordianLogs.test.tsx +++ b/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/AccordianLogs.test.tsx @@ -103,10 +103,10 @@ describe('AccordianLogs tests', () => { setup({ isOpen: true, openedItems: new Set() } as AccordianLogsProps); expect( screen.getByRole('switch', { - name: '15μs (foo event name) : message oh the next log message more stuff', + name: '15μs (foo event name) message oh the next log message more stuff', }) ).toBeInTheDocument(); - expect(screen.getByRole('switch', { name: '5μs: message oh the log message something else' })).toBeInTheDocument(); + expect(screen.getByRole('switch', { name: '5μs message oh the log message something else' })).toBeInTheDocument(); }); it('renders event name and duration when events list is open', () => { diff --git a/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/AccordianLogs.tsx b/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/AccordianLogs.tsx index 5584760b04b..b708b7f3ec8 100644 --- a/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/AccordianLogs.tsx +++ b/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/AccordianLogs.tsx @@ -32,14 +32,12 @@ const getStyles = (theme: GrafanaTheme2) => { AccordianLogs: css({ label: 'AccordianLogs', position: 'relative', - marginBottom: '0.25rem', }), AccordianLogsHeader: css({ label: 'AccordianLogsHeader', color: 'inherit', display: 'flex', alignItems: 'center', - padding: '0.25rem 0.1em', '&:hover': { background: autoColor(theme, '#e8e8e8'), }, diff --git a/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/AccordianReferences.test.tsx b/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/AccordianReferences.test.tsx index d7296055bfc..14fa5187396 100644 --- a/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/AccordianReferences.test.tsx +++ b/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/AccordianReferences.test.tsx @@ -75,7 +75,7 @@ describe('AccordianReferences tests', () => { it('renders the correct number of references', () => { setup(); - expect(screen.getByRole('switch', { name: 'References (3)' })).toBeInTheDocument(); + expect(screen.getByRole('switch', { name: 'References 3' })).toBeInTheDocument(); }); it('content doesnt show when not expanded', () => { @@ -88,7 +88,7 @@ describe('AccordianReferences tests', () => { it('renders the content when it is expanded', () => { setup({ isOpen: true } as AccordianReferencesProps); - expect(screen.getByRole('switch', { name: 'References (3)' })).toBeInTheDocument(); + expect(screen.getByRole('switch', { name: 'References 3' })).toBeInTheDocument(); expect(screen.getAllByRole('link', { name: /^service\d\sop\d/ })).toHaveLength(2); expect(screen.getByRole('link', { name: /^View\sLinked/ })).toBeInTheDocument(); }); diff --git a/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/AccordianReferences.tsx b/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/AccordianReferences.tsx index de60443707e..10c31897ec6 100644 --- a/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/AccordianReferences.tsx +++ b/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/AccordianReferences.tsx @@ -17,7 +17,7 @@ import * as React from 'react'; import { Field, GrafanaTheme2, LinkModel } from '@grafana/data'; import { Trans, t } from '@grafana/i18n'; -import { Icon, useStyles2 } from '@grafana/ui'; +import { Counter, Icon, useStyles2 } from '@grafana/ui'; import { autoColor } from '../../Theme'; import { TraceSpanReference } from '../../types/trace'; @@ -36,16 +36,14 @@ const getStyles = (theme: GrafanaTheme2) => ({ }), AccordianReferences: css({ label: 'AccordianReferences', - border: `1px solid ${autoColor(theme, '#d8d8d8')}`, position: 'relative', marginBottom: '0.25rem', }), AccordianReferencesHeader: css({ label: 'AccordianReferencesHeader', - background: autoColor(theme, '#e4e4e4'), color: 'inherit', display: 'block', - padding: '0.25rem 0.5rem', + padding: '0.25rem 0', '&:hover': { background: autoColor(theme, '#dadada'), }, @@ -223,7 +221,7 @@ const AccordianReferences = ({ References {' '} - ({data.length}) + {isOpen && ( ', () => { - const props = { - compact: false, - data: warnings, - highContrast: false, - isOpen: false, - label: 'le-label', - onToggle: jest.fn(), - }; - - it('renders without exploding', () => { - render(); - expect(() => render()).not.toThrow(); - }); - - it('renders the label', () => { - render(); - const { getByText } = within(screen.getByTestId('AccordianText--header')); - expect(getByText(props.label)).toBeInTheDocument(); - }); - - it('renders the content when it is expanded', () => { - props.isOpen = true; - render(); - warnings.forEach((warning) => { - expect(screen.getByText(warning)).toBeInTheDocument(); - }); - }); -}); diff --git a/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/AccordianText.tsx b/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/AccordianText.tsx deleted file mode 100644 index f0efc64b10a..00000000000 --- a/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/AccordianText.tsx +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright (c) 2019 Uber Technologies, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { css } from '@emotion/css'; -import cx from 'classnames'; -import * as React from 'react'; - -import { GrafanaTheme2 } from '@grafana/data'; -import { Icon, useStyles2 } from '@grafana/ui'; - -import { autoColor } from '../../Theme'; -import TNil from '../../types/TNil'; - -import { getStyles as getAccordianKeyValuesStyles } from './AccordianKeyValues'; -import TextList from './TextList'; - -import { alignIcon } from '.'; - -const getStyles = (theme: GrafanaTheme2) => ({ - header: css({ - cursor: 'pointer', - overflow: 'hidden', - padding: '0.25em 0.1em', - textOverflow: 'ellipsis', - whiteSpace: 'nowrap', - '&:hover': { - background: autoColor(theme, '#e8e8e8'), - }, - }), -}); - -type AccordianTextProps = { - className?: string | TNil; - headerClassName?: string | TNil; - data: string[]; - highContrast?: boolean; - interactive?: boolean; - isOpen: boolean; - label: React.ReactNode | string; - onToggle?: null | (() => void); - TextComponent?: React.ElementType<{ data: string[] }>; -}; - -function DefaultTextComponent({ data }: { data: string[] }) { - return ; -} - -export default function AccordianText({ - className = null, - data, - headerClassName, - highContrast = false, - interactive = true, - isOpen, - label, - onToggle = null, - TextComponent = DefaultTextComponent, -}: AccordianTextProps) { - const isEmpty = !Array.isArray(data) || !data.length; - const accordianKeyValuesStyles = useStyles2(getAccordianKeyValuesStyles); - const iconCls = cx(alignIcon, { [accordianKeyValuesStyles.emptyIcon]: isEmpty }); - let arrow: React.ReactNode | null = null; - let headerProps: {} | null = null; - if (interactive) { - arrow = isOpen ? ( - - ) : ( - - ); - headerProps = { - 'aria-checked': isOpen, - onClick: isEmpty ? null : onToggle, - role: 'switch', - }; - } - const styles = useStyles2(getStyles); - return ( -
-
- {arrow} - {label} ({data.length}) -
- {isOpen && } -
- ); -} diff --git a/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/KeyValuesTable.tsx b/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/KeyValuesTable.tsx index 83632c9351d..a6c79b2d2cf 100644 --- a/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/KeyValuesTable.tsx +++ b/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/KeyValuesTable.tsx @@ -47,7 +47,7 @@ export const getStyles = (theme: GrafanaTheme2) => { row: css({ label: 'row', '& > td': { - padding: '0.5rem 0.5rem', + padding: '0 0.5rem', height: '30px', }, '&:nth-child(2n) > td': { @@ -68,7 +68,6 @@ export const getStyles = (theme: GrafanaTheme2) => { color: autoColor(theme, '#888'), whiteSpace: 'pre', width: '125px', - verticalAlign: 'top', }), copyColumn: css({ label: 'copyColumn', @@ -118,19 +117,32 @@ export const LinkValue = ({ link, children }: PropsWithChildren) export type KeyValuesTableProps = { data: TraceKeyValuePair[]; linksGetter?: ((pairs: TraceKeyValuePair[], index: number) => KeyValuesTableLink[]) | TNil; + onlyValues?: boolean; }; export default function KeyValuesTable(props: KeyValuesTableProps) { - const { data, linksGetter } = props; + const { data, linksGetter, onlyValues } = props; const styles = useStyles2(getStyles); return (
{data.map((row, i) => { - const markup = { - __html: jsonMarkup(parseIfComplexJson(row.value)), - }; + let markup = { __html: '' }; + if (row.type === 'code') { + markup = { + __html: `
${row.value}
`, + }; + } else if (row.type === 'text') { + markup = { + __html: `${row.value}`, + }; + } else { + markup = { + __html: jsonMarkup(parseIfComplexJson(row.value)), + }; + } + const jsonTable =
; const links = linksGetter?.(data, i); let valueMarkup; @@ -147,15 +159,17 @@ export default function KeyValuesTable(props: KeyValuesTableProps) { return ( // `i` is necessary in the key because row.key can repeat
- + {!onlyValues && ( + + )} diff --git a/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/TextList.tsx b/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/TextList.tsx index f59b1fb4421..29ee78fb866 100644 --- a/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/TextList.tsx +++ b/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/TextList.tsx @@ -15,9 +15,10 @@ import { css } from '@emotion/css'; import cx from 'classnames'; +import { GrafanaTheme2 } from '@grafana/data'; import { useStyles2 } from '@grafana/ui'; -const getStyles = () => ({ +const getStyles = (theme: GrafanaTheme2) => ({ TextList: css({ maxHeight: '450px', overflow: 'auto', @@ -32,7 +33,7 @@ const getStyles = () => ({ padding: '0.25rem 0.5rem', verticalAlign: 'top', '&:nth-child(2n)': { - background: '#f5f5f5', + background: theme.colors.background.secondary, }, }), }); diff --git a/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/index.tsx b/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/index.tsx index 67a6043f397..627e8fc2ec4 100644 --- a/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/index.tsx +++ b/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/index.tsx @@ -29,11 +29,11 @@ import { PluginExtensionPoints, IconName, } from '@grafana/data'; -import { Trans, t } from '@grafana/i18n'; +import { t } from '@grafana/i18n'; import { TraceToProfilesOptions } from '@grafana/o11y-ds-frontend'; import { usePluginLinks } from '@grafana/runtime'; import { TimeZone } from '@grafana/schema'; -import { TextArea, useStyles2 } from '@grafana/ui'; +import { useStyles2 } from '@grafana/ui'; import { pyroscopeProfileIdTagKey } from '../../../createSpanLink'; import { autoColor } from '../../Theme'; @@ -46,7 +46,6 @@ import { formatDuration } from '../utils'; import AccordianKeyValues from './AccordianKeyValues'; import AccordianLogs from './AccordianLogs'; import AccordianReferences from './AccordianReferences'; -import AccordianText from './AccordianText'; import DetailState from './DetailState'; import { ShareSpanButton } from './ShareSpanButton'; import { getSpanDetailLinkButtons } from './SpanDetailLinkButtons'; @@ -99,6 +98,9 @@ const getStyles = (theme: GrafanaTheme2) => { gap: '0 1rem', marginBottom: '0.25rem', }), + content: css({ + fontSize: theme.typography.bodySmall.fontSize, + }), listWrapper: css({ overflow: 'hidden', flexGrow: 1, @@ -340,7 +342,7 @@ export default function SpanDetail(props: SpanDetailProps) {
{linksComponent}
-
+
)} + {warnings && warnings.length > 0 && ( - - Warnings - - } - data={warnings} + ({ + key: '', + value: warning, + type: 'text', + }))} + showSummary={false} + showCountBadge={true} isOpen={isWarningsOpen} + onlyValues={true} onToggle={() => warningsToggle(spanID)} + label={t('explore.span-detail.warnings', 'Warnings')} /> )} + {stackTraces?.length ? ( - ({ + key: '', + value: stackTrace, + type: 'code', + }))} + onlyValues={true} + showSummary={false} + showCountBadge={true} isOpen={isStackTracesOpen} - TextComponent={(textComponentProps) => { - let text; - if (textComponentProps.data?.length > 1) { - text = textComponentProps.data - .map((stackTrace, index) => `StackTrace ${index + 1}:\n${stackTrace}`) - .join('\n'); - } else { - text = textComponentProps.data?.[0]; - } - return ( -
- {row.key} - + {row.key} + {valueMarkup}