From 36275a551041d507896c9c061b1d7e6398e73bba Mon Sep 17 00:00:00 2001 From: Andres Martinez Gotor Date: Thu, 6 Feb 2025 09:55:17 +0100 Subject: [PATCH 01/34] Advisor: Refactor check interfaces (#100043) --- apps/advisor/pkg/app/app.go | 2 +- .../pkg/app/checks/datasourcecheck/check.go | 102 ++++++++++++++---- .../app/checks/datasourcecheck/check_test.go | 43 ++++++-- apps/advisor/pkg/app/checks/ifaces.go | 22 +++- .../pkg/app/checks/plugincheck/check.go | 100 ++++++++++++++--- .../pkg/app/checks/plugincheck/check_test.go | 13 ++- apps/advisor/pkg/app/utils.go | 24 ++++- apps/advisor/pkg/app/utils_test.go | 38 ++++++- 8 files changed, 284 insertions(+), 60 deletions(-) diff --git a/apps/advisor/pkg/app/app.go b/apps/advisor/pkg/app/app.go index 42848758d5a..554357caca2 100644 --- a/apps/advisor/pkg/app/app.go +++ b/apps/advisor/pkg/app/app.go @@ -37,7 +37,7 @@ func New(cfg app.Config) (app.App, error) { // Initialize checks checkMap := map[string]checks.Check{} for _, c := range checkRegistry.Checks() { - checkMap[c.Type()] = c + checkMap[c.ID()] = c } simpleConfig := simple.AppConfig{ diff --git a/apps/advisor/pkg/app/checks/datasourcecheck/check.go b/apps/advisor/pkg/app/checks/datasourcecheck/check.go index df05bf40688..f7dbcc53024 100644 --- a/apps/advisor/pkg/app/checks/datasourcecheck/check.go +++ b/apps/advisor/pkg/app/checks/datasourcecheck/check.go @@ -15,6 +15,13 @@ import ( "k8s.io/klog/v2" ) +type check struct { + DatasourceSvc datasources.DataSourceService + PluginStore pluginstore.Store + PluginContextProvider pluginContextProvider + PluginClient plugins.Client +} + func New( datasourceSvc datasources.DataSourceService, pluginStore pluginstore.Store, @@ -29,28 +36,53 @@ func New( } } -type check struct { - DatasourceSvc datasources.DataSourceService - PluginStore pluginstore.Store - PluginContextProvider pluginContextProvider - PluginClient plugins.Client -} - -func (c *check) Type() string { - return "datasource" -} - -func (c *check) Run(ctx context.Context, obj *advisor.CheckSpec) (*advisor.CheckV0alpha1StatusReport, error) { - // Optionally read the check input encoded in the object - // fmt.Println(obj.Data) - +func (c *check) Items(ctx context.Context) ([]any, error) { dss, err := c.DatasourceSvc.GetAllDataSources(ctx, &datasources.GetAllDataSourcesQuery{}) if err != nil { return nil, err } + res := make([]any, len(dss)) + for i, ds := range dss { + res[i] = ds + } + return res, nil +} +func (c *check) ID() string { + return "datasource" +} + +func (c *check) Steps() []checks.Step { + return []checks.Step{ + &uidValidationStep{}, + &healthCheckStep{ + PluginContextProvider: c.PluginContextProvider, + PluginClient: c.PluginClient, + }, + } +} + +type uidValidationStep struct{} + +func (s *uidValidationStep) ID() string { + return "uid-validation" +} + +func (s *uidValidationStep) Title() string { + return "UID validation" +} + +func (s *uidValidationStep) Description() string { + return "Check if the UID of each data source is valid." +} + +func (s *uidValidationStep) Run(ctx context.Context, obj *advisor.CheckSpec, items []any) ([]advisor.CheckReportError, error) { dsErrs := []advisor.CheckReportError{} - for _, ds := range dss { + for _, i := range items { + ds, ok := i.(*datasources.DataSource) + if !ok { + return nil, fmt.Errorf("invalid item type %T", i) + } // Data source UID validation err := util.ValidateUID(ds.UID) if err != nil { @@ -60,13 +92,41 @@ func (c *check) Run(ctx context.Context, obj *advisor.CheckSpec) (*advisor.Check Action: "Check the documentation for more information.", }) } + } + return dsErrs, nil +} + +type healthCheckStep struct { + PluginContextProvider pluginContextProvider + PluginClient plugins.Client +} + +func (s *healthCheckStep) Title() string { + return "Health check" +} + +func (s *healthCheckStep) Description() string { + return "Check if all data sources are healthy." +} + +func (s *healthCheckStep) ID() string { + return "health-check" +} + +func (s *healthCheckStep) Run(ctx context.Context, obj *advisor.CheckSpec, items []any) ([]advisor.CheckReportError, error) { + dsErrs := []advisor.CheckReportError{} + for _, i := range items { + ds, ok := i.(*datasources.DataSource) + if !ok { + return nil, fmt.Errorf("invalid item type %T", i) + } // Health check execution requester, err := identity.GetRequester(ctx) if err != nil { return nil, err } - pCtx, err := c.PluginContextProvider.GetWithDataSource(ctx, ds.Type, requester, ds) + pCtx, err := s.PluginContextProvider.GetWithDataSource(ctx, ds.Type, requester, ds) if err != nil { klog.ErrorS(err, "Error creating plugin context", "datasource", ds.Name) continue @@ -75,7 +135,7 @@ func (c *check) Run(ctx context.Context, obj *advisor.CheckSpec) (*advisor.Check PluginContext: pCtx, Headers: map[string]string{}, } - resp, err := c.PluginClient.CheckHealth(ctx, req) + resp, err := s.PluginClient.CheckHealth(ctx, req) if err != nil { fmt.Println("Error checking health", err) continue @@ -90,11 +150,7 @@ func (c *check) Run(ctx context.Context, obj *advisor.CheckSpec) (*advisor.Check }) } } - - return &advisor.CheckV0alpha1StatusReport{ - Count: int64(len(dss)), - Errors: dsErrs, - }, nil + return dsErrs, nil } type pluginContextProvider interface { diff --git a/apps/advisor/pkg/app/checks/datasourcecheck/check_test.go b/apps/advisor/pkg/app/checks/datasourcecheck/check_test.go index 5c3dfb913e3..c360f96ad14 100644 --- a/apps/advisor/pkg/app/checks/datasourcecheck/check_test.go +++ b/apps/advisor/pkg/app/checks/datasourcecheck/check_test.go @@ -31,11 +31,18 @@ func TestCheck_Run(t *testing.T) { } ctx := identity.WithRequester(context.Background(), &user.SignedInUser{}) - report, err := check.Run(ctx, &advisor.CheckSpec{}) + items, err := check.Items(ctx) + assert.NoError(t, err) + errs := []advisor.CheckReportError{} + for _, step := range check.Steps() { + stepErrs, err := step.Run(ctx, &advisor.CheckSpec{}, items) + assert.NoError(t, err) + errs = append(errs, stepErrs...) + } assert.NoError(t, err) - assert.Equal(t, int64(2), report.Count) - assert.Empty(t, report.Errors) + assert.Equal(t, 2, len(items)) + assert.Empty(t, errs) }) t.Run("should return errors when datasource UID is invalid", func(t *testing.T) { @@ -54,12 +61,19 @@ func TestCheck_Run(t *testing.T) { } ctx := identity.WithRequester(context.Background(), &user.SignedInUser{}) - report, err := check.Run(ctx, &advisor.CheckSpec{}) + items, err := check.Items(ctx) + assert.NoError(t, err) + errs := []advisor.CheckReportError{} + for _, step := range check.Steps() { + stepErrs, err := step.Run(ctx, &advisor.CheckSpec{}, items) + assert.NoError(t, err) + errs = append(errs, stepErrs...) + } assert.NoError(t, err) - assert.Equal(t, int64(1), report.Count) - assert.Len(t, report.Errors, 1) - assert.Equal(t, "Invalid UID 'invalid uid' for data source Prometheus", report.Errors[0].Reason) + assert.Equal(t, 1, len(items)) + assert.Len(t, errs, 1) + assert.Equal(t, "Invalid UID 'invalid uid' for data source Prometheus", errs[0].Reason) }) t.Run("should return errors when datasource health check fails", func(t *testing.T) { @@ -78,12 +92,19 @@ func TestCheck_Run(t *testing.T) { } ctx := identity.WithRequester(context.Background(), &user.SignedInUser{}) - report, err := check.Run(ctx, &advisor.CheckSpec{}) + items, err := check.Items(ctx) + assert.NoError(t, err) + errs := []advisor.CheckReportError{} + for _, step := range check.Steps() { + stepErrs, err := step.Run(ctx, &advisor.CheckSpec{}, items) + assert.NoError(t, err) + errs = append(errs, stepErrs...) + } assert.NoError(t, err) - assert.Equal(t, int64(1), report.Count) - assert.Len(t, report.Errors, 1) - assert.Equal(t, "Health check failed for Prometheus", report.Errors[0].Reason) + assert.Equal(t, 1, len(items)) + assert.Len(t, errs, 1) + assert.Equal(t, "Health check failed for Prometheus", errs[0].Reason) }) } diff --git a/apps/advisor/pkg/app/checks/ifaces.go b/apps/advisor/pkg/app/checks/ifaces.go index 008f5656298..b630a84871c 100644 --- a/apps/advisor/pkg/app/checks/ifaces.go +++ b/apps/advisor/pkg/app/checks/ifaces.go @@ -6,8 +6,24 @@ import ( advisorv0alpha1 "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1" ) -// Check defines the methods that a check must implement to be executed. +// Check returns metadata about the check being executed and the list of Steps type Check interface { - Run(ctx context.Context, obj *advisorv0alpha1.CheckSpec) (*advisorv0alpha1.CheckV0alpha1StatusReport, error) - Type() string + // ID returns the unique identifier of the check + ID() string + // Items returns the list of items that will be checked + Items(ctx context.Context) ([]any, error) + // Steps returns the list of steps that will be executed + Steps() []Step +} + +// Step is a single step in a check, including its metadata +type Step interface { + // ID returns the unique identifier of the step + ID() string + // Title returns the title of the step + Title() string + // Description returns the description of the step + Description() string + // Run executes the step and returns a list of errors + Run(ctx context.Context, obj *advisorv0alpha1.CheckSpec, items []any) ([]advisorv0alpha1.CheckReportError, error) } diff --git a/apps/advisor/pkg/app/checks/plugincheck/check.go b/apps/advisor/pkg/app/checks/plugincheck/check.go index 8a20820f450..c64bc1e7579 100644 --- a/apps/advisor/pkg/app/checks/plugincheck/check.go +++ b/apps/advisor/pkg/app/checks/plugincheck/check.go @@ -36,22 +36,63 @@ type check struct { ManagedPlugins managedplugins.Manager } -func (c *check) Type() string { +func (c *check) ID() string { return "plugin" } -func (c *check) Run(ctx context.Context, _ *advisor.CheckSpec) (*advisor.CheckV0alpha1StatusReport, error) { +func (c *check) Items(ctx context.Context) ([]any, error) { ps := c.PluginStore.Plugins(ctx) + res := make([]any, len(ps)) + for i, p := range ps { + res[i] = p + } + return res, nil +} +func (c *check) Steps() []checks.Step { + return []checks.Step{ + &deprecationStep{ + PluginRepo: c.PluginRepo, + }, + &updateStep{ + PluginRepo: c.PluginRepo, + PluginPreinstall: c.PluginPreinstall, + ManagedPlugins: c.ManagedPlugins, + }, + } +} + +type deprecationStep struct { + PluginRepo repo.Service +} + +func (s *deprecationStep) Title() string { + return "Deprecation check" +} + +func (s *deprecationStep) Description() string { + return "Check if any installed plugins are deprecated." +} + +func (s *deprecationStep) ID() string { + return "deprecation" +} + +func (s *deprecationStep) Run(ctx context.Context, _ *advisor.CheckSpec, items []any) ([]advisor.CheckReportError, error) { errs := []advisor.CheckReportError{} - for _, p := range ps { + for _, i := range items { + p, ok := i.(pluginstore.Plugin) + if !ok { + return nil, fmt.Errorf("invalid item type %T", i) + } + // Skip if it's a core plugin if p.IsCorePlugin() { continue } // Check if plugin is deprecated - i, err := c.PluginRepo.PluginInfo(ctx, p.ID) + i, err := s.PluginRepo.PluginInfo(ctx, p.ID) if err != nil { continue } @@ -62,13 +103,49 @@ func (c *check) Run(ctx context.Context, _ *advisor.CheckSpec) (*advisor.CheckV0 Action: "Check the documentation for recommended steps.", }) } + } + return errs, nil +} - // Check if plugin has a newer version, only if it's not managed or pinned - if c.isManaged(ctx, p.ID) || c.PluginPreinstall.IsPinned(p.ID) { +type updateStep struct { + PluginRepo repo.Service + PluginPreinstall plugininstaller.Preinstall + ManagedPlugins managedplugins.Manager +} + +func (s *updateStep) Title() string { + return "Update check" +} + +func (s *updateStep) Description() string { + return "Check if any installed plugins have a newer version available." +} + +func (s *updateStep) ID() string { + return "update" +} + +func (s *updateStep) Run(ctx context.Context, _ *advisor.CheckSpec, items []any) ([]advisor.CheckReportError, error) { + errs := []advisor.CheckReportError{} + for _, i := range items { + p, ok := i.(pluginstore.Plugin) + if !ok { + return nil, fmt.Errorf("invalid item type %T", i) + } + + // Skip if it's a core plugin + if p.IsCorePlugin() { continue } + + // Skip if it's managed or pinned + if s.isManaged(ctx, p.ID) || s.PluginPreinstall.IsPinned(p.ID) { + continue + } + + // Check if plugin has a newer version available compatOpts := repo.NewCompatOpts(services.GrafanaVersion, sysruntime.GOOS, sysruntime.GOARCH) - info, err := c.PluginRepo.GetPluginArchiveInfo(ctx, p.ID, "", compatOpts) + info, err := s.PluginRepo.GetPluginArchiveInfo(ctx, p.ID, "", compatOpts) if err != nil { continue } @@ -83,10 +160,7 @@ func (c *check) Run(ctx context.Context, _ *advisor.CheckSpec) (*advisor.CheckV0 } } - return &advisor.CheckV0alpha1StatusReport{ - Count: int64(len(ps)), - Errors: errs, - }, nil + return errs, nil } func hasUpdate(current pluginstore.Plugin, latest *repo.PluginArchiveInfo) bool { @@ -100,8 +174,8 @@ func hasUpdate(current pluginstore.Plugin, latest *repo.PluginArchiveInfo) bool return current.Info.Version != latest.Version } -func (c *check) isManaged(ctx context.Context, pluginID string) bool { - for _, managedPlugin := range c.ManagedPlugins.ManagedPlugins(ctx) { +func (s *updateStep) isManaged(ctx context.Context, pluginID string) bool { + for _, managedPlugin := range s.ManagedPlugins.ManagedPlugins(ctx) { if managedPlugin == pluginID { return true } diff --git a/apps/advisor/pkg/app/checks/plugincheck/check_test.go b/apps/advisor/pkg/app/checks/plugincheck/check_test.go index 41c0705e904..53450ce7bc5 100644 --- a/apps/advisor/pkg/app/checks/plugincheck/check_test.go +++ b/apps/advisor/pkg/app/checks/plugincheck/check_test.go @@ -126,10 +126,17 @@ func TestRun(t *testing.T) { managedPlugins := &mockManagedPlugins{managed: tt.pluginManaged} check := New(pluginStore, pluginRepo, pluginPreinstall, managedPlugins) - report, err := check.Run(context.Background(), nil) + items, err := check.Items(context.Background()) assert.NoError(t, err) - assert.Equal(t, int64(len(tt.plugins)), report.Count) - assert.Equal(t, tt.expectedErrors, report.Errors) + errs := []advisor.CheckReportError{} + for _, step := range check.Steps() { + stepErrs, err := step.Run(context.Background(), &advisor.CheckSpec{}, items) + assert.NoError(t, err) + errs = append(errs, stepErrs...) + } + assert.NoError(t, err) + assert.Equal(t, len(tt.plugins), len(items)) + assert.Equal(t, tt.expectedErrors, errs) }) } } diff --git a/apps/advisor/pkg/app/utils.go b/apps/advisor/pkg/app/utils.go index 4eae090198b..c59dbaca21e 100644 --- a/apps/advisor/pkg/app/utils.go +++ b/apps/advisor/pkg/app/utils.go @@ -72,14 +72,32 @@ func processCheck(ctx context.Context, client resource.Client, obj resource.Obje UserUID: uid, FallbackType: typ, }) - // Run the checks - report, err := check.Run(ctx, &c.Spec) + // Get the items to check + items, err := check.Items(ctx) if err != nil { setErr := setStatusAnnotation(ctx, client, obj, "error") if setErr != nil { return setErr } - return err + return fmt.Errorf("error initializing check: %w", err) + } + // Run the steps + steps := check.Steps() + errs := []advisorv0alpha1.CheckReportError{} + for _, step := range steps { + stepErrs, err := step.Run(ctx, &c.Spec, items) + if err != nil { + setErr := setStatusAnnotation(ctx, client, obj, "error") + if setErr != nil { + return setErr + } + return fmt.Errorf("error running step %s: %w", step.Title(), err) + } + errs = append(errs, stepErrs...) + } + report := &advisorv0alpha1.CheckV0alpha1StatusReport{ + Errors: errs, + Count: int64(len(items)), } err = setStatusAnnotation(ctx, client, obj, "processed") if err != nil { diff --git a/apps/advisor/pkg/app/utils_test.go b/apps/advisor/pkg/app/utils_test.go index 6626f645eb7..a13880f8209 100644 --- a/apps/advisor/pkg/app/utils_test.go +++ b/apps/advisor/pkg/app/utils_test.go @@ -115,10 +115,42 @@ func (m *mockClient) PatchInto(ctx context.Context, id resource.Identifier, req } type mockCheck struct { - checks.Check err error } -func (m *mockCheck) Run(ctx context.Context, spec *advisorv0alpha1.CheckSpec) (*advisorv0alpha1.CheckV0alpha1StatusReport, error) { - return &advisorv0alpha1.CheckV0alpha1StatusReport{}, m.err +func (m *mockCheck) ID() string { + return "mock" +} + +func (m *mockCheck) Items(ctx context.Context) ([]any, error) { + return []any{}, nil +} + +func (m *mockCheck) Steps() []checks.Step { + return []checks.Step{ + &mockStep{err: m.err}, + } +} + +type mockStep struct { + err error +} + +func (m *mockStep) Run(ctx context.Context, obj *advisorv0alpha1.CheckSpec, items []any) ([]advisorv0alpha1.CheckReportError, error) { + if m.err != nil { + return nil, m.err + } + return nil, nil +} + +func (m *mockStep) Title() string { + return "mock" +} + +func (m *mockStep) Description() string { + return "mock" +} + +func (m *mockStep) ID() string { + return "mock" } From f89da88f0fbdbc7d253171f1e92b5c6fe09ae5c7 Mon Sep 17 00:00:00 2001 From: Ashley Harrison Date: Thu, 6 Feb 2025 09:16:47 +0000 Subject: [PATCH 02/34] Storybook: Support an arbitrary number of themes (#100111) * support more themes in storybook * default to dark theme * fix type error * change theme in docs container * add TODO * only show extra themes in development mode * add comment --- .github/renovate.json5 | 1 - packages/grafana-ui/.storybook/main.ts | 1 - packages/grafana-ui/.storybook/manager.ts | 6 +++-- packages/grafana-ui/.storybook/preview.ts | 26 ++++++++++++------ .../grafana-ui/.storybook/storybookTheme.ts | 10 ++----- packages/grafana-ui/package.json | 1 - .../utils/storybook/ThemedDocsContainer.tsx | 19 +++++++++---- .../src/utils/storybook/withTheme.tsx | 20 ++++++++------ yarn.lock | 27 ++++--------------- 9 files changed, 55 insertions(+), 56 deletions(-) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 6cc2ac80423..a782ac2c0a0 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -13,7 +13,6 @@ "slate-react", // we don't want to continue using this on the long run, use Monaco editor instead of Slate "@types/slate-react", // we don't want to continue using this on the long run, use Monaco editor instead of Slate "@types/slate", // we don't want to continue using this on the long run, use Monaco editor instead of Slate - "storybook-dark-mode", // 4.0.2 causes storybook 8.4 to break with react hooks errors // Temporarily pause updating lerna and nx until we resolve build issues "lerna", "nx" diff --git a/packages/grafana-ui/.storybook/main.ts b/packages/grafana-ui/.storybook/main.ts index 5b169002dc9..401a79ef7ea 100644 --- a/packages/grafana-ui/.storybook/main.ts +++ b/packages/grafana-ui/.storybook/main.ts @@ -43,7 +43,6 @@ const mainConfig: StorybookConfig = { }, }, getAbsolutePath('@storybook/addon-storysource'), - getAbsolutePath('storybook-dark-mode'), getAbsolutePath('@storybook/addon-webpack5-compiler-swc'), ], framework: { diff --git a/packages/grafana-ui/.storybook/manager.ts b/packages/grafana-ui/.storybook/manager.ts index 1caddf59335..1a32c4aeaa4 100644 --- a/packages/grafana-ui/.storybook/manager.ts +++ b/packages/grafana-ui/.storybook/manager.ts @@ -1,6 +1,8 @@ import { addons } from '@storybook/manager-api'; -import { GrafanaDark } from './storybookTheme'; +import { getThemeById } from '@grafana/data'; +import { createStorybookTheme } from './storybookTheme'; +const systemTheme = getThemeById('system'); addons.setConfig({ isFullscreen: false, panelPosition: 'right', @@ -10,5 +12,5 @@ addons.setConfig({ sidebar: { showRoots: true, }, - theme: GrafanaDark, + theme: createStorybookTheme(systemTheme), }); diff --git a/packages/grafana-ui/.storybook/preview.ts b/packages/grafana-ui/.storybook/preview.ts index cc4be9bfbe8..a8dc3e9f062 100644 --- a/packages/grafana-ui/.storybook/preview.ts +++ b/packages/grafana-ui/.storybook/preview.ts @@ -1,6 +1,6 @@ import { Preview } from '@storybook/react'; import 'jquery'; -import { getTimeZone, getTimeZones } from '@grafana/data'; +import { getBuiltInThemes, getTimeZone, getTimeZones, GrafanaTheme2 } from '@grafana/data'; import '../../../public/vendor/flot/jquery.flot.js'; import '../../../public/vendor/flot/jquery.flot.selection'; @@ -20,10 +20,9 @@ import { ThemedDocsContainer } from '../src/utils/storybook/ThemedDocsContainer' import lightTheme from '../../../public/sass/grafana.light.scss'; // @ts-ignore import darkTheme from '../../../public/sass/grafana.dark.scss'; -import { GrafanaDark, GrafanaLight } from './storybookTheme'; -const handleThemeChange = (theme: any) => { - if (theme !== 'light') { +const handleThemeChange = (theme: GrafanaTheme2) => { + if (theme.colors.mode !== 'light') { lightTheme.unuse(); darkTheme.use(); } else { @@ -32,14 +31,12 @@ const handleThemeChange = (theme: any) => { } }; +const showExtraThemes = process.env.NODE_ENV === 'development'; + const preview: Preview = { decorators: [withTheme(handleThemeChange), withTimeZone()], parameters: { actions: { argTypesRegex: '^on[A-Z].*' }, - darkMode: { - dark: GrafanaDark, - light: GrafanaLight, - }, docs: { container: ThemedDocsContainer, }, @@ -68,6 +65,19 @@ const preview: Preview = { }, }, globalTypes: { + theme: { + name: 'Theme', + description: 'Global theme for components', + defaultValue: 'system', + toolbar: { + icon: 'paintbrush', + items: getBuiltInThemes(showExtraThemes).map((theme) => ({ + value: theme.id, + title: theme.name, + })), + showName: true, + }, + }, timeZone: { description: 'Set the timezone for the storybook preview', defaultValue: getTimeZone(), diff --git a/packages/grafana-ui/.storybook/storybookTheme.ts b/packages/grafana-ui/.storybook/storybookTheme.ts index 32649630d9f..7bb3b29e2cb 100644 --- a/packages/grafana-ui/.storybook/storybookTheme.ts +++ b/packages/grafana-ui/.storybook/storybookTheme.ts @@ -1,8 +1,7 @@ -import { GrafanaTheme2, createTheme } from '@grafana/data'; -//@ts-ignore +import { GrafanaTheme2 } from '@grafana/data'; import { create } from '@storybook/theming'; -const createStorybookTheme = (theme: GrafanaTheme2) => { +export const createStorybookTheme = (theme: GrafanaTheme2) => { return create({ base: theme.colors.mode, colorPrimary: theme.colors.primary.main, @@ -38,8 +37,3 @@ const createStorybookTheme = (theme: GrafanaTheme2) => { brandImage: `public/img/grafana_text_logo-${theme.colors.mode}.svg`, }); }; - -const GrafanaLight = createStorybookTheme(createTheme({ colors: { mode: 'light' } })); -const GrafanaDark = createStorybookTheme(createTheme({ colors: { mode: 'dark' } })); - -export { GrafanaLight, GrafanaDark }; diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json index ebe6a77ecdc..c7bc33a0a57 100644 --- a/packages/grafana-ui/package.json +++ b/packages/grafana-ui/package.json @@ -180,7 +180,6 @@ "rollup-plugin-svg-import": "3.0.0", "sass-loader": "16.0.4", "storybook": "^8.4.2", - "storybook-dark-mode": "4.0.1", "style-loader": "4.0.0", "typescript": "5.7.3", "webpack": "5.97.1" diff --git a/packages/grafana-ui/src/utils/storybook/ThemedDocsContainer.tsx b/packages/grafana-ui/src/utils/storybook/ThemedDocsContainer.tsx index 1c989e3fd3d..d6485671d84 100644 --- a/packages/grafana-ui/src/utils/storybook/ThemedDocsContainer.tsx +++ b/packages/grafana-ui/src/utils/storybook/ThemedDocsContainer.tsx @@ -1,9 +1,10 @@ -// Wrap the DocsContainer for storybook-dark-mode theme switching support. +// Wrap the DocsContainer for theme switching support. import { DocsContainer, DocsContextProps } from '@storybook/addon-docs'; import * as React from 'react'; -import { useDarkMode } from 'storybook-dark-mode'; -import { GrafanaLight, GrafanaDark } from '../../../.storybook/storybookTheme'; +import { getThemeById } from '@grafana/data'; + +import { createStorybookTheme } from '../../../.storybook/storybookTheme'; import { GlobalStyles } from '../../themes'; type Props = { @@ -12,10 +13,18 @@ type Props = { }; export const ThemedDocsContainer = ({ children, context }: Props) => { - const dark = useDarkMode(); + // Default to system theme for pages that don't have associated stories + // Currently this is only the case for the docs `Intro` page + let themeId = 'system'; + if (context.componentStories().length > 0) { + const story = context.storyById(); + const { globals } = context.getStoryContext(story); + themeId = globals.theme; + } + const theme = getThemeById(themeId); return ( - + {children} diff --git a/packages/grafana-ui/src/utils/storybook/withTheme.tsx b/packages/grafana-ui/src/utils/storybook/withTheme.tsx index d865769c5ab..aa1f761f85f 100644 --- a/packages/grafana-ui/src/utils/storybook/withTheme.tsx +++ b/packages/grafana-ui/src/utils/storybook/withTheme.tsx @@ -1,17 +1,17 @@ import { Decorator } from '@storybook/react'; import * as React from 'react'; -import { useDarkMode } from 'storybook-dark-mode'; -import { createTheme, GrafanaTheme2, ThemeContext } from '@grafana/data'; +import { getThemeById, GrafanaTheme2, ThemeContext } from '@grafana/data'; import { GlobalStyles } from '../../themes/GlobalStyles/GlobalStyles'; type SassThemeChangeHandler = (theme: GrafanaTheme2) => void; -const ThemeableStory = ({ - children, - handleSassThemeChange, -}: React.PropsWithChildren<{ handleSassThemeChange: SassThemeChangeHandler }>) => { - const theme = createTheme({ colors: { mode: useDarkMode() ? 'dark' : 'light' } }); +interface ThemeableStoryProps { + themeId: string; + handleSassThemeChange: SassThemeChangeHandler; +} +const ThemeableStory = ({ children, handleSassThemeChange, themeId }: React.PropsWithChildren) => { + const theme = getThemeById(themeId); handleSassThemeChange(theme); @@ -38,4 +38,8 @@ const ThemeableStory = ({ export const withTheme = (handleSassThemeChange: SassThemeChangeHandler): Decorator => // eslint-disable-next-line react/display-name - (story) => {story()}; + (story, context) => ( + + {story()} + + ); diff --git a/yarn.lock b/yarn.lock index 3ebde167b43..1c4fee5ed1d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4129,7 +4129,6 @@ __metadata: slate-plain-serializer: "npm:0.7.13" slate-react: "npm:0.22.10" storybook: "npm:^8.4.2" - storybook-dark-mode: "npm:4.0.1" style-loader: "npm:4.0.0" tinycolor2: "npm:1.6.0" tslib: "npm:2.8.1" @@ -7524,7 +7523,7 @@ __metadata: languageName: node linkType: hard -"@storybook/components@npm:8.4.4, @storybook/components@npm:^8.0.0, @storybook/components@npm:^8.4.2": +"@storybook/components@npm:8.4.4, @storybook/components@npm:^8.4.2": version: 8.4.4 resolution: "@storybook/components@npm:8.4.4" peerDependencies: @@ -7533,7 +7532,7 @@ __metadata: languageName: node linkType: hard -"@storybook/core-events@npm:^8.0.0, @storybook/core-events@npm:^8.4.2": +"@storybook/core-events@npm:^8.4.2": version: 8.4.4 resolution: "@storybook/core-events@npm:8.4.4" peerDependencies: @@ -7605,7 +7604,7 @@ __metadata: languageName: node linkType: hard -"@storybook/icons@npm:^1.2.12, @storybook/icons@npm:^1.2.5": +"@storybook/icons@npm:^1.2.12": version: 1.2.12 resolution: "@storybook/icons@npm:1.2.12" peerDependencies: @@ -7615,7 +7614,7 @@ __metadata: languageName: node linkType: hard -"@storybook/manager-api@npm:8.4.4, @storybook/manager-api@npm:^8.0.0, @storybook/manager-api@npm:^8.4.2": +"@storybook/manager-api@npm:8.4.4, @storybook/manager-api@npm:^8.4.2": version: 8.4.4 resolution: "@storybook/manager-api@npm:8.4.4" peerDependencies: @@ -7766,7 +7765,7 @@ __metadata: languageName: node linkType: hard -"@storybook/theming@npm:8.4.4, @storybook/theming@npm:^8.0.0, @storybook/theming@npm:^8.4.2": +"@storybook/theming@npm:8.4.4, @storybook/theming@npm:^8.4.2": version: 8.4.4 resolution: "@storybook/theming@npm:8.4.4" peerDependencies: @@ -28886,22 +28885,6 @@ __metadata: languageName: node linkType: hard -"storybook-dark-mode@npm:4.0.1": - version: 4.0.1 - resolution: "storybook-dark-mode@npm:4.0.1" - dependencies: - "@storybook/components": "npm:^8.0.0" - "@storybook/core-events": "npm:^8.0.0" - "@storybook/global": "npm:^5.0.0" - "@storybook/icons": "npm:^1.2.5" - "@storybook/manager-api": "npm:^8.0.0" - "@storybook/theming": "npm:^8.0.0" - fast-deep-equal: "npm:^3.1.3" - memoizerific: "npm:^1.11.3" - checksum: 10/3225e5bdaba0ea76b65d642202d9712d7de234e3b5673fb46e444892ab114be207dd287778e2002b662ec35bb8153d2624ff280ce51c5299fb13c711431dad40 - languageName: node - linkType: hard - "storybook@npm:^8.4.2": version: 8.4.4 resolution: "storybook@npm:8.4.4" From d5f1f4eb5cffde05f4e88b546fbece9cd51e353f Mon Sep 17 00:00:00 2001 From: Leon Sorokin Date: Thu, 6 Feb 2025 03:33:08 -0600 Subject: [PATCH 03/34] ComboBox: Fall back to substring matching for symbols/operators (#100148) --- .../src/components/Combobox/filter.test.ts | 13 +++++++++++++ .../grafana-ui/src/components/Combobox/filter.ts | 5 +++++ 2 files changed, 18 insertions(+) diff --git a/packages/grafana-ui/src/components/Combobox/filter.test.ts b/packages/grafana-ui/src/components/Combobox/filter.test.ts index f7a5eb534fd..8b3c13ed354 100644 --- a/packages/grafana-ui/src/components/Combobox/filter.test.ts +++ b/packages/grafana-ui/src/components/Combobox/filter.test.ts @@ -58,4 +58,17 @@ describe('combobox filter', () => { expect(matches.map((m) => m.value)).toEqual(['台南市', '南投縣']); }); }); + + describe('operators', () => { + it('should do substring match when needle is only symbols', () => { + const needle = '='; + + const stringOptions = ['=', '<=', '>', '!~']; + const options = stringOptions.map((value) => ({ value })); + + const matches = fuzzyFind(options, stringOptions, needle); + + expect(matches.map((m) => m.value)).toEqual(['=', '<=']); + }); + }); }); diff --git a/packages/grafana-ui/src/components/Combobox/filter.ts b/packages/grafana-ui/src/components/Combobox/filter.ts index a03cbec475c..7653286f42a 100644 --- a/packages/grafana-ui/src/components/Combobox/filter.ts +++ b/packages/grafana-ui/src/components/Combobox/filter.ts @@ -4,6 +4,9 @@ import { ALL_OPTION_VALUE, ComboboxOption } from './types'; // https://catonmat.net/my-favorite-regex :) const REGEXP_NON_ASCII = /[^ -~]/m; +// https://www.asciitable.com/ +// matches only these: `~!@#$%^&*()_+-=[]\{}|;':",./<>? +const REGEXP_ONLY_SYMBOLS = /^[\x21-\x2F\x3A-\x40\x5B-\x60\x7B-\x7E]+$/m; // limit max terms in needle that qualify for re-ordering const outOfOrderLimit = 5; // beyond 25 chars fall back to substring search @@ -51,6 +54,8 @@ export function fuzzyFind( else if ( // contains non-ascii REGEXP_NON_ASCII.test(needle) || + // is only ascii symbols (operators) + REGEXP_ONLY_SYMBOLS.test(needle) || // too long (often copy-paste from somewhere) needle.length > maxNeedleLength || uf.split(needle).length > maxFuzzyTerms From 0916994d0af3107853393557ab5153414a7f4fa2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 6 Feb 2025 10:57:08 +0100 Subject: [PATCH 04/34] Dashboard: Various fixes to new layouts (#100107) * Dashboard: Various fixes to new layouts * review fixes * Fix * Update * Fix test --- .../dashboard-scene/scene/DashboardScene.tsx | 16 ++--- .../DefaultGridLayoutManager.test.tsx | 16 ----- .../DefaultGridLayoutManager.tsx | 56 ++--------------- .../ResponsiveGridLayoutManager.tsx | 63 +++++++++++-------- .../scene/layout-rows/RowsLayoutManager.tsx | 8 --- .../scene/types/DashboardLayoutManager.ts | 5 -- .../utils/dashboardSceneGraph.test.ts | 24 ++++++- .../utils/dashboardSceneGraph.ts | 29 ++++++++- 8 files changed, 99 insertions(+), 118 deletions(-) diff --git a/public/app/features/dashboard-scene/scene/DashboardScene.tsx b/public/app/features/dashboard-scene/scene/DashboardScene.tsx index e015172d4c6..29c9380039c 100644 --- a/public/app/features/dashboard-scene/scene/DashboardScene.tsx +++ b/public/app/features/dashboard-scene/scene/DashboardScene.tsx @@ -58,7 +58,13 @@ import { dashboardSceneGraph } from '../utils/dashboardSceneGraph'; import { djb2Hash } from '../utils/djb2Hash'; import { getDashboardUrl } from '../utils/getDashboardUrl'; import { getViewPanelUrl } from '../utils/urlBuilders'; -import { getClosestVizPanel, getDashboardSceneFor, getDefaultVizPanel, getPanelIdForVizPanel } from '../utils/utils'; +import { + getClosestVizPanel, + getDashboardSceneFor, + getDefaultVizPanel, + getLayoutManagerFor, + getPanelIdForVizPanel, +} from '../utils/utils'; import { SchemaV2EditorDrawer } from '../v2schema/SchemaV2EditorDrawer'; import { AddLibraryPanelDrawer } from './AddLibraryPanelDrawer'; @@ -475,10 +481,6 @@ export class DashboardScene extends SceneObjectBase { return this._initialState; } - public getNextPanelId(): number { - return this.state.body.getMaxPanelId() + 1; - } - public addPanel(vizPanel: VizPanel): void { if (!this.state.isEditing) { this.onEnterEditMode(); @@ -502,7 +504,7 @@ export class DashboardScene extends SceneObjectBase { } public duplicatePanel(vizPanel: VizPanel) { - this.state.body.duplicatePanel(vizPanel); + getLayoutManagerFor(vizPanel).duplicatePanel(vizPanel); } public copyPanel(vizPanel: VizPanel) { @@ -536,7 +538,7 @@ export class DashboardScene extends SceneObjectBase { } public removePanel(panel: VizPanel) { - this.state.body.removePanel(panel); + getLayoutManagerFor(panel).removePanel(panel); } public unlinkLibraryPanel(panel: VizPanel) { diff --git a/public/app/features/dashboard-scene/scene/layout-default/DefaultGridLayoutManager.test.tsx b/public/app/features/dashboard-scene/scene/layout-default/DefaultGridLayoutManager.test.tsx index 8a06e7c9031..fdb9dd9fc38 100644 --- a/public/app/features/dashboard-scene/scene/layout-default/DefaultGridLayoutManager.test.tsx +++ b/public/app/features/dashboard-scene/scene/layout-default/DefaultGridLayoutManager.test.tsx @@ -26,22 +26,6 @@ describe('DefaultGridLayoutManager', () => { }); }); - describe('getMaxPanelId', () => { - it('should get max panel id in a simple 3 panel layout', () => { - const { manager } = setup(); - const id = manager.getMaxPanelId(); - - expect(id).toBe(3); - }); - - it('should return 0 if no panels are found', () => { - const { manager } = setup({ gridItems: [] }); - const id = manager.getMaxPanelId(); - - expect(id).toBe(0); - }); - }); - describe('addPanel', () => { it('Should add a new panel', () => { const { manager } = setup(); diff --git a/public/app/features/dashboard-scene/scene/layout-default/DefaultGridLayoutManager.tsx b/public/app/features/dashboard-scene/scene/layout-default/DefaultGridLayoutManager.tsx index d714bc87d4d..c7389618108 100644 --- a/public/app/features/dashboard-scene/scene/layout-default/DefaultGridLayoutManager.tsx +++ b/public/app/features/dashboard-scene/scene/layout-default/DefaultGridLayoutManager.tsx @@ -14,6 +14,7 @@ import { GRID_COLUMN_COUNT } from 'app/core/constants'; import { t } from 'app/core/internationalization'; import { isClonedKey, joinCloneKeys } from '../../utils/clone'; +import { dashboardSceneGraph } from '../../utils/dashboardSceneGraph'; import { forceRenderChildren, getPanelIdForVizPanel, @@ -21,7 +22,6 @@ import { NEW_PANEL_WIDTH, getVizPanelKeyForPanelId, getGridItemKeyForPanelId, - getDashboardSceneFor, } from '../../utils/utils'; import { DashboardLayoutManager } from '../types/DashboardLayoutManager'; @@ -72,7 +72,7 @@ export class DefaultGridLayoutManager } public addPanel(vizPanel: VizPanel): void { - const panelId = this.getNextPanelId(); + const panelId = dashboardSceneGraph.getNextPanelId(this); vizPanel.setState({ key: getVizPanelKeyForPanelId(panelId) }); vizPanel.clearParent(); @@ -95,7 +95,8 @@ export class DefaultGridLayoutManager * Adds a new empty row */ public addNewRow(): SceneGridRow { - const id = this.getNextPanelId(); + const id = dashboardSceneGraph.getNextPanelId(this); + const row = new SceneGridRow({ key: getVizPanelKeyForPanelId(id), title: 'Row title', @@ -183,7 +184,7 @@ export class DefaultGridLayoutManager let panelData; let newGridItem; - const newPanelId = this.getNextPanelId(); + const newPanelId = dashboardSceneGraph.getNextPanelId(this); const grid = this.state.grid; if (gridItem instanceof DashboardGridItem) { @@ -248,53 +249,6 @@ export class DefaultGridLayoutManager return panels; } - public getMaxPanelId(): number { - let max = 0; - - for (const child of this.state.grid.state.children) { - if (child instanceof DashboardGridItem) { - const vizPanel = child.state.body; - - if (vizPanel) { - const panelId = getPanelIdForVizPanel(vizPanel); - - if (panelId > max) { - max = panelId; - } - } - } - - if (child instanceof SceneGridRow) { - //rows follow the same key pattern --- e.g.: `panel-6` - const panelId = getPanelIdForVizPanel(child); - - if (panelId > max) { - max = panelId; - } - - for (const rowChild of child.state.children) { - if (rowChild instanceof DashboardGridItem) { - const vizPanel = rowChild.state.body; - - if (vizPanel) { - const panelId = getPanelIdForVizPanel(vizPanel); - - if (panelId > max) { - max = panelId; - } - } - } - } - } - } - - return max; - } - - public getNextPanelId(): number { - return getDashboardSceneFor(this).getNextPanelId(); - } - public collapseAllRows(): void { this.state.grid.state.children.forEach((child) => { if (!(child instanceof SceneGridRow)) { diff --git a/public/app/features/dashboard-scene/scene/layout-responsive-grid/ResponsiveGridLayoutManager.tsx b/public/app/features/dashboard-scene/scene/layout-responsive-grid/ResponsiveGridLayoutManager.tsx index a1e4798b6c2..91fcbec0a48 100644 --- a/public/app/features/dashboard-scene/scene/layout-responsive-grid/ResponsiveGridLayoutManager.tsx +++ b/public/app/features/dashboard-scene/scene/layout-responsive-grid/ResponsiveGridLayoutManager.tsx @@ -4,7 +4,8 @@ import { Select } from '@grafana/ui'; import { t } from 'app/core/internationalization'; import { OptionsPaneItemDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor'; -import { getDashboardSceneFor, getPanelIdForVizPanel, getVizPanelKeyForPanelId } from '../../utils/utils'; +import { dashboardSceneGraph } from '../../utils/dashboardSceneGraph'; +import { getDashboardSceneFor, getGridItemKeyForPanelId, getVizPanelKeyForPanelId } from '../../utils/utils'; import { RowsLayoutManager } from '../layout-rows/RowsLayoutManager'; import { DashboardLayoutManager } from '../types/DashboardLayoutManager'; @@ -33,10 +34,17 @@ export class ResponsiveGridLayoutManager public readonly descriptor = ResponsiveGridLayoutManager.descriptor; + public constructor(state: ResponsiveGridLayoutManagerState) { + super(state); + + //@ts-ignore + this.state.layout.getDragClassCancel = () => 'drag-cancel'; + } + public editModeChanged(isEditing: boolean): void {} public addPanel(vizPanel: VizPanel): void { - const panelId = this.getNextPanelId(); + const panelId = dashboardSceneGraph.getNextPanelId(this); vizPanel.setState({ key: getVizPanelKeyForPanelId(panelId) }); vizPanel.clearParent(); @@ -52,33 +60,35 @@ export class ResponsiveGridLayoutManager getDashboardSceneFor(this).switchLayout(rowsLayout); } - public getMaxPanelId(): number { - let max = 0; - - for (const child of this.state.layout.state.children) { - if (child instanceof VizPanel) { - let panelId = getPanelIdForVizPanel(child); - - if (panelId > max) { - max = panelId; - } - } - } - - return max; - } - - public getNextPanelId(): number { - return getDashboardSceneFor(this).getNextPanelId(); - } - public removePanel(panel: VizPanel) { const element = panel.parent; this.state.layout.setState({ children: this.state.layout.state.children.filter((child) => child !== element) }); } public duplicatePanel(panel: VizPanel): void { - throw new Error('Method not implemented.'); + const gridItem = panel.parent; + if (!(gridItem instanceof ResponsiveGridItem)) { + console.error('Trying to duplicate a panel that is not inside a DashboardGridItem'); + return; + } + + const newPanelId = dashboardSceneGraph.getNextPanelId(this); + const grid = this.state.layout; + + const newGridItem = gridItem.clone({ + key: getGridItemKeyForPanelId(newPanelId), + body: panel.clone({ + key: getVizPanelKeyForPanelId(newPanelId), + }), + }); + + const sourceIndex = grid.state.children.indexOf(gridItem); + const newChildren = [...grid.state.children]; + + // insert after + newChildren.splice(sourceIndex + 1, 0, newGridItem); + + grid.setState({ children: newChildren }); } public getVizPanels(): VizPanel[] { @@ -124,9 +134,10 @@ export class ResponsiveGridLayoutManager }); } - activateRepeaters?(): void { - throw new Error('Method not implemented.'); - } + /** + * Might as well implement this as a no-top function as vs-code very eagerly adds optional functions that throw Method not implemented + */ + public activateRepeaters(): void {} public static Component = ({ model }: SceneComponentProps) => { return ; diff --git a/public/app/features/dashboard-scene/scene/layout-rows/RowsLayoutManager.tsx b/public/app/features/dashboard-scene/scene/layout-rows/RowsLayoutManager.tsx index 10ff3f66550..dc9154d3b2b 100644 --- a/public/app/features/dashboard-scene/scene/layout-rows/RowsLayoutManager.tsx +++ b/public/app/features/dashboard-scene/scene/layout-rows/RowsLayoutManager.tsx @@ -75,14 +75,6 @@ export class RowsLayoutManager extends SceneObjectBase i }); } - public getMaxPanelId(): number { - return Math.max(...this.state.rows.map((row) => row.getLayout().getMaxPanelId())); - } - - public getNextPanelId(): number { - return 0; - } - public removePanel(panel: VizPanel) {} public removeRow(row: RowItem) { diff --git a/public/app/features/dashboard-scene/scene/types/DashboardLayoutManager.ts b/public/app/features/dashboard-scene/scene/types/DashboardLayoutManager.ts index 572d7da9549..fbcd4200d36 100644 --- a/public/app/features/dashboard-scene/scene/types/DashboardLayoutManager.ts +++ b/public/app/features/dashboard-scene/scene/types/DashboardLayoutManager.ts @@ -38,11 +38,6 @@ export interface DashboardLayoutManager extends SceneObject { */ getVizPanels(): VizPanel[]; - /** - * Returns the highest panel id in the layout - */ - getMaxPanelId(): number; - /** * Add row */ diff --git a/public/app/features/dashboard-scene/utils/dashboardSceneGraph.test.ts b/public/app/features/dashboard-scene/utils/dashboardSceneGraph.test.ts index 6e0b078ea22..2aa39746339 100644 --- a/public/app/features/dashboard-scene/utils/dashboardSceneGraph.test.ts +++ b/public/app/features/dashboard-scene/utils/dashboardSceneGraph.test.ts @@ -9,7 +9,7 @@ import { VizPanelLinks, VizPanelLinksMenu } from '../scene/PanelLinks'; import { DashboardGridItem } from '../scene/layout-default/DashboardGridItem'; import { DefaultGridLayoutManager } from '../scene/layout-default/DefaultGridLayoutManager'; -import { dashboardSceneGraph } from './dashboardSceneGraph'; +import { dashboardSceneGraph, getNextPanelId } from './dashboardSceneGraph'; import { findVizPanelByKey } from './utils'; describe('dashboardSceneGraph', () => { @@ -22,7 +22,7 @@ describe('dashboardSceneGraph', () => { it('should resolve VizPanelLinks object', () => { const scene = buildTestScene(); - const panelWithNoLinks = findVizPanelByKey(scene, 'panel-with-links')!; + const panelWithNoLinks = findVizPanelByKey(scene, 'panel-2')!; expect(dashboardSceneGraph.getPanelLinks(panelWithNoLinks)).toBeInstanceOf(VizPanelLinks); }); }); @@ -66,6 +66,24 @@ describe('dashboardSceneGraph', () => { expect(cursorSync).toBeUndefined(); }); }); + + describe('getNextPanelId', () => { + it('should get next panel id in a simple 3 panel layout', () => { + const scene = buildTestScene(); + const id = getNextPanelId(scene); + + expect(id).toBe(3); + }); + + it('should return 1 if no panels are found', () => { + const scene = buildTestScene(); + + const grid = scene.state.body as DefaultGridLayoutManager; + grid.state.grid.setState({ children: [] }); + const id = getNextPanelId(scene); + expect(id).toBe(1); + }); + }); }); function buildTestScene(overrides?: Partial) { @@ -111,7 +129,7 @@ function buildTestScene(overrides?: Partial) { new DashboardGridItem({ body: new VizPanel({ title: 'Panel D', - key: 'panel-with-links', + key: 'panel-2', pluginId: 'table', $data: new SceneQueryRunner({ key: 'data-query-runner3', queries: [{ refId: 'A' }] }), titleItems: [new VizPanelLinks({ menu: new VizPanelLinksMenu({}) })], diff --git a/public/app/features/dashboard-scene/utils/dashboardSceneGraph.ts b/public/app/features/dashboard-scene/utils/dashboardSceneGraph.ts index 10edc9904d7..8d1a8176754 100644 --- a/public/app/features/dashboard-scene/utils/dashboardSceneGraph.ts +++ b/public/app/features/dashboard-scene/utils/dashboardSceneGraph.ts @@ -1,10 +1,11 @@ -import { VizPanel, sceneGraph, behaviors } from '@grafana/scenes'; +import { VizPanel, sceneGraph, behaviors, SceneObject, SceneGridRow } from '@grafana/scenes'; import { DashboardDataLayerSet } from '../scene/DashboardDataLayerSet'; import { DashboardScene } from '../scene/DashboardScene'; import { VizPanelLinks } from '../scene/PanelLinks'; -import { getLayoutManagerFor } from './utils'; +import { isClonedKey } from './clone'; +import { getLayoutManagerFor, getPanelIdForVizPanel } from './utils'; function getTimePicker(scene: DashboardScene) { return scene.state.controls?.state.timePicker; @@ -28,6 +29,29 @@ function getVizPanels(scene: DashboardScene): VizPanel[] { return scene.state.body.getVizPanels(); } +/** + * Will look for all panels in the entire scene starting from root + * and find the next free panel id + */ +export function getNextPanelId(scene: SceneObject): number { + let max = 0; + + sceneGraph + .findAllObjects(scene.getRoot(), (obj) => obj instanceof VizPanel || obj instanceof SceneGridRow) + .forEach((panel) => { + if (isClonedKey(panel.state.key!)) { + return; + } + + const panelId = getPanelIdForVizPanel(panel); + if (panelId > max) { + max = panelId; + } + }); + + return max + 1; +} + function getDataLayers(scene: DashboardScene): DashboardDataLayerSet { const data = sceneGraph.getData(scene); @@ -56,4 +80,5 @@ export const dashboardSceneGraph = { getDataLayers, getCursorSync, getLayoutManagerFor, + getNextPanelId, }; From e05413dcc43f72e1323dd3c65fefcbb0400f913d Mon Sep 17 00:00:00 2001 From: Karl Persson <23356117+kalleep@users.noreply.github.com> Date: Thu, 6 Feb 2025 12:07:52 +0100 Subject: [PATCH 05/34] Dashboards+Folders: Ensure the service identity is used for resolvers (#100128) * Dashboards+Folders: Ensure the service identity is used for dashboard and folder resolvers * Add convinient function to call closure with service context --- pkg/api/annotations_test.go | 2 +- pkg/apimachinery/identity/context.go | 13 ++- pkg/services/dashboards/accesscontrol.go | 81 +++++++++---------- pkg/services/dashboards/accesscontrol_test.go | 8 +- .../dashboards/service/dashboard_service.go | 4 +- .../guardian/accesscontrol_guardian_test.go | 2 +- 6 files changed, 58 insertions(+), 52 deletions(-) diff --git a/pkg/api/annotations_test.go b/pkg/api/annotations_test.go index 977f3307ca1..83e11eb91cf 100644 --- a/pkg/api/annotations_test.go +++ b/pkg/api/annotations_test.go @@ -403,7 +403,7 @@ func TestAPI_Annotations(t *testing.T) { hs.folderService = folderService hs.AccessControl = acimpl.ProvideAccessControl(featuremgmt.WithFeatures()) hs.AccessControl.RegisterScopeAttributeResolver(AnnotationTypeScopeResolver(hs.annotationsRepo, hs.Features, dashService, folderService)) - hs.AccessControl.RegisterScopeAttributeResolver(dashboards.NewDashboardIDScopeResolver(folderDB, dashService, folderService)) + hs.AccessControl.RegisterScopeAttributeResolver(dashboards.NewDashboardIDScopeResolver(dashService, folderService)) }) var body io.Reader if tt.body != "" { diff --git a/pkg/apimachinery/identity/context.go b/pkg/apimachinery/identity/context.go index e71ec51d376..627cace5d61 100644 --- a/pkg/apimachinery/identity/context.go +++ b/pkg/apimachinery/identity/context.go @@ -32,7 +32,7 @@ func checkNilRequester(r Requester) bool { const serviceName = "service" -// WithServiceIdentity sets creates an identity representing the service itself in provided org and store it in context. +// WithServiceIdentity sets an identity representing the service itself in provided org and store it in context. // This is useful for background tasks that has to communicate with unfied storage. It also returns a Requester with // static permissions so it can be used in legacy code paths. func WithServiceIdentity(ctx context.Context, orgID int64) (context.Context, Requester) { @@ -53,6 +53,17 @@ func WithServiceIdentity(ctx context.Context, orgID int64) (context.Context, Req return WithRequester(ctx, r), r } +// WithServiceIdentityContext sets an identity representing the service itself in context. +func WithServiceIdentityContext(ctx context.Context, orgID int64) context.Context { + ctx, _ = WithServiceIdentity(ctx, orgID) + return ctx +} + +// WithServiceIdentityFN calls provided closure with an context contaning the identity of the service. +func WithServiceIdentityFn[T any](ctx context.Context, orgID int64, fn func(ctx context.Context) (T, error)) (T, error) { + return fn(WithServiceIdentityContext(ctx, orgID)) +} + func getWildcardPermissions(actions ...string) map[string][]string { permissions := make(map[string][]string, len(actions)) for _, a := range actions { diff --git a/pkg/services/dashboards/accesscontrol.go b/pkg/services/dashboards/accesscontrol.go index 759c60b2e4a..664874d872e 100644 --- a/pkg/services/dashboards/accesscontrol.go +++ b/pkg/services/dashboards/accesscontrol.go @@ -5,7 +5,7 @@ import ( "errors" "strings" - "github.com/grafana/grafana/pkg/infra/metrics" + "github.com/grafana/grafana/pkg/apimachinery/identity" ac "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/folder" "go.opentelemetry.io/otel" @@ -65,18 +65,19 @@ func NewFolderIDScopeResolver(folderDB folder.FolderStore, folderSvc folder.Serv return []string{ScopeFoldersProvider.GetResourceScopeUID(ac.GeneralFolderUID)}, nil } - folder, err := folderDB.GetFolderByID(ctx, orgID, id) - if err != nil { - return nil, err - } + return identity.WithServiceIdentityFn(ctx, orgID, func(ctx context.Context) ([]string, error) { + folder, err := folderDB.GetFolderByID(ctx, orgID, id) + if err != nil { + return nil, err + } - result, err := GetInheritedScopes(ctx, folder.OrgID, folder.UID, folderSvc) - if err != nil { - return nil, err - } + result, err := GetInheritedScopes(ctx, folder.OrgID, folder.UID, folderSvc) + if err != nil { + return nil, err + } - result = append([]string{ScopeFoldersProvider.GetResourceScopeUID(folder.UID)}, result...) - return result, nil + return append([]string{ScopeFoldersProvider.GetResourceScopeUID(folder.UID)}, result...), nil + }) }) } @@ -97,17 +98,19 @@ func NewFolderUIDScopeResolver(folderSvc folder.Service) (string, ac.ScopeAttrib return nil, err } - inheritedScopes, err := GetInheritedScopes(ctx, orgID, uid, folderSvc) - if err != nil { - return nil, err - } - return append(inheritedScopes, ScopeFoldersProvider.GetResourceScopeUID(uid)), nil + return identity.WithServiceIdentityFn(ctx, orgID, func(ctx context.Context) ([]string, error) { + inheritedScopes, err := GetInheritedScopes(ctx, orgID, uid, folderSvc) + if err != nil { + return nil, err + } + return append(inheritedScopes, ScopeFoldersProvider.GetResourceScopeUID(uid)), nil + }) }) } // NewDashboardIDScopeResolver provides an ScopeAttributeResolver that is able to convert a scope prefixed with "dashboards:id:" // into uid based scopes for both dashboard and folder -func NewDashboardIDScopeResolver(folderDB folder.FolderStore, ds DashboardService, folderSvc folder.Service) (string, ac.ScopeAttributeResolver) { +func NewDashboardIDScopeResolver(ds DashboardService, folderSvc folder.Service) (string, ac.ScopeAttributeResolver) { prefix := ScopeDashboardsProvider.GetResourceScope("") return prefix, ac.ScopeAttributeResolverFunc(func(ctx context.Context, orgID int64, scope string) ([]string, error) { ctx, span := tracer.Start(ctx, "dashboards.NewDashboardIDScopeResolver") @@ -122,18 +125,20 @@ func NewDashboardIDScopeResolver(folderDB folder.FolderStore, ds DashboardServic return nil, err } - dashboard, err := ds.GetDashboard(ctx, &GetDashboardQuery{ID: id, OrgID: orgID}) - if err != nil { - return nil, err - } + return identity.WithServiceIdentityFn(ctx, orgID, func(ctx context.Context) ([]string, error) { + dashboard, err := ds.GetDashboard(ctx, &GetDashboardQuery{ID: id, OrgID: orgID}) + if err != nil { + return nil, err + } - return resolveDashboardScope(ctx, folderDB, orgID, dashboard, folderSvc) + return resolveDashboardScope(ctx, orgID, dashboard, folderSvc) + }) }) } // NewDashboardUIDScopeResolver provides an ScopeAttributeResolver that is able to convert a scope prefixed with "dashboards:uid:" // into uid based scopes for both dashboard and folder -func NewDashboardUIDScopeResolver(folderDB folder.FolderStore, ds DashboardService, folderSvc folder.Service) (string, ac.ScopeAttributeResolver) { +func NewDashboardUIDScopeResolver(ds DashboardService, folderSvc folder.Service) (string, ac.ScopeAttributeResolver) { prefix := ScopeDashboardsProvider.GetResourceScopeUID("") return prefix, ac.ScopeAttributeResolverFunc(func(ctx context.Context, orgID int64, scope string) ([]string, error) { ctx, span := tracer.Start(ctx, "dashboards.NewDashboardUIDScopeResolver") @@ -148,36 +153,26 @@ func NewDashboardUIDScopeResolver(folderDB folder.FolderStore, ds DashboardServi return nil, err } - dashboard, err := ds.GetDashboard(ctx, &GetDashboardQuery{UID: uid, OrgID: orgID}) - if err != nil { - return nil, err - } + return identity.WithServiceIdentityFn(ctx, orgID, func(ctx context.Context) ([]string, error) { + dashboard, err := ds.GetDashboard(ctx, &GetDashboardQuery{UID: uid, OrgID: orgID}) + if err != nil { + return nil, err + } - return resolveDashboardScope(ctx, folderDB, orgID, dashboard, folderSvc) + return resolveDashboardScope(ctx, orgID, dashboard, folderSvc) + }) }) } -func resolveDashboardScope(ctx context.Context, folderDB folder.FolderStore, orgID int64, dashboard *Dashboard, folderSvc folder.Service) ([]string, error) { +func resolveDashboardScope(ctx context.Context, orgID int64, dashboard *Dashboard, folderSvc folder.Service) ([]string, error) { ctx, span := tracer.Start(ctx, "dashboards.resolveDashboardScope") span.End() var folderUID string - metrics.MFolderIDsServiceCount.WithLabelValues(metrics.Dashboard).Inc() - // nolint:staticcheck - if dashboard.FolderID < 0 { - return []string{ScopeDashboardsProvider.GetResourceScopeUID(dashboard.UID)}, nil - } - - metrics.MFolderIDsServiceCount.WithLabelValues(metrics.Dashboard).Inc() - // nolint:staticcheck - if dashboard.FolderID == 0 { + if dashboard.FolderUID == "" { folderUID = ac.GeneralFolderUID } else { - folder, err := folderDB.GetFolderByID(ctx, orgID, dashboard.FolderID) - if err != nil { - return nil, err - } - folderUID = folder.UID + folderUID = dashboard.FolderUID } result, err := GetInheritedScopes(ctx, orgID, folderUID, folderSvc) diff --git a/pkg/services/dashboards/accesscontrol_test.go b/pkg/services/dashboards/accesscontrol_test.go index 026081d0dd0..a08313052f8 100644 --- a/pkg/services/dashboards/accesscontrol_test.go +++ b/pkg/services/dashboards/accesscontrol_test.go @@ -60,12 +60,12 @@ func TestNewFolderIDScopeResolver(t *testing.T) { func TestNewDashboardIDScopeResolver(t *testing.T) { t.Run("prefix should be expected", func(t *testing.T) { - prefix, _ := NewDashboardIDScopeResolver(foldertest.NewFakeFolderStore(t), &FakeDashboardService{}, foldertest.NewFakeService()) + prefix, _ := NewDashboardIDScopeResolver(&FakeDashboardService{}, foldertest.NewFakeService()) require.Equal(t, "dashboards:id:", prefix) }) t.Run("resolver should fail if input scope is not expected", func(t *testing.T) { - _, resolver := NewDashboardIDScopeResolver(foldertest.NewFakeFolderStore(t), &FakeDashboardService{}, foldertest.NewFakeService()) + _, resolver := NewDashboardIDScopeResolver(&FakeDashboardService{}, foldertest.NewFakeService()) _, err := resolver.Resolve(context.Background(), rand.Int63(), "dashboards:uid:123") require.ErrorIs(t, err, ac.ErrInvalidScope) }) @@ -73,12 +73,12 @@ func TestNewDashboardIDScopeResolver(t *testing.T) { func TestNewDashboardUIDScopeResolver(t *testing.T) { t.Run("prefix should be expected", func(t *testing.T) { - prefix, _ := NewDashboardUIDScopeResolver(foldertest.NewFakeFolderStore(t), &FakeDashboardService{}, foldertest.NewFakeService()) + prefix, _ := NewDashboardUIDScopeResolver(&FakeDashboardService{}, foldertest.NewFakeService()) require.Equal(t, "dashboards:uid:", prefix) }) t.Run("resolver should fail if input scope is not expected", func(t *testing.T) { - _, resolver := NewDashboardUIDScopeResolver(foldertest.NewFakeFolderStore(t), &FakeDashboardService{}, foldertest.NewFakeService()) + _, resolver := NewDashboardUIDScopeResolver(&FakeDashboardService{}, foldertest.NewFakeService()) _, err := resolver.Resolve(context.Background(), rand.Int63(), "dashboards:id:123") require.ErrorIs(t, err, ac.ErrInvalidScope) }) diff --git a/pkg/services/dashboards/service/dashboard_service.go b/pkg/services/dashboards/service/dashboard_service.go index d4abb3627fc..efbd0787a45 100644 --- a/pkg/services/dashboards/service/dashboard_service.go +++ b/pkg/services/dashboards/service/dashboard_service.go @@ -123,8 +123,8 @@ func ProvideDashboardServiceImpl( return nil, err } - ac.RegisterScopeAttributeResolver(dashboards.NewDashboardIDScopeResolver(folderStore, dashSvc, folderSvc)) - ac.RegisterScopeAttributeResolver(dashboards.NewDashboardUIDScopeResolver(folderStore, dashSvc, folderSvc)) + ac.RegisterScopeAttributeResolver(dashboards.NewDashboardIDScopeResolver(dashSvc, folderSvc)) + ac.RegisterScopeAttributeResolver(dashboards.NewDashboardUIDScopeResolver(dashSvc, folderSvc)) if err := folderSvc.RegisterService(dashSvc); err != nil { return nil, err diff --git a/pkg/services/guardian/accesscontrol_guardian_test.go b/pkg/services/guardian/accesscontrol_guardian_test.go index 59ac7d49828..5de4e91da24 100644 --- a/pkg/services/guardian/accesscontrol_guardian_test.go +++ b/pkg/services/guardian/accesscontrol_guardian_test.go @@ -962,7 +962,7 @@ func setupAccessControlGuardianTest( folderStore := foldertest.NewFakeFolderStore(t) - ac.RegisterScopeAttributeResolver(dashboards.NewDashboardUIDScopeResolver(folderStore, fakeDashboardService, folderSvc)) + ac.RegisterScopeAttributeResolver(dashboards.NewDashboardUIDScopeResolver(fakeDashboardService, folderSvc)) ac.RegisterScopeAttributeResolver(dashboards.NewFolderUIDScopeResolver(folderSvc)) ac.RegisterScopeAttributeResolver(dashboards.NewFolderIDScopeResolver(folderStore, folderSvc)) From 7d3a77a45c4a48582892a95b60b160b69543e51f Mon Sep 17 00:00:00 2001 From: Ashley Harrison Date: Thu, 6 Feb 2025 11:08:04 +0000 Subject: [PATCH 06/34] Themes: Add new theme definitions behind feature toggle (#100129) * create new toggle * add survey link behind feature toggle * fix translations * better theme structure * add all the themes back * update matrix * fix mars contrast * fix color contrast probs with tron * fix a11y issues with synthwave/victorian themes * fix aubergine/zen * rename green + gold * rename to space * rename, only enable 4 for grafanacon * add survey link * fix info color in sapphiredusk * handle extra themes in storybook --- packages/grafana-data/src/themes/registry.ts | 7 +- .../src/themes/themeDefinitions/aubergine.ts | 55 +++++++++++++ .../themes/themeDefinitions/desertbloom.ts | 79 ++++++++++++++++++ .../themes/themeDefinitions/gildedgrove.ts | 67 +++++++++++++++ .../src/themes/themeDefinitions/index.ts | 10 +++ .../src/themes/themeDefinitions/mars.ts | 55 +++++++++++++ .../src/themes/themeDefinitions/matrix.ts | 43 ++++++++++ .../themes/themeDefinitions/sapphiredusk.ts | 81 +++++++++++++++++++ .../src/themes/themeDefinitions/synthwave.ts | 55 +++++++++++++ .../src/themes/themeDefinitions/tron.ts | 55 +++++++++++++ .../src/themes/themeDefinitions/victorian.ts | 59 ++++++++++++++ .../src/themes/themeDefinitions/zen.ts | 55 +++++++++++++ .../src/types/featureToggles.gen.ts | 1 + packages/grafana-ui/.storybook/preview.ts | 12 ++- pkg/services/featuremgmt/registry.go | 9 +++ pkg/services/featuremgmt/toggles_gen.csv | 1 + pkg/services/featuremgmt/toggles_gen.go | 4 + pkg/services/featuremgmt/toggles_gen.json | 15 ++++ pkg/services/preference/themes.go | 10 +++ .../SharedPreferences/SharedPreferences.tsx | 30 ++++++- public/locales/en-US/grafana.json | 1 + public/locales/pseudo-LOCALE/grafana.json | 1 + 22 files changed, 700 insertions(+), 5 deletions(-) create mode 100644 packages/grafana-data/src/themes/themeDefinitions/aubergine.ts create mode 100644 packages/grafana-data/src/themes/themeDefinitions/desertbloom.ts create mode 100644 packages/grafana-data/src/themes/themeDefinitions/gildedgrove.ts create mode 100644 packages/grafana-data/src/themes/themeDefinitions/mars.ts create mode 100644 packages/grafana-data/src/themes/themeDefinitions/matrix.ts create mode 100644 packages/grafana-data/src/themes/themeDefinitions/sapphiredusk.ts create mode 100644 packages/grafana-data/src/themes/themeDefinitions/synthwave.ts create mode 100644 packages/grafana-data/src/themes/themeDefinitions/tron.ts create mode 100644 packages/grafana-data/src/themes/themeDefinitions/victorian.ts create mode 100644 packages/grafana-data/src/themes/themeDefinitions/zen.ts diff --git a/packages/grafana-data/src/themes/registry.ts b/packages/grafana-data/src/themes/registry.ts index 240e5b75717..c4cf352b622 100644 --- a/packages/grafana-data/src/themes/registry.ts +++ b/packages/grafana-data/src/themes/registry.ts @@ -22,9 +22,12 @@ export function getThemeById(id: string): GrafanaTheme2 { * @internal * For internal use only */ -export function getBuiltInThemes(includeExtras?: boolean) { +export function getBuiltInThemes(allowedExtras: string[]) { const themes = themeRegistry.list().filter((item) => { - return includeExtras ? true : !item.isExtra; + if (item.isExtra) { + return allowedExtras.includes(item.id); + } + return true; }); // sort themes alphabetically, but put built-in themes (default, dark, light, system) first const sortedThemes = themes.sort((a, b) => { diff --git a/packages/grafana-data/src/themes/themeDefinitions/aubergine.ts b/packages/grafana-data/src/themes/themeDefinitions/aubergine.ts new file mode 100644 index 00000000000..45260c2fae0 --- /dev/null +++ b/packages/grafana-data/src/themes/themeDefinitions/aubergine.ts @@ -0,0 +1,55 @@ +import { NewThemeOptions } from '../createTheme'; + +const aubergineTheme: NewThemeOptions = { + name: 'Aubergine', + colors: { + mode: 'dark', + border: { + weak: '#4F2A3D', + medium: '#6A3C4B', + strong: '#8C5A69', + }, + text: { + primary: '#E5D0D6', + secondary: '#D1A8C4', + disabled: '#B7A0A6', + link: '#A56BB6', + maxContrast: '#FFFFFF', + }, + primary: { + main: '#8C5A69', + }, + secondary: { + main: '#6A3C4B', + text: '#D1A8C4', + border: '#8C5A69', + }, + background: { + canvas: '#2E1F2D', + primary: '#3C2136', + secondary: '#4A2D47', + }, + action: { + hover: '#6A3C4B', + selected: '#8C5A69', + selectedBorder: '#FFB300', + focus: '#A56BB6', + hoverOpacity: 0.1, + disabledText: '#B7A0A6', + disabledBackground: '#4A2D47', + disabledOpacity: 0.38, + }, + gradients: { + brandHorizontal: 'linear-gradient(270deg, #6A3C4B 0%, #A56BB6 100%)', + brandVertical: 'linear-gradient(0deg, #6A3C4B 0%, #A56BB6 100%)', + }, + contrastThreshold: 4, + hoverFactor: 0.07, + tonalOffset: 0.15, + }, + shape: { + borderRadius: 6, + }, +}; + +export default aubergineTheme; diff --git a/packages/grafana-data/src/themes/themeDefinitions/desertbloom.ts b/packages/grafana-data/src/themes/themeDefinitions/desertbloom.ts new file mode 100644 index 00000000000..20e6938ab36 --- /dev/null +++ b/packages/grafana-data/src/themes/themeDefinitions/desertbloom.ts @@ -0,0 +1,79 @@ +import { NewThemeOptions } from '../createTheme'; + +const desertBloomTheme: NewThemeOptions = { + name: 'Desert bloom', + colors: { + mode: 'light', + border: { + weak: 'rgba(0, 0, 0, 0.12)', + medium: 'rgba(0, 0, 0, 0.20)', + strong: 'rgba(0, 0, 0, 0.30)', + }, + text: { + primary: '#333333', + secondary: '#555555', + disabled: 'rgba(0, 0, 0, 0.5)', + link: '#1A82E2', + maxContrast: '#000000', + }, + primary: { + main: '#FF6F61', + text: '#FE6F61', + border: '#E55B4D', + name: 'primary', + shade: '#E55B4D', + transparent: '#FF6F6126', + contrastText: '#FFFFFF', + borderTransparent: '#FF6F6140', + }, + secondary: { + main: '#FFFFFF', + text: '#695f53', + border: '#d9cec0', + name: 'secondary', + shade: '#d9cec0', + transparent: '#FFFFFF26', + contrastText: '#4c4339', + borderTransparent: '#FFFFFF40', + }, + info: { + main: '#1A82E2', + }, + error: { + main: '#FF6B6B', + }, + success: { + main: '#4CAF50', + }, + warning: { + main: '#FFC107', + }, + background: { + canvas: '#FFF8F0', + primary: '#FFFFFF', + secondary: '#f9f3e8', + }, + action: { + hover: 'rgba(168, 156, 134, 0.12)', + selected: 'rgba(168, 156, 134, 0.36)', + selectedBorder: '#FF6F61', + focus: 'rgba(168, 156, 134, 0.50)', + hoverOpacity: 0.08, + disabledText: 'rgba(168, 156, 134, 0.5)', + disabledBackground: 'rgba(168, 156, 134, 0.06)', + disabledOpacity: 0.38, + }, + gradients: { + brandHorizontal: 'linear-gradient(270deg, #FF6F61 0%, #ece0d1 100%)', + brandVertical: 'linear-gradient(0.01deg, #FF6F61 0.01%, #ece0d1 99.99%)', + }, + contrastThreshold: 3, + hoverFactor: 0.03, + tonalOffset: 0.15, + }, + shape: { + borderRadius: 6, + }, +}; + +export default desertBloomTheme; diff --git a/packages/grafana-data/src/themes/themeDefinitions/gildedgrove.ts b/packages/grafana-data/src/themes/themeDefinitions/gildedgrove.ts new file mode 100644 index 00000000000..546003ba07d --- /dev/null +++ b/packages/grafana-data/src/themes/themeDefinitions/gildedgrove.ts @@ -0,0 +1,67 @@ +import { NewThemeOptions } from '../createTheme'; + +const gildedGroveTheme: NewThemeOptions = { + name: 'Gilded grove', + colors: { + mode: 'dark', + border: { + weak: 'rgba(200, 200, 180, 0.12)', + medium: 'rgba(200, 200, 180, 0.20)', + strong: 'rgba(200, 200, 180, 0.30)', + }, + text: { + primary: 'rgb(250, 250, 239)', + secondary: 'rgba(200, 200, 180, 0.85)', + disabled: 'rgba(200, 200, 180, 0.6)', + link: '#FEAC34', + maxContrast: '#FFFFFF', + }, + primary: { + main: '#FEAC34', + text: '#FFD783', + border: '#FFD783', + name: 'primary', + shade: 'rgb(255, 173, 80)', + transparent: '#FEAC3426', + contrastText: '#111614', + borderTransparent: '#FFD78340', + }, + secondary: { + main: 'rgba(200, 200, 180, 0.10)', + shade: 'rgba(200, 200, 180, 0.14)', + transparent: 'rgba(200, 200, 180, 0.08)', + text: 'rgb(200, 200, 180)', + contrastText: 'rgb(200, 200, 180)', + border: 'rgba(200, 200, 180, 0.08)', + name: 'secondary', + borderTransparent: 'rgba(200, 200, 180, 0.25)', + }, + background: { + canvas: '#111614', + primary: '#1d2220', + secondary: '#27312E', + }, + action: { + hover: 'rgba(200, 200, 180, 0.16)', + selected: 'rgba(200, 200, 180, 0.12)', + selectedBorder: '#FEAC34', + focus: 'rgba(200, 200, 180, 0.16)', + hoverOpacity: 0.08, + disabledText: 'rgba(200, 200, 180, 0.6)', + disabledBackground: 'rgba(200, 200, 180, 0.04)', + disabledOpacity: 0.38, + }, + gradients: { + brandHorizontal: 'linear-gradient(270deg, #FEAC34 0%, #FFD783 100%)', + brandVertical: 'linear-gradient(0.01deg, #FEAC34 0.01%, #FFD783 99.99%)', + }, + contrastThreshold: 3, + hoverFactor: 0.03, + tonalOffset: 0.15, + }, + shape: { + borderRadius: 5, + }, +}; + +export default gildedGroveTheme; diff --git a/packages/grafana-data/src/themes/themeDefinitions/index.ts b/packages/grafana-data/src/themes/themeDefinitions/index.ts index a88efba4add..f0277312fe4 100644 --- a/packages/grafana-data/src/themes/themeDefinitions/index.ts +++ b/packages/grafana-data/src/themes/themeDefinitions/index.ts @@ -1 +1,11 @@ +export { default as aubergine } from './aubergine'; export { default as debug } from './debug'; +export { default as desertbloom } from './desertbloom'; +export { default as gildedgrove } from './gildedgrove'; +export { default as mars } from './mars'; +export { default as matrix } from './matrix'; +export { default as sapphiredusk } from './sapphiredusk'; +export { default as synthwave } from './synthwave'; +export { default as tron } from './tron'; +export { default as victorian } from './victorian'; +export { default as zen } from './zen'; diff --git a/packages/grafana-data/src/themes/themeDefinitions/mars.ts b/packages/grafana-data/src/themes/themeDefinitions/mars.ts new file mode 100644 index 00000000000..c695e65d992 --- /dev/null +++ b/packages/grafana-data/src/themes/themeDefinitions/mars.ts @@ -0,0 +1,55 @@ +import { NewThemeOptions } from '../createTheme'; + +const marsTheme: NewThemeOptions = { + name: 'Mars', + colors: { + mode: 'dark', + border: { + weak: 'rgba(210, 90, 60, 0.2)', + medium: 'rgba(210, 90, 60, 0.35)', + strong: 'rgba(210, 90, 60, 0.5)', + }, + text: { + primary: '#DDDDDD', + secondary: '#BBBBBB', + disabled: 'rgba(221, 221, 221, 0.5)', + link: '#FF6F61', + maxContrast: '#FFFFFF', + }, + primary: { + main: '#FF6F61', + }, + secondary: { + main: '#6a2f2f', + text: '#BBBBBB', + border: 'rgba(210, 90, 60, 0.2)', + }, + background: { + canvas: '#3C1E1E', + primary: '#522626', + secondary: '#6A2F2F', + }, + action: { + hover: 'rgba(210, 90, 60, 0.16)', + selected: 'rgba(210, 90, 60, 0.12)', + selectedBorder: '#FF6F61', + focus: 'rgba(210, 90, 60, 0.16)', + hoverOpacity: 0.08, + disabledText: 'rgba(221, 221, 221, 0.5)', + disabledBackground: 'rgba(210, 90, 60, 0.08)', + disabledOpacity: 0.38, + }, + gradients: { + brandHorizontal: 'linear-gradient(270deg, #FF6F61 0%, #D25A3C 100%)', + brandVertical: 'linear-gradient(0.01deg, #FF6F61 0.01%, #D25A3C 99.99%)', + }, + contrastThreshold: 3, + hoverFactor: 0.05, + tonalOffset: 0.2, + }, + shape: { + borderRadius: 4, + }, +}; + +export default marsTheme; diff --git a/packages/grafana-data/src/themes/themeDefinitions/matrix.ts b/packages/grafana-data/src/themes/themeDefinitions/matrix.ts new file mode 100644 index 00000000000..3d4350e4f0a --- /dev/null +++ b/packages/grafana-data/src/themes/themeDefinitions/matrix.ts @@ -0,0 +1,43 @@ +import { NewThemeOptions } from '../createTheme'; + +const matrixTheme: NewThemeOptions = { + name: 'Matrix', + colors: { + mode: 'dark', + background: { + canvas: '#000000', + primary: '#020202', + secondary: '#080808', + }, + text: { + primary: '#00c017', + secondary: '#008910', + disabled: '#006a0c', + link: '#00ff41', + maxContrast: '#00ff41', + }, + border: { + weak: '#008f1144', + medium: '#008f1188', + strong: '#008910', + }, + primary: { + main: '#008910', + }, + secondary: { + text: '#008910', + }, + gradients: { + brandVertical: 'linear-gradient(0deg, #008910 0%, #00ff41 100%)', + brandHorizontal: 'linear-gradient(90deg, #008910 0%, #00ff41 100%)', + }, + }, + shape: { + borderRadius: 0, + }, + typography: { + fontFamily: 'monospace', + }, +}; + +export default matrixTheme; diff --git a/packages/grafana-data/src/themes/themeDefinitions/sapphiredusk.ts b/packages/grafana-data/src/themes/themeDefinitions/sapphiredusk.ts new file mode 100644 index 00000000000..353ff32b712 --- /dev/null +++ b/packages/grafana-data/src/themes/themeDefinitions/sapphiredusk.ts @@ -0,0 +1,81 @@ +import { NewThemeOptions } from '../createTheme'; + +const sapphireDuskTheme: NewThemeOptions = { + name: 'Sapphire dusk', + colors: { + mode: 'dark', + border: { + weak: '#232e47', + medium: '#2c3853', + strong: '#404d6b', + }, + text: { + primary: '#FFFFFF', + secondary: '#bcccdd', + disabled: '#838da5', + link: '#93EBF0', + maxContrast: '#FFFFFF', + }, + primary: { + main: '#93EBF0', + text: '#a8e9ed', + border: '#93ebf0', + name: 'primary', + shade: '#c0f5d9', + transparent: '#93EBF029', + contrastText: '#111614', + borderTransparent: '#93ebf040', + }, + secondary: { + main: '#2c364f', + shade: '#36415e', + transparent: 'rgba(200, 200, 180, 0.08)', + text: '#d1dfff', + contrastText: '#acfeff', + border: 'rgba(200, 200, 180, 0.08)', + name: 'secondary', + borderTransparent: 'rgba(200, 200, 180, 0.25)', + }, + info: { + main: '#4d4593', + text: '#a8e9ed', + border: '#5d54a7', + }, + error: { + main: '#c63370', + }, + success: { + main: '#1A7F4B', + }, + warning: { + main: '#D448EA', + }, + background: { + canvas: '#1e273d', + primary: '#12192e', + secondary: '#212c47', + }, + action: { + hover: '#364057', + selected: '#364260', + selectedBorder: '#D448EA', + focus: '#364057', + hoverOpacity: 0.08, + disabledText: 'rgba(54, 64, 87, 0.6)', + disabledBackground: 'rgba(54, 64, 87, 0.04)', + disabledOpacity: 0.38, + }, + gradients: { + brandHorizontal: 'linear-gradient(270deg, #D346EF 0%, #2C83FE 100%)', + brandVertical: 'linear-gradient(0deg, #D346EF 0%, #2C83FE 100%)', + }, + contrastThreshold: 3, + hoverFactor: 0.03, + tonalOffset: 0.15, + }, + shape: { + borderRadius: 5, + }, +}; + +export default sapphireDuskTheme; diff --git a/packages/grafana-data/src/themes/themeDefinitions/synthwave.ts b/packages/grafana-data/src/themes/themeDefinitions/synthwave.ts new file mode 100644 index 00000000000..c54eaf71731 --- /dev/null +++ b/packages/grafana-data/src/themes/themeDefinitions/synthwave.ts @@ -0,0 +1,55 @@ +import { NewThemeOptions } from '../createTheme'; + +const synthwaveTheme: NewThemeOptions = { + name: 'Synthwave', + colors: { + mode: 'dark', + border: { + weak: 'rgba(255, 20, 147, 0.12)', + medium: 'rgba(255, 20, 147, 0.20)', + strong: 'rgba(255, 20, 147, 0.30)', + }, + text: { + primary: '#E0E0E0', + secondary: 'rgba(224, 224, 224, 0.75)', + disabled: 'rgba(224, 224, 224, 0.5)', + link: '#FF69B4', + maxContrast: '#FFFFFF', + }, + primary: { + main: '#FF1493', + }, + secondary: { + main: '#37183a', + text: 'rgba(224, 224, 224, 0.75)', + border: 'rgba(255, 20, 147, 0.10)', + }, + background: { + canvas: '#1A1A2E', + primary: '#16213E', + secondary: '#0F3460', + }, + action: { + hover: 'rgba(255, 20, 147, 0.16)', + selected: 'rgba(255, 20, 147, 0.12)', + selectedBorder: '#FF1493', + focus: 'rgba(255, 20, 147, 0.16)', + hoverOpacity: 0.08, + disabledText: 'rgba(224, 224, 224, 0.5)', + disabledBackground: 'rgba(255, 20, 147, 0.08)', + disabledOpacity: 0.38, + }, + gradients: { + brandHorizontal: 'linear-gradient(270deg, #FF1493 0%, #1E90FF 100%)', + brandVertical: 'linear-gradient(0.01deg, #FF1493 0.01%, #1E90FF 99.99%)', + }, + contrastThreshold: 3, + hoverFactor: 0.03, + tonalOffset: 0.15, + }, + shape: { + borderRadius: 6, + }, +}; + +export default synthwaveTheme; diff --git a/packages/grafana-data/src/themes/themeDefinitions/tron.ts b/packages/grafana-data/src/themes/themeDefinitions/tron.ts new file mode 100644 index 00000000000..e3e761e012b --- /dev/null +++ b/packages/grafana-data/src/themes/themeDefinitions/tron.ts @@ -0,0 +1,55 @@ +import { NewThemeOptions } from '../createTheme'; + +const tronTheme: NewThemeOptions = { + name: 'Tron', + colors: { + mode: 'dark', + border: { + weak: 'rgba(0, 255, 255, 0.12)', + medium: 'rgba(0, 255, 255, 0.20)', + strong: 'rgba(0, 255, 255, 0.30)', + }, + text: { + primary: '#E0E0E0', + secondary: 'rgba(224, 224, 224, 0.75)', + disabled: 'rgba(224, 224, 224, 0.5)', + link: '#00FFFF', + maxContrast: '#FFFFFF', + }, + primary: { + main: '#00FFFF', + }, + secondary: { + main: '#0b2e36', + text: 'rgba(224, 224, 224, 0.75)', + border: 'rgba(0, 255, 255, 0.10)', + }, + background: { + canvas: '#0A0F18', + primary: '#0F1B2A', + secondary: '#152234', + }, + action: { + hover: 'rgba(0, 255, 255, 0.16)', + selected: 'rgba(0, 255, 255, 0.12)', + selectedBorder: '#00FFFF', + focus: 'rgba(0, 255, 255, 0.16)', + hoverOpacity: 0.08, + disabledText: 'rgba(224, 224, 224, 0.5)', + disabledBackground: 'rgba(0, 255, 255, 0.08)', + disabledOpacity: 0.38, + }, + gradients: { + brandHorizontal: 'linear-gradient(270deg, #00FFFF 0%, #29ABE2 100%)', + brandVertical: 'linear-gradient(0.01deg, #00FFFF 0.01%, #29ABE2 99.99%)', + }, + contrastThreshold: 3, + hoverFactor: 0.05, + tonalOffset: 0.2, + }, + shape: { + borderRadius: 6, + }, +}; + +export default tronTheme; diff --git a/packages/grafana-data/src/themes/themeDefinitions/victorian.ts b/packages/grafana-data/src/themes/themeDefinitions/victorian.ts new file mode 100644 index 00000000000..a90879ea1fb --- /dev/null +++ b/packages/grafana-data/src/themes/themeDefinitions/victorian.ts @@ -0,0 +1,59 @@ +import { NewThemeOptions } from '../createTheme'; + +const victorianTheme: NewThemeOptions = { + name: 'Victorian', + colors: { + mode: 'dark', + border: { + weak: '#3A2C22', + medium: '#3A2C22', + strong: '#4B3D32', + }, + text: { + primary: '#D9D0A2', + secondary: '#C4B89B', + disabled: '#A89F91', + link: '#C28A4D', + maxContrast: '#FFFFFF', + }, + primary: { + main: '#C28A4D', + }, + secondary: { + main: '#3A2C22', + text: '#C4B89B', + border: '#4B3D32', + }, + background: { + canvas: '#1F1510', + primary: '#2C1A13', + secondary: '#402A21', + }, + action: { + hover: '#3A2C22', + selected: '#4B3D32', + selectedBorder: '#C28A4D', + focus: '#C28A4D', + hoverOpacity: 0.1, + disabledText: '#A89F91', + disabledBackground: '#402A21', + disabledOpacity: 0.38, + }, + gradients: { + brandHorizontal: 'linear-gradient(270deg, #D9D0a1 0%, #C28A4D 100%)', + brandVertical: 'linear-gradient(0.01deg, #D9D0a1 0.01%, #C28A4D 99.99%)', + }, + contrastThreshold: 4, + hoverFactor: 0.07, + tonalOffset: 0.15, + }, + shape: { + borderRadius: 6, + }, + typography: { + fontFamily: '"Georgia", "Times New Roman", serif', + fontFamilyMonospace: "'Courier New', monospace", + }, +}; + +export default victorianTheme; diff --git a/packages/grafana-data/src/themes/themeDefinitions/zen.ts b/packages/grafana-data/src/themes/themeDefinitions/zen.ts new file mode 100644 index 00000000000..0867de1e63e --- /dev/null +++ b/packages/grafana-data/src/themes/themeDefinitions/zen.ts @@ -0,0 +1,55 @@ +import { NewThemeOptions } from '../createTheme'; + +const zenTheme: NewThemeOptions = { + name: 'Zen', + colors: { + mode: 'light', + text: { + primary: '#333333', + secondary: '#666666', + disabled: '#B8B8B8', + link: '#4F9F6E', + maxContrast: '#000000', + }, + border: { + weak: '#B1B7B3', + medium: '#A2A8A2', + strong: '#7C7F7A', + }, + primary: { + main: '#6D8E6D', + }, + secondary: { + main: '#E0E0E0', + text: '#666666', + border: '#A2A8A2', + }, + background: { + canvas: '#F4F4F4', + primary: '#E9E9E9', + secondary: '#D8D8D8', + }, + action: { + hover: '#D1D1D1', + selected: '#B8B8B8', + selectedBorder: '#88B88B', + hoverOpacity: 0.1, + focus: '#D1D1D1', + disabledBackground: '#E0E0E0', + disabledText: '#B8B8B8', + disabledOpacity: 0.5, + }, + gradients: { + brandHorizontal: 'linear-gradient(270deg, #88B88B 0%, #6D8E6D 100%)', + brandVertical: 'linear-gradient(0.01deg, #88B88B 0.01%, #6D8E6D 99.99%)', + }, + contrastThreshold: 3, + hoverFactor: 0.03, + tonalOffset: 0.2, + }, + shape: { + borderRadius: 8, + }, +}; + +export default zenTheme; diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index 452659bcf74..3e0490dd97b 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -256,4 +256,5 @@ export interface FeatureToggles { alertingAlertmanagerExtraDedupStage?: boolean; alertingAlertmanagerExtraDedupStageStopPipeline?: boolean; newLogsPanel?: boolean; + grafanaconThemes?: boolean; } diff --git a/packages/grafana-ui/.storybook/preview.ts b/packages/grafana-ui/.storybook/preview.ts index a8dc3e9f062..61206e5ab7f 100644 --- a/packages/grafana-ui/.storybook/preview.ts +++ b/packages/grafana-ui/.storybook/preview.ts @@ -31,7 +31,15 @@ const handleThemeChange = (theme: GrafanaTheme2) => { } }; -const showExtraThemes = process.env.NODE_ENV === 'development'; +const allowedExtraThemes: string[] = []; + +if (process.env.NODE_ENV === 'development') { + allowedExtraThemes.push('debug'); + allowedExtraThemes.push('desertbloom'); + allowedExtraThemes.push('gildedgrove'); + allowedExtraThemes.push('sapphiredusk'); + allowedExtraThemes.push('tron'); +} const preview: Preview = { decorators: [withTheme(handleThemeChange), withTimeZone()], @@ -71,7 +79,7 @@ const preview: Preview = { defaultValue: 'system', toolbar: { icon: 'paintbrush', - items: getBuiltInThemes(showExtraThemes).map((theme) => ({ + items: getBuiltInThemes(allowedExtraThemes).map((theme) => ({ value: theme.id, title: theme.name, })), diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index f0def056b77..bf5496b9c3c 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -1784,6 +1784,15 @@ var ( FrontendOnly: true, Owner: grafanaObservabilityLogsSquad, }, + { + Name: "grafanaconThemes", + Description: "Enables the temporary themes for GrafanaCon", + Stage: FeatureStageExperimental, + Owner: grafanaFrontendPlatformSquad, + HideFromAdminPage: true, + HideFromDocs: true, + RequiresRestart: true, + }, } ) diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index ad6203ebdc7..66125da1954 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -237,3 +237,4 @@ fetchRulesUsingPost,experimental,@grafana/alerting-squad,false,false,false alertingAlertmanagerExtraDedupStage,experimental,@grafana/alerting-squad,false,true,false alertingAlertmanagerExtraDedupStageStopPipeline,experimental,@grafana/alerting-squad,false,true,false newLogsPanel,experimental,@grafana/observability-logs,false,false,true +grafanaconThemes,experimental,@grafana/grafana-frontend-platform,false,true,false diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go index 8c36acbcc51..e910ed15857 100644 --- a/pkg/services/featuremgmt/toggles_gen.go +++ b/pkg/services/featuremgmt/toggles_gen.go @@ -958,4 +958,8 @@ const ( // FlagNewLogsPanel // Enables the new logs panel in Explore FlagNewLogsPanel = "newLogsPanel" + + // FlagGrafanaconThemes + // Enables the temporary themes for GrafanaCon + FlagGrafanaconThemes = "grafanaconThemes" ) diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json index e1673e7c8ab..bc2a152baaf 100644 --- a/pkg/services/featuremgmt/toggles_gen.json +++ b/pkg/services/featuremgmt/toggles_gen.json @@ -1865,6 +1865,21 @@ "hideFromDocs": true } }, + { + "metadata": { + "name": "grafanaconThemes", + "resourceVersion": "1738661140740", + "creationTimestamp": "2025-02-04T09:25:40Z" + }, + "spec": { + "description": "Enables the temporary themes for GrafanaCon", + "stage": "experimental", + "codeowner": "@grafana/grafana-frontend-platform", + "requiresRestart": true, + "hideFromAdminPage": true, + "hideFromDocs": true + } + }, { "metadata": { "name": "groupAttributeSync", diff --git a/pkg/services/preference/themes.go b/pkg/services/preference/themes.go index f1332d33f0b..4e88235d5d2 100644 --- a/pkg/services/preference/themes.go +++ b/pkg/services/preference/themes.go @@ -11,6 +11,16 @@ var themes = []ThemeDTO{ {ID: "dark", Type: "dark"}, {ID: "system", Type: "dark"}, {ID: "debug", Type: "dark", IsExtra: true}, + {ID: "aubergine", Type: "dark", IsExtra: true}, + {ID: "desertbloom", Type: "light", IsExtra: true}, + {ID: "gildedgrove", Type: "dark", IsExtra: true}, + {ID: "mars", Type: "dark", IsExtra: true}, + {ID: "matrix", Type: "dark", IsExtra: true}, + {ID: "sapphiredusk", Type: "dark", IsExtra: true}, + {ID: "synthwave", Type: "dark", IsExtra: true}, + {ID: "tron", Type: "dark", IsExtra: true}, + {ID: "victorian", Type: "dark", IsExtra: true}, + {ID: "zen", Type: "light", IsExtra: true}, } func GetThemeByID(id string) *ThemeDTO { diff --git a/public/app/core/components/SharedPreferences/SharedPreferences.tsx b/public/app/core/components/SharedPreferences/SharedPreferences.tsx index a24bd4b027b..16d91551060 100644 --- a/public/app/core/components/SharedPreferences/SharedPreferences.tsx +++ b/public/app/core/components/SharedPreferences/SharedPreferences.tsx @@ -17,6 +17,7 @@ import { FeatureBadge, Combobox, ComboboxOption, + TextLink, } from '@grafana/ui'; import { DashboardPicker } from 'app/core/components/Select/DashboardPicker'; import { t, Trans } from 'app/core/internationalization'; @@ -80,7 +81,20 @@ export class SharedPreferences extends PureComponent { navbar: { bookmarkUrls: [] }, }; - this.themeOptions = getBuiltInThemes(config.featureToggles.extraThemes).map((theme) => ({ + const allowedExtraThemes = []; + + if (config.featureToggles.extraThemes) { + allowedExtraThemes.push('debug'); + } + + if (config.featureToggles.grafanaconThemes) { + allowedExtraThemes.push('desertbloom'); + allowedExtraThemes.push('gildedgrove'); + allowedExtraThemes.push('sapphiredusk'); + allowedExtraThemes.push('tron'); + } + + this.themeOptions = getBuiltInThemes(allowedExtraThemes).map((theme) => ({ value: theme.id, label: getTranslatedThemeName(theme), })); @@ -168,6 +182,20 @@ export class SharedPreferences extends PureComponent { loading={isLoading} disabled={isLoading} label={t('shared-preferences.fields.theme-label', 'Interface theme')} + description={ + config.featureToggles.grafanaconThemes ? ( + + Enjoying the limited edition themes? Tell us what you'd like to see{' '} + + here. + + + ) : undefined + } > here.", "theme-label": "Interface theme", "week-start-label": "Week start" }, diff --git a/public/locales/pseudo-LOCALE/grafana.json b/public/locales/pseudo-LOCALE/grafana.json index e3194c0093e..ad00f61eef6 100644 --- a/public/locales/pseudo-LOCALE/grafana.json +++ b/public/locales/pseudo-LOCALE/grafana.json @@ -3398,6 +3398,7 @@ "home-dashboard-placeholder": "Đęƒäūľŧ đäşĥþőäřđ", "locale-label": "Ŀäʼnģūäģę", "locale-placeholder": "Cĥőőşę ľäʼnģūäģę", + "theme-description": "Ēʼnĵőyįʼnģ ŧĥę ľįmįŧęđ ęđįŧįőʼn ŧĥęmęş? Ŧęľľ ūş ŵĥäŧ yőū'đ ľįĸę ŧő şęę <2>ĥęřę.", "theme-label": "Ĩʼnŧęřƒäčę ŧĥęmę", "week-start-label": "Ŵęęĸ şŧäřŧ" }, From fde475e3d9a28b19fc6a1decd7fa911123c00a2e Mon Sep 17 00:00:00 2001 From: Matheus Macabu Date: Thu, 6 Feb 2025 12:35:59 +0100 Subject: [PATCH 07/34] CloudMigrations: save snapshot of alert rule groups (#100109) --- pkg/services/cloudmigration/api/dtos.go | 1 + .../cloudmigrationimpl/cloudmigration_test.go | 5 +- .../cloudmigrationimpl/snapshot_mgmt.go | 17 +++ .../snapshot_mgmt_alerts.go | 58 ++++++++++ .../snapshot_mgmt_alerts_test.go | 105 +++++++++++++++++- pkg/services/cloudmigration/model.go | 1 + public/api-enterprise-spec.json | 1 + public/api-merged.json | 1 + .../migrate-to-cloud/api/endpoints.gen.ts | 1 + .../migrate-to-cloud/onprem/NameCell.tsx | 2 + .../migrate-to-cloud/onprem/TypeCell.tsx | 2 + .../onprem/useNotifyOnSuccess.tsx | 2 + public/locales/en-US/grafana.json | 2 + public/locales/pseudo-LOCALE/grafana.json | 2 + public/openapi3.json | 1 + 15 files changed, 194 insertions(+), 7 deletions(-) diff --git a/pkg/services/cloudmigration/api/dtos.go b/pkg/services/cloudmigration/api/dtos.go index 6e1154489bf..1efa21fc77c 100644 --- a/pkg/services/cloudmigration/api/dtos.go +++ b/pkg/services/cloudmigration/api/dtos.go @@ -127,6 +127,7 @@ const ( FolderDataType MigrateDataType = "FOLDER" LibraryElementDataType MigrateDataType = "LIBRARY_ELEMENT" AlertRuleType MigrateDataType = "ALERT_RULE" + AlertRuleGroupType MigrateDataType = "ALERT_RULE_GROUP" ContactPointType MigrateDataType = "CONTACT_POINT" NotificationPolicyType MigrateDataType = "NOTIFICATION_POLICY" NotificationTemplateType MigrateDataType = "NOTIFICATION_TEMPLATE" diff --git a/pkg/services/cloudmigration/cloudmigrationimpl/cloudmigration_test.go b/pkg/services/cloudmigration/cloudmigrationimpl/cloudmigration_test.go index 257d2e23a07..348bf8b1e98 100644 --- a/pkg/services/cloudmigration/cloudmigrationimpl/cloudmigration_test.go +++ b/pkg/services/cloudmigration/cloudmigrationimpl/cloudmigration_test.go @@ -821,8 +821,11 @@ func setUpServiceTest(t *testing.T, withDashboardMock bool, cfgOverrides ...conf secretsService := secretsfakes.NewFakeSecretsService() rr := routing.NewRouteRegister() tracer := tracing.InitializeTracerForTest() + + fakeFolder := &folder.Folder{UID: "folderUID", Title: "Folder"} mockFolder := &foldertest.FakeService{ - ExpectedFolder: &folder.Folder{UID: "folderUID", Title: "Folder"}, + ExpectedFolders: []*folder.Folder{fakeFolder}, + ExpectedFolder: fakeFolder, } cfg := setting.NewCfg() diff --git a/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt.go b/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt.go index 870c4949533..034c601f510 100644 --- a/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt.go +++ b/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt.go @@ -43,6 +43,7 @@ var currentMigrationTypes = []cloudmigration.MigrateDataType{ cloudmigration.NotificationTemplateType, cloudmigration.ContactPointType, cloudmigration.NotificationPolicyType, + cloudmigration.AlertRuleGroupType, cloudmigration.AlertRuleType, cloudmigration.PluginDataType, } @@ -106,6 +107,13 @@ func (s *Service) getMigrationDataJSON(ctx context.Context, signedInUser *user.S return nil, err } + // Alerts: Alert Rule Groups + alertRuleGroups, err := s.getAlertRuleGroups(ctx, signedInUser) + if err != nil { + s.log.Error("Failed to get alert rule groups", "err", err) + return nil, err + } + // Alerts: Alert Rules alertRules, err := s.getAlertRules(ctx, signedInUser) if err != nil { @@ -209,6 +217,15 @@ func (s *Service) getMigrationDataJSON(ctx context.Context, signedInUser *user.S }) } + for _, alertRuleGroup := range alertRuleGroups { + migrationDataSlice = append(migrationDataSlice, cloudmigration.MigrateDataRequestItem{ + Type: cloudmigration.AlertRuleGroupType, + RefID: alertRuleGroup.Title, // no UID available + Name: alertRuleGroup.Title, + Data: alertRuleGroup, + }) + } + for _, alertRule := range alertRules { migrationDataSlice = append(migrationDataSlice, cloudmigration.MigrateDataRequestItem{ Type: cloudmigration.AlertRuleType, diff --git a/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt_alerts.go b/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt_alerts.go index a848508b7eb..c75d8a35df0 100644 --- a/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt_alerts.go +++ b/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt_alerts.go @@ -180,3 +180,61 @@ func (s *Service) getAlertRules(ctx context.Context, signedInUser *user.SignedIn return provisionedAlertRules, nil } + +type alertRuleGroup struct { + Title string `json:"title"` + FolderUID string `json:"folderUid"` + Interval int64 `json:"interval"` + Rules []alertRule `json:"rules"` +} + +func (s *Service) getAlertRuleGroups(ctx context.Context, signedInUser *user.SignedInUser) ([]alertRuleGroup, error) { + alertRuleGroupsWithFolder, err := s.ngAlert.Api.AlertRules.GetAlertGroupsWithFolderFullpath(ctx, signedInUser, nil) + if err != nil { + return nil, fmt.Errorf("fetching alert rule groups with folders: %w", err) + } + + settingAlertRulesPaused := s.cfg.CloudMigration.AlertRulesState == setting.GMSAlertRulesPaused + + alertRuleGroups := make([]alertRuleGroup, 0, len(alertRuleGroupsWithFolder)) + + for _, ruleGroup := range alertRuleGroupsWithFolder { + provisionedAlertRules := make([]alertRule, 0, len(ruleGroup.Rules)) + + for _, rule := range ruleGroup.Rules { + isPaused := rule.IsPaused + if settingAlertRulesPaused { + isPaused = true + } + + provisionedAlertRules = append(provisionedAlertRules, alertRule{ + ID: rule.ID, + UID: rule.UID, + OrgID: rule.OrgID, + FolderUID: rule.NamespaceUID, + RuleGroup: rule.RuleGroup, + Title: rule.Title, + For: model.Duration(rule.For), + Condition: rule.Condition, + Data: ngalertapi.ApiAlertQueriesFromAlertQueries(rule.Data), + Updated: rule.Updated, + NoDataState: rule.NoDataState.String(), + ExecErrState: rule.ExecErrState.String(), + Annotations: rule.Annotations, + Labels: rule.Labels, + IsPaused: isPaused, + NotificationSettings: ngalertapi.AlertRuleNotificationSettingsFromNotificationSettings(rule.NotificationSettings), + Record: ngalertapi.ApiRecordFromModelRecord(rule.Record), + }) + } + + alertRuleGroups = append(alertRuleGroups, alertRuleGroup{ + Title: ruleGroup.Title, + FolderUID: ruleGroup.FolderUID, + Interval: ruleGroup.Interval, + Rules: provisionedAlertRules, + }) + } + + return alertRuleGroups, nil +} diff --git a/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt_alerts_test.go b/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt_alerts_test.go index f8e99aed021..91d7ecc5cd7 100644 --- a/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt_alerts_test.go +++ b/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt_alerts_test.go @@ -121,7 +121,7 @@ func TestGetAlertRules(t *testing.T) { user := &user.SignedInUser{OrgID: 1} - alertRule := createAlertRule(t, ctx, s, user, false) + alertRule := createAlertRule(t, ctx, s, user, false, "") alertRules, err := s.getAlertRules(ctx, user) require.NoError(t, err) @@ -138,10 +138,10 @@ func TestGetAlertRules(t *testing.T) { user := &user.SignedInUser{OrgID: 1} - alertRulePaused := createAlertRule(t, ctx, s, user, true) + alertRulePaused := createAlertRule(t, ctx, s, user, true, "") require.True(t, alertRulePaused.IsPaused) - alertRuleUnpaused := createAlertRule(t, ctx, s, user, false) + alertRuleUnpaused := createAlertRule(t, ctx, s, user, false, "") require.False(t, alertRuleUnpaused.IsPaused) alertRules, err := s.getAlertRules(ctx, user) @@ -152,6 +152,83 @@ func TestGetAlertRules(t *testing.T) { }) } +func TestGetAlertRuleGroups(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + t.Run("it returns the alert rule groups", func(t *testing.T) { + s := setUpServiceTest(t, false).(*Service) + + user := &user.SignedInUser{OrgID: 1} + + ruleGroupTitle := "ruleGroupTitle" + + alertRule1 := createAlertRule(t, ctx, s, user, true, ruleGroupTitle) + alertRule2 := createAlertRule(t, ctx, s, user, false, ruleGroupTitle) + alertRule3 := createAlertRule(t, ctx, s, user, false, "anotherRuleGroup") + + createAlertRuleGroup(t, ctx, s, user, ruleGroupTitle, []models.AlertRule{alertRule1, alertRule2}) + + ruleGroups, err := s.getAlertRuleGroups(ctx, user) + require.NoError(t, err) + require.Len(t, ruleGroups, 2) + + for _, ruleGroup := range ruleGroups { + alertRuleUIDs := make([]string, 0) + for _, alertRule := range ruleGroup.Rules { + alertRuleUIDs = append(alertRuleUIDs, alertRule.UID) + } + + if ruleGroup.Title == ruleGroupTitle { + require.Len(t, ruleGroup.Rules, 2) + require.ElementsMatch(t, []string{alertRule1.UID, alertRule2.UID}, alertRuleUIDs) + } else { + require.Len(t, ruleGroup.Rules, 1) + require.ElementsMatch(t, []string{alertRule3.UID}, alertRuleUIDs) + } + } + }) + + t.Run("with the alert rules state set to paused, it returns the alert rule groups with alert rules paused", func(t *testing.T) { + alertRulesState := func(c *setting.Cfg) { + c.CloudMigration.AlertRulesState = setting.GMSAlertRulesPaused + } + + s := setUpServiceTest(t, false, alertRulesState).(*Service) + + user := &user.SignedInUser{OrgID: 1} + + ruleGroupTitle := "ruleGroupTitle" + + alertRule1 := createAlertRule(t, ctx, s, user, true, ruleGroupTitle) + alertRule2 := createAlertRule(t, ctx, s, user, false, ruleGroupTitle) + alertRule3 := createAlertRule(t, ctx, s, user, false, "anotherRuleGroup") + + createAlertRuleGroup(t, ctx, s, user, ruleGroupTitle, []models.AlertRule{alertRule1, alertRule2}) + + ruleGroups, err := s.getAlertRuleGroups(ctx, user) + require.NoError(t, err) + require.Len(t, ruleGroups, 2) + + for _, ruleGroup := range ruleGroups { + alertRuleUIDs := make([]string, 0) + for _, alertRule := range ruleGroup.Rules { + alertRuleUIDs = append(alertRuleUIDs, alertRule.UID) + + require.True(t, alertRule.IsPaused) + } + + if ruleGroup.Title == ruleGroupTitle { + require.Len(t, ruleGroup.Rules, 2) + require.ElementsMatch(t, []string{alertRule1.UID, alertRule2.UID}, alertRuleUIDs) + } else { + require.Len(t, ruleGroup.Rules, 1) + require.ElementsMatch(t, []string{alertRule3.UID}, alertRuleUIDs) + } + } + }) +} + func createMuteTiming(t *testing.T, ctx context.Context, service *Service, user *user.SignedInUser) definitions.MuteTimeInterval { t.Helper() @@ -267,12 +344,12 @@ func updateNotificationPolicyTree(t *testing.T, ctx context.Context, service *Se require.NoError(t, err) } -func createAlertRule(t *testing.T, ctx context.Context, service *Service, user *user.SignedInUser, isPaused bool) models.AlertRule { +func createAlertRule(t *testing.T, ctx context.Context, service *Service, user *user.SignedInUser, isPaused bool, ruleGroup string) models.AlertRule { t.Helper() rule := models.AlertRule{ OrgID: user.GetOrgID(), - Title: fmt.Sprintf("Alert Rule SLO (Paused: %v)", isPaused), + Title: fmt.Sprintf("Alert Rule SLO (Paused: %v) - %v", isPaused, ruleGroup), NamespaceUID: "folderUID", Condition: "A", Data: []models.AlertQuery{ @@ -286,7 +363,7 @@ func createAlertRule(t *testing.T, ctx context.Context, service *Service, user * }, }, IsPaused: isPaused, - RuleGroup: "ruleGroup", + RuleGroup: ruleGroup, For: time.Minute, IntervalSeconds: 60, NoDataState: models.OK, @@ -298,3 +375,19 @@ func createAlertRule(t *testing.T, ctx context.Context, service *Service, user * return createdRule } + +func createAlertRuleGroup(t *testing.T, ctx context.Context, service *Service, user *user.SignedInUser, title string, rules []models.AlertRule) models.AlertRuleGroup { + t.Helper() + + group := models.AlertRuleGroup{ + Title: title, + FolderUID: "folderUID", + Interval: 300, + Rules: rules, + } + + err := service.ngAlert.Api.AlertRules.ReplaceRuleGroup(ctx, user, group, "") + require.NoError(t, err) + + return group +} diff --git a/pkg/services/cloudmigration/model.go b/pkg/services/cloudmigration/model.go index 7d0b4b58f49..a46a3b46910 100644 --- a/pkg/services/cloudmigration/model.go +++ b/pkg/services/cloudmigration/model.go @@ -88,6 +88,7 @@ const ( FolderDataType MigrateDataType = "FOLDER" LibraryElementDataType MigrateDataType = "LIBRARY_ELEMENT" AlertRuleType MigrateDataType = "ALERT_RULE" + AlertRuleGroupType MigrateDataType = "ALERT_RULE_GROUP" ContactPointType MigrateDataType = "CONTACT_POINT" NotificationPolicyType MigrateDataType = "NOTIFICATION_POLICY" NotificationTemplateType MigrateDataType = "NOTIFICATION_TEMPLATE" diff --git a/public/api-enterprise-spec.json b/public/api-enterprise-spec.json index 7667a82756c..e2e8df6926b 100644 --- a/public/api-enterprise-spec.json +++ b/public/api-enterprise-spec.json @@ -5670,6 +5670,7 @@ "FOLDER", "LIBRARY_ELEMENT", "ALERT_RULE", + "ALERT_RULE_GROUP", "CONTACT_POINT", "NOTIFICATION_POLICY", "NOTIFICATION_TEMPLATE", diff --git a/public/api-merged.json b/public/api-merged.json index e063c8c959c..3e2239131d4 100644 --- a/public/api-merged.json +++ b/public/api-merged.json @@ -17182,6 +17182,7 @@ "FOLDER", "LIBRARY_ELEMENT", "ALERT_RULE", + "ALERT_RULE_GROUP", "CONTACT_POINT", "NOTIFICATION_POLICY", "NOTIFICATION_TEMPLATE", diff --git a/public/app/features/migrate-to-cloud/api/endpoints.gen.ts b/public/app/features/migrate-to-cloud/api/endpoints.gen.ts index e24b6b424f0..98e962b61e0 100644 --- a/public/app/features/migrate-to-cloud/api/endpoints.gen.ts +++ b/public/app/features/migrate-to-cloud/api/endpoints.gen.ts @@ -194,6 +194,7 @@ export type MigrateDataResponseItemDto = { | 'FOLDER' | 'LIBRARY_ELEMENT' | 'ALERT_RULE' + | 'ALERT_RULE_GROUP' | 'CONTACT_POINT' | 'NOTIFICATION_POLICY' | 'NOTIFICATION_TEMPLATE' diff --git a/public/app/features/migrate-to-cloud/onprem/NameCell.tsx b/public/app/features/migrate-to-cloud/onprem/NameCell.tsx index c6b8f78f04b..3080a26fdd9 100644 --- a/public/app/features/migrate-to-cloud/onprem/NameCell.tsx +++ b/public/app/features/migrate-to-cloud/onprem/NameCell.tsx @@ -231,6 +231,8 @@ function ResourceIcon({ resource }: { resource: ResourceTableItem }) { return ; case 'ALERT_RULE': return ; + case 'ALERT_RULE_GROUP': + return ; case 'PLUGIN': if (pluginLogo) { return ; diff --git a/public/app/features/migrate-to-cloud/onprem/TypeCell.tsx b/public/app/features/migrate-to-cloud/onprem/TypeCell.tsx index 399945cdaba..00d576db9c5 100644 --- a/public/app/features/migrate-to-cloud/onprem/TypeCell.tsx +++ b/public/app/features/migrate-to-cloud/onprem/TypeCell.tsx @@ -23,6 +23,8 @@ export function prettyTypeName(type: ResourceTableItem['type']) { return t('migrate-to-cloud.resource-type.notification_policy', 'Notification Policy'); case 'ALERT_RULE': return t('migrate-to-cloud.resource-type.alert_rule', 'Alert Rule'); + case 'ALERT_RULE_GROUP': + return t('migrate-to-cloud.resource-type.alert_rule_group', 'Alert Rule Group'); case 'PLUGIN': return t('migrate-to-cloud.resource-type.plugin', 'Plugin'); default: diff --git a/public/app/features/migrate-to-cloud/onprem/useNotifyOnSuccess.tsx b/public/app/features/migrate-to-cloud/onprem/useNotifyOnSuccess.tsx index e21fd83e815..e98a8221bb6 100644 --- a/public/app/features/migrate-to-cloud/onprem/useNotifyOnSuccess.tsx +++ b/public/app/features/migrate-to-cloud/onprem/useNotifyOnSuccess.tsx @@ -62,6 +62,8 @@ function getTranslatedMessage(snapshot: GetSnapshotResponseDto) { types.push(t('migrate-to-cloud.migrated-counts.notification_policies', 'notification policies')); } else if (type === 'ALERT_RULE') { types.push(t('migrate-to-cloud.migrated-counts.alert_rules', 'alert rules')); + } else if (type === 'ALERT_RULE_GROUP') { + types.push(t('migrate-to-cloud.migrated-counts.alert_rule_groups', 'alert rule groups')); } else if (type === 'PLUGIN') { types.push(t('migrate-to-cloud.migrated-counts.plugins', 'plugins')); } diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index f1b5d7ba851..66492eac6b1 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -2117,6 +2117,7 @@ "title": "Let us help you migrate to this stack" }, "migrated-counts": { + "alert_rule_groups": "alert rule groups", "alert_rules": "alert rules", "contact_points": "contact points", "dashboards": "dashboards", @@ -2221,6 +2222,7 @@ }, "resource-type": { "alert_rule": "Alert Rule", + "alert_rule_group": "Alert Rule Group", "contact_point": "Contact Point", "dashboard": "Dashboard", "datasource": "Data source", diff --git a/public/locales/pseudo-LOCALE/grafana.json b/public/locales/pseudo-LOCALE/grafana.json index ad00f61eef6..193643a5111 100644 --- a/public/locales/pseudo-LOCALE/grafana.json +++ b/public/locales/pseudo-LOCALE/grafana.json @@ -2117,6 +2117,7 @@ "title": "Ŀęŧ ūş ĥęľp yőū mįģřäŧę ŧő ŧĥįş şŧäčĸ" }, "migrated-counts": { + "alert_rule_groups": "äľęřŧ řūľę ģřőūpş", "alert_rules": "äľęřŧ řūľęş", "contact_points": "čőʼnŧäčŧ pőįʼnŧş", "dashboards": "đäşĥþőäřđş", @@ -2221,6 +2222,7 @@ }, "resource-type": { "alert_rule": "Åľęřŧ Ŗūľę", + "alert_rule_group": "Åľęřŧ Ŗūľę Ğřőūp", "contact_point": "Cőʼnŧäčŧ Pőįʼnŧ", "dashboard": "Đäşĥþőäřđ", "datasource": "Đäŧä şőūřčę", diff --git a/public/openapi3.json b/public/openapi3.json index f326616d62d..89612da9cf1 100644 --- a/public/openapi3.json +++ b/public/openapi3.json @@ -7250,6 +7250,7 @@ "FOLDER", "LIBRARY_ELEMENT", "ALERT_RULE", + "ALERT_RULE_GROUP", "CONTACT_POINT", "NOTIFICATION_POLICY", "NOTIFICATION_TEMPLATE", From 9fc82faea71a75b19de0376451e09fab70b74a76 Mon Sep 17 00:00:00 2001 From: Sriram <153843+yesoreyeram@users.noreply.github.com> Date: Thu, 6 Feb 2025 11:50:48 +0000 Subject: [PATCH 08/34] [analytics] added plugin version to grafana_ds_test_datasource_clicked event (#100168) --- public/app/features/datasources/state/actions.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/public/app/features/datasources/state/actions.ts b/public/app/features/datasources/state/actions.ts index f1b2fdd4ffb..6229f0d1a02 100644 --- a/public/app/features/datasources/state/actions.ts +++ b/public/app/features/datasources/state/actions.ts @@ -5,6 +5,7 @@ import { TestDataSourceResponse, DataSourceTestSucceeded, DataSourceTestFailed, + DataSourceApi, } from '@grafana/data'; import { config, @@ -125,6 +126,11 @@ export const initDataSourceSettings = ( }; }; +const getPluginVersion = (dsApi: DataSourceApi) => { + const isCorePlugin = (dsApi?.meta?.module || '').startsWith('core'); + return isCorePlugin ? config?.buildInfo?.version : dsApi?.meta?.info?.version; +}; + export const testDataSource = ( dataSourceName: string, editRoute = DATASOURCES_ROUTES.Edit, @@ -153,6 +159,7 @@ export const testDataSource = ( trackDataSourceTested({ grafana_version: config.buildInfo.version, plugin_id: dsApi.type, + plugin_version: getPluginVersion(dsApi), datasource_uid: dsApi.uid, success: true, path: editLink, @@ -165,6 +172,7 @@ export const testDataSource = ( trackDataSourceTested({ grafana_version: config.buildInfo.version, plugin_id: dsApi.type, + plugin_version: getPluginVersion(dsApi), datasource_uid: dsApi.uid, success: false, path: editLink, From 4e6bdce41c89ba7a121762d8f4a3b26a1f1ed854 Mon Sep 17 00:00:00 2001 From: Matias Chomicki Date: Thu, 6 Feb 2025 12:02:43 +0000 Subject: [PATCH 09/34] Loki query direction: run initialization only in Explore and Dashboards (#100182) --- .../LokiQueryBuilderOptions.test.tsx | 52 +++++++++---------- .../components/LokiQueryBuilderOptions.tsx | 9 +++- 2 files changed, 34 insertions(+), 27 deletions(-) diff --git a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.test.tsx b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.test.tsx index bbd8f242906..10253f5851a 100644 --- a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.test.tsx +++ b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.test.tsx @@ -1,4 +1,4 @@ -import { render, screen, waitFor } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { CoreApp, LogSortOrderChangeEvent, LogsSortOrder, store } from '@grafana/data'; @@ -214,42 +214,42 @@ describe('LokiQueryBuilderOptions', () => { }); describe('Query direction', () => { - it("initializes query direction when it's empty", async () => { + it("initializes query direction when it's empty in Explore or Dashboards", () => { const onChange = jest.fn(); - setup({ expr: '{foo="bar"}' }, onChange); - await waitFor(() => - expect(onChange).toHaveBeenCalledWith({ - expr: '{foo="bar"}', - refId: 'A', - direction: LokiQueryDirection.Backward, - }) - ); + setup({ expr: '{foo="bar"}' }, onChange, { app: CoreApp.Explore }); + expect(onChange).toHaveBeenCalledWith({ + expr: '{foo="bar"}', + refId: 'A', + direction: LokiQueryDirection.Backward, + }); }); - it('uses backward as default in Explore with no previous stored preference', async () => { + it('does not change direction on initialization elsewhere', () => { + const onChange = jest.fn(); + setup({ expr: '{foo="bar"}' }, onChange, { app: undefined }); + expect(onChange).not.toHaveBeenCalled(); + }); + + it('uses backward as default in Explore with no previous stored preference', () => { const onChange = jest.fn(); store.delete('grafana.explore.logs.sortOrder'); setup({ expr: '{foo="bar"}' }, onChange, { app: CoreApp.Explore }); - await waitFor(() => - expect(onChange).toHaveBeenCalledWith({ - expr: '{foo="bar"}', - refId: 'A', - direction: LokiQueryDirection.Backward, - }) - ); + expect(onChange).toHaveBeenCalledWith({ + expr: '{foo="bar"}', + refId: 'A', + direction: LokiQueryDirection.Backward, + }); }); - it('uses the stored sorting option to determine direction in Explore', async () => { + it('uses the stored sorting option to determine direction in Explore', () => { store.set('grafana.explore.logs.sortOrder', LogsSortOrder.Ascending); const onChange = jest.fn(); setup({ expr: '{foo="bar"}' }, onChange, { app: CoreApp.Explore }); - await waitFor(() => - expect(onChange).toHaveBeenCalledWith({ - expr: '{foo="bar"}', - refId: 'A', - direction: LokiQueryDirection.Forward, - }) - ); + expect(onChange).toHaveBeenCalledWith({ + expr: '{foo="bar"}', + refId: 'A', + direction: LokiQueryDirection.Forward, + }); store.delete('grafana.explore.logs.sortOrder'); }); diff --git a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.tsx b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.tsx index 2d36a70f1af..cb4fed551f5 100644 --- a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.tsx +++ b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.tsx @@ -39,6 +39,9 @@ export const LokiQueryBuilderOptions = React.memo( const [splitDurationValid, setSplitDurationValid] = useState(true); useEffect(() => { + if (app !== CoreApp.Explore && app !== CoreApp.Dashboard && app !== CoreApp.PanelEditor) { + return; + } // Initialize the query direction according to the current environment. if (!query.direction) { onChange({ ...query, direction: getDefaultQueryDirection(app) }); @@ -182,7 +185,11 @@ export const LokiQueryBuilderOptions = React.memo( /> - + )} From d64f41afdc975482029a994ac7d891e84ee260ba Mon Sep 17 00:00:00 2001 From: Kyle Brandt Date: Thu, 6 Feb 2025 07:27:28 -0500 Subject: [PATCH 10/34] SQL Expressions: Re-implement feature using go-mysql-server (#99521) * Under feature flag `sqlExpressions` and is experimental * Excluded from arm32 * Will not work with the Query Service yet * Does not have limits in place yet * Does not working with alerting yet * Currently requires "prepare time series" Transform for time series viz --------- Co-authored-by: Sam Jewell --- go.mod | 12 +- go.sum | 21 +- go.work.sum | 7 +- pkg/expr/convert_to_long.go | 311 ++++++++++++++ pkg/expr/convert_to_long_test.go | 48 +++ pkg/expr/converter.go | 3 +- pkg/expr/converter_test.go | 6 +- pkg/expr/graph.go | 57 +-- pkg/expr/mathexp/types.go | 2 +- pkg/expr/ml.go | 2 +- pkg/expr/nodes.go | 49 ++- pkg/expr/service.go | 5 +- pkg/expr/service_sql_test.go | 104 +++++ pkg/expr/service_test.go | 89 ++-- pkg/expr/sql/db.go | 61 ++- pkg/expr/sql/db_test.go | 187 +++++++++ pkg/expr/sql/dummy_arm.go | 18 + pkg/expr/sql/frame_db.go | 65 +++ pkg/expr/sql/frame_db_conv.go | 474 ++++++++++++++++++++++ pkg/expr/sql/frame_table.go | 126 ++++++ pkg/expr/sql/parser.go | 101 ++--- pkg/expr/sql/parser_allow.go | 136 +++++++ pkg/expr/sql/parser_allow_test.go | 80 ++++ pkg/expr/sql/parser_test.go | 331 ++++++--------- pkg/expr/sql_command.go | 8 +- pkg/registry/apis/query/query.go | 6 +- pkg/services/featuremgmt/codeowners.go | 1 + pkg/services/featuremgmt/registry.go | 2 +- pkg/services/featuremgmt/toggles_gen.csv | 2 +- pkg/services/featuremgmt/toggles_gen.json | 9 +- pkg/storage/unified/apistore/go.mod | 10 +- pkg/storage/unified/apistore/go.sum | 21 +- pkg/storage/unified/resource/go.sum | 20 +- 33 files changed, 1969 insertions(+), 405 deletions(-) create mode 100644 pkg/expr/convert_to_long.go create mode 100644 pkg/expr/convert_to_long_test.go create mode 100644 pkg/expr/service_sql_test.go create mode 100644 pkg/expr/sql/db_test.go create mode 100644 pkg/expr/sql/dummy_arm.go create mode 100644 pkg/expr/sql/frame_db.go create mode 100644 pkg/expr/sql/frame_db_conv.go create mode 100644 pkg/expr/sql/frame_table.go create mode 100644 pkg/expr/sql/parser_allow.go create mode 100644 pkg/expr/sql/parser_allow_test.go diff --git a/go.mod b/go.mod index ebfdb244396..82a3b4de7e3 100644 --- a/go.mod +++ b/go.mod @@ -41,6 +41,8 @@ require ( github.com/centrifugal/centrifuge v0.33.3 // @grafana/grafana-app-platform-squad github.com/crewjam/saml v0.4.13 // @grafana/identity-access-team github.com/dlmiddlecote/sqlstats v1.0.2 // @grafana/grafana-backend-group + github.com/dolthub/go-mysql-server v0.19.0 // @grafana/grafana-datasources-core-services + github.com/dolthub/vitess v0.0.0-20241211024425-b00987f7ba54 // @grafana/grafana-datasources-core-services github.com/fatih/color v1.17.0 // @grafana/grafana-backend-group github.com/fullstorydev/grpchan v1.1.1 // @grafana/grafana-backend-group github.com/gchaincl/sqlhooks v1.3.0 // @grafana/grafana-search-and-storage @@ -104,7 +106,6 @@ require ( github.com/influxdata/influxdb-client-go/v2 v2.13.0 // @grafana/partner-datasources github.com/influxdata/influxql v1.4.0 // @grafana/partner-datasources github.com/influxdata/line-protocol v0.0.0-20210922203350-b1ad95c89adf // @grafana/grafana-app-platform-squad - github.com/jeremywohl/flatten v1.0.1 // @grafana/grafana-app-platform-squad github.com/jmespath-community/go-jmespath v1.1.1 // @grafana/identity-access-team github.com/jmespath/go-jmespath v0.4.0 // indirect; // @grafana/grafana-backend-group github.com/jmoiron/sqlx v1.3.5 // @grafana/grafana-backend-group @@ -315,6 +316,9 @@ require ( github.com/dgryski/go-metro v0.0.0-20180109044635-280f6062b5bc // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/docker/go-units v0.5.0 // indirect + github.com/dolthub/flatbuffers/v23 v23.3.3-dh.2 // indirect + github.com/dolthub/go-icu-regex v0.0.0-20241215010122-db690dd53c90 // indirect + github.com/dolthub/jsonpath v0.0.2-0.20240227200619-19675ab05c71 // indirect github.com/dolthub/maphash v0.1.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/edsrzf/mmap-go v1.2.0 // indirect @@ -399,6 +403,7 @@ require ( github.com/kylelemons/godebug v1.1.0 // indirect github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect + github.com/lestrrat-go/strftime v1.0.4 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattbaird/jsonpatch v0.0.0-20240118010651-0ba75a80ca38 // indirect @@ -466,9 +471,10 @@ require ( github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect github.com/sethvargo/go-retry v0.3.0 // indirect github.com/shadowspore/fossil-delta v0.0.0-20240102155221-e3a8590b820b // indirect - github.com/shopspring/decimal v1.4.0 // indirect + github.com/shopspring/decimal v1.4.0 // @grafana/grafana-datasources-core-services github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c // indirect github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect github.com/sony/gobreaker v0.5.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.11.0 // indirect @@ -477,6 +483,7 @@ require ( github.com/stoewer/go-strcase v1.3.0 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/subosito/gotenv v1.6.0 // indirect + github.com/tetratelabs/wazero v1.8.2 // indirect github.com/uber/jaeger-client-go v2.30.0+incompatible // indirect github.com/uber/jaeger-lib v2.4.1+incompatible // indirect github.com/unknwon/bra v0.0.0-20200517080246-1e3013ecaff8 // indirect @@ -517,6 +524,7 @@ require ( gopkg.in/fsnotify/fsnotify.v1 v1.4.7 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect + gopkg.in/src-d/go-errors.v1 v1.0.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect k8s.io/apiextensions-apiserver v0.32.1 // indirect k8s.io/kms v0.32.1 // indirect diff --git a/go.sum b/go.sum index ce0e5946b08..efdc65bf13c 100644 --- a/go.sum +++ b/go.sum @@ -1057,8 +1057,18 @@ github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4 github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/dolthub/flatbuffers/v23 v23.3.3-dh.2 h1:u3PMzfF8RkKd3lB9pZ2bfn0qEG+1Gms9599cr0REMww= +github.com/dolthub/flatbuffers/v23 v23.3.3-dh.2/go.mod h1:mIEZOHnFx4ZMQeawhw9rhsj+0zwQj7adVsnBX7t+eKY= +github.com/dolthub/go-icu-regex v0.0.0-20241215010122-db690dd53c90 h1:Sni8jrP0sy/w9ZYXoff4g/ixe+7bFCZlfCqXKJSU+zM= +github.com/dolthub/go-icu-regex v0.0.0-20241215010122-db690dd53c90/go.mod h1:ylU4XjUpsMcvl/BKeRRMXSH7e7WBrPXdSLvnRJYrxEA= +github.com/dolthub/go-mysql-server v0.19.0 h1:NdcXyGt9v7m4sQOahU+ss++iyPy4Q3viuVvbnn3rUTQ= +github.com/dolthub/go-mysql-server v0.19.0/go.mod h1:elfIatfq2fkU5lqTBrTcpL0RcHZOgYPE8EzBD7yQFiY= +github.com/dolthub/jsonpath v0.0.2-0.20240227200619-19675ab05c71 h1:bMGS25NWAGTEtT5tOBsCuCrlYnLRKpbJVJkDbrTRhwQ= +github.com/dolthub/jsonpath v0.0.2-0.20240227200619-19675ab05c71/go.mod h1:2/2zjLQ/JOOSbbSboojeg+cAwcRV0fDLzIiWch/lhqI= github.com/dolthub/maphash v0.1.0 h1:bsQ7JsF4FkkWyrP3oCnFJgrCUAFbFf3kOl4L/QxPDyQ= github.com/dolthub/maphash v0.1.0/go.mod h1:gkg4Ch4CdCDu5h6PMriVLawB7koZ+5ijb9puGMV50a4= +github.com/dolthub/vitess v0.0.0-20241211024425-b00987f7ba54 h1:nzBnC0Rt1gFtscJEz4veYd/mazZEdbdmed+tujdaKOo= +github.com/dolthub/vitess v0.0.0-20241211024425-b00987f7ba54/go.mod h1:1gQZs/byeHLMSul3Lvl3MzioMtOW1je79QYGyi2fd70= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= 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= @@ -1731,8 +1741,6 @@ github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh6 github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs= github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY= github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= -github.com/jeremywohl/flatten v1.0.1 h1:LrsxmB3hfwJuE+ptGOijix1PIfOoKLJ3Uee/mzbgtrs= -github.com/jeremywohl/flatten v1.0.1/go.mod h1:4AmD/VxjWcI5SRB0n6szE2A6s2fsNHDLO0nAlMHgfLQ= github.com/jessevdk/go-flags v1.5.0 h1:1jKYvbxEjfUl0fmqTCOfonvskHHXMjBySTLW4y9LFvc= github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= github.com/jhump/gopoet v0.0.0-20190322174617-17282ff210b3/go.mod h1:me9yfT6IJSlOL3FCfrg+L6yzUEZ+5jW6WHt4Sk+UPUI= @@ -1826,6 +1834,10 @@ github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6Fm github.com/leesper/go_rng v0.0.0-20190531154944-a612b043e353 h1:X/79QL0b4YJVO5+OsPH9rF2u428CIrGL/jLmPsoOQQ4= github.com/leesper/go_rng v0.0.0-20190531154944-a612b043e353/go.mod h1:N0SVk0uhy+E1PZ3C9ctsPRlvOPAFPkCNlcPBDkt0N3U= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= +github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc h1:RKf14vYWi2ttpEmkA4aQ3j4u9dStX2t4M8UM6qqNsG8= +github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc/go.mod h1:kopuH9ugFRkIXf3YoqHKyrJ9YfUFsckUU9S7B+XP+is= +github.com/lestrrat-go/strftime v1.0.4 h1:T1Rb9EPkAhgxKqbcMIPguPq8glqXTA1koF8n9BHElA8= +github.com/lestrrat-go/strftime v1.0.4/go.mod h1:E1nN3pCbtMSu1yjSVeyuRFVm/U0xoR76fd03sz+Qz4g= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= @@ -2308,6 +2320,8 @@ github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8 github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/teris-io/shortid v0.0.0-20171029131806-771a37caa5cf h1:Z2X3Os7oRzpdJ75iPqWZc0HeJWFYNCvKsfpQwFpRNTA= github.com/teris-io/shortid v0.0.0-20171029131806-771a37caa5cf/go.mod h1:M8agBzgqHIhgj7wEn9/0hJUZcrvt9VY+Ln+S1I5Mha0= +github.com/tetratelabs/wazero v1.8.2 h1:yIgLR/b2bN31bjxwXHD8a3d+BogigR952csSDdLYEv4= +github.com/tetratelabs/wazero v1.8.2/go.mod h1:yAI0XTsMBhREkM/YDAK/zNou3GoiAce1P6+rp/wQhjs= github.com/thanos-io/objstore v0.0.0-20240818203309-0363dadfdfb1 h1:z0v9BB/p7s4J6R//+0a5M3wCld8KzNjrGRLIwXfrAZk= github.com/thanos-io/objstore v0.0.0-20240818203309-0363dadfdfb1/go.mod h1:3ukSkG4rIRUGkKM4oIz+BSuUx2e3RlQVVv3Cc3W+Tv4= github.com/tidwall/pretty v0.0.0-20180105212114-65a9db5fad51/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= @@ -2847,6 +2861,7 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -3346,6 +3361,8 @@ gopkg.in/mail.v2 v2.3.1/go.mod h1:htwXN1Qh09vZJ1NVKxQqHPBaCBbzKhp5GzuJEA4VJWw= gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/src-d/go-errors.v1 v1.0.0 h1:cooGdZnCjYbeS1zb1s6pVAAimTdKceRrpn7aKOnNIfc= +gopkg.in/src-d/go-errors.v1 v1.0.0/go.mod h1:q1cBlomlw2FnDBDNGlnh6X0jPihy+QxZfMMNxPCbdYg= gopkg.in/telebot.v3 v3.2.1/go.mod h1:GJKwwWqp9nSkIVN51eRKU78aB5f5OnQuWdwiIZfPbko= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= diff --git a/go.work.sum b/go.work.sum index 0f433090da0..14d84965e7b 100644 --- a/go.work.sum +++ b/go.work.sum @@ -1321,6 +1321,8 @@ github.com/docker/go-plugins-helpers v0.0.0-20240701071450-45e2431495c8 h1:IMfrF github.com/docker/go-plugins-helpers v0.0.0-20240701071450-45e2431495c8/go.mod h1:LFyLie6XcDbyKGeVK6bHe+9aJTYCxWLBg5IrJZOaXKA= github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96 h1:cenwrSVm+Z7QLSV/BsnenAOcDXdX4cMv4wP0B/5QbPg= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815 h1:bWDMxwH3px2JBh6AyO7hdCn/PkvCZXii8TGj7sbtEbQ= +github.com/dolthub/sqllogictest/go v0.0.0-20201107003712-816f3ae12d81 h1:7/v8q9XGFa6q5Ap4Z/OhNkAMBaK5YeuEzwJt+NZdhiE= +github.com/dolthub/sqllogictest/go v0.0.0-20201107003712-816f3ae12d81/go.mod h1:siLfyv2c92W1eN/R4QqG/+RjjX5W2+gCTRjZxBjI3TY= github.com/dolthub/swiss v0.2.1 h1:gs2osYs5SJkAaH5/ggVJqXQxRXtWshF6uE0lgR/Y3Gw= github.com/dolthub/swiss v0.2.1/go.mod h1:8AhKZZ1HK7g18j7v7k6c5cYIGEZJcPn0ARsai8cUrh0= github.com/drone/funcmap v0.0.0-20220929084810-72602997d16f h1:/jEs7lulqVO2u1+XI5rW4oFwIIusxuDOVKD9PAzlW2E= @@ -1444,6 +1446,8 @@ github.com/goccy/go-yaml v1.11.0 h1:n7Z+zx8S9f9KgzG6KtQKf+kwqXZlLNR2F6018Dgau54= github.com/goccy/go-yaml v1.11.0/go.mod h1:H+mJrWtjPTJAHvRbV09MCK9xYwODM+wRTVFFTWckfng= github.com/gocql/gocql v0.0.0-20200526081602-cd04bd7f22a7 h1:TvUE5vjfoa7fFHMlmGOk0CsauNj1w4yJjR9+/GnWVCw= github.com/gocql/gocql v0.0.0-20200526081602-cd04bd7f22a7/go.mod h1:DL0ekTmBSTdlNF25Orwt/JMzqIq3EJ4MVa/J/uK64OY= +github.com/gocraft/dbr/v2 v2.7.2 h1:ccUxMuz6RdZvD7VPhMRRMSS/ECF3gytPhPtcavjktHk= +github.com/gocraft/dbr/v2 v2.7.2/go.mod h1:5bCqyIXO5fYn3jEp/L06QF4K1siFdhxChMjdNu6YJrg= github.com/godbus/dbus/v5 v5.0.4 h1:9349emZab16e7zQvpmsbtjc18ykshndd8y2PG3sgJbA= github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= @@ -1512,7 +1516,6 @@ github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB7 github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grafana/alerting v0.0.0-20250115195200-209e052dba64/go.mod h1:QsnoKX/iYZxA4Cv+H+wC7uxutBD8qi8ZW5UJvD2TYmU= -github.com/grafana/alerting v0.0.0-20250129195454-3e5b80036b7a/go.mod h1:QsnoKX/iYZxA4Cv+H+wC7uxutBD8qi8ZW5UJvD2TYmU= github.com/grafana/authlib v0.0.0-20250120144156-d6737a7dc8f5/go.mod h1:V63rh3udd7sqXJeaG+nGUmViwVnM/bY6t8U9Tols2GU= github.com/grafana/authlib v0.0.0-20250120145936-5f0e28e7a87c/go.mod h1:/gYfphsNu9v1qYWXxpv1NSvMEMSwvdf8qb8YlgwIRl8= github.com/grafana/authlib/types v0.0.0-20250120144156-d6737a7dc8f5/go.mod h1:qYjSd1tmJiuVoSICp7Py9/zD54O9uQQA3wuM6Gg4DFM= @@ -1608,6 +1611,8 @@ github.com/jarcoal/httpmock v1.3.1 h1:iUx3whfZWVf3jT01hQTO/Eo5sAYtB2/rqaUuOtpInw github.com/jarcoal/httpmock v1.3.1/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg= github.com/jedib0t/go-pretty/v6 v6.2.4 h1:wdaj2KHD2W+mz8JgJ/Q6L/T5dB7kyqEFI16eLq7GEmk= github.com/jedib0t/go-pretty/v6 v6.2.4/go.mod h1:+nE9fyyHGil+PuISTCrp7avEdo6bqoMwqZnuiK2r2a0= +github.com/jeremywohl/flatten v1.0.1 h1:LrsxmB3hfwJuE+ptGOijix1PIfOoKLJ3Uee/mzbgtrs= +github.com/jeremywohl/flatten v1.0.1/go.mod h1:4AmD/VxjWcI5SRB0n6szE2A6s2fsNHDLO0nAlMHgfLQ= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.1-0.20181029123624-5de817a9aa20/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jhump/gopoet v0.1.0 h1:gYjOPnzHd2nzB37xYQZxj4EIQNpBrBskRqQQ3q4ZgSg= diff --git a/pkg/expr/convert_to_long.go b/pkg/expr/convert_to_long.go new file mode 100644 index 00000000000..3ab3339b8b0 --- /dev/null +++ b/pkg/expr/convert_to_long.go @@ -0,0 +1,311 @@ +package expr + +import ( + "fmt" + "sort" + "time" + + "github.com/grafana/grafana-plugin-sdk-go/data" +) + +func ConvertToLong(frames data.Frames) (data.Frames, error) { + if len(frames) == 0 { + // general empty case for now + return frames, nil + } + // Four Conversion Possible Cases + // 1. NumericMulti -> NumericLong + // 2. NumericWide -> NumericLong + // 3. TimeSeriesMulti -> TimeSeriesLong + // 4. TimeSeriesWide -> TimeSeriesLong + + // Detect if input type is declared + // First Check Frame Meta Type + + var inputType data.FrameType + if frames[0].Meta != nil && frames[0].Meta.Type != "" { + inputType = frames[0].Meta.Type + } + + // TODO: Add some guessing of Type if not declared + if inputType == "" { + return frames, fmt.Errorf("no input dataframe type set") + } + + if !supportedToLongConversion(inputType) { + return frames, fmt.Errorf("unsupported input dataframe type %s for SQL expression", inputType) + } + + toLong := getToLongConversionFunc(inputType) + if toLong == nil { + return frames, fmt.Errorf("could not get conversion function for input type %s", inputType) + } + + return toLong(frames) +} + +func convertNumericMultiToNumericLong(frames data.Frames) (data.Frames, error) { + // Apart from metadata, NumericMulti is basically NumericWide, except one frame per thing + // so we collapse into wide and call the wide conversion + wide := convertNumericMultiToNumericWide(frames) + return convertNumericWideToNumericLong(wide) +} + +func convertNumericMultiToNumericWide(frames data.Frames) data.Frames { + newFrame := data.NewFrame("") + for _, frame := range frames { + for _, field := range frame.Fields { + if !field.Type().Numeric() { + continue + } + newField := data.NewFieldFromFieldType(field.Type(), field.Len()) + newField.Name = field.Name + newField.Labels = field.Labels.Copy() + if field.Len() == 1 { + newField.Set(0, field.CopyAt(0)) + } + newFrame.Fields = append(newFrame.Fields, newField) + } + } + return data.Frames{newFrame} +} + +func convertNumericWideToNumericLong(frames data.Frames) (data.Frames, error) { + // Wide should only be one frame + if len(frames) != 1 { + return nil, fmt.Errorf("expected exactly one frame for wide format, but got %d", len(frames)) + } + inputFrame := frames[0] + + // The Frame should have no more than one row + if inputFrame.Rows() > 1 { + return nil, fmt.Errorf("expected no more than one row in the frame, but got %d", inputFrame.Rows()) + } + + // Gather: + // - unique numeric Field Names, and + // - unique Label Keys (from Numeric Fields only) + // each one maps to a field in the output long Frame. + uniqueNames := make([]string, 0) + uniqueKeys := make([]string, 0) + + uniqueNamesMap := make(map[string]data.FieldType) + uniqueKeysMap := make(map[string]struct{}) + + prints := make(map[string]int) + + registerPrint := func(labels data.Labels) { + fp := labels.Fingerprint().String() + if _, ok := prints[fp]; !ok { + prints[fp] = len(prints) + } + } + + for _, field := range inputFrame.Fields { + if field.Type().Numeric() { + if _, ok := uniqueNamesMap[field.Name]; !ok { + uniqueNames = append(uniqueNames, field.Name) + uniqueNamesMap[field.Name] = field.Type() + } + + if field.Labels != nil { + registerPrint(field.Labels) + for key := range field.Labels { + if _, ok := uniqueKeysMap[key]; !ok { + uniqueKeys = append(uniqueKeys, key) + } + uniqueKeysMap[key] = struct{}{} + } + } + } + } + + // Create new fields for output Long frame + fields := make([]*data.Field, 0, len(uniqueNames)+len(uniqueKeys)) + + // Create the Numeric Fields, tracking the index of each field by name + // Note: May want to use FloatAt and and prepopulate with NaN so missing + // combinations of value can be NA instead of the zero value of 0. + var nameIndexMap = make(map[string]int, len(uniqueNames)) + for i, name := range uniqueNames { + field := data.NewFieldFromFieldType(uniqueNamesMap[name], len(prints)) + field.Name = name + fields = append(fields, field) + nameIndexMap[name] = i + } + + // Create the String fields, tracking the index of each field by key + var keyIndexMap = make(map[string]int, len(uniqueKeys)) + for i, k := range uniqueKeys { + fields = append(fields, data.NewField(k, nil, make([]string, len(prints)))) + keyIndexMap[k] = len(nameIndexMap) + i + } + + longFrame := data.NewFrame("", fields...) + + if inputFrame.Rows() == 0 { + return data.Frames{longFrame}, nil + } + + // Add Rows to the fields + for _, field := range inputFrame.Fields { + if !field.Type().Numeric() { + continue + } + fieldIdx := prints[field.Labels.Fingerprint().String()] + longFrame.Fields[nameIndexMap[field.Name]].Set(fieldIdx, field.CopyAt(0)) + for key, value := range field.Labels { + longFrame.Fields[keyIndexMap[key]].Set(fieldIdx, value) + } + } + + return data.Frames{longFrame}, nil +} + +func convertTimeSeriesMultiToTimeSeriesLong(frames data.Frames) (data.Frames, error) { + // Collect all time values and ensure no duplicates + timeSet := make(map[time.Time]struct{}) + labelKeys := make(map[string]struct{}) // Collect all unique label keys + numericFields := make(map[string]struct{}) // Collect unique numeric field names + + for _, frame := range frames { + for _, field := range frame.Fields { + if field.Type() == data.FieldTypeTime { + for i := 0; i < field.Len(); i++ { + t := field.At(i).(time.Time) + timeSet[t] = struct{}{} + } + } else if field.Type().Numeric() { + numericFields[field.Name] = struct{}{} + if field.Labels != nil { + for key := range field.Labels { + labelKeys[key] = struct{}{} + } + } + } + } + } + + // Create a sorted slice of unique time values + times := make([]time.Time, 0, len(timeSet)) + for t := range timeSet { + times = append(times, t) + } + sort.Slice(times, func(i, j int) bool { return times[i].Before(times[j]) }) + + // Create output fields: Time, one numeric field per unique numeric name, and label fields + timeField := data.NewField("Time", nil, times) + outputNumericFields := make(map[string]*data.Field) + for name := range numericFields { + outputNumericFields[name] = data.NewField(name, nil, make([]float64, len(times))) + } + outputLabelFields := make(map[string]*data.Field) + for key := range labelKeys { + outputLabelFields[key] = data.NewField(key, nil, make([]string, len(times))) + } + + // Map time to index for quick lookup + timeIndexMap := make(map[time.Time]int, len(times)) + for i, t := range times { + timeIndexMap[t] = i + } + + // Populate output fields + for _, frame := range frames { + var timeField *data.Field + for _, field := range frame.Fields { + if field.Type() == data.FieldTypeTime { + timeField = field + break + } + } + + if timeField == nil { + return nil, fmt.Errorf("no time field found in frame") + } + + for _, field := range frame.Fields { + if field.Type().Numeric() { + for i := 0; i < field.Len(); i++ { + t := timeField.At(i).(time.Time) + val, err := field.FloatAt(i) + if err != nil { + val = 0 // Default value for missing data + } + idx := timeIndexMap[t] + if outputField, exists := outputNumericFields[field.Name]; exists { + outputField.Set(idx, val) + } + + // Add labels for the numeric field + for key, value := range field.Labels { + if outputField, exists := outputLabelFields[key]; exists { + outputField.Set(idx, value) + } + } + } + } + } + } + + // Build the output frame + outputFields := []*data.Field{timeField} + for _, field := range outputNumericFields { + outputFields = append(outputFields, field) + } + for _, field := range outputLabelFields { + outputFields = append(outputFields, field) + } + outputFrame := data.NewFrame("time_series_long", outputFields...) + + // Set metadata + if outputFrame.Meta == nil { + outputFrame.Meta = &data.FrameMeta{} + } + outputFrame.Meta.Type = data.FrameTypeTimeSeriesLong + + return data.Frames{outputFrame}, nil +} + +func convertTimeSeriesWideToTimeSeriesLong(frames data.Frames) (data.Frames, error) { + // Wide should only be one frame + if len(frames) != 1 { + return nil, fmt.Errorf("expected exactly one frame for wide format, but got %d", len(frames)) + } + inputFrame := frames[0] + longFrame, err := data.WideToLong(inputFrame) + if err != nil { + return nil, fmt.Errorf("failed to convert wide time series to long timeseries for sql expression: %w", err) + } + return data.Frames{longFrame}, nil +} + +func getToLongConversionFunc(inputType data.FrameType) func(data.Frames) (data.Frames, error) { + switch inputType { + case data.FrameTypeNumericMulti: + return convertNumericMultiToNumericLong + case data.FrameTypeNumericWide: + return convertNumericWideToNumericLong + case data.FrameTypeTimeSeriesMulti: + return convertTimeSeriesMultiToTimeSeriesLong + case data.FrameTypeTimeSeriesWide: + return convertTimeSeriesWideToTimeSeriesLong + default: + return convertErr + } +} + +func convertErr(_ data.Frames) (data.Frames, error) { + return nil, fmt.Errorf("unsupported input type for SQL expression") +} + +func supportedToLongConversion(inputType data.FrameType) bool { + switch inputType { + case data.FrameTypeNumericMulti, data.FrameTypeNumericWide: + return true + case data.FrameTypeTimeSeriesMulti, data.FrameTypeTimeSeriesWide: + return true + default: + return false + } +} diff --git a/pkg/expr/convert_to_long_test.go b/pkg/expr/convert_to_long_test.go new file mode 100644 index 00000000000..291fdb62f17 --- /dev/null +++ b/pkg/expr/convert_to_long_test.go @@ -0,0 +1,48 @@ +package expr + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/grafana/grafana-plugin-sdk-go/data" + "github.com/stretchr/testify/require" +) + +func TestConvertNumericMultiToLong(t *testing.T) { + input := data.Frames{ + data.NewFrame("test", + data.NewField("Value", data.Labels{"city": "MIA"}, []int64{5})), + data.NewFrame("test", + data.NewField("Value", data.Labels{"city": "LGA"}, []int64{7}), + ), + } + expectedFrame := data.NewFrame("", + data.NewField("Value", nil, []int64{5, 7}), + data.NewField("city", nil, []string{"MIA", "LGA"}), + ) + output, err := convertNumericMultiToNumericLong(input) + require.NoError(t, err) + + if diff := cmp.Diff(expectedFrame, output[0], data.FrameTestCompareOptions()...); diff != "" { + require.FailNowf(t, "Result mismatch (-want +got):%s\n", diff) + } +} + +func TestConvertNumericWideToLong(t *testing.T) { + input := data.Frames{ + data.NewFrame("test", + data.NewField("Value", data.Labels{"city": "MIA"}, []int64{5}), + data.NewField("Value", data.Labels{"city": "LGA"}, []int64{7}), + ), + } + expectedFrame := data.NewFrame("", + data.NewField("Value", nil, []int64{5, 7}), + data.NewField("city", nil, []string{"MIA", "LGA"}), + ) + output, err := convertNumericWideToNumericLong(input) + require.NoError(t, err) + + if diff := cmp.Diff(expectedFrame, output[0], data.FrameTestCompareOptions()...); diff != "" { + require.FailNowf(t, "Result mismatch (-want +got):%s\n", diff) + } +} diff --git a/pkg/expr/converter.go b/pkg/expr/converter.go index 1eae19a972d..0e8cc461d47 100644 --- a/pkg/expr/converter.go +++ b/pkg/expr/converter.go @@ -23,7 +23,6 @@ type ResultConverter struct { func (c *ResultConverter) Convert(ctx context.Context, datasourceType string, frames data.Frames, - allowLongFrames bool, ) (string, mathexp.Results, error) { if len(frames) == 0 { return "no-data", mathexp.Results{Values: mathexp.Values{mathexp.NewNoData()}}, nil @@ -80,7 +79,7 @@ func (c *ResultConverter) Convert(ctx context.Context, continue } - if schema.Type != data.TimeSeriesTypeWide && !allowLongFrames { + if schema.Type != data.TimeSeriesTypeWide { return "", mathexp.Results{}, fmt.Errorf("%w but got type %s (input refid)", ErrSeriesMustBeWide, schema.Type) } filtered = append(filtered, frame) diff --git a/pkg/expr/converter_test.go b/pkg/expr/converter_test.go index c2a8a445247..1509294e3de 100644 --- a/pkg/expr/converter_test.go +++ b/pkg/expr/converter_test.go @@ -40,7 +40,7 @@ func TestConvertDataFramesToResults(t *testing.T) { for _, dtype := range supported { t.Run(dtype, func(t *testing.T) { - resultType, res, err := converter.Convert(context.Background(), dtype, frames, s.allowLongFrames) + resultType, res, err := converter.Convert(context.Background(), dtype, frames) require.NoError(t, err) assert.Equal(t, "single frame series", resultType) require.Len(t, res.Values, 2) @@ -68,7 +68,7 @@ func TestConvertDataFramesToResults(t *testing.T) { for _, dtype := range supported { t.Run(dtype, func(t *testing.T) { - resultType, res, err := converter.Convert(context.Background(), dtype, frames, s.allowLongFrames) + resultType, res, err := converter.Convert(context.Background(), dtype, frames) require.NoError(t, err) assert.Equal(t, "multi frame series", resultType) require.Len(t, res.Values, 2) @@ -101,7 +101,7 @@ func TestConvertDataFramesToResults(t *testing.T) { for _, dtype := range supported { t.Run(dtype, func(t *testing.T) { - resultType, res, err := converter.Convert(context.Background(), dtype, frames, s.allowLongFrames) + resultType, res, err := converter.Convert(context.Background(), dtype, frames) require.NoError(t, err) assert.Equal(t, "multi frame series", resultType) require.Len(t, res.Values, 2) diff --git a/pkg/expr/graph.go b/pkg/expr/graph.go index 369ff116a94..6632a6b74c1 100644 --- a/pkg/expr/graph.go +++ b/pkg/expr/graph.go @@ -77,8 +77,6 @@ func (dp *DataPipeline) execute(c context.Context, now time.Time, s *Service) (m executeDSNodesGrouped(c, now, vars, s, dsNodes) } - s.allowLongFrames = hasSqlExpression(*dp) - for _, node := range *dp { if groupByDSFlag && node.NodeType() == TypeDatasourceNode { continue // already executed via executeDSNodesGrouped @@ -321,12 +319,26 @@ func buildGraphEdges(dp *simple.DirectedGraph, registry map[string]Node) error { neededNode, ok := registry[neededVar] if !ok { _, ok := cmdNode.Command.(*SQLCommand) + // If the SSE is a SQL expression, and the node can't be found, it might be a CTE table name + // CTEs are calculated during the evaluation of the SQL, so we won't have a node for them + // So we `continue` in order to support CTE functionality + // TODO: remove CTE table names from the list of table names during parsing of the SQL if ok { continue } return fmt.Errorf("unable to find dependent node '%v'", neededVar) } + // If the input is SQL, conversion is handled differently + if _, ok := cmdNode.Command.(*SQLCommand); ok { + if dsNode, ok := neededNode.(*DSNode); ok { + dsNode.isInputToSQLExpr = true + } else { + // Only allow data source nodes as SQL expression inputs for now + return fmt.Errorf("only data source queries may be inputs to a sql expression, %v is the input for %v", neededVar, cmdNode.RefID()) + } + } + if neededNode.ID() == cmdNode.ID() { return fmt.Errorf("expression '%v' cannot reference itself. Must be query or another expression", neededVar) } @@ -343,6 +355,13 @@ func buildGraphEdges(dp *simple.DirectedGraph, registry map[string]Node) error { } } + if neededNode.NodeType() == TypeCMDNode { + if neededNode.(*CMDNode).CMDType == TypeSQL { + // Do not allow SQL expressions to be inputs for other expressions for now + return fmt.Errorf("sql expressions can not be the input for other expressions, but %v in the input for %v", neededVar, cmdNode.RefID()) + } + } + edge := dp.NewEdge(neededNode, cmdNode) dp.SetEdge(edge) @@ -370,37 +389,3 @@ func GetCommandsFromPipeline[T Command](pipeline DataPipeline) []T { } return results } - -func hasSqlExpression(dp DataPipeline) bool { - for _, node := range dp { - if node.NodeType() == TypeCMDNode { - cmdNode := node.(*CMDNode) - _, ok := cmdNode.Command.(*SQLCommand) - if ok { - return true - } - } - } - return false -} - -// func graphHasSqlExpresssion(dp *simple.DirectedGraph) bool { -// node := dp.Nodes() -// for node.Next() { -// if cmdNode, ok := node.Node().(*CMDNode); ok { -// // res[dpNode.RefID()] = dpNode -// _, ok := cmdNode.Command.(*SQLCommand) -// if ok { -// return true -// } -// } -// // if node.NodeType() == TypeCMDNode { -// // cmdNode := node.(*CMDNode) -// // _, ok := cmdNode.Command.(*SQLCommand) -// // if ok { -// // return true -// // } -// // } -// } -// return false -// } diff --git a/pkg/expr/mathexp/types.go b/pkg/expr/mathexp/types.go index 8317f97c5bd..fd79061af82 100644 --- a/pkg/expr/mathexp/types.go +++ b/pkg/expr/mathexp/types.go @@ -250,7 +250,7 @@ func NewNoData() NoData { return NoData{data.NewFrame("no data")} } -// TableData is an untyped no data response. +// TableData is a single table data frame with no labels on any fields. type TableData struct{ Frame *data.Frame } // Type returns the Value type and allows it to fulfill the Value interface. diff --git a/pkg/expr/ml.go b/pkg/expr/ml.go index e7bde5487f2..4c0dba76b89 100644 --- a/pkg/expr/ml.go +++ b/pkg/expr/ml.go @@ -130,7 +130,7 @@ func (m *MLNode) Execute(ctx context.Context, now time.Time, _ mathexp.Vars, s * } // process the response the same way DSNode does. Use plugin ID as data source type. Semantically, they are the same. - responseType, result, err = s.converter.Convert(ctx, mlPluginID, dataFrames, s.allowLongFrames) + responseType, result, err = s.converter.Convert(ctx, mlPluginID, dataFrames) return result, err } diff --git a/pkg/expr/nodes.go b/pkg/expr/nodes.go index 80de28e9883..1159ef13d04 100644 --- a/pkg/expr/nodes.go +++ b/pkg/expr/nodes.go @@ -112,6 +112,12 @@ func buildCMDNode(rn *rawNode, toggles featuremgmt.FeatureToggles) (*CMDNode, er return nil, fmt.Errorf("invalid command type in expression '%v': %w", rn.RefID, err) } + if commandType == TypeSQL { + if !toggles.IsEnabledGlobally(featuremgmt.FlagSqlExpressions) { + return nil, fmt.Errorf("sql expressions are disabled") + } + } + node := &CMDNode{ baseNode: baseNode{ id: rn.idx, @@ -185,6 +191,8 @@ type DSNode struct { intervalMS int64 maxDP int64 request Request + + isInputToSQLExpr bool } func (dn *DSNode) String() string { @@ -333,7 +341,7 @@ func executeDSNodesGrouped(ctx context.Context, now time.Time, vars mathexp.Vars } var result mathexp.Results - responseType, result, err := s.converter.Convert(ctx, dn.datasource.Type, dataFrames, s.allowLongFrames) + responseType, result, err := s.converter.Convert(ctx, dn.datasource.Type, dataFrames) if err != nil { result.Error = makeConversionError(dn.RefID(), err) } @@ -401,7 +409,44 @@ func (dn *DSNode) Execute(ctx context.Context, now time.Time, _ mathexp.Vars, s } var result mathexp.Results - responseType, result, err = s.converter.Convert(ctx, dn.datasource.Type, dataFrames, s.allowLongFrames) + // If the datasource node is an input to a SQL expression, + // the data must be in the Long format + if dn.isInputToSQLExpr { + var needsConversion bool + // Convert it if Multi: + if len(dataFrames) > 1 { + needsConversion = true + } + + // Convert it if Wide (has labels): + if len(dataFrames) == 1 { + for _, field := range dataFrames[0].Fields { + if len(field.Labels) > 0 { + needsConversion = true + break + } + } + } + + if needsConversion { + convertedFrames, err := ConvertToLong(dataFrames) + if err != nil { + return result, fmt.Errorf("failed to convert data frames to long format for sql: %w", err) + } + result.Values = mathexp.Values{ + mathexp.TableData{Frame: convertedFrames[0]}, + } + return result, nil + } + + // Otherwise it is already Long format; return as is + result.Values = mathexp.Values{ + mathexp.TableData{Frame: dataFrames[0]}, + } + return result, nil + } + + responseType, result, err = s.converter.Convert(ctx, dn.datasource.Type, dataFrames) if err != nil { err = makeConversionError(dn.refID, err) } diff --git a/pkg/expr/service.go b/pkg/expr/service.go index 1f78491d9e3..1b04ce41c3f 100644 --- a/pkg/expr/service.go +++ b/pkg/expr/service.go @@ -64,9 +64,8 @@ type Service struct { pluginsClient backend.CallResourceHandler - tracer tracing.Tracer - metrics *metrics - allowLongFrames bool + tracer tracing.Tracer + metrics *metrics } type pluginContextProvider interface { diff --git a/pkg/expr/service_sql_test.go b/pkg/expr/service_sql_test.go new file mode 100644 index 00000000000..5730ca82fd0 --- /dev/null +++ b/pkg/expr/service_sql_test.go @@ -0,0 +1,104 @@ +package expr + +import ( + "context" + "encoding/json" + "fmt" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/grafana/grafana-plugin-sdk-go/data" + "github.com/grafana/grafana/pkg/services/datasources" + "github.com/grafana/grafana/pkg/services/featuremgmt" + "github.com/stretchr/testify/require" +) + +func TestSQLService(t *testing.T) { + inputFrame := data.NewFrame("", + data.NewField("time", nil, []time.Time{time.Unix(1, 0)}), + data.NewField("value", nil, []*float64{fp(2)}), + ) + + resp := map[string]backend.DataResponse{ + "A": {Frames: data.Frames{inputFrame}}, + } + + newABSQLQueries := func(q string) []Query { + q, err := jsonEscape(q) + require.NoError(t, err) + return []Query{ + { + RefID: "A", + DataSource: &datasources.DataSource{ + OrgID: 1, + UID: "test", + Type: "test", + }, + JSON: json.RawMessage(`{ "datasource": { "uid": "1" }, "intervalMs": 1000, "maxDataPoints": 1000 }`), + TimeRange: AbsoluteTimeRange{ + From: time.Time{}, + To: time.Time{}, + }, + }, + { + RefID: "B", + DataSource: dataSourceModel(), + JSON: json.RawMessage(fmt.Sprintf(`{ "datasource": { "uid": "__expr__", "type": "__expr__"}, "type": "sql", "expression": "%s" }`, q)), + TimeRange: AbsoluteTimeRange{ + From: time.Time{}, + To: time.Time{}, + }, + }, + } + } + t.Run("no feature flag no queries for you", func(t *testing.T) { + s, req := newMockQueryService(resp, newABSQLQueries("")) + + _, err := s.BuildPipeline(req) + require.Error(t, err, "should not be able to build pipeline without feature flag") + }) + + t.Run("with feature flag basic select works", func(t *testing.T) { + s, req := newMockQueryService(resp, newABSQLQueries("SELECT * FROM A")) + s.features = featuremgmt.WithFeatures(featuremgmt.FlagSqlExpressions) + pl, err := s.BuildPipeline(req) + require.NoError(t, err) + + res, err := s.ExecutePipeline(context.Background(), time.Now(), pl) + require.NoError(t, err) + + inputFrame.RefID = "B" + inputFrame.Name = "B" + if diff := cmp.Diff(res.Responses["B"].Frames[0], inputFrame, data.FrameTestCompareOptions()...); diff != "" { + require.FailNowf(t, "Result mismatch (-want +got):%s\n", diff) + } + }) + + t.Run("load_file is blocked", func(t *testing.T) { + s, req := newMockQueryService(resp, + newABSQLQueries(`SELECT CAST(load_file('/etc/topSecretz') AS CHAR(10000) CHARACTER SET utf8)`), + ) + + s.features = featuremgmt.WithFeatures(featuremgmt.FlagSqlExpressions) + + pl, err := s.BuildPipeline(req) + require.NoError(t, err) + + rsp, err := s.ExecutePipeline(context.Background(), time.Now(), pl) + require.NoError(t, err) + + require.Error(t, rsp.Responses["B"].Error, "should return invalid sql error") + require.ErrorContains(t, rsp.Responses["B"].Error, "blocked function load_file") + }) +} + +func jsonEscape(input string) (string, error) { + escaped, err := json.Marshal(input) + if err != nil { + return "", err + } + // json.Marshal returns the escaped string with quotes, so we need to trim them + return string(escaped[1 : len(escaped)-1]), nil +} diff --git a/pkg/expr/service_test.go b/pkg/expr/service_test.go index 35378b289e8..2fe6f6e1e9c 100644 --- a/pkg/expr/service_test.go +++ b/pkg/expr/service_test.go @@ -29,32 +29,11 @@ import ( func TestService(t *testing.T) { dsDF := data.NewFrame("test", data.NewField("time", nil, []time.Time{time.Unix(1, 0)}), - data.NewField("value", data.Labels{"test": "label"}, []*float64{fp(2)})) + data.NewField("value", data.Labels{"test": "label"}, []*float64{fp(2)}), + ) - me := &mockEndpoint{ - Responses: map[string]backend.DataResponse{ - "A": {Frames: data.Frames{dsDF}}, - }, - } - - pCtxProvider := plugincontext.ProvideService(setting.NewCfg(), nil, &pluginstore.FakePluginStore{ - PluginList: []pluginstore.Plugin{ - {JSONData: plugins.JSONData{ID: "test"}}, - }, - }, &datafakes.FakeCacheService{}, &datafakes.FakeDataSourceService{}, nil, pluginconfig.NewFakePluginRequestConfigProvider()) - - features := featuremgmt.WithFeatures() - s := Service{ - cfg: setting.NewCfg(), - dataService: me, - pCtxProvider: pCtxProvider, - features: features, - tracer: tracing.InitializeTracerForTest(), - metrics: newMetrics(nil), - converter: &ResultConverter{ - Features: features, - Tracer: tracing.InitializeTracerForTest(), - }, + resp := map[string]backend.DataResponse{ + "A": {Frames: data.Frames{dsDF}}, } queries := []Query{ @@ -78,7 +57,7 @@ func TestService(t *testing.T) { }, } - req := &Request{Queries: queries, User: &user.SignedInUser{}} + s, req := newMockQueryService(resp, queries) pl, err := s.BuildPipeline(req) require.NoError(t, err) @@ -121,26 +100,9 @@ func TestService(t *testing.T) { } func TestDSQueryError(t *testing.T) { - me := &mockEndpoint{ - Responses: map[string]backend.DataResponse{ - "A": {Error: fmt.Errorf("womp womp")}, - "B": {Frames: data.Frames{}}, - }, - } - - pCtxProvider := plugincontext.ProvideService(setting.NewCfg(), nil, &pluginstore.FakePluginStore{ - PluginList: []pluginstore.Plugin{ - {JSONData: plugins.JSONData{ID: "test"}}, - }, - }, &datafakes.FakeCacheService{}, &datafakes.FakeDataSourceService{}, nil, pluginconfig.NewFakePluginRequestConfigProvider()) - - s := Service{ - cfg: setting.NewCfg(), - dataService: me, - pCtxProvider: pCtxProvider, - features: featuremgmt.WithFeatures(), - tracer: tracing.InitializeTracerForTest(), - metrics: newMetrics(nil), + resp := map[string]backend.DataResponse{ + "A": {Error: fmt.Errorf("womp womp")}, + "B": {Frames: data.Frames{}}, } queries := []Query{ @@ -169,19 +131,19 @@ func TestDSQueryError(t *testing.T) { }, } - req := &Request{Queries: queries, User: &user.SignedInUser{}} + s, req := newMockQueryService(resp, queries) pl, err := s.BuildPipeline(req) require.NoError(t, err) - resp, err := s.ExecutePipeline(context.Background(), time.Now(), pl) + res, err := s.ExecutePipeline(context.Background(), time.Now(), pl) require.NoError(t, err) var utilErr errutil.Error - require.ErrorContains(t, resp.Responses["A"].Error, "womp womp") - require.ErrorAs(t, resp.Responses["B"].Error, &utilErr) + require.ErrorContains(t, res.Responses["A"].Error, "womp womp") + require.ErrorAs(t, res.Responses["B"].Error, &utilErr) require.ErrorIs(t, utilErr, DependencyError) - require.Equal(t, fp(42), resp.Responses["C"].Frames[0].Fields[0].At(0)) + require.Equal(t, fp(42), res.Responses["C"].Frames[0].Fields[0].At(0)) } func fp(f float64) *float64 { @@ -204,3 +166,28 @@ func dataSourceModel() *datasources.DataSource { d, _ := DataSourceModelFromNodeType(TypeCMDNode) return d } + +func newMockQueryService(responses map[string]backend.DataResponse, queries []Query) (*Service, *Request) { + me := &mockEndpoint{ + Responses: responses, + } + pCtxProvider := plugincontext.ProvideService(setting.NewCfg(), nil, &pluginstore.FakePluginStore{ + PluginList: []pluginstore.Plugin{ + {JSONData: plugins.JSONData{ID: "test"}}, + }, + }, &datafakes.FakeCacheService{}, &datafakes.FakeDataSourceService{}, nil, pluginconfig.NewFakePluginRequestConfigProvider()) + + features := featuremgmt.WithFeatures() + return &Service{ + cfg: setting.NewCfg(), + dataService: me, + pCtxProvider: pCtxProvider, + features: featuremgmt.WithFeatures(), + tracer: tracing.InitializeTracerForTest(), + metrics: newMetrics(nil), + converter: &ResultConverter{ + Features: features, + Tracer: tracing.InitializeTracerForTest(), + }, + }, &Request{Queries: queries, User: &user.SignedInUser{}} +} diff --git a/pkg/expr/sql/db.go b/pkg/expr/sql/db.go index 90e23342c4b..f1af425ad6d 100644 --- a/pkg/expr/sql/db.go +++ b/pkg/expr/sql/db.go @@ -1,22 +1,61 @@ +//go:build !arm + package sql import ( - "errors" + "context" + sqle "github.com/dolthub/go-mysql-server" + mysql "github.com/dolthub/go-mysql-server/sql" + + "github.com/dolthub/go-mysql-server/sql/analyzer" "github.com/grafana/grafana-plugin-sdk-go/data" ) -type DB struct { -} +// DB is a database that can execute SQL queries against a set of Frames. +type DB struct{} -func (db *DB) RunCommands(commands []string) (string, error) { - return "", errors.New("not implemented") -} +// QueryFrames runs the sql query query against a database created from frames, and returns the frame. +// The RefID of each frame becomes a table in the database. +// It is expected that there is only one frame per RefID. +// The name becomes the name and RefID of the returned frame. +func (db *DB) QueryFrames(ctx context.Context, name string, query string, frames []*data.Frame) (*data.Frame, error) { + // We are parsing twice due to TablesList, but don't care fow now. We can save the parsed query and reuse it later if we want. + if allow, err := AllowQuery(query); err != nil || !allow { + if err != nil { + return nil, err + } + return nil, err + } -func (db *DB) QueryFramesInto(name string, query string, frames []*data.Frame, f *data.Frame) error { - return errors.New("not implemented") -} + pro := NewFramesDBProvider(frames) + session := mysql.NewBaseSession() + mCtx := mysql.NewContext(ctx, mysql.WithSession(session)) -func NewInMemoryDB() *DB { - return &DB{} + // Select the database in the context + mCtx.SetCurrentDatabase(dbName) + + // Empty dir does not disable secure_file_priv + //ctx.SetSessionVariable(ctx, "secure_file_priv", "") + + // TODO: Check if it's wise to reuse the existing provider, rather than creating a new one + a := analyzer.NewDefault(pro) + + engine := sqle.New(a, &sqle.Config{ + IsReadOnly: true, + }) + + schema, iter, _, err := engine.Query(mCtx, query) + if err != nil { + return nil, err + } + + f, err := convertToDataFrame(mCtx, iter, schema) + if err != nil { + return nil, err + } + f.Name = name + f.RefID = name + + return f, nil } diff --git a/pkg/expr/sql/db_test.go b/pkg/expr/sql/db_test.go new file mode 100644 index 00000000000..258507121c2 --- /dev/null +++ b/pkg/expr/sql/db_test.go @@ -0,0 +1,187 @@ +//go:build !arm + +package sql + +import ( + "context" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/grafana/grafana-plugin-sdk-go/data" + "github.com/stretchr/testify/require" +) + +func TestQueryFrames(t *testing.T) { + db := DB{} + + tests := []struct { + name string + query string + input_frames []*data.Frame + expected *data.Frame + }{ + { + name: "valid query with no input frames, one row one column", + query: `SELECT '1' AS 'n';`, + input_frames: []*data.Frame{}, + expected: data.NewFrame( + "sqlExpressionRefId", + data.NewField("n", nil, []string{"1"}), + ), + }, + { + name: "valid query with no input frames, one row two columns", + query: `SELECT 'sam' AS 'name', 40 AS 'age';`, + input_frames: []*data.Frame{}, + expected: data.NewFrame( + "sqlExpressionRefId", + data.NewField("name", nil, []string{"sam"}), + data.NewField("age", nil, []int8{40}), + ), + }, + { + // TODO: Also ORDER BY to ensure the order is preserved + name: "query all rows from single input frame", + query: `SELECT * FROM inputFrameRefId LIMIT 1;`, + input_frames: []*data.Frame{ + setRefID(data.NewFrame( + "", + //nolint:misspell + data.NewField("OSS Projects with Typos", nil, []string{"Garfana", "Pormetheus"}), + ), "inputFrameRefId"), + }, + expected: data.NewFrame( + "sqlExpressionRefId", + data.NewField("OSS Projects with Typos", nil, []string{"Garfana"}), + ), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + frame, err := db.QueryFrames(context.Background(), "sqlExpressionRefId", tt.query, tt.input_frames) + require.NoError(t, err) + require.NotNil(t, frame.Fields) + + require.Equal(t, tt.expected.Name, frame.RefID) + require.Equal(t, len(tt.expected.Fields), len(frame.Fields)) + for i := range tt.expected.Fields { + require.Equal(t, tt.expected.Fields[i].Name, frame.Fields[i].Name) + require.Equal(t, tt.expected.Fields[i].At(0), frame.Fields[i].At(0)) + } + }) + } +} + +func TestQueryFramesInOut(t *testing.T) { + frameA := &data.Frame{ + RefID: "a", + Name: "a", + Fields: []*data.Field{ + data.NewField("time", nil, []time.Time{time.Now(), time.Now()}), + data.NewField("time_nullable", nil, []*time.Time{p(time.Now()), nil}), + + data.NewField("string", nil, []string{"cat", "dog"}), + data.NewField("null_nullable", nil, []*string{p("cat"), nil}), + + data.NewField("float64", nil, []float64{1, 3}), + data.NewField("float64_nullable", nil, []*float64{p(2.0), nil}), + + data.NewField("int64", nil, []int64{1, 3}), + data.NewField("int64_nullable", nil, []*int64{p(int64(2)), nil}), + + data.NewField("bool", nil, []bool{true, false}), + data.NewField("bool_nullable", nil, []*bool{p(true), nil}), + }, + } + + db := DB{} + qry := `SELECT * from a` + + resultFrame, err := db.QueryFrames(context.Background(), "a", qry, []*data.Frame{frameA}) + require.NoError(t, err) + + if diff := cmp.Diff(frameA, resultFrame, data.FrameTestCompareOptions()...); diff != "" { + require.FailNowf(t, "Result mismatch (-want +got):%s\n", diff) + } +} + +func TestQueryFramesNumericSelect(t *testing.T) { + expectedFrame := &data.Frame{ + RefID: "a", + Name: "a", + Fields: []*data.Field{ + data.NewField("decimal", nil, []float64{2.35}), + data.NewField("tinySigned", nil, []int8{-128}), + data.NewField("smallSigned", nil, []int16{-32768}), + data.NewField("mediumSigned", nil, []int32{-8388608}), + data.NewField("intSigned", nil, []int32{-2147483648}), + data.NewField("bigSigned", nil, []int64{-9223372036854775808}), + data.NewField("tinyUnsigned", nil, []uint8{255}), + data.NewField("smallUnsigned", nil, []uint16{65535}), + data.NewField("mediumUnsigned", nil, []int32{16777215}), + data.NewField("intUnsigned", nil, []uint32{4294967295}), + data.NewField("bigUnsigned", nil, []uint64{18446744073709551615}), + }, + } + + db := DB{} + qry := `SELECT 2.35 AS 'decimal', + -128 AS 'tinySigned', + -32768 AS 'smallSigned', + -8388608 AS 'mediumSigned', + -2147483648 AS 'intSigned', + -9223372036854775808 AS 'bigSigned', + 255 AS 'tinyUnsigned', + 65535 AS 'smallUnsigned', + 16777215 AS 'mediumUnsigned', + 4294967295 AS 'intUnsigned', + 18446744073709551615 AS 'bigUnsigned'` + + resultFrame, err := db.QueryFrames(context.Background(), "a", qry, []*data.Frame{}) + require.NoError(t, err) + + if diff := cmp.Diff(expectedFrame, resultFrame, data.FrameTestCompareOptions()...); diff != "" { + require.FailNowf(t, "Result mismatch (-want +got):%s\n", diff) + } +} + +func TestQueryFramesDateTimeSelect(t *testing.T) { + t.Skip("need a fix in go-mysql-server, and then handle the datetime strings (or figure out why strings and not time.Time)") + expectedFrame := &data.Frame{ + RefID: "a", + Name: "a", + Fields: []*data.Field{ + data.NewField("ts", nil, []time.Time{}), + }, + } + + db := DB{} + + // It doesn't like the T in the time string + qry := `SELECT str_to_date('2025-02-03T03:00:00','%Y-%m-%dT%H:%i:%s') as ts` + + // This comes back as a string, which needs to be dealt with? + //qry := `SELECT str_to_date('2025-02-03-03:00:00','%Y-%m-%d-%H:%i:%s') as ts` + + // This is a datetime(6), need to deal with that as well + //qry := `SELECT current_timestamp() as ts` + + f, err := db.QueryFrames(context.Background(), "b", qry, []*data.Frame{}) + require.NoError(t, err) + + if diff := cmp.Diff(expectedFrame, f, data.FrameTestCompareOptions()...); diff != "" { + require.FailNowf(t, "Result mismatch (-want +got):%s\n", diff) + } +} + +// p is a utility for pointers from constants +func p[T any](v T) *T { + return &v +} + +func setRefID(f *data.Frame, refID string) *data.Frame { + f.RefID = refID + return f +} diff --git a/pkg/expr/sql/dummy_arm.go b/pkg/expr/sql/dummy_arm.go new file mode 100644 index 00000000000..95d20159e08 --- /dev/null +++ b/pkg/expr/sql/dummy_arm.go @@ -0,0 +1,18 @@ +//go:build arm + +package sql + +import ( + "context" + "fmt" + + "github.com/grafana/grafana-plugin-sdk-go/data" +) + +type DB struct{} + +// Stub out the QueryFrames method for ARM builds +// See github.com/dolthub/go-mysql-server/issues/2837 +func (db *DB) QueryFrames(_ context.Context, _, _ string, _ []*data.Frame) (*data.Frame, error) { + return nil, fmt.Errorf("sql expressions not supported in arm") +} diff --git a/pkg/expr/sql/frame_db.go b/pkg/expr/sql/frame_db.go new file mode 100644 index 00000000000..26317594746 --- /dev/null +++ b/pkg/expr/sql/frame_db.go @@ -0,0 +1,65 @@ +//go:build !arm + +package sql + +import ( + mysql "github.com/dolthub/go-mysql-server/sql" + "github.com/grafana/grafana-plugin-sdk-go/data" +) + +var dbName = "frames" + +// FramesDBProvider is a go-mysql-server DatabaseProvider that provides access to a set of Frames. +type FramesDBProvider struct { + db mysql.Database +} + +func (p *FramesDBProvider) Database(_ *mysql.Context, _ string) (mysql.Database, error) { + return p.db, nil +} + +func (p *FramesDBProvider) HasDatabase(_ *mysql.Context, _ string) bool { + return true +} + +func (p *FramesDBProvider) AllDatabases(_ *mysql.Context) []mysql.Database { + return []mysql.Database{p.db} +} + +// NewFramesDBProvider creates a new FramesDBProvider with the given set of Frames. +func NewFramesDBProvider(frames data.Frames) mysql.DatabaseProvider { + fMap := make(map[string]mysql.Table, len(frames)) + for _, frame := range frames { + fMap[frame.RefID] = &FrameTable{Frame: frame} + } + return &FramesDBProvider{ + db: &framesDB{ + frames: fMap, + }, + } +} + +// framesDB is a go-mysql-server Database that provides access to a set of Frames. +type framesDB struct { + frames map[string]mysql.Table +} + +func (db *framesDB) GetTableInsensitive(_ *mysql.Context, tblName string) (mysql.Table, bool, error) { + tbl, ok := mysql.GetTableInsensitive(tblName, db.frames) + if !ok { + return nil, false, nil + } + return tbl, ok, nil +} + +func (db *framesDB) GetTableNames(_ *mysql.Context) ([]string, error) { + s := make([]string, 0, len(db.frames)) + for k := range db.frames { + s = append(s, k) + } + return s, nil +} + +func (db *framesDB) Name() string { + return dbName +} diff --git a/pkg/expr/sql/frame_db_conv.go b/pkg/expr/sql/frame_db_conv.go new file mode 100644 index 00000000000..ce0edfc2341 --- /dev/null +++ b/pkg/expr/sql/frame_db_conv.go @@ -0,0 +1,474 @@ +//go:build !arm + +package sql + +import ( + "errors" + "fmt" + "io" + "time" + + mysql "github.com/dolthub/go-mysql-server/sql" + "github.com/dolthub/go-mysql-server/sql/types" + "github.com/grafana/grafana-plugin-sdk-go/data" + "github.com/shopspring/decimal" +) + +// TODO: Should this accept a row limit and converters, like sqlutil.FrameFromRows? +func convertToDataFrame(ctx *mysql.Context, iter mysql.RowIter, schema mysql.Schema) (*data.Frame, error) { + f := &data.Frame{} + // Create fields based on the schema + for _, col := range schema { + fT, err := MySQLColToFieldType(col) + if err != nil { + return nil, err + } + + field := data.NewFieldFromFieldType(fT, 0) + field.Name = col.Name + f.Fields = append(f.Fields, field) + } + + // Iterate through the rows and append data to fields + for { + row, err := iter.Next(ctx) + if errors.Is(err, io.EOF) { + break + } + if err != nil { + return nil, fmt.Errorf("error reading row: %v", err) + } + + for i, val := range row { + v, err := fieldValFromRowVal(f.Fields[i].Type(), val) + if err != nil { + return nil, fmt.Errorf("unexpected type for column %s: %w", schema[i].Name, err) + } + f.Fields[i].Append(v) + } + } + + return f, nil +} + +// MySQLColToFieldType converts a MySQL column to a data.FieldType +func MySQLColToFieldType(col *mysql.Column) (data.FieldType, error) { + var fT data.FieldType + + switch col.Type { + case types.Int8: + fT = data.FieldTypeInt8 + case types.Uint8: + fT = data.FieldTypeUint8 + case types.Int16: + fT = data.FieldTypeInt16 + case types.Uint16: + fT = data.FieldTypeUint16 + case types.Int32: + fT = data.FieldTypeInt32 + case types.Uint32: + fT = data.FieldTypeUint32 + case types.Int64: + fT = data.FieldTypeInt64 + case types.Uint64: + fT = data.FieldTypeUint64 + case types.Float64: + fT = data.FieldTypeFloat64 + // StringType represents all string types, including VARCHAR and BLOB. + case types.Text, types.LongText: + fT = data.FieldTypeString + case types.Timestamp: + fT = data.FieldTypeTime + case types.Datetime: + fT = data.FieldTypeTime + case types.Boolean: + fT = data.FieldTypeBool + default: + if types.IsDecimal(col.Type) { + fT = data.FieldTypeFloat64 + } else { + return fT, fmt.Errorf("unsupported type for column %s of type %v", col.Name, col.Type) + } + } + + if col.Nullable { + fT = fT.NullableType() + } + + return fT, nil +} + +// Helper function to convert data.FieldType to types.Type +func convertDataType(fieldType data.FieldType) mysql.Type { + switch fieldType { + case data.FieldTypeInt8, data.FieldTypeNullableInt8: + return types.Int8 + case data.FieldTypeUint8, data.FieldTypeNullableUint8: + return types.Uint8 + case data.FieldTypeInt16, data.FieldTypeNullableInt16: + return types.Int16 + case data.FieldTypeUint16, data.FieldTypeNullableUint16: + return types.Uint16 + case data.FieldTypeInt32, data.FieldTypeNullableInt32: + return types.Int32 + case data.FieldTypeUint32, data.FieldTypeNullableUint32: + return types.Uint32 + case data.FieldTypeInt64, data.FieldTypeNullableInt64: + return types.Int64 + case data.FieldTypeUint64, data.FieldTypeNullableUint64: + return types.Uint64 + case data.FieldTypeFloat32, data.FieldTypeNullableFloat32: + return types.Float32 + case data.FieldTypeFloat64, data.FieldTypeNullableFloat64: + return types.Float64 + case data.FieldTypeString, data.FieldTypeNullableString: + return types.Text + case data.FieldTypeBool, data.FieldTypeNullableBool: + return types.Boolean + case data.FieldTypeTime, data.FieldTypeNullableTime: + return types.Timestamp + default: + fmt.Printf("------- Unsupported field type: %v", fieldType) + return types.JSON + } +} + +// fieldValFromRowVal converts a go-mysql-server row value to a data.field value +// +//nolint:gocyclo +func fieldValFromRowVal(fieldType data.FieldType, val interface{}) (interface{}, error) { + // the input val may be nil, it also may not be a pointer even if the fieldtype is a nullable pointer type + if val == nil { + return nil, nil + } + + switch fieldType { + // ---------------------------- + // Int8 / Nullable Int8 + // ---------------------------- + case data.FieldTypeInt8: + v, ok := val.(int8) + if !ok { + return nil, fmt.Errorf("unexpected value type for interface %v of type %T, expected int8", val, val) + } + return v, nil + + case data.FieldTypeNullableInt8: + vP, ok := val.(*int8) + if ok { + return vP, nil + } + v, ok := val.(int8) + if ok { + return &v, nil + } + return nil, fmt.Errorf("unexpected value type for interface %v of type %T, expected int8 or *int8", val, val) + + // ---------------------------- + // Uint8 / Nullable Uint8 + // ---------------------------- + case data.FieldTypeUint8: + v, ok := val.(uint8) + if !ok { + return nil, fmt.Errorf("unexpected value type for interface %v of type %T, expected uint8", val, val) + } + return v, nil + + case data.FieldTypeNullableUint8: + vP, ok := val.(*uint8) + if ok { + return vP, nil + } + v, ok := val.(uint8) + if ok { + return &v, nil + } + return nil, fmt.Errorf("unexpected value type for interface %v of type %T, expected uint8 or *uint8", val, val) + + // ---------------------------- + // Int16 / Nullable Int16 + // ---------------------------- + case data.FieldTypeInt16: + v, ok := val.(int16) + if !ok { + return nil, fmt.Errorf("unexpected value type for interface %v of type %T, expected int16", val, val) + } + return v, nil + + case data.FieldTypeNullableInt16: + vP, ok := val.(*int16) + if ok { + return vP, nil + } + v, ok := val.(int16) + if ok { + return &v, nil + } + return nil, fmt.Errorf("unexpected value type for interface %v of type %T, expected int16 or *int16", val, val) + + // ---------------------------- + // Uint16 / Nullable Uint16 + // ---------------------------- + case data.FieldTypeUint16: + v, ok := val.(uint16) + if !ok { + return nil, fmt.Errorf("unexpected value type for interface %v of type %T, expected uint16", val, val) + } + return v, nil + + case data.FieldTypeNullableUint16: + vP, ok := val.(*uint16) + if ok { + return vP, nil + } + v, ok := val.(uint16) + if ok { + return &v, nil + } + return nil, fmt.Errorf("unexpected value type for interface %v of type %T, expected uint16 or *uint16", val, val) + + // ---------------------------- + // Int32 / Nullable Int32 + // ---------------------------- + case data.FieldTypeInt32: + v, ok := val.(int32) + if !ok { + return nil, fmt.Errorf("unexpected value type for interface %v of type %T, expected int32", val, val) + } + return v, nil + + case data.FieldTypeNullableInt32: + vP, ok := val.(*int32) + if ok { + return vP, nil + } + v, ok := val.(int32) + if ok { + return &v, nil + } + return nil, fmt.Errorf("unexpected value type for interface %v of type %T, expected int32 or *int32", val, val) + + // ---------------------------- + // Uint32 / Nullable Uint32 + // ---------------------------- + case data.FieldTypeUint32: + v, ok := val.(uint32) + if !ok { + return nil, fmt.Errorf("unexpected value type for interface %v of type %T, expected uint32", val, val) + } + return v, nil + + case data.FieldTypeNullableUint32: + vP, ok := val.(*uint32) + if ok { + return vP, nil + } + v, ok := val.(uint32) + if ok { + return &v, nil + } + return nil, fmt.Errorf("unexpected value type for interface %v of type %T, expected uint32 or *uint32", val, val) + + // ---------------------------- + // Int64 / Nullable Int64 + // ---------------------------- + case data.FieldTypeInt64: + v, ok := val.(int64) + if !ok { + return nil, fmt.Errorf("unexpected value type for interface %v of type %T, expected int64", val, val) + } + return v, nil + + case data.FieldTypeNullableInt64: + vP, ok := val.(*int64) + if ok { + return vP, nil + } + v, ok := val.(int64) + if ok { + return &v, nil + } + return nil, fmt.Errorf("unexpected value type for interface %v of type %T, expected int64 or *int64", val, val) + + // ---------------------------- + // Uint64 / Nullable Uint64 + // ---------------------------- + case data.FieldTypeUint64: + v, ok := val.(uint64) + if !ok { + return nil, fmt.Errorf("unexpected value type for interface %v of type %T, expected uint64", val, val) + } + return v, nil + + case data.FieldTypeNullableUint64: + vP, ok := val.(*uint64) + if ok { + return vP, nil + } + v, ok := val.(uint64) + if ok { + return &v, nil + } + return nil, fmt.Errorf("unexpected value type for interface %v of type %T, expected uint64 or *uint64", val, val) + + // ---------------------------- + // Float64 / Nullable Float64 + // ---------------------------- + case data.FieldTypeFloat64: + // Accept float64 or decimal.Decimal, convert decimal.Decimal -> float64 + if v, ok := val.(float64); ok { + return v, nil + } + if d, ok := val.(decimal.Decimal); ok { + return d.InexactFloat64(), nil + } + return nil, fmt.Errorf("unexpected value type for interface %v of type %T, expected float64 or decimal.Decimal", val, val) + + case data.FieldTypeNullableFloat64: + // Possibly already *float64 + if vP, ok := val.(*float64); ok { + return vP, nil + } + // Possibly float64 + if v, ok := val.(float64); ok { + return &v, nil + } + // Possibly decimal.Decimal + if d, ok := val.(decimal.Decimal); ok { + f := d.InexactFloat64() + return &f, nil + } + return nil, fmt.Errorf("unexpected value type for interface %v of type %T, expected float64, *float64, or decimal.Decimal", val, val) + + // ---------------------------- + // Time / Nullable Time + // ---------------------------- + case data.FieldTypeTime: + v, ok := val.(time.Time) + if !ok { + return nil, fmt.Errorf("unexpected value type for interface %v of type %T, expected time.Time", val, val) + } + return v, nil + + case data.FieldTypeNullableTime: + vP, ok := val.(*time.Time) + if ok { + return vP, nil + } + v, ok := val.(time.Time) + if ok { + return &v, nil + } + return nil, fmt.Errorf("unexpected value type for interface %v of type %T, expected time.Time or *time.Time", val, val) + + // ---------------------------- + // String / Nullable String + // ---------------------------- + case data.FieldTypeString: + v, ok := val.(string) + if !ok { + return nil, fmt.Errorf("unexpected value type for interface %v of type %T, expected string", val, val) + } + return v, nil + + case data.FieldTypeNullableString: + vP, ok := val.(*string) + if ok { + return vP, nil + } + v, ok := val.(string) + if ok { + return &v, nil + } + return nil, fmt.Errorf("unexpected value type for interface %v of type %T, expected string or *string", val, val) + + // ---------------------------- + // Bool / Nullable Bool + // ---------------------------- + case data.FieldTypeBool: + v, ok := val.(bool) + if !ok { + return nil, fmt.Errorf("unexpected value type for interface %v of type %T, expected bool", val, val) + } + return v, nil + + case data.FieldTypeNullableBool: + vP, ok := val.(*bool) + if ok { + return vP, nil + } + v, ok := val.(bool) + if ok { + return &v, nil + } + return nil, fmt.Errorf("unexpected value type for interface %v of type %T, expected bool or *bool", val, val) + + // ---------------------------- + // Fallback / Unsupported + // ---------------------------- + default: + return nil, fmt.Errorf("unsupported field type %s for val %v", fieldType, val) + } +} + +// Is the field nilAt the index. Can panic if out of range. +// TODO: Maybe this should be a method on data.Field? +func nilAt(field data.Field, at int) bool { + if !field.Nullable() { + return false + } + + switch field.Type() { + case data.FieldTypeNullableInt8: + v := field.At(at).(*int8) + return v == nil + + case data.FieldTypeNullableUint8: + v := field.At(at).(*uint8) + return v == nil + + case data.FieldTypeNullableInt16: + v := field.At(at).(*int16) + return v == nil + + case data.FieldTypeNullableUint16: + v := field.At(at).(*uint16) + return v == nil + + case data.FieldTypeNullableInt32: + v := field.At(at).(*int32) + return v == nil + + case data.FieldTypeNullableUint32: + v := field.At(at).(*uint32) + return v == nil + + case data.FieldTypeNullableInt64: + v := field.At(at).(*int64) + return v == nil + + case data.FieldTypeNullableUint64: + v := field.At(at).(*uint64) + return v == nil + + case data.FieldTypeNullableFloat64: + v := field.At(at).(*float64) + return v == nil + + case data.FieldTypeNullableString: + v := field.At(at).(*string) + return v == nil + + case data.FieldTypeNullableTime: + v := field.At(at).(*time.Time) + return v == nil + + case data.FieldTypeNullableBool: + v := field.At(at).(*bool) + return v == nil + + default: + // Either it's not a nullable type or it's unsupported + return false + } +} diff --git a/pkg/expr/sql/frame_table.go b/pkg/expr/sql/frame_table.go new file mode 100644 index 00000000000..a511a3606fa --- /dev/null +++ b/pkg/expr/sql/frame_table.go @@ -0,0 +1,126 @@ +//go:build !arm + +package sql + +import ( + "io" + "strings" + + mysql "github.com/dolthub/go-mysql-server/sql" + "github.com/grafana/grafana-plugin-sdk-go/data" +) + +// FrameTable fulfills the mysql.Table interface for a data.Frame. +type FrameTable struct { + Frame *data.Frame + schema mysql.Schema +} + +// Name implements the sql.Nameable interface +func (ft *FrameTable) Name() string { + return ft.Frame.RefID +} + +// String implements the fmt.Stringer interface +func (ft *FrameTable) String() string { + return ft.Name() +} + +func schemaFromFrame(frame *data.Frame) mysql.Schema { + schema := make(mysql.Schema, len(frame.Fields)) + + for i, field := range frame.Fields { + schema[i] = &mysql.Column{ + Name: field.Name, + Type: convertDataType(field.Type()), + Nullable: field.Type().Nullable(), + Source: strings.ToLower(frame.RefID), + } + } + + return schema +} + +// Schema implements the mysql.Table interface +func (ft *FrameTable) Schema() mysql.Schema { + if ft.schema == nil { + ft.schema = schemaFromFrame(ft.Frame) + } + return ft.schema +} + +// Collation implements the mysql.Table interface +func (ft *FrameTable) Collation() mysql.CollationID { + return mysql.Collation_Unspecified +} + +// Partitions implements the mysql.Table interface +func (ft *FrameTable) Partitions(ctx *mysql.Context) (mysql.PartitionIter, error) { + return &noopPartitionIter{}, nil +} + +// PartitionRows implements the mysql.Table interface +func (ft *FrameTable) PartitionRows(ctx *mysql.Context, _ mysql.Partition) (mysql.RowIter, error) { + return &rowIter{ft: ft, row: 0}, nil +} + +type rowIter struct { + ft *FrameTable + row int +} + +func (ri *rowIter) Next(_ *mysql.Context) (mysql.Row, error) { + // We assume each field in the Frame has the same number of rows. + numRows := 0 + if len(ri.ft.Frame.Fields) > 0 { + numRows = ri.ft.Frame.Fields[0].Len() + } + + // If we've already exhausted all rows, return EOF + if ri.row >= numRows { + return nil, io.EOF + } + + // Construct a Row (which is []interface{} under the hood) by pulling + // the value from each column at the current row index. + row := make(mysql.Row, len(ri.ft.Frame.Fields)) + for colIndex, field := range ri.ft.Frame.Fields { + if nilAt(*field, ri.row) { + continue + } + row[colIndex], _ = field.ConcreteAt(ri.row) + } + + ri.row++ + return row, nil +} + +// Close implements the mysql.RowIter interface. +// In this no-op example, there isn't anything to do here. +func (ri *rowIter) Close(*mysql.Context) error { + return nil +} + +type noopPartitionIter struct { + done bool +} + +func (i *noopPartitionIter) Next(*mysql.Context) (mysql.Partition, error) { + if !i.done { + i.done = true + return noopParition, nil + } + return nil, io.EOF +} + +func (i *noopPartitionIter) Close(*mysql.Context) error { + return nil +} + +var noopParition = partition(nil) + +type partition []byte + +func (p partition) Key() []byte { + return p +} diff --git a/pkg/expr/sql/parser.go b/pkg/expr/sql/parser.go index b5456b3b6ff..12269b4a68f 100644 --- a/pkg/expr/sql/parser.go +++ b/pkg/expr/sql/parser.go @@ -1,89 +1,62 @@ package sql import ( - "encoding/json" "fmt" "sort" - "strings" + "github.com/dolthub/vitess/go/vt/sqlparser" "github.com/grafana/grafana/pkg/infra/log" - "github.com/jeremywohl/flatten" -) - -const ( - TABLE_NAME = "table_name" - ERROR = ".error" - ERROR_MESSAGE = ".error_message" ) var logger = log.New("sql_expr") // TablesList returns a list of tables for the sql statement func TablesList(rawSQL string) ([]string, error) { - db := NewInMemoryDB() - rawSQL = strings.Replace(rawSQL, "'", "''", -1) - cmd := fmt.Sprintf("SELECT json_serialize_sql('%s')", rawSQL) - ret, err := db.RunCommands([]string{cmd}) + stmt, err := sqlparser.Parse(rawSQL) if err != nil { - logger.Error("error serializing sql", "error", err.Error(), "sql", rawSQL, "cmd", cmd) - return nil, fmt.Errorf("error serializing sql: %s", err.Error()) + logger.Error("error parsing sql: %s", err.Error(), "sql", rawSQL) + return nil, fmt.Errorf("error parsing sql: %s", err.Error()) } - ast := []map[string]any{} - err = json.Unmarshal([]byte(ret), &ast) - if err != nil { - logger.Error("error converting json sql to ast", "error", err.Error(), "ret", ret) - return nil, fmt.Errorf("error converting json to ast: %s", err.Error()) - } + tables := make(map[string]struct{}) - return tablesFromAST(ast) -} - -// tablesFromAST returns a list of tables from the ast -func tablesFromAST(ast []map[string]any) ([]string, error) { - flat, err := flatten.Flatten(ast[0], "", flatten.DotStyle) - if err != nil { - logger.Error("error flattening ast", "error", err.Error(), "ast", ast) - return nil, fmt.Errorf("error flattening ast: %s", err.Error()) - } - - tables := []string{} - for k, v := range flat { - if strings.HasSuffix(k, ERROR) { - v, ok := v.(bool) - if ok && v { - logger.Error("error in sql", "error", k) - return nil, astError(k, flat) + walkSubtree := func(node sqlparser.SQLNode) error { + err = sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) { + switch v := node.(type) { + case *sqlparser.AliasedTableExpr: + if tableName, ok := v.Expr.(sqlparser.TableName); ok { + tables[tableName.Name.String()] = struct{}{} + } + case *sqlparser.TableName: + tables[v.Name.String()] = struct{}{} } + return true, nil + }, node) + + if err != nil { + logger.Error("error walking sql", "error", err, "node", node) + return fmt.Errorf("failed to parse SQL expression: %w", err) } - if strings.Contains(k, TABLE_NAME) { - table, ok := v.(string) - if ok && !existsInList(table, tables) { - tables = append(tables, v.(string)) - } + return nil + } + + if err := walkSubtree(stmt); err != nil { + return nil, err + } + + result := make([]string, 0, len(tables)) + for table := range tables { + // Remove 'dual' table if it exists + // This is a special table in MySQL that always returns a single row with a single column + // See: https://dev.mysql.com/doc/refman/5.7/en/select.html#:~:text=You%20are%20permitted%20to%20specify%20DUAL%20as%20a%20dummy%20table%20name%20in%20situations%20where%20no%20tables%20are%20referenced + if table != "dual" { + result = append(result, table) } } - sort.Strings(tables) + + sort.Strings(result) logger.Debug("tables found in sql", "tables", tables) - return tables, nil -} - -func astError(k string, flat map[string]any) error { - key := strings.Replace(k, ERROR, "", 1) - message, ok := flat[key+ERROR_MESSAGE] - if !ok { - message = "unknown error in sql" - } - return fmt.Errorf("error in sql: %s", message) -} - -func existsInList(table string, list []string) bool { - for _, t := range list { - if t == table { - return true - } - } - return false + return result, nil } diff --git a/pkg/expr/sql/parser_allow.go b/pkg/expr/sql/parser_allow.go new file mode 100644 index 00000000000..8a2ad436920 --- /dev/null +++ b/pkg/expr/sql/parser_allow.go @@ -0,0 +1,136 @@ +package sql + +import ( + "fmt" + "strings" + + "github.com/dolthub/vitess/go/vt/sqlparser" +) + +// AllowQuery parses the query and checks it against an allow list of allowed SQL nodes +// and functions. +func AllowQuery(rawSQL string) (bool, error) { + s, err := sqlparser.Parse(rawSQL) + if err != nil { + return false, fmt.Errorf("error parsing sql: %s", err.Error()) + } + + walkSubtree := func(node sqlparser.SQLNode) error { + err := sqlparser.Walk(func(node sqlparser.SQLNode) (bool, error) { + if !allowedNode(node) { + if fT, ok := node.(*sqlparser.FuncExpr); ok { + return false, fmt.Errorf("blocked function %s - not supported in queries", fT.Name) + } + return false, fmt.Errorf("blocked node %T - not supported in queries", node) + } + return true, nil + }, node) + + if err != nil { + return fmt.Errorf("failed to parse SQL expression: %w", err) + } + + return nil + } + + if err := walkSubtree(s); err != nil { + return false, err + } + + return true, nil +} + +// nolint:gocyclo,nakedret +func allowedNode(node sqlparser.SQLNode) (b bool) { + b = true // so don't have to return true in every case but default + + switch v := node.(type) { + case *sqlparser.FuncExpr: + return allowedFunction(v) + + case *sqlparser.AsOf: + return + + case *sqlparser.AliasedExpr, *sqlparser.AliasedTableExpr: + return + + case *sqlparser.BinaryExpr: + return + + case sqlparser.ColIdent, *sqlparser.ColName, sqlparser.Columns: + return + + case sqlparser.Comments: // TODO: understand why some are pointer vs not + return + + case *sqlparser.CommonTableExpr: + return + + case *sqlparser.ComparisonExpr: + return + + case *sqlparser.ConvertExpr: + return + + case sqlparser.GroupBy: + return + + case *sqlparser.IndexHints: + return + + case *sqlparser.Into: + return + + case *sqlparser.JoinTableExpr, sqlparser.JoinCondition: + return + + case *sqlparser.Select, sqlparser.SelectExprs: + return + + case *sqlparser.StarExpr: + return + + case *sqlparser.SQLVal: + return + + case *sqlparser.Limit: + return + + case *sqlparser.Order, sqlparser.OrderBy: + return + + case *sqlparser.Over: + return + + case *sqlparser.Subquery: + return + + case sqlparser.TableName, sqlparser.TableExprs, sqlparser.TableIdent: + return + + case *sqlparser.With: + return + + case *sqlparser.Where: + return + + default: + return false + } +} + +// nolint:gocyclo,nakedret +func allowedFunction(f *sqlparser.FuncExpr) (b bool) { + b = true // so don't have to return true in every case but default + + switch strings.ToLower(f.Name.String()) { + case "sum", "avg", "count", "min", "max": + return + + case "coalesce": + return + + default: + return false + } +} diff --git a/pkg/expr/sql/parser_allow_test.go b/pkg/expr/sql/parser_allow_test.go new file mode 100644 index 00000000000..aeeb1a40c69 --- /dev/null +++ b/pkg/expr/sql/parser_allow_test.go @@ -0,0 +1,80 @@ +package sql + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestAllowQuery(t *testing.T) { + testCases := []struct { + name string + q string + err error + }{ + { + name: "a big catch all for now", + q: example_metrics_query, + err: nil, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + _, err := AllowQuery(tc.q) + if tc.err != nil { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + } +} + +var example_metrics_query = `WITH + metrics_this_month AS ( + SELECT + Month, + namespace, + sum(BillableSeries) AS billable_series + FROM metrics + WHERE + Month = "2024-11" + GROUP BY + Month, + namespace + ORDER BY billable_series DESC + ), + total_metrics AS ( + SELECT SUM(billable_series) AS metrics_billable_series_total + FROM metrics_this_month + ), + total_traces AS ( + -- "usage" is a reserved keyword in MySQL. Quote it with backticks. + SELECT SUM(value) AS traces_usage_total + FROM traces + ), + usage_by_team AS ( + SELECT + COALESCE(teams.team, 'unaccounted') AS team, + 1 + 0 AS team_count, + -- Metrics + SUM(COALESCE(metrics_this_month.billable_series, 0)) AS metrics_billable_series, + -- Traces + SUM(COALESCE(traces.value, 0)) AS traces_usage + -- FROM teams + -- FULL OUTER JOIN metrics_this_month + FROM metrics_this_month + FULL OUTER JOIN teams + ON teams.namespace = metrics_this_month.namespace + FULL OUTER JOIN traces + ON teams.namespace = traces.namespace + GROUP BY + -- COALESCE(teams.team, 'unaccounted') + teams.team + ORDER BY metrics_billable_series DESC + ) + +SELECT * +FROM usage_by_team +CROSS JOIN total_metrics +CROSS JOIN total_traces` diff --git a/pkg/expr/sql/parser_test.go b/pkg/expr/sql/parser_test.go index 24303ce0178..17235684388 100644 --- a/pkg/expr/sql/parser_test.go +++ b/pkg/expr/sql/parser_test.go @@ -3,214 +3,131 @@ package sql import ( "testing" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) -func TestParse(t *testing.T) { - t.Skip() - sql := "select * from foo" - tables, err := TablesList((sql)) - assert.Nil(t, err) +func TestTablesList(t *testing.T) { + tests := []struct { + name string + sql string + expected []string + expectError bool + }{ + { + name: "simple select", + sql: "select * from foo", + expected: []string{"foo"}, + }, + { + name: "select with comma", + sql: "select * from foo,bar", + expected: []string{"bar", "foo"}, + }, + { + name: "select with multiple commas", + sql: "select * from foo,bar,baz", + expected: []string{"bar", "baz", "foo"}, + }, + { + name: "no table", + sql: "select 1 as 'n'", + expected: []string{}, + }, + { + name: "json array", + sql: "SELECT JSON_ARRAY(1, 2, 3) AS array_value", + expected: []string{}, + }, + { + name: "json extract", + sql: "SELECT JSON_EXTRACT(JSON_ARRAY(1, 2, 3), '$[0]') AS first_element;", + expected: []string{}, + }, + { + name: "json int array", + sql: "SELECT JSON_ARRAY(3, 2, 1) AS int_array;", + expected: []string{}, + }, + { + name: "subquery", + sql: "select * from (select * from people limit 1) AS subquery", + expected: []string{"people"}, + }, + { + name: "join", + sql: `select * from A + JOIN B ON A.name = B.name + LIMIT 10`, + expected: []string{"A", "B"}, + }, + { + name: "right join", + sql: `select * from A + RIGHT JOIN B ON A.name = B.name + LIMIT 10`, + expected: []string{"A", "B"}, + }, + { + name: "alias with join", + sql: `select * from A as X + RIGHT JOIN B ON A.name = X.name + LIMIT 10`, + expected: []string{"A", "B"}, + }, + { + name: "alias", + sql: "select * from A as X LIMIT 10", + expected: []string{"A"}, + }, + { + name: "error case", + sql: "select * from zzz aaa zzz", + expectError: true, + }, + { + name: "parens", + sql: `SELECT t1.Col1, + t2.Col1, + t3.Col1 + FROM table1 AS t1 + LEFT JOIN ( + table2 AS t2 + INNER JOIN table3 AS t3 ON t3.Col1 = t2.Col1 + ) ON t2.Col1 = t1.Col1;`, + expected: []string{"table1", "table2", "table3"}, + }, + { + name: "with clause", + sql: `WITH top_products AS ( + SELECT * FROM products + ORDER BY price DESC + LIMIT 5 + ) + SELECT name, price + FROM top_products;`, + expected: []string{"products", "top_products"}, + }, + { + name: "with quote", + sql: "select *,'junk' from foo", + expected: []string{"foo"}, + }, + { + name: "with quote 2", + sql: "SELECT json_serialize_sql('SELECT 1')", + expected: []string{}, + }, + } - assert.Equal(t, "foo", tables[0]) -} - -func TestParseWithComma(t *testing.T) { - t.Skip() - sql := "select * from foo,bar" - tables, err := TablesList((sql)) - assert.Nil(t, err) - - assert.Equal(t, "bar", tables[0]) - assert.Equal(t, "foo", tables[1]) -} - -func TestParseWithCommas(t *testing.T) { - t.Skip() - sql := "select * from foo,bar,baz" - tables, err := TablesList((sql)) - assert.Nil(t, err) - - assert.Equal(t, "bar", tables[0]) - assert.Equal(t, "baz", tables[1]) - assert.Equal(t, "foo", tables[2]) -} - -func TestArray(t *testing.T) { - t.Skip() - sql := "SELECT array_value(1, 2, 3)" - tables, err := TablesList((sql)) - assert.Nil(t, err) - - assert.Equal(t, 0, len(tables)) -} - -func TestArray2(t *testing.T) { - t.Skip() - sql := "SELECT array_value(1, 2, 3)[2]" - tables, err := TablesList((sql)) - assert.Nil(t, err) - - assert.Equal(t, 0, len(tables)) -} - -func TestXxx(t *testing.T) { - t.Skip() - sql := "SELECT [3, 2, 1]::INT[3];" - tables, err := TablesList((sql)) - assert.Nil(t, err) - - assert.Equal(t, 0, len(tables)) -} - -func TestParseSubquery(t *testing.T) { - t.Skip() - sql := "select * from (select * from people limit 1)" - tables, err := TablesList((sql)) - assert.Nil(t, err) - - assert.Equal(t, 1, len(tables)) - assert.Equal(t, "people", tables[0]) -} - -func TestJoin(t *testing.T) { - t.Skip() - sql := `select * from A - JOIN B ON A.name = B.name - LIMIT 10` - tables, err := TablesList((sql)) - assert.Nil(t, err) - - assert.Equal(t, 2, len(tables)) - assert.Equal(t, "A", tables[0]) - assert.Equal(t, "B", tables[1]) -} - -func TestRightJoin(t *testing.T) { - t.Skip() - sql := `select * from A - RIGHT JOIN B ON A.name = B.name - LIMIT 10` - tables, err := TablesList((sql)) - assert.Nil(t, err) - - assert.Equal(t, 2, len(tables)) - assert.Equal(t, "A", tables[0]) - assert.Equal(t, "B", tables[1]) -} - -func TestAliasWithJoin(t *testing.T) { - t.Skip() - sql := `select * from A as X - RIGHT JOIN B ON A.name = X.name - LIMIT 10` - tables, err := TablesList((sql)) - assert.Nil(t, err) - - assert.Equal(t, 2, len(tables)) - assert.Equal(t, "A", tables[0]) - assert.Equal(t, "B", tables[1]) -} - -func TestAlias(t *testing.T) { - t.Skip() - sql := `select * from A as X LIMIT 10` - tables, err := TablesList((sql)) - assert.Nil(t, err) - - assert.Equal(t, 1, len(tables)) - assert.Equal(t, "A", tables[0]) -} - -func TestError(t *testing.T) { - t.Skip() - sql := `select * from zzz aaa zzz` - _, err := TablesList((sql)) - assert.NotNil(t, err) -} - -func TestParens(t *testing.T) { - t.Skip() - sql := `SELECT t1.Col1, - t2.Col1, - t3.Col1 - FROM table1 AS t1 - LEFT JOIN ( - table2 AS t2 - INNER JOIN table3 AS t3 ON t3.Col1 = t2.Col1 - ) ON t2.Col1 = t1.Col1;` - tables, err := TablesList((sql)) - assert.Nil(t, err) - - assert.Equal(t, 3, len(tables)) - assert.Equal(t, "table1", tables[0]) - assert.Equal(t, "table2", tables[1]) - assert.Equal(t, "table3", tables[2]) -} - -func TestWith(t *testing.T) { - t.Skip() - sql := `WITH - - current_month AS ( - select - distinct "Month(ISO)" as mth - from A - ORDER BY mth DESC - LIMIT 1 - ), - - last_month_bill AS ( - select - CAST ( - sum( - CAST(BillableSeries AS INTEGER) - ) AS INTEGER - ) AS BillableSeries, - "Month(ISO)", - label_namespace - -- , B.activeseries_count - from A - JOIN current_month - ON current_month.mth = A."Month(ISO)" - JOIN B - ON B.namespace = A.label_namespace - GROUP BY - label_namespace, - "Month(ISO)" - ORDER BY BillableSeries DESC - ) - - SELECT - last_month_bill.*, - BEE.activeseries_count - FROM last_month_bill - JOIN BEE - ON BEE.namespace = last_month_bill.label_namespace` - - tables, err := TablesList((sql)) - assert.Nil(t, err) - - assert.Equal(t, 5, len(tables)) - assert.Equal(t, "A", tables[0]) - assert.Equal(t, "B", tables[1]) - assert.Equal(t, "BEE", tables[2]) -} - -func TestWithQuote(t *testing.T) { - t.Skip() - sql := "select *,'junk' from foo" - tables, err := TablesList((sql)) - assert.Nil(t, err) - - assert.Equal(t, "foo", tables[0]) -} - -func TestWithQuote2(t *testing.T) { - t.Skip() - sql := "SELECT json_serialize_sql('SELECT 1')" - tables, err := TablesList((sql)) - assert.Nil(t, err) - - assert.Equal(t, 0, len(tables)) + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + tables, err := TablesList(tc.sql) + if tc.expectError { + require.NotNil(t, err, "expected error for SQL: %s", tc.sql) + } else { + require.Nil(t, err, "unexpected error for SQL: %s", tc.sql) + require.Equal(t, tc.expected, tables, "mismatched tables for SQL: %s", tc.sql) + } + }) + } } diff --git a/pkg/expr/sql_command.go b/pkg/expr/sql_command.go index 9f0e1faf3e8..60d23f6e82f 100644 --- a/pkg/expr/sql_command.go +++ b/pkg/expr/sql_command.go @@ -93,11 +93,10 @@ func (gr *SQLCommand) Execute(ctx context.Context, now time.Time, vars mathexp.V rsp := mathexp.Results{} - db := sql.NewInMemoryDB() - var frame = &data.Frame{} + db := sql.DB{} logger.Debug("Executing query", "query", gr.query, "frames", len(allFrames)) - err := db.QueryFramesInto(gr.refID, gr.query, allFrames, frame) + frame, err := db.QueryFrames(ctx, gr.refID, gr.query, allFrames) if err != nil { logger.Error("Failed to query frames", "error", err.Error()) rsp.Error = err @@ -105,12 +104,11 @@ func (gr *SQLCommand) Execute(ctx context.Context, now time.Time, vars mathexp.V } logger.Debug("Done Executing query", "query", gr.query, "rows", frame.Rows()) - frame.RefID = gr.refID - if frame.Rows() == 0 { rsp.Values = mathexp.Values{ mathexp.NoData{Frame: frame}, } + return rsp, nil } rsp.Values = mathexp.Values{ diff --git a/pkg/registry/apis/query/query.go b/pkg/registry/apis/query/query.go index f86bf0fc75f..95a27508b5a 100644 --- a/pkg/registry/apis/query/query.go +++ b/pkg/registry/apis/query/query.go @@ -368,8 +368,7 @@ func (b *QueryAPIBuilder) handleExpressions(ctx context.Context, req parsedReque if !ok { dr, ok := qdr.Responses[refId] if ok { - allowLongFrames := false // TODO -- depends on input type and only if SQL? - _, res, err := b.converter.Convert(ctx, req.RefIDTypes[refId], dr.Frames, allowLongFrames) + _, res, err := b.converter.Convert(ctx, req.RefIDTypes[refId], dr.Frames) if err != nil { expressionsLogger.Error("error converting frames for expressions", "error", err) res.Error = err @@ -409,13 +408,12 @@ func (b *QueryAPIBuilder) convertQueryWithoutExpression(ctx context.Context, req if qdr == nil { return nil, errors.New("queryDataResponse is nil") } - allowLongFrames := false refID := req.Request.Queries[0].RefID if _, exist := qdr.Responses[refID]; !exist { return nil, fmt.Errorf("refID '%s' does not exist", refID) } frames := qdr.Responses[refID].Frames - _, results, err := b.converter.Convert(ctx, req.PluginId, frames, allowLongFrames) + _, results, err := b.converter.Convert(ctx, req.PluginId, frames) if err != nil { results.Error = err } diff --git a/pkg/services/featuremgmt/codeowners.go b/pkg/services/featuremgmt/codeowners.go index 85bb0739e32..13520e2a02b 100644 --- a/pkg/services/featuremgmt/codeowners.go +++ b/pkg/services/featuremgmt/codeowners.go @@ -29,4 +29,5 @@ const ( grafanaDatabasesFrontend codeowner = "@grafana/databases-frontend" grafanaOSSBigTent codeowner = "@grafana/oss-big-tent" growthAndOnboarding codeowner = "@grafana/growth-and-onboarding" + grafanaDatasourcesCoreServicesSquad codeowner = "@grafana/grafana-datasources-core-services" ) diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index bf5496b9c3c..5589a2cdb6b 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -1054,7 +1054,7 @@ var ( Description: "Enables using SQL and DuckDB functions as Expressions.", Stage: FeatureStageExperimental, FrontendOnly: false, - Owner: grafanaAppPlatformSquad, + Owner: grafanaDatasourcesCoreServicesSquad, }, { Name: "nodeGraphDotLayout", diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index 66125da1954..5ee9c0c2f1f 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -138,7 +138,7 @@ alertingSaveStateCompressed,experimental,@grafana/alerting-squad,false,false,fal scopeApi,experimental,@grafana/grafana-app-platform-squad,false,false,false promQLScope,GA,@grafana/oss-big-tent,false,false,false logQLScope,privatePreview,@grafana/observability-logs,false,false,false -sqlExpressions,experimental,@grafana/grafana-app-platform-squad,false,false,false +sqlExpressions,experimental,@grafana/grafana-datasources-core-services,false,false,false nodeGraphDotLayout,experimental,@grafana/observability-traces-and-profiling,false,false,true groupToNestedTableTransformation,GA,@grafana/dataviz-squad,false,false,true newPDFRendering,GA,@grafana/sharing-squad,false,false,false diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json index bc2a152baaf..970b8edf66f 100644 --- a/pkg/services/featuremgmt/toggles_gen.json +++ b/pkg/services/featuremgmt/toggles_gen.json @@ -3658,13 +3658,16 @@ { "metadata": { "name": "sqlExpressions", - "resourceVersion": "1718727528075", - "creationTimestamp": "2024-02-27T21:16:00Z" + "resourceVersion": "1738589190784", + "creationTimestamp": "2024-02-27T21:16:00Z", + "annotations": { + "grafana.app/updatedTimestamp": "2025-02-03 13:26:30.784245615 +0000 UTC" + } }, "spec": { "description": "Enables using SQL and DuckDB functions as Expressions.", "stage": "experimental", - "codeowner": "@grafana/grafana-app-platform-squad" + "codeowner": "@grafana/grafana-datasources-core-services" } }, { diff --git a/pkg/storage/unified/apistore/go.mod b/pkg/storage/unified/apistore/go.mod index 7e3edf8535b..2ac8214df01 100644 --- a/pkg/storage/unified/apistore/go.mod +++ b/pkg/storage/unified/apistore/go.mod @@ -117,6 +117,11 @@ require ( github.com/distribution/reference v0.6.0 // indirect github.com/dlmiddlecote/sqlstats v1.0.2 // indirect github.com/docker/go-units v0.5.0 // indirect + github.com/dolthub/flatbuffers/v23 v23.3.3-dh.2 // indirect + github.com/dolthub/go-icu-regex v0.0.0-20241215010122-db690dd53c90 // indirect + github.com/dolthub/go-mysql-server v0.19.0 // indirect + github.com/dolthub/jsonpath v0.0.2-0.20240227200619-19675ab05c71 // indirect + github.com/dolthub/vitess v0.0.0-20241211024425-b00987f7ba54 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/elazarl/goproxy v1.3.0 // indirect github.com/emicklei/go-restful/v3 v3.11.0 // indirect @@ -205,7 +210,6 @@ require ( github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/pgx/v5 v5.7.2 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect - github.com/jeremywohl/flatten v1.0.1 // indirect github.com/jessevdk/go-flags v1.5.0 // indirect github.com/jhump/protoreflect v1.15.1 // indirect github.com/jmespath-community/go-jmespath v1.1.1 // indirect @@ -220,6 +224,7 @@ require ( github.com/kylelemons/godebug v1.1.0 // indirect github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect + github.com/lestrrat-go/strftime v1.0.4 // indirect github.com/lib/pq v1.10.9 // indirect github.com/magefile/mage v1.15.0 // indirect github.com/magiconair/properties v1.8.7 // indirect @@ -286,6 +291,7 @@ require ( github.com/shopspring/decimal v1.4.0 // indirect github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c // indirect github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect github.com/smartystreets/goconvey v1.6.4 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.11.0 // indirect @@ -296,6 +302,7 @@ require ( github.com/stoewer/go-strcase v1.3.0 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/subosito/gotenv v1.6.0 // indirect + github.com/tetratelabs/wazero v1.8.2 // indirect github.com/tjhop/slog-gokit v0.1.3 // indirect github.com/uber/jaeger-client-go v2.30.0+incompatible // indirect github.com/uber/jaeger-lib v2.4.1+incompatible // indirect @@ -352,6 +359,7 @@ require ( gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/mail.v2 v2.3.1 // indirect + gopkg.in/src-d/go-errors.v1 v1.0.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/api v0.32.1 // indirect diff --git a/pkg/storage/unified/apistore/go.sum b/pkg/storage/unified/apistore/go.sum index e74589ac323..3c8ae34e004 100644 --- a/pkg/storage/unified/apistore/go.sum +++ b/pkg/storage/unified/apistore/go.sum @@ -312,6 +312,16 @@ github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6 github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/dolthub/flatbuffers/v23 v23.3.3-dh.2 h1:u3PMzfF8RkKd3lB9pZ2bfn0qEG+1Gms9599cr0REMww= +github.com/dolthub/flatbuffers/v23 v23.3.3-dh.2/go.mod h1:mIEZOHnFx4ZMQeawhw9rhsj+0zwQj7adVsnBX7t+eKY= +github.com/dolthub/go-icu-regex v0.0.0-20241215010122-db690dd53c90 h1:Sni8jrP0sy/w9ZYXoff4g/ixe+7bFCZlfCqXKJSU+zM= +github.com/dolthub/go-icu-regex v0.0.0-20241215010122-db690dd53c90/go.mod h1:ylU4XjUpsMcvl/BKeRRMXSH7e7WBrPXdSLvnRJYrxEA= +github.com/dolthub/go-mysql-server v0.19.0 h1:NdcXyGt9v7m4sQOahU+ss++iyPy4Q3viuVvbnn3rUTQ= +github.com/dolthub/go-mysql-server v0.19.0/go.mod h1:elfIatfq2fkU5lqTBrTcpL0RcHZOgYPE8EzBD7yQFiY= +github.com/dolthub/jsonpath v0.0.2-0.20240227200619-19675ab05c71 h1:bMGS25NWAGTEtT5tOBsCuCrlYnLRKpbJVJkDbrTRhwQ= +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-20241211024425-b00987f7ba54 h1:nzBnC0Rt1gFtscJEz4veYd/mazZEdbdmed+tujdaKOo= +github.com/dolthub/vitess v0.0.0-20241211024425-b00987f7ba54/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= @@ -654,8 +664,6 @@ github.com/jackc/pgx/v5 v5.7.2 h1:mLoDLV6sonKlvjIEsV56SkWNCnuNv531l94GaIzO+XI= github.com/jackc/pgx/v5 v5.7.2/go.mod h1:ncY89UGWxg82EykZUwSpUKEfccBGGYq1xjrOpsbsfGQ= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= -github.com/jeremywohl/flatten v1.0.1 h1:LrsxmB3hfwJuE+ptGOijix1PIfOoKLJ3Uee/mzbgtrs= -github.com/jeremywohl/flatten v1.0.1/go.mod h1:4AmD/VxjWcI5SRB0n6szE2A6s2fsNHDLO0nAlMHgfLQ= github.com/jessevdk/go-flags v1.5.0 h1:1jKYvbxEjfUl0fmqTCOfonvskHHXMjBySTLW4y9LFvc= github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= github.com/jhump/gopoet v0.0.0-20190322174617-17282ff210b3/go.mod h1:me9yfT6IJSlOL3FCfrg+L6yzUEZ+5jW6WHt4Sk+UPUI= @@ -725,6 +733,10 @@ github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= +github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc h1:RKf14vYWi2ttpEmkA4aQ3j4u9dStX2t4M8UM6qqNsG8= +github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc/go.mod h1:kopuH9ugFRkIXf3YoqHKyrJ9YfUFsckUU9S7B+XP+is= +github.com/lestrrat-go/strftime v1.0.4 h1:T1Rb9EPkAhgxKqbcMIPguPq8glqXTA1koF8n9BHElA8= +github.com/lestrrat-go/strftime v1.0.4/go.mod h1:E1nN3pCbtMSu1yjSVeyuRFVm/U0xoR76fd03sz+Qz4g= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= @@ -1004,6 +1016,8 @@ github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8 github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/teris-io/shortid v0.0.0-20171029131806-771a37caa5cf h1:Z2X3Os7oRzpdJ75iPqWZc0HeJWFYNCvKsfpQwFpRNTA= github.com/teris-io/shortid v0.0.0-20171029131806-771a37caa5cf/go.mod h1:M8agBzgqHIhgj7wEn9/0hJUZcrvt9VY+Ln+S1I5Mha0= +github.com/tetratelabs/wazero v1.8.2 h1:yIgLR/b2bN31bjxwXHD8a3d+BogigR952csSDdLYEv4= +github.com/tetratelabs/wazero v1.8.2/go.mod h1:yAI0XTsMBhREkM/YDAK/zNou3GoiAce1P6+rp/wQhjs= github.com/tidwall/pretty v0.0.0-20180105212114-65a9db5fad51/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tjhop/slog-gokit v0.1.3 h1:6SdexP3UIeg93KLFeiM1Wp1caRwdTLgsD/THxBUy1+o= github.com/tjhop/slog-gokit v0.1.3/go.mod h1:Bbu5v2748qpAWH7k6gse/kw3076IJf6owJmh7yArmJs= @@ -1306,6 +1320,7 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1536,6 +1551,8 @@ gopkg.in/mail.v2 v2.3.1 h1:WYFn/oANrAGP2C0dcV6/pbkPzv8yGzqTjPmTeO7qoXk= gopkg.in/mail.v2 v2.3.1/go.mod h1:htwXN1Qh09vZJ1NVKxQqHPBaCBbzKhp5GzuJEA4VJWw= gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= +gopkg.in/src-d/go-errors.v1 v1.0.0 h1:cooGdZnCjYbeS1zb1s6pVAAimTdKceRrpn7aKOnNIfc= +gopkg.in/src-d/go-errors.v1 v1.0.0/go.mod h1:q1cBlomlw2FnDBDNGlnh6X0jPihy+QxZfMMNxPCbdYg= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/pkg/storage/unified/resource/go.sum b/pkg/storage/unified/resource/go.sum index 172975106c5..17017755d74 100644 --- a/pkg/storage/unified/resource/go.sum +++ b/pkg/storage/unified/resource/go.sum @@ -210,6 +210,16 @@ github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5Xh github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/dolthub/flatbuffers/v23 v23.3.3-dh.2 h1:u3PMzfF8RkKd3lB9pZ2bfn0qEG+1Gms9599cr0REMww= +github.com/dolthub/flatbuffers/v23 v23.3.3-dh.2/go.mod h1:mIEZOHnFx4ZMQeawhw9rhsj+0zwQj7adVsnBX7t+eKY= +github.com/dolthub/go-icu-regex v0.0.0-20241215010122-db690dd53c90 h1:Sni8jrP0sy/w9ZYXoff4g/ixe+7bFCZlfCqXKJSU+zM= +github.com/dolthub/go-icu-regex v0.0.0-20241215010122-db690dd53c90/go.mod h1:ylU4XjUpsMcvl/BKeRRMXSH7e7WBrPXdSLvnRJYrxEA= +github.com/dolthub/go-mysql-server v0.19.0 h1:NdcXyGt9v7m4sQOahU+ss++iyPy4Q3viuVvbnn3rUTQ= +github.com/dolthub/go-mysql-server v0.19.0/go.mod h1:elfIatfq2fkU5lqTBrTcpL0RcHZOgYPE8EzBD7yQFiY= +github.com/dolthub/jsonpath v0.0.2-0.20240227200619-19675ab05c71 h1:bMGS25NWAGTEtT5tOBsCuCrlYnLRKpbJVJkDbrTRhwQ= +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-20241211024425-b00987f7ba54 h1:nzBnC0Rt1gFtscJEz4veYd/mazZEdbdmed+tujdaKOo= +github.com/dolthub/vitess v0.0.0-20241211024425-b00987f7ba54/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= @@ -490,8 +500,6 @@ github.com/jackc/pgx/v5 v5.7.2 h1:mLoDLV6sonKlvjIEsV56SkWNCnuNv531l94GaIzO+XI= github.com/jackc/pgx/v5 v5.7.2/go.mod h1:ncY89UGWxg82EykZUwSpUKEfccBGGYq1xjrOpsbsfGQ= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= -github.com/jeremywohl/flatten v1.0.1 h1:LrsxmB3hfwJuE+ptGOijix1PIfOoKLJ3Uee/mzbgtrs= -github.com/jeremywohl/flatten v1.0.1/go.mod h1:4AmD/VxjWcI5SRB0n6szE2A6s2fsNHDLO0nAlMHgfLQ= github.com/jessevdk/go-flags v1.5.0 h1:1jKYvbxEjfUl0fmqTCOfonvskHHXMjBySTLW4y9LFvc= github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= github.com/jhump/gopoet v0.0.0-20190322174617-17282ff210b3/go.mod h1:me9yfT6IJSlOL3FCfrg+L6yzUEZ+5jW6WHt4Sk+UPUI= @@ -553,6 +561,8 @@ github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= +github.com/lestrrat-go/strftime v1.0.4 h1:T1Rb9EPkAhgxKqbcMIPguPq8glqXTA1koF8n9BHElA8= +github.com/lestrrat-go/strftime v1.0.4/go.mod h1:E1nN3pCbtMSu1yjSVeyuRFVm/U0xoR76fd03sz+Qz4g= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= @@ -747,6 +757,8 @@ github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546/go.mod h1:TrYk7fJV github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304 h1:Jpy1PXuP99tXNrhbq2BaPz9B+jNAvH1JPQQpG/9GCXY= github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c h1:Ho+uVpkel/udgjbwB5Lktg9BtvJSh2DT0Hi6LPSyI2w= @@ -788,6 +800,8 @@ github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8 github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/teris-io/shortid v0.0.0-20171029131806-771a37caa5cf h1:Z2X3Os7oRzpdJ75iPqWZc0HeJWFYNCvKsfpQwFpRNTA= github.com/teris-io/shortid v0.0.0-20171029131806-771a37caa5cf/go.mod h1:M8agBzgqHIhgj7wEn9/0hJUZcrvt9VY+Ln+S1I5Mha0= +github.com/tetratelabs/wazero v1.8.2 h1:yIgLR/b2bN31bjxwXHD8a3d+BogigR952csSDdLYEv4= +github.com/tetratelabs/wazero v1.8.2/go.mod h1:yAI0XTsMBhREkM/YDAK/zNou3GoiAce1P6+rp/wQhjs= github.com/tidwall/pretty v0.0.0-20180105212114-65a9db5fad51/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tjhop/slog-gokit v0.1.3 h1:6SdexP3UIeg93KLFeiM1Wp1caRwdTLgsD/THxBUy1+o= github.com/tjhop/slog-gokit v0.1.3/go.mod h1:Bbu5v2748qpAWH7k6gse/kw3076IJf6owJmh7yArmJs= @@ -1103,6 +1117,8 @@ gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/mail.v2 v2.3.1 h1:WYFn/oANrAGP2C0dcV6/pbkPzv8yGzqTjPmTeO7qoXk= gopkg.in/mail.v2 v2.3.1/go.mod h1:htwXN1Qh09vZJ1NVKxQqHPBaCBbzKhp5GzuJEA4VJWw= +gopkg.in/src-d/go-errors.v1 v1.0.0 h1:cooGdZnCjYbeS1zb1s6pVAAimTdKceRrpn7aKOnNIfc= +gopkg.in/src-d/go-errors.v1 v1.0.0/go.mod h1:q1cBlomlw2FnDBDNGlnh6X0jPihy+QxZfMMNxPCbdYg= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= From 52db2070a065d370f0543d1e5af416859af9912e Mon Sep 17 00:00:00 2001 From: Andres Martinez Gotor Date: Thu, 6 Feb 2025 13:35:40 +0100 Subject: [PATCH 11/34] Advisor: Expose check types as a resource (#100058) --- apps/advisor/kinds/check.cue | 4 + apps/advisor/kinds/checktype.cue | 26 ++ apps/advisor/kinds/manifest.cue | 1 + .../apis/advisor/v0alpha1/check_status_gen.go | 4 + .../advisor/v0alpha1/checktype_codec_gen.go | 28 ++ .../v0alpha1/checktype_metadata_gen.go | 28 ++ .../advisor/v0alpha1/checktype_object_gen.go | 266 ++++++++++++++++ .../advisor/v0alpha1/checktype_schema_gen.go | 34 ++ .../advisor/v0alpha1/checktype_spec_gen.go | 26 ++ .../advisor/v0alpha1/checktype_status_gen.go | 44 +++ .../apis/advisor/v0alpha1/zz_openapi_gen.go | 297 +++++++++++++++++- apps/advisor/pkg/apis/advisor_manifest.go | 21 +- apps/advisor/pkg/app/app.go | 12 + .../pkg/app/checks/datasourcecheck/check.go | 24 +- .../pkg/app/checks/plugincheck/check.go | 24 +- apps/advisor/pkg/app/checks/utils.go | 21 ++ .../checktyperegisterer.go | 82 +++++ .../checktyperegisterer_test.go | 169 ++++++++++ 18 files changed, 1080 insertions(+), 31 deletions(-) create mode 100644 apps/advisor/kinds/checktype.cue create mode 100644 apps/advisor/pkg/apis/advisor/v0alpha1/checktype_codec_gen.go create mode 100644 apps/advisor/pkg/apis/advisor/v0alpha1/checktype_metadata_gen.go create mode 100644 apps/advisor/pkg/apis/advisor/v0alpha1/checktype_object_gen.go create mode 100644 apps/advisor/pkg/apis/advisor/v0alpha1/checktype_schema_gen.go create mode 100644 apps/advisor/pkg/apis/advisor/v0alpha1/checktype_spec_gen.go create mode 100644 apps/advisor/pkg/apis/advisor/v0alpha1/checktype_status_gen.go create mode 100644 apps/advisor/pkg/app/checks/utils.go create mode 100644 apps/advisor/pkg/app/checktyperegisterer/checktyperegisterer.go create mode 100644 apps/advisor/pkg/app/checktyperegisterer/checktyperegisterer_test.go diff --git a/apps/advisor/kinds/check.cue b/apps/advisor/kinds/check.cue index fdf641abdf3..a564f888e2f 100644 --- a/apps/advisor/kinds/check.cue +++ b/apps/advisor/kinds/check.cue @@ -28,6 +28,10 @@ check: { reason: string // Action to take to resolve the error action: string + // Step ID that the error is associated with + stepID: string + // Item ID that the error is associated with + itemID: string } #Report: { // Number of elements analyzed diff --git a/apps/advisor/kinds/checktype.cue b/apps/advisor/kinds/checktype.cue new file mode 100644 index 00000000000..6a4bfa4a0df --- /dev/null +++ b/apps/advisor/kinds/checktype.cue @@ -0,0 +1,26 @@ +package advisor + +checktype: { + kind: "CheckType" + pluralName: "CheckTypes" + current: "v0alpha1" + versions: { + "v0alpha1": { + codegen: { + frontend: false + backend: true + } + schema: { + #Step: { + title: string + description: string + stepID: string + } + spec: { + name: string + steps: [...#Step] + } + } + } + } +} diff --git a/apps/advisor/kinds/manifest.cue b/apps/advisor/kinds/manifest.cue index 6f96066967d..75a0eee05ef 100644 --- a/apps/advisor/kinds/manifest.cue +++ b/apps/advisor/kinds/manifest.cue @@ -5,5 +5,6 @@ manifest: { groupOverride: "advisor.grafana.app" kinds: [ check, + checktype, ] } diff --git a/apps/advisor/pkg/apis/advisor/v0alpha1/check_status_gen.go b/apps/advisor/pkg/apis/advisor/v0alpha1/check_status_gen.go index ac73ee86ec4..edc67575fe8 100644 --- a/apps/advisor/pkg/apis/advisor/v0alpha1/check_status_gen.go +++ b/apps/advisor/pkg/apis/advisor/v0alpha1/check_status_gen.go @@ -10,6 +10,10 @@ type CheckReportError struct { Reason string `json:"reason"` // Action to take to resolve the error Action string `json:"action"` + // Step ID that the error is associated with + StepID string `json:"stepID"` + // Item ID that the error is associated with + ItemID string `json:"itemID"` } // NewCheckReportError creates a new CheckReportError object. diff --git a/apps/advisor/pkg/apis/advisor/v0alpha1/checktype_codec_gen.go b/apps/advisor/pkg/apis/advisor/v0alpha1/checktype_codec_gen.go new file mode 100644 index 00000000000..9881ce9d96f --- /dev/null +++ b/apps/advisor/pkg/apis/advisor/v0alpha1/checktype_codec_gen.go @@ -0,0 +1,28 @@ +// +// Code generated by grafana-app-sdk. DO NOT EDIT. +// + +package v0alpha1 + +import ( + "encoding/json" + "io" + + "github.com/grafana/grafana-app-sdk/resource" +) + +// CheckTypeJSONCodec is an implementation of resource.Codec for kubernetes JSON encoding +type CheckTypeJSONCodec struct{} + +// Read reads JSON-encoded bytes from `reader` and unmarshals them into `into` +func (*CheckTypeJSONCodec) Read(reader io.Reader, into resource.Object) error { + return json.NewDecoder(reader).Decode(into) +} + +// Write writes JSON-encoded bytes into `writer` marshaled from `from` +func (*CheckTypeJSONCodec) Write(writer io.Writer, from resource.Object) error { + return json.NewEncoder(writer).Encode(from) +} + +// Interface compliance checks +var _ resource.Codec = &CheckTypeJSONCodec{} diff --git a/apps/advisor/pkg/apis/advisor/v0alpha1/checktype_metadata_gen.go b/apps/advisor/pkg/apis/advisor/v0alpha1/checktype_metadata_gen.go new file mode 100644 index 00000000000..9998cffb37d --- /dev/null +++ b/apps/advisor/pkg/apis/advisor/v0alpha1/checktype_metadata_gen.go @@ -0,0 +1,28 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. + +package v0alpha1 + +import ( + time "time" +) + +// metadata contains embedded CommonMetadata and can be extended with custom string fields +// TODO: use CommonMetadata instead of redefining here; currently needs to be defined here +// without external reference as using the CommonMetadata reference breaks thema codegen. +type CheckTypeMetadata struct { + UpdateTimestamp time.Time `json:"updateTimestamp"` + CreatedBy string `json:"createdBy"` + Uid string `json:"uid"` + CreationTimestamp time.Time `json:"creationTimestamp"` + DeletionTimestamp *time.Time `json:"deletionTimestamp,omitempty"` + Finalizers []string `json:"finalizers"` + ResourceVersion string `json:"resourceVersion"` + Generation int64 `json:"generation"` + UpdatedBy string `json:"updatedBy"` + Labels map[string]string `json:"labels"` +} + +// NewCheckTypeMetadata creates a new CheckTypeMetadata object. +func NewCheckTypeMetadata() *CheckTypeMetadata { + return &CheckTypeMetadata{} +} diff --git a/apps/advisor/pkg/apis/advisor/v0alpha1/checktype_object_gen.go b/apps/advisor/pkg/apis/advisor/v0alpha1/checktype_object_gen.go new file mode 100644 index 00000000000..80bd02b1cc8 --- /dev/null +++ b/apps/advisor/pkg/apis/advisor/v0alpha1/checktype_object_gen.go @@ -0,0 +1,266 @@ +// +// Code generated by grafana-app-sdk. DO NOT EDIT. +// + +package v0alpha1 + +import ( + "fmt" + "github.com/grafana/grafana-app-sdk/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + "time" +) + +// +k8s:openapi-gen=true +type CheckType struct { + metav1.TypeMeta `json:",inline" yaml:",inline"` + metav1.ObjectMeta `json:"metadata" yaml:"metadata"` + Spec CheckTypeSpec `json:"spec" yaml:"spec"` + CheckTypeStatus CheckTypeStatus `json:"status" yaml:"status"` +} + +func (o *CheckType) GetSpec() any { + return o.Spec +} + +func (o *CheckType) SetSpec(spec any) error { + cast, ok := spec.(CheckTypeSpec) + if !ok { + return fmt.Errorf("cannot set spec type %#v, not of type Spec", spec) + } + o.Spec = cast + return nil +} + +func (o *CheckType) GetSubresources() map[string]any { + return map[string]any{ + "status": o.CheckTypeStatus, + } +} + +func (o *CheckType) GetSubresource(name string) (any, bool) { + switch name { + case "status": + return o.CheckTypeStatus, true + default: + return nil, false + } +} + +func (o *CheckType) SetSubresource(name string, value any) error { + switch name { + case "status": + cast, ok := value.(CheckTypeStatus) + if !ok { + return fmt.Errorf("cannot set status type %#v, not of type CheckTypeStatus", value) + } + o.CheckTypeStatus = cast + return nil + default: + return fmt.Errorf("subresource '%s' does not exist", name) + } +} + +func (o *CheckType) GetStaticMetadata() resource.StaticMetadata { + gvk := o.GroupVersionKind() + return resource.StaticMetadata{ + Name: o.ObjectMeta.Name, + Namespace: o.ObjectMeta.Namespace, + Group: gvk.Group, + Version: gvk.Version, + Kind: gvk.Kind, + } +} + +func (o *CheckType) SetStaticMetadata(metadata resource.StaticMetadata) { + o.Name = metadata.Name + o.Namespace = metadata.Namespace + o.SetGroupVersionKind(schema.GroupVersionKind{ + Group: metadata.Group, + Version: metadata.Version, + Kind: metadata.Kind, + }) +} + +func (o *CheckType) GetCommonMetadata() resource.CommonMetadata { + dt := o.DeletionTimestamp + var deletionTimestamp *time.Time + if dt != nil { + deletionTimestamp = &dt.Time + } + // Legacy ExtraFields support + extraFields := make(map[string]any) + if o.Annotations != nil { + extraFields["annotations"] = o.Annotations + } + if o.ManagedFields != nil { + extraFields["managedFields"] = o.ManagedFields + } + if o.OwnerReferences != nil { + extraFields["ownerReferences"] = o.OwnerReferences + } + return resource.CommonMetadata{ + UID: string(o.UID), + ResourceVersion: o.ResourceVersion, + Generation: o.Generation, + Labels: o.Labels, + CreationTimestamp: o.CreationTimestamp.Time, + DeletionTimestamp: deletionTimestamp, + Finalizers: o.Finalizers, + UpdateTimestamp: o.GetUpdateTimestamp(), + CreatedBy: o.GetCreatedBy(), + UpdatedBy: o.GetUpdatedBy(), + ExtraFields: extraFields, + } +} + +func (o *CheckType) SetCommonMetadata(metadata resource.CommonMetadata) { + o.UID = types.UID(metadata.UID) + o.ResourceVersion = metadata.ResourceVersion + o.Generation = metadata.Generation + o.Labels = metadata.Labels + o.CreationTimestamp = metav1.NewTime(metadata.CreationTimestamp) + if metadata.DeletionTimestamp != nil { + dt := metav1.NewTime(*metadata.DeletionTimestamp) + o.DeletionTimestamp = &dt + } else { + o.DeletionTimestamp = nil + } + o.Finalizers = metadata.Finalizers + if o.Annotations == nil { + o.Annotations = make(map[string]string) + } + if !metadata.UpdateTimestamp.IsZero() { + o.SetUpdateTimestamp(metadata.UpdateTimestamp) + } + if metadata.CreatedBy != "" { + o.SetCreatedBy(metadata.CreatedBy) + } + if metadata.UpdatedBy != "" { + o.SetUpdatedBy(metadata.UpdatedBy) + } + // Legacy support for setting Annotations, ManagedFields, and OwnerReferences via ExtraFields + if metadata.ExtraFields != nil { + if annotations, ok := metadata.ExtraFields["annotations"]; ok { + if cast, ok := annotations.(map[string]string); ok { + o.Annotations = cast + } + } + if managedFields, ok := metadata.ExtraFields["managedFields"]; ok { + if cast, ok := managedFields.([]metav1.ManagedFieldsEntry); ok { + o.ManagedFields = cast + } + } + if ownerReferences, ok := metadata.ExtraFields["ownerReferences"]; ok { + if cast, ok := ownerReferences.([]metav1.OwnerReference); ok { + o.OwnerReferences = cast + } + } + } +} + +func (o *CheckType) GetCreatedBy() string { + if o.ObjectMeta.Annotations == nil { + o.ObjectMeta.Annotations = make(map[string]string) + } + + return o.ObjectMeta.Annotations["grafana.com/createdBy"] +} + +func (o *CheckType) SetCreatedBy(createdBy string) { + if o.ObjectMeta.Annotations == nil { + o.ObjectMeta.Annotations = make(map[string]string) + } + + o.ObjectMeta.Annotations["grafana.com/createdBy"] = createdBy +} + +func (o *CheckType) GetUpdateTimestamp() time.Time { + if o.ObjectMeta.Annotations == nil { + o.ObjectMeta.Annotations = make(map[string]string) + } + + parsed, _ := time.Parse(time.RFC3339, o.ObjectMeta.Annotations["grafana.com/updateTimestamp"]) + return parsed +} + +func (o *CheckType) SetUpdateTimestamp(updateTimestamp time.Time) { + if o.ObjectMeta.Annotations == nil { + o.ObjectMeta.Annotations = make(map[string]string) + } + + o.ObjectMeta.Annotations["grafana.com/updateTimestamp"] = updateTimestamp.Format(time.RFC3339) +} + +func (o *CheckType) GetUpdatedBy() string { + if o.ObjectMeta.Annotations == nil { + o.ObjectMeta.Annotations = make(map[string]string) + } + + return o.ObjectMeta.Annotations["grafana.com/updatedBy"] +} + +func (o *CheckType) SetUpdatedBy(updatedBy string) { + if o.ObjectMeta.Annotations == nil { + o.ObjectMeta.Annotations = make(map[string]string) + } + + o.ObjectMeta.Annotations["grafana.com/updatedBy"] = updatedBy +} + +func (o *CheckType) Copy() resource.Object { + return resource.CopyObject(o) +} + +func (o *CheckType) DeepCopyObject() runtime.Object { + return o.Copy() +} + +// Interface compliance compile-time check +var _ resource.Object = &CheckType{} + +// +k8s:openapi-gen=true +type CheckTypeList struct { + metav1.TypeMeta `json:",inline" yaml:",inline"` + metav1.ListMeta `json:"metadata" yaml:"metadata"` + Items []CheckType `json:"items" yaml:"items"` +} + +func (o *CheckTypeList) DeepCopyObject() runtime.Object { + return o.Copy() +} + +func (o *CheckTypeList) Copy() resource.ListObject { + cpy := &CheckTypeList{ + TypeMeta: o.TypeMeta, + Items: make([]CheckType, len(o.Items)), + } + o.ListMeta.DeepCopyInto(&cpy.ListMeta) + for i := 0; i < len(o.Items); i++ { + if item, ok := o.Items[i].Copy().(*CheckType); ok { + cpy.Items[i] = *item + } + } + return cpy +} + +func (o *CheckTypeList) GetItems() []resource.Object { + items := make([]resource.Object, len(o.Items)) + for i := 0; i < len(o.Items); i++ { + items[i] = &o.Items[i] + } + return items +} + +func (o *CheckTypeList) SetItems(items []resource.Object) { + o.Items = make([]CheckType, len(items)) + for i := 0; i < len(items); i++ { + o.Items[i] = *items[i].(*CheckType) + } +} + +// Interface compliance compile-time check +var _ resource.ListObject = &CheckTypeList{} diff --git a/apps/advisor/pkg/apis/advisor/v0alpha1/checktype_schema_gen.go b/apps/advisor/pkg/apis/advisor/v0alpha1/checktype_schema_gen.go new file mode 100644 index 00000000000..15b4a6fbb11 --- /dev/null +++ b/apps/advisor/pkg/apis/advisor/v0alpha1/checktype_schema_gen.go @@ -0,0 +1,34 @@ +// +// Code generated by grafana-app-sdk. DO NOT EDIT. +// + +package v0alpha1 + +import ( + "github.com/grafana/grafana-app-sdk/resource" +) + +// schema is unexported to prevent accidental overwrites +var ( + schemaCheckType = resource.NewSimpleSchema("advisor.grafana.app", "v0alpha1", &CheckType{}, &CheckTypeList{}, resource.WithKind("CheckType"), + resource.WithPlural("checktypes"), resource.WithScope(resource.NamespacedScope)) + kindCheckType = resource.Kind{ + Schema: schemaCheckType, + Codecs: map[resource.KindEncoding]resource.Codec{ + resource.KindEncodingJSON: &CheckTypeJSONCodec{}, + }, + } +) + +// Kind returns a resource.Kind for this Schema with a JSON codec +func CheckTypeKind() resource.Kind { + return kindCheckType +} + +// Schema returns a resource.SimpleSchema representation of CheckType +func CheckTypeSchema() *resource.SimpleSchema { + return schemaCheckType +} + +// Interface compliance checks +var _ resource.Schema = kindCheckType diff --git a/apps/advisor/pkg/apis/advisor/v0alpha1/checktype_spec_gen.go b/apps/advisor/pkg/apis/advisor/v0alpha1/checktype_spec_gen.go new file mode 100644 index 00000000000..a0392a49c18 --- /dev/null +++ b/apps/advisor/pkg/apis/advisor/v0alpha1/checktype_spec_gen.go @@ -0,0 +1,26 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. + +package v0alpha1 + +// +k8s:openapi-gen=true +type CheckTypeStep struct { + Title string `json:"title"` + Description string `json:"description"` + StepID string `json:"stepID"` +} + +// NewCheckTypeStep creates a new CheckTypeStep object. +func NewCheckTypeStep() *CheckTypeStep { + return &CheckTypeStep{} +} + +// +k8s:openapi-gen=true +type CheckTypeSpec struct { + Name string `json:"name"` + Steps []CheckTypeStep `json:"steps"` +} + +// NewCheckTypeSpec creates a new CheckTypeSpec object. +func NewCheckTypeSpec() *CheckTypeSpec { + return &CheckTypeSpec{} +} diff --git a/apps/advisor/pkg/apis/advisor/v0alpha1/checktype_status_gen.go b/apps/advisor/pkg/apis/advisor/v0alpha1/checktype_status_gen.go new file mode 100644 index 00000000000..87db65316d0 --- /dev/null +++ b/apps/advisor/pkg/apis/advisor/v0alpha1/checktype_status_gen.go @@ -0,0 +1,44 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. + +package v0alpha1 + +// +k8s:openapi-gen=true +type CheckTypestatusOperatorState struct { + // lastEvaluation is the ResourceVersion last evaluated + LastEvaluation string `json:"lastEvaluation"` + // state describes the state of the lastEvaluation. + // It is limited to three possible states for machine evaluation. + State CheckTypeStatusOperatorStateState `json:"state"` + // descriptiveState is an optional more descriptive state field which has no requirements on format + DescriptiveState *string `json:"descriptiveState,omitempty"` + // details contains any extra information that is operator-specific + Details map[string]interface{} `json:"details,omitempty"` +} + +// NewCheckTypestatusOperatorState creates a new CheckTypestatusOperatorState object. +func NewCheckTypestatusOperatorState() *CheckTypestatusOperatorState { + return &CheckTypestatusOperatorState{} +} + +// +k8s:openapi-gen=true +type CheckTypeStatus struct { + // operatorStates is a map of operator ID to operator state evaluations. + // Any operator which consumes this kind SHOULD add its state evaluation information to this field. + OperatorStates map[string]CheckTypestatusOperatorState `json:"operatorStates,omitempty"` + // additionalFields is reserved for future use + AdditionalFields map[string]interface{} `json:"additionalFields,omitempty"` +} + +// NewCheckTypeStatus creates a new CheckTypeStatus object. +func NewCheckTypeStatus() *CheckTypeStatus { + return &CheckTypeStatus{} +} + +// +k8s:openapi-gen=true +type CheckTypeStatusOperatorStateState string + +const ( + CheckTypeStatusOperatorStateStateSuccess CheckTypeStatusOperatorStateState = "success" + CheckTypeStatusOperatorStateStateInProgress CheckTypeStatusOperatorStateState = "in_progress" + CheckTypeStatusOperatorStateStateFailed CheckTypeStatusOperatorStateState = "failed" +) diff --git a/apps/advisor/pkg/apis/advisor/v0alpha1/zz_openapi_gen.go b/apps/advisor/pkg/apis/advisor/v0alpha1/zz_openapi_gen.go index 74ccb4861e6..10a2de58e84 100644 --- a/apps/advisor/pkg/apis/advisor/v0alpha1/zz_openapi_gen.go +++ b/apps/advisor/pkg/apis/advisor/v0alpha1/zz_openapi_gen.go @@ -12,13 +12,19 @@ import ( func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition { return map[string]common.OpenAPIDefinition{ - "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.Check": schema_pkg_apis_advisor_v0alpha1_Check(ref), - "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckList": schema_pkg_apis_advisor_v0alpha1_CheckList(ref), - "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckReportError": schema_pkg_apis_advisor_v0alpha1_CheckReportError(ref), - "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckSpec": schema_pkg_apis_advisor_v0alpha1_CheckSpec(ref), - "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckStatus": schema_pkg_apis_advisor_v0alpha1_CheckStatus(ref), - "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckV0alpha1StatusReport": schema_pkg_apis_advisor_v0alpha1_CheckV0alpha1StatusReport(ref), - "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckstatusOperatorState": schema_pkg_apis_advisor_v0alpha1_CheckstatusOperatorState(ref), + "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.Check": schema_pkg_apis_advisor_v0alpha1_Check(ref), + "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckList": schema_pkg_apis_advisor_v0alpha1_CheckList(ref), + "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckReportError": schema_pkg_apis_advisor_v0alpha1_CheckReportError(ref), + "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckSpec": schema_pkg_apis_advisor_v0alpha1_CheckSpec(ref), + "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckStatus": schema_pkg_apis_advisor_v0alpha1_CheckStatus(ref), + "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckType": schema_pkg_apis_advisor_v0alpha1_CheckType(ref), + "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckTypeList": schema_pkg_apis_advisor_v0alpha1_CheckTypeList(ref), + "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckTypeSpec": schema_pkg_apis_advisor_v0alpha1_CheckTypeSpec(ref), + "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckTypeStatus": schema_pkg_apis_advisor_v0alpha1_CheckTypeStatus(ref), + "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckTypeStep": schema_pkg_apis_advisor_v0alpha1_CheckTypeStep(ref), + "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckTypestatusOperatorState": schema_pkg_apis_advisor_v0alpha1_CheckTypestatusOperatorState(ref), + "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckV0alpha1StatusReport": schema_pkg_apis_advisor_v0alpha1_CheckV0alpha1StatusReport(ref), + "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckstatusOperatorState": schema_pkg_apis_advisor_v0alpha1_CheckstatusOperatorState(ref), } } @@ -147,8 +153,24 @@ func schema_pkg_apis_advisor_v0alpha1_CheckReportError(ref common.ReferenceCallb Format: "", }, }, + "stepID": { + SchemaProps: spec.SchemaProps{ + Description: "Step ID that the error is associated with", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "itemID": { + SchemaProps: spec.SchemaProps{ + Description: "Item ID that the error is associated with", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, }, - Required: []string{"severity", "reason", "action"}, + Required: []string{"severity", "reason", "action", "stepID", "itemID"}, }, }, } @@ -233,6 +255,265 @@ func schema_pkg_apis_advisor_v0alpha1_CheckStatus(ref common.ReferenceCallback) } } +func schema_pkg_apis_advisor_v0alpha1_CheckType(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + }, + }, + "spec": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckTypeSpec"), + }, + }, + "status": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckTypeStatus"), + }, + }, + }, + Required: []string{"metadata", "spec", "status"}, + }, + }, + Dependencies: []string{ + "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckTypeSpec", "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckTypeStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + +func schema_pkg_apis_advisor_v0alpha1_CheckTypeList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), + }, + }, + "items": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckType"), + }, + }, + }, + }, + }, + }, + Required: []string{"metadata", "items"}, + }, + }, + Dependencies: []string{ + "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckType", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + } +} + +func schema_pkg_apis_advisor_v0alpha1_CheckTypeSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "steps": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckTypeStep"), + }, + }, + }, + }, + }, + }, + Required: []string{"name", "steps"}, + }, + }, + Dependencies: []string{ + "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckTypeStep"}, + } +} + +func schema_pkg_apis_advisor_v0alpha1_CheckTypeStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "operatorStates": { + SchemaProps: spec.SchemaProps{ + Description: "operatorStates is a map of operator ID to operator state evaluations. Any operator which consumes this kind SHOULD add its state evaluation information to this field.", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckTypestatusOperatorState"), + }, + }, + }, + }, + }, + "additionalFields": { + SchemaProps: spec.SchemaProps{ + Description: "additionalFields is reserved for future use", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Format: "", + }, + }, + }, + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1.CheckTypestatusOperatorState"}, + } +} + +func schema_pkg_apis_advisor_v0alpha1_CheckTypeStep(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "title": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "description": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "stepID": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"title", "description", "stepID"}, + }, + }, + } +} + +func schema_pkg_apis_advisor_v0alpha1_CheckTypestatusOperatorState(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "lastEvaluation": { + SchemaProps: spec.SchemaProps{ + Description: "lastEvaluation is the ResourceVersion last evaluated", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "state": { + SchemaProps: spec.SchemaProps{ + Description: "state describes the state of the lastEvaluation. It is limited to three possible states for machine evaluation.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "descriptiveState": { + SchemaProps: spec.SchemaProps{ + Description: "descriptiveState is an optional more descriptive state field which has no requirements on format", + Type: []string{"string"}, + Format: "", + }, + }, + "details": { + SchemaProps: spec.SchemaProps{ + Description: "details contains any extra information that is operator-specific", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Format: "", + }, + }, + }, + }, + }, + }, + Required: []string{"lastEvaluation", "state"}, + }, + }, + } +} + func schema_pkg_apis_advisor_v0alpha1_CheckV0alpha1StatusReport(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ diff --git a/apps/advisor/pkg/apis/advisor_manifest.go b/apps/advisor/pkg/apis/advisor_manifest.go index f62ea3addb8..8b39d626436 100644 --- a/apps/advisor/pkg/apis/advisor_manifest.go +++ b/apps/advisor/pkg/apis/advisor_manifest.go @@ -12,9 +12,12 @@ import ( ) var ( - rawSchemaCheckv0alpha1 = []byte(`{"spec":{"properties":{"data":{"additionalProperties":{"type":"string"},"description":"Generic data input that a check can receive","type":"object"}},"type":"object"},"status":{"properties":{"additionalFields":{"description":"additionalFields is reserved for future use","type":"object","x-kubernetes-preserve-unknown-fields":true},"operatorStates":{"additionalProperties":{"properties":{"descriptiveState":{"description":"descriptiveState is an optional more descriptive state field which has no requirements on format","type":"string"},"details":{"description":"details contains any extra information that is operator-specific","type":"object","x-kubernetes-preserve-unknown-fields":true},"lastEvaluation":{"description":"lastEvaluation is the ResourceVersion last evaluated","type":"string"},"state":{"description":"state describes the state of the lastEvaluation.\nIt is limited to three possible states for machine evaluation.","enum":["success","in_progress","failed"],"type":"string"}},"required":["lastEvaluation","state"],"type":"object"},"description":"operatorStates is a map of operator ID to operator state evaluations.\nAny operator which consumes this kind SHOULD add its state evaluation information to this field.","type":"object"},"report":{"properties":{"count":{"description":"Number of elements analyzed","type":"integer"},"errors":{"description":"List of errors","items":{"properties":{"action":{"description":"Action to take to resolve the error","type":"string"},"reason":{"description":"Human readable reason for the error","type":"string"},"severity":{"description":"Severity of the error","enum":["high","low"],"type":"string"}},"required":["severity","reason","action"],"type":"object"},"type":"array"}},"required":["count","errors"],"type":"object"}},"required":["report"],"type":"object","x-kubernetes-preserve-unknown-fields":true}}`) - versionSchemaCheckv0alpha1 app.VersionSchema - _ = json.Unmarshal(rawSchemaCheckv0alpha1, &versionSchemaCheckv0alpha1) + rawSchemaCheckv0alpha1 = []byte(`{"spec":{"properties":{"data":{"additionalProperties":{"type":"string"},"description":"Generic data input that a check can receive","type":"object"}},"type":"object"},"status":{"properties":{"additionalFields":{"description":"additionalFields is reserved for future use","type":"object","x-kubernetes-preserve-unknown-fields":true},"operatorStates":{"additionalProperties":{"properties":{"descriptiveState":{"description":"descriptiveState is an optional more descriptive state field which has no requirements on format","type":"string"},"details":{"description":"details contains any extra information that is operator-specific","type":"object","x-kubernetes-preserve-unknown-fields":true},"lastEvaluation":{"description":"lastEvaluation is the ResourceVersion last evaluated","type":"string"},"state":{"description":"state describes the state of the lastEvaluation.\nIt is limited to three possible states for machine evaluation.","enum":["success","in_progress","failed"],"type":"string"}},"required":["lastEvaluation","state"],"type":"object"},"description":"operatorStates is a map of operator ID to operator state evaluations.\nAny operator which consumes this kind SHOULD add its state evaluation information to this field.","type":"object"},"report":{"properties":{"count":{"description":"Number of elements analyzed","type":"integer"},"errors":{"description":"List of errors","items":{"properties":{"action":{"description":"Action to take to resolve the error","type":"string"},"itemID":{"description":"Item ID that the error is associated with","type":"string"},"reason":{"description":"Human readable reason for the error","type":"string"},"severity":{"description":"Severity of the error","enum":["high","low"],"type":"string"},"stepID":{"description":"Step ID that the error is associated with","type":"string"}},"required":["severity","reason","action","stepID","itemID"],"type":"object"},"type":"array"}},"required":["count","errors"],"type":"object"}},"required":["report"],"type":"object","x-kubernetes-preserve-unknown-fields":true}}`) + versionSchemaCheckv0alpha1 app.VersionSchema + _ = json.Unmarshal(rawSchemaCheckv0alpha1, &versionSchemaCheckv0alpha1) + rawSchemaCheckTypev0alpha1 = []byte(`{"spec":{"properties":{"name":{"type":"string"},"steps":{"items":{"properties":{"description":{"type":"string"},"stepID":{"type":"string"},"title":{"type":"string"}},"required":["title","description","stepID"],"type":"object"},"type":"array"}},"required":["name","steps"],"type":"object"},"status":{"properties":{"additionalFields":{"description":"additionalFields is reserved for future use","type":"object","x-kubernetes-preserve-unknown-fields":true},"operatorStates":{"additionalProperties":{"properties":{"descriptiveState":{"description":"descriptiveState is an optional more descriptive state field which has no requirements on format","type":"string"},"details":{"description":"details contains any extra information that is operator-specific","type":"object","x-kubernetes-preserve-unknown-fields":true},"lastEvaluation":{"description":"lastEvaluation is the ResourceVersion last evaluated","type":"string"},"state":{"description":"state describes the state of the lastEvaluation.\nIt is limited to three possible states for machine evaluation.","enum":["success","in_progress","failed"],"type":"string"}},"required":["lastEvaluation","state"],"type":"object"},"description":"operatorStates is a map of operator ID to operator state evaluations.\nAny operator which consumes this kind SHOULD add its state evaluation information to this field.","type":"object"}},"type":"object","x-kubernetes-preserve-unknown-fields":true}}`) + versionSchemaCheckTypev0alpha1 app.VersionSchema + _ = json.Unmarshal(rawSchemaCheckTypev0alpha1, &versionSchemaCheckTypev0alpha1) ) var appManifestData = app.ManifestData{ @@ -40,6 +43,18 @@ var appManifestData = app.ManifestData{ }, }, }, + + { + Kind: "CheckType", + Scope: "Namespaced", + Conversion: false, + Versions: []app.ManifestKindVersion{ + { + Name: "v0alpha1", + Schema: &versionSchemaCheckTypev0alpha1, + }, + }, + }, }, } diff --git a/apps/advisor/pkg/app/app.go b/apps/advisor/pkg/app/app.go index 554357caca2..7982661c3b9 100644 --- a/apps/advisor/pkg/app/app.go +++ b/apps/advisor/pkg/app/app.go @@ -11,6 +11,7 @@ import ( advisorv0alpha1 "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1" "github.com/grafana/grafana/apps/advisor/pkg/app/checkregistry" "github.com/grafana/grafana/apps/advisor/pkg/app/checks" + "github.com/grafana/grafana/apps/advisor/pkg/app/checktyperegisterer" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/klog/v2" ) @@ -70,6 +71,9 @@ func New(cfg app.Config) (app.App, error) { }, }, }, + { + Kind: advisorv0alpha1.CheckTypeKind(), + }, }, } @@ -83,6 +87,13 @@ func New(cfg app.Config) (app.App, error) { return nil, err } + // Save check types as resources + ctr, err := checktyperegisterer.New(cfg) + if err != nil { + return nil, err + } + a.AddRunnable(ctr) + return a, nil } @@ -95,6 +106,7 @@ func GetKinds() map[schema.GroupVersion][]resource.Kind { return map[schema.GroupVersion][]resource.Kind{ gv: { advisorv0alpha1.CheckKind(), + advisorv0alpha1.CheckTypeKind(), }, } } diff --git a/apps/advisor/pkg/app/checks/datasourcecheck/check.go b/apps/advisor/pkg/app/checks/datasourcecheck/check.go index f7dbcc53024..88e10a72719 100644 --- a/apps/advisor/pkg/app/checks/datasourcecheck/check.go +++ b/apps/advisor/pkg/app/checks/datasourcecheck/check.go @@ -86,11 +86,13 @@ func (s *uidValidationStep) Run(ctx context.Context, obj *advisor.CheckSpec, ite // Data source UID validation err := util.ValidateUID(ds.UID) if err != nil { - dsErrs = append(dsErrs, advisor.CheckReportError{ - Severity: advisor.CheckReportErrorSeverityLow, - Reason: fmt.Sprintf("Invalid UID '%s' for data source %s", ds.UID, ds.Name), - Action: "Check the documentation for more information.", - }) + dsErrs = append(dsErrs, checks.NewCheckReportError( + advisor.CheckReportErrorSeverityLow, + fmt.Sprintf("Invalid UID '%s' for data source %s", ds.UID, ds.Name), + "Check the documentation for more information.", + s.ID(), + ds.UID, + )) } } return dsErrs, nil @@ -141,13 +143,15 @@ func (s *healthCheckStep) Run(ctx context.Context, obj *advisor.CheckSpec, items continue } if resp.Status != backend.HealthStatusOk { - dsErrs = append(dsErrs, advisor.CheckReportError{ - Severity: advisor.CheckReportErrorSeverityHigh, - Reason: fmt.Sprintf("Health check failed for %s", ds.Name), - Action: fmt.Sprintf( + dsErrs = append(dsErrs, checks.NewCheckReportError( + advisor.CheckReportErrorSeverityHigh, + fmt.Sprintf("Health check failed for %s", ds.Name), + fmt.Sprintf( "Go to the data source configuration"+ " and address the issues reported.", ds.UID), - }) + s.ID(), + ds.UID, + )) } } return dsErrs, nil diff --git a/apps/advisor/pkg/app/checks/plugincheck/check.go b/apps/advisor/pkg/app/checks/plugincheck/check.go index c64bc1e7579..c024b2608b0 100644 --- a/apps/advisor/pkg/app/checks/plugincheck/check.go +++ b/apps/advisor/pkg/app/checks/plugincheck/check.go @@ -97,11 +97,13 @@ func (s *deprecationStep) Run(ctx context.Context, _ *advisor.CheckSpec, items [ continue } if i.Status == "deprecated" { - errs = append(errs, advisor.CheckReportError{ - Severity: advisor.CheckReportErrorSeverityHigh, - Reason: fmt.Sprintf("Plugin deprecated: %s", p.ID), - Action: "Check the documentation for recommended steps.", - }) + errs = append(errs, checks.NewCheckReportError( + advisor.CheckReportErrorSeverityHigh, + fmt.Sprintf("Plugin deprecated: %s", p.ID), + "Check the documentation for recommended steps.", + s.ID(), + p.ID, + )) } } return errs, nil @@ -150,13 +152,15 @@ func (s *updateStep) Run(ctx context.Context, _ *advisor.CheckSpec, items []any) continue } if hasUpdate(p, info) { - errs = append(errs, advisor.CheckReportError{ - Severity: advisor.CheckReportErrorSeverityLow, - Reason: fmt.Sprintf("New version available for %s", p.ID), - Action: fmt.Sprintf( + errs = append(errs, checks.NewCheckReportError( + advisor.CheckReportErrorSeverityLow, + fmt.Sprintf("New version available for %s", p.ID), + fmt.Sprintf( "Go to the plugin admin page"+ " and upgrade to the latest version.", p.ID), - }) + s.ID(), + p.ID, + )) } } diff --git a/apps/advisor/pkg/app/checks/utils.go b/apps/advisor/pkg/app/checks/utils.go new file mode 100644 index 00000000000..a6c4089da44 --- /dev/null +++ b/apps/advisor/pkg/app/checks/utils.go @@ -0,0 +1,21 @@ +package checks + +import ( + advisor "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1" +) + +func NewCheckReportError( + severity advisor.CheckReportErrorSeverity, + reason string, + action string, + stepID string, + itemID string, +) advisor.CheckReportError { + return advisor.CheckReportError{ + Severity: severity, + Reason: reason, + Action: action, + StepID: stepID, + ItemID: itemID, + } +} diff --git a/apps/advisor/pkg/app/checktyperegisterer/checktyperegisterer.go b/apps/advisor/pkg/app/checktyperegisterer/checktyperegisterer.go new file mode 100644 index 00000000000..fc5ff150a18 --- /dev/null +++ b/apps/advisor/pkg/app/checktyperegisterer/checktyperegisterer.go @@ -0,0 +1,82 @@ +package checktyperegisterer + +import ( + "context" + "fmt" + + "github.com/grafana/grafana-app-sdk/app" + "github.com/grafana/grafana-app-sdk/k8s" + "github.com/grafana/grafana-app-sdk/resource" + advisorv0alpha1 "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1" + "github.com/grafana/grafana/apps/advisor/pkg/app/checkregistry" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// Runner is a "runnable" app used to be able to expose and API endpoint +// with the existing checks types. This does not need to be a CRUD resource, but it is +// the only way existing at the moment to expose the check types. +type Runner struct { + checkRegistry checkregistry.CheckService + client resource.Client +} + +// NewRunner creates a new Runner. +func New(cfg app.Config) (app.Runnable, error) { + // Read config + checkRegistry, ok := cfg.SpecificConfig.(checkregistry.CheckService) + if !ok { + return nil, fmt.Errorf("invalid config type") + } + + // Prepare storage client + clientGenerator := k8s.NewClientRegistry(cfg.KubeConfig, k8s.ClientConfig{}) + client, err := clientGenerator.ClientFor(advisorv0alpha1.CheckTypeKind()) + if err != nil { + return nil, err + } + + return &Runner{ + checkRegistry: checkRegistry, + client: client, + }, nil +} + +func (r *Runner) Run(ctx context.Context) error { + for _, t := range r.checkRegistry.Checks() { + steps := t.Steps() + stepTypes := make([]advisorv0alpha1.CheckTypeStep, len(steps)) + for i, s := range steps { + stepTypes[i] = advisorv0alpha1.CheckTypeStep{ + Title: s.Title(), + Description: s.Description(), + StepID: s.ID(), + } + } + obj := &advisorv0alpha1.CheckType{ + ObjectMeta: metav1.ObjectMeta{ + Name: t.ID(), + Namespace: metav1.NamespaceDefault, + }, + Spec: advisorv0alpha1.CheckTypeSpec{ + Name: t.ID(), + Steps: stepTypes, + }, + } + id := obj.GetStaticMetadata().Identifier() + _, err := r.client.Create(ctx, id, obj, resource.CreateOptions{}) + if err != nil { + if errors.IsAlreadyExists(err) { + // Already exists, update + _, err = r.client.Update(ctx, id, obj, resource.UpdateOptions{}) + if err != nil { + return err + } else { + continue + } + } + return err + } + } + return nil +} diff --git a/apps/advisor/pkg/app/checktyperegisterer/checktyperegisterer_test.go b/apps/advisor/pkg/app/checktyperegisterer/checktyperegisterer_test.go new file mode 100644 index 00000000000..e10ecab9876 --- /dev/null +++ b/apps/advisor/pkg/app/checktyperegisterer/checktyperegisterer_test.go @@ -0,0 +1,169 @@ +package checktyperegisterer + +import ( + "context" + "errors" + "testing" + + "github.com/grafana/grafana-app-sdk/resource" + "github.com/grafana/grafana/apps/advisor/pkg/app/checks" + k8sErrs "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +func TestCheckTypesRegisterer_Run(t *testing.T) { + tests := []struct { + name string + checks []checks.Check + createFunc func(ctx context.Context, id resource.Identifier, obj resource.Object, opts resource.CreateOptions) (resource.Object, error) + updateFunc func(ctx context.Context, id resource.Identifier, obj resource.Object, opts resource.UpdateOptions) (resource.Object, error) + expectedErr error + }{ + { + name: "successful create", + checks: []checks.Check{ + &mockCheck{ + id: "check1", + steps: []checks.Step{ + &mockStep{id: "step1", title: "Step 1", description: "Description 1"}, + }, + }, + }, + createFunc: func(ctx context.Context, id resource.Identifier, obj resource.Object, opts resource.CreateOptions) (resource.Object, error) { + return obj, nil + }, + updateFunc: nil, + expectedErr: nil, + }, + { + name: "create already exists, successful update", + checks: []checks.Check{ + &mockCheck{ + id: "check1", + steps: []checks.Step{ + &mockStep{id: "step1", title: "Step 1", description: "Description 1"}, + }, + }, + }, + createFunc: func(ctx context.Context, id resource.Identifier, obj resource.Object, opts resource.CreateOptions) (resource.Object, error) { + return nil, k8sErrs.NewAlreadyExists(schema.GroupResource{}, obj.GetName()) + }, + updateFunc: func(ctx context.Context, id resource.Identifier, obj resource.Object, opts resource.UpdateOptions) (resource.Object, error) { + return obj, nil + }, + expectedErr: nil, + }, + { + name: "create error", + checks: []checks.Check{ + &mockCheck{ + id: "check1", + steps: []checks.Step{ + &mockStep{id: "step1", title: "Step 1", description: "Description 1"}, + }, + }, + }, + createFunc: func(ctx context.Context, id resource.Identifier, obj resource.Object, opts resource.CreateOptions) (resource.Object, error) { + return nil, errors.New("create error") + }, + updateFunc: nil, + expectedErr: errors.New("create error"), + }, + { + name: "update error", + checks: []checks.Check{ + &mockCheck{ + id: "check1", + steps: []checks.Step{ + &mockStep{id: "step1", title: "Step 1", description: "Description 1"}, + }, + }, + }, + createFunc: func(ctx context.Context, id resource.Identifier, obj resource.Object, opts resource.CreateOptions) (resource.Object, error) { + return nil, k8sErrs.NewAlreadyExists(schema.GroupResource{}, obj.GetName()) + }, + updateFunc: func(ctx context.Context, id resource.Identifier, obj resource.Object, opts resource.UpdateOptions) (resource.Object, error) { + return nil, errors.New("update error") + }, + expectedErr: errors.New("update error"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := &Runner{ + checkRegistry: &mockCheckRegistry{checks: tt.checks}, + client: &mockClient{ + createFunc: tt.createFunc, + updateFunc: tt.updateFunc, + }, + } + err := r.Run(context.Background()) + if err != nil { + if tt.expectedErr == nil { + t.Errorf("unexpected error: %v", err) + } else if err.Error() != tt.expectedErr.Error() { + t.Errorf("expected error: %v, got: %v", tt.expectedErr, err) + } + } + }) + } +} + +type mockCheckRegistry struct { + checks []checks.Check +} + +func (m *mockCheckRegistry) Checks() []checks.Check { + return m.checks +} + +type mockCheck struct { + checks.Check + + id string + steps []checks.Step +} + +func (m *mockCheck) ID() string { + return m.id +} + +func (m *mockCheck) Steps() []checks.Step { + return m.steps +} + +type mockStep struct { + checks.Step + + id string + title string + description string +} + +func (m *mockStep) ID() string { + return m.id +} + +func (m *mockStep) Title() string { + return m.title +} + +func (m *mockStep) Description() string { + return m.description +} + +type mockClient struct { + resource.Client + + createFunc func(ctx context.Context, id resource.Identifier, obj resource.Object, opts resource.CreateOptions) (resource.Object, error) + updateFunc func(ctx context.Context, id resource.Identifier, obj resource.Object, opts resource.UpdateOptions) (resource.Object, error) +} + +func (m *mockClient) Create(ctx context.Context, id resource.Identifier, obj resource.Object, opts resource.CreateOptions) (resource.Object, error) { + return m.createFunc(ctx, id, obj, opts) +} + +func (m *mockClient) Update(ctx context.Context, id resource.Identifier, obj resource.Object, opts resource.UpdateOptions) (resource.Object, error) { + return m.updateFunc(ctx, id, obj, opts) +} From 57e30633e9f34a2850201824eb623d67a2e1ba8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ida=20=C5=A0tambuk?= Date: Thu, 6 Feb 2025 15:02:34 +0100 Subject: [PATCH 12/34] Docs: Add a note on query caching for Cloudwatch datasource (#100180) --- .../sources/administration/data-source-management/_index.md | 4 ++++ docs/sources/datasources/aws-cloudwatch/_index.md | 6 +++++- .../datasources/aws-cloudwatch/query-editor/index.md | 4 ++-- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/docs/sources/administration/data-source-management/_index.md b/docs/sources/administration/data-source-management/_index.md index 71c5d87d625..ce91fc46eae 100644 --- a/docs/sources/administration/data-source-management/_index.md +++ b/docs/sources/administration/data-source-management/_index.md @@ -110,6 +110,10 @@ By reducing the number of queries and requests sent to data sources, caching can Query caching works for Grafana's [built-in data sources]({{< relref "../../datasources/#built-in-core-data-sources" >}}), and [backend data source plugins](https://grafana.com/grafana/plugins/?type=datasource) that extend the `DataSourceWithBackend` class in the plugins SDK. +{{% admonition type="note" %}} +Logs Insights for the CloudWatch data source does not support query caching due to the way logs are requested from AWS. +{{% /admonition %}} + To verify that a data source works with query caching, follow the [instructions below](#enable-and-configure-query-caching) to **Enable and Configure query caching**. If caching is enabled in Grafana but the Caching tab is not visible for the given data source, then query caching is not available for that data source. {{% admonition type="note" %}} diff --git a/docs/sources/datasources/aws-cloudwatch/_index.md b/docs/sources/datasources/aws-cloudwatch/_index.md index 6e5d3a0618e..414e770d2bc 100644 --- a/docs/sources/datasources/aws-cloudwatch/_index.md +++ b/docs/sources/datasources/aws-cloudwatch/_index.md @@ -356,6 +356,10 @@ The CloudWatch data source can query data from both CloudWatch metrics and Cloud For details, see the [query editor documentation]({{< relref "./query-editor" >}}). +## Query caching + +When you enable [query and resource caching]({{< relref "/administration/data-source-management/#query-and-resource-caching" >}}), Grafana temporarily stores the results of data source queries and resource requests. Query caching is available in CloudWatch Metrics in Grafana Cloud and Grafana Enterprise. It is not available in CloudWatch Logs Insights due to how query results are polled from AWS. + ## Use template variables Instead of hard-coding details such as server, application, and sensor names in metric queries, you can use variables. @@ -438,7 +442,7 @@ For more information, refer to the AWS documentation for [Service Quotas](https: The CloudWatch plugin enables you to monitor and troubleshoot applications across multiple regional accounts. Using cross-account observability, you can seamlessly search, visualize and analyze metrics and logs without worrying about account boundaries. -To use this feature, configure in the [AWS console under Cloudwatch Settings](https://aws.amazon.com/blogs/aws/new-amazon-cloudwatch-cross-account-observability/), a monitoring and source account, and then add the necessary IAM permissions as described above. +To use this feature, configure in the [AWS console under CloudWatch Settings](https://aws.amazon.com/blogs/aws/new-amazon-cloudwatch-cross-account-observability/), a monitoring and source account, and then add the necessary IAM permissions as described above. ## CloudWatch Logs data protection diff --git a/docs/sources/datasources/aws-cloudwatch/query-editor/index.md b/docs/sources/datasources/aws-cloudwatch/query-editor/index.md index b65bd308e77..05b5eb3a7f4 100644 --- a/docs/sources/datasources/aws-cloudwatch/query-editor/index.md +++ b/docs/sources/datasources/aws-cloudwatch/query-editor/index.md @@ -226,7 +226,7 @@ The label field allows you to override the default name of the metric legend usi ## Query CloudWatch Logs The logs query editor helps you write CloudWatch Logs Query Language queries across defined regions and log groups. -It supports querying Cloudwatch logs with Logs Insights Query Language, OpenSearch PPL and OpenSearch SQL. +It supports querying CloudWatch logs with Logs Insights Query Language, OpenSearch PPL and OpenSearch SQL. ### Create a CloudWatch Logs query @@ -237,7 +237,7 @@ It supports querying Cloudwatch logs with Logs Insights Query Language, OpenSear Region and log groups are mandatory fields when querying with Logs Insights QL and OpenSearch PPL. Log group selection is not necessary when querying with OpenSearch SQL. However, selecting log groups simplifies writing logs queries by populating syntax suggestions with discovered log group fields. {{< /admonition >}} -1. Use the main input area to write your logs query. AWS Cloudwatch only supports a subset of OpenSearch SQL and PPL commands. To find out more about the syntax supported, consult [Amazon CloudWatch Logs documentation](https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/CWL_AnalyzeLogData_Languages.html) +1. Use the main input area to write your logs query. AWS CloudWatch only supports a subset of OpenSearch SQL and PPL commands. To find out more about the syntax supported, consult [Amazon CloudWatch Logs documentation](https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/CWL_AnalyzeLogData_Languages.html) #### Querying Log groups with OpenSearch SQL From 98e3237ce27455700f802cd4dc7318e3cdd1f55c Mon Sep 17 00:00:00 2001 From: Eric Leijonmarck Date: Thu, 6 Feb 2025 14:05:52 +0000 Subject: [PATCH 13/34] LBAC for data sources: Update to indicate that we have `experimental` and `GA` (#100187) update confusing docs --- .../data-source-management/teamlbac/_index.md | 9 ++------- .../teamlbac/configure-teamlbac-for-prometheus/index.md | 2 +- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/docs/sources/administration/data-source-management/teamlbac/_index.md b/docs/sources/administration/data-source-management/teamlbac/_index.md index 170104fab71..74470c792bf 100644 --- a/docs/sources/administration/data-source-management/teamlbac/_index.md +++ b/docs/sources/administration/data-source-management/teamlbac/_index.md @@ -20,7 +20,7 @@ Label-Based Access Control (LBAC) allows fine-grained access control to data sou ## Supported Data Sources -LBAC for data sources is currently available for `Loki, Prometheus` with basic authentication. Support for additional data sources may be added in future updates. +LBAC for data sources is currently generally available for `Loki` and in **experimental** for `Prometheus`. Support for additional data sources may be added in future updates. **LBAC for data sources offers:** @@ -28,11 +28,6 @@ LBAC for data sources is currently available for `Loki, Prometheus` with basic a - Simplified data source management by consolidating multiple sources into one. - Dashboard reuse across teams with tailored access. -{{< admonition type="note" >}} -LBAC rules is available for **private preview** in Grafana Cloud. -Report any unexpected behavior to the Grafana Support team. -{{< /admonition >}} - You can configure user access based upon team memberships using `LogQL`. LBAC for data sources controls access to logs or metrics depending on the rules set for each team. @@ -57,7 +52,7 @@ This flexibility allows teams to use the same data source for multiple use cases ## Before you begin -To be able to use LBAC for data sources, you need to enable the feature toggle `teamHttpHeaders` on your Grafana instance. +To be able to use LBAC for data sources metrics, you need to enable the feature toggle `teamHttpHeadersMimir` on your Grafana instance. ## Limitations diff --git a/docs/sources/administration/data-source-management/teamlbac/configure-teamlbac-for-prometheus/index.md b/docs/sources/administration/data-source-management/teamlbac/configure-teamlbac-for-prometheus/index.md index aa54d970e6f..1cc06175900 100644 --- a/docs/sources/administration/data-source-management/teamlbac/configure-teamlbac-for-prometheus/index.md +++ b/docs/sources/administration/data-source-management/teamlbac/configure-teamlbac-for-prometheus/index.md @@ -22,7 +22,7 @@ You cannot configure LBAC rules for Grafana-provisioned data sources from the UI ## Before you begin -To be able to use LBAC for data sources rules, you need to enable the feature toggle `teamHttpHeaders` on your Grafana instance. Contact support to enable the feature toggle for you. +To be able to use LBAC for data sources rules, you need to enable the feature toggle `teamHttpHeadersMimir` on your Grafana instance. Contact support to enable the feature toggle for you. - Be sure that you have the permission setup to create a Prometheus tenant in Grafana Cloud - Be sure that you have admin data source permissions for Grafana. From d3ce9e1fe2240d213815f514cfbf7eb7ad9fd36f Mon Sep 17 00:00:00 2001 From: Jacob Valdez Date: Thu, 6 Feb 2025 08:24:30 -0600 Subject: [PATCH 14/34] Docs: adding actions_allow_post_url example to plugin docs (#96157) --- .../administration/plugin-management/index.md | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/docs/sources/administration/plugin-management/index.md b/docs/sources/administration/plugin-management/index.md index 09566ccf250..21e048ed9ea 100644 --- a/docs/sources/administration/plugin-management/index.md +++ b/docs/sources/administration/plugin-management/index.md @@ -66,7 +66,7 @@ To prevent users from seeing an app plugin, refer to [these permissions scenario ## Plugin catalog -The Grafana plugin catalog allows you to browse and manage plugins from within Grafana. Only Grafana server administrators and Organization administrators can access and use the plugin catalog. For more information about Grafana roles and permissions, refer to [Roles and permissions]({{< relref "../administration/roles-and-permissions" >}}). +The Grafana plugin catalog allows you to browse and manage plugins from within Grafana. Only Grafana server administrators and Organization administrators can access and use the plugin catalog. For more information about Grafana roles and permissions, refer to [Roles and permissions]({{< relref "../roles-and-permissions" >}}). The following access rules apply depending on the user role: @@ -236,6 +236,32 @@ WARN[06-01|16:45:59] Running an unsigned plugin pluginID= If you're developing a plugin, then you can enable development mode to allow all unsigned plugins. {{% /admonition %}} +## Integrate plugins + +You can configure your Grafana instance to let the frontends of installed plugins directly communicate locally with the backends of other installed plugins. By default, you can only communicate with plugin backends remotely. You can use this configuration to, for example, enable a [canvas panel](https://grafana.com/docs/grafana/latest/panels-visualizations/visualizations/canvas/) to call an application resource API that is permitted by the `actions_allow_post_url` option. + +To enable backend communication between plugins: + +1. Set the plugins you want to communicate with. In your configuration file (`grafana.ini` or `custom.ini` depending on your operating system) remove the semicolon to enable and then set the following configuration option: + + ``` + actions_allow_post_url= + ``` + + This is a comma-separated list that uses glob matching. + + - To allow access to all plugins that have a backend: + + ``` + actions_allow_post_url=/api/plugins/* + ``` + + - To access to the backend of only one plugin: + + ``` + actions_allow_post_url=/api/plugins/ + ``` + ## Plugin Frontend Sandbox {{% admonition type="caution" %}} From ae33d4903657a2b08a9e7ca74b5f4f14edf69b20 Mon Sep 17 00:00:00 2001 From: Mariell Hoversholm Date: Thu, 6 Feb 2025 15:29:54 +0100 Subject: [PATCH 15/34] Advisor: Assert new fields (#100199) --- apps/advisor/pkg/app/checks/plugincheck/check_test.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/apps/advisor/pkg/app/checks/plugincheck/check_test.go b/apps/advisor/pkg/app/checks/plugincheck/check_test.go index 53450ce7bc5..29dc7ccaabd 100644 --- a/apps/advisor/pkg/app/checks/plugincheck/check_test.go +++ b/apps/advisor/pkg/app/checks/plugincheck/check_test.go @@ -44,6 +44,8 @@ func TestRun(t *testing.T) { Severity: advisor.CheckReportErrorSeverityHigh, Reason: "Plugin deprecated: plugin1", Action: "Check the documentation for recommended steps.", + StepID: "deprecation", + ItemID: "plugin1", }, }, }, @@ -63,6 +65,8 @@ func TestRun(t *testing.T) { Severity: advisor.CheckReportErrorSeverityLow, Reason: "New version available for plugin2", Action: "Go to the plugin admin page and upgrade to the latest version.", + StepID: "update", + ItemID: "plugin2", }, }, }, @@ -82,6 +86,8 @@ func TestRun(t *testing.T) { Severity: advisor.CheckReportErrorSeverityLow, Reason: "New version available for plugin2", Action: "Go to the plugin admin page and upgrade to the latest version.", + StepID: "update", + ItemID: "plugin2", }, }, }, From f1eac34b54bceb70e2bca5454360b006cf3a0c77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 6 Feb 2025 15:30:54 +0100 Subject: [PATCH 16/34] Dashboard: Simpify is empty handling (#100189) * Dashboard: Simpify is empty handling * remove unused imports * Update * Update * Update * add code for other layouts --------- Co-authored-by: Victor Marin --- .../dashboard-scene/scene/DashboardScene.tsx | 2 -- .../scene/DashboardSceneRenderer.tsx | 6 +----- .../DefaultGridLayoutManager.tsx | 13 ++++++++++++ .../ResponsiveGridLayoutManager.tsx | 20 ++++++++++++++++++- .../scene/layout-rows/RowsLayoutManager.tsx | 10 ++++++++++ .../transformSaveModelSchemaV2ToScene.ts | 17 +--------------- .../transformSaveModelToScene.ts | 17 +--------------- 7 files changed, 45 insertions(+), 40 deletions(-) diff --git a/public/app/features/dashboard-scene/scene/DashboardScene.tsx b/public/app/features/dashboard-scene/scene/DashboardScene.tsx index 29c9380039c..14f289bf559 100644 --- a/public/app/features/dashboard-scene/scene/DashboardScene.tsx +++ b/public/app/features/dashboard-scene/scene/DashboardScene.tsx @@ -128,8 +128,6 @@ export interface DashboardSceneState extends SceneObjectState { editPanel?: PanelEditor; /** Scene object that handles the current drawer or modal */ overlay?: SceneObject; - /** The dashboard doesn't have panels */ - isEmpty?: boolean; /** Kiosk mode */ kioskMode?: KioskMode; /** Share view */ diff --git a/public/app/features/dashboard-scene/scene/DashboardSceneRenderer.tsx b/public/app/features/dashboard-scene/scene/DashboardSceneRenderer.tsx index 794cbe2b2d8..e30068924e7 100644 --- a/public/app/features/dashboard-scene/scene/DashboardSceneRenderer.tsx +++ b/public/app/features/dashboard-scene/scene/DashboardSceneRenderer.tsx @@ -5,7 +5,6 @@ import { PageLayoutType } from '@grafana/data'; import { SceneComponentProps } from '@grafana/scenes'; import { Page } from 'app/core/components/Page/Page'; import { getNavModel } from 'app/core/selectors/navModel'; -import DashboardEmpty from 'app/features/dashboard/dashgrid/DashboardEmpty'; import { useSelector } from 'app/types'; import { DashboardEditPaneSplitter } from '../edit-pane/DashboardEditPaneSplitter'; @@ -15,7 +14,7 @@ import { PanelSearchLayout } from './PanelSearchLayout'; import { DashboardAngularDeprecationBanner } from './angular/DashboardAngularDeprecationBanner'; export function DashboardSceneRenderer({ model }: SceneComponentProps) { - const { controls, overlay, editview, editPanel, isEmpty, viewPanelScene, panelSearch, panelsPerRow, isEditing } = + const { controls, overlay, editview, editPanel, viewPanelScene, panelSearch, panelsPerRow, isEditing } = model.useState(); const { type } = useParams(); const location = useLocation(); @@ -56,9 +55,6 @@ export function DashboardSceneRenderer({ model }: SceneComponentProps - {isEmpty && ( - - )} ); diff --git a/public/app/features/dashboard-scene/scene/layout-default/DefaultGridLayoutManager.tsx b/public/app/features/dashboard-scene/scene/layout-default/DefaultGridLayoutManager.tsx index c7389618108..a1416c5deb0 100644 --- a/public/app/features/dashboard-scene/scene/layout-default/DefaultGridLayoutManager.tsx +++ b/public/app/features/dashboard-scene/scene/layout-default/DefaultGridLayoutManager.tsx @@ -9,9 +9,11 @@ import { sceneUtils, SceneComponentProps, SceneGridItemLike, + useSceneObjectState, } from '@grafana/scenes'; import { GRID_COLUMN_COUNT } from 'app/core/constants'; import { t } from 'app/core/internationalization'; +import DashboardEmpty from 'app/features/dashboard/dashgrid/DashboardEmpty'; import { isClonedKey, joinCloneKeys } from '../../utils/clone'; import { dashboardSceneGraph } from '../../utils/dashboardSceneGraph'; @@ -22,6 +24,7 @@ import { NEW_PANEL_WIDTH, getVizPanelKeyForPanelId, getGridItemKeyForPanelId, + getDashboardSceneFor, } from '../../utils/utils'; import { DashboardLayoutManager } from '../types/DashboardLayoutManager'; @@ -447,6 +450,16 @@ export class DefaultGridLayoutManager } public static Component = ({ model }: SceneComponentProps) => { + const { children } = useSceneObjectState(model.state.grid, { shouldActivateOrKeepAlive: true }); + const dashboard = getDashboardSceneFor(model); + + // If we are top level layout and have no children, show empty state + if (model.parent === dashboard && children.length === 0) { + return ( + + ); + } + return ; }; } diff --git a/public/app/features/dashboard-scene/scene/layout-responsive-grid/ResponsiveGridLayoutManager.tsx b/public/app/features/dashboard-scene/scene/layout-responsive-grid/ResponsiveGridLayoutManager.tsx index 91fcbec0a48..f936d8617eb 100644 --- a/public/app/features/dashboard-scene/scene/layout-responsive-grid/ResponsiveGridLayoutManager.tsx +++ b/public/app/features/dashboard-scene/scene/layout-responsive-grid/ResponsiveGridLayoutManager.tsx @@ -1,8 +1,16 @@ import { SelectableValue } from '@grafana/data'; -import { SceneComponentProps, SceneCSSGridLayout, SceneObjectBase, SceneObjectState, VizPanel } from '@grafana/scenes'; +import { + SceneComponentProps, + SceneCSSGridLayout, + SceneObjectBase, + SceneObjectState, + useSceneObjectState, + VizPanel, +} from '@grafana/scenes'; import { Select } from '@grafana/ui'; import { t } from 'app/core/internationalization'; import { OptionsPaneItemDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor'; +import DashboardEmpty from 'app/features/dashboard/dashgrid/DashboardEmpty'; import { dashboardSceneGraph } from '../../utils/dashboardSceneGraph'; import { getDashboardSceneFor, getGridItemKeyForPanelId, getVizPanelKeyForPanelId } from '../../utils/utils'; @@ -140,6 +148,16 @@ export class ResponsiveGridLayoutManager public activateRepeaters(): void {} public static Component = ({ model }: SceneComponentProps) => { + const { children } = useSceneObjectState(model.state.layout, { shouldActivateOrKeepAlive: true }); + const dashboard = getDashboardSceneFor(model); + + // If we are top level layout and have no children, show empty state + if (model.parent === dashboard && children.length === 0) { + return ( + + ); + } + return ; }; } diff --git a/public/app/features/dashboard-scene/scene/layout-rows/RowsLayoutManager.tsx b/public/app/features/dashboard-scene/scene/layout-rows/RowsLayoutManager.tsx index dc9154d3b2b..1aafc0adeb2 100644 --- a/public/app/features/dashboard-scene/scene/layout-rows/RowsLayoutManager.tsx +++ b/public/app/features/dashboard-scene/scene/layout-rows/RowsLayoutManager.tsx @@ -12,8 +12,10 @@ import { } from '@grafana/scenes'; import { useStyles2 } from '@grafana/ui'; import { t } from 'app/core/internationalization'; +import DashboardEmpty from 'app/features/dashboard/dashgrid/DashboardEmpty'; import { isClonedKey } from '../../utils/clone'; +import { getDashboardSceneFor } from '../../utils/utils'; import { DashboardScene } from '../DashboardScene'; import { DashboardGridItem } from '../layout-default/DashboardGridItem'; import { DefaultGridLayoutManager } from '../layout-default/DefaultGridLayoutManager'; @@ -195,6 +197,14 @@ export class RowsLayoutManager extends SceneObjectBase i public static Component = ({ model }: SceneComponentProps) => { const { rows } = model.useState(); const styles = useStyles2(getStyles); + const dashboard = getDashboardSceneFor(model); + + // If we are top level layout and have no children, show empty state + if (model.parent === dashboard && rows.length === 0) { + return ( + + ); + } return (
diff --git a/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts b/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts index 75be7ae3a42..fd82cc7c161 100644 --- a/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts +++ b/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts @@ -88,7 +88,7 @@ import { RowRepeaterBehavior } from '../scene/layout-default/RowRepeaterBehavior import { RowActions } from '../scene/layout-default/row-actions/RowActions'; import { setDashboardPanelContext } from '../scene/setDashboardPanelContext'; import { preserveDashboardSceneStateInLocalStorage } from '../utils/dashboardSessionState'; -import { getDashboardSceneFor, getIntervalsFromQueryString, getVizPanelKeyForPanelId } from '../utils/utils'; +import { getIntervalsFromQueryString, getVizPanelKeyForPanelId } from '../utils/utils'; import { GRID_ROW_HEIGHT } from './const'; import { SnapshotVariable } from './custom-variables/SnapshotVariable'; @@ -181,7 +181,6 @@ export function transformSaveModelSchemaV2ToScene(dto: DashboardWithAccessInfo { - if (n.children.length !== p.children.length || n.children !== p.children) { - getDashboardSceneFor(grid).setState({ isEmpty: n.children.length === 0 }); - } - }); - - return () => { - sub.unsubscribe(); - }; -} - function getPanelDataSource(panel: PanelKind): DataSourceRef | undefined { if (!panel.spec.data?.spec.queries?.length) { return undefined; diff --git a/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts b/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts index 77960f558ce..b5ca18ea717 100644 --- a/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts +++ b/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts @@ -50,7 +50,7 @@ import { setDashboardPanelContext } from '../scene/setDashboardPanelContext'; import { createPanelDataProvider } from '../utils/createPanelDataProvider'; import { preserveDashboardSceneStateInLocalStorage } from '../utils/dashboardSessionState'; import { DashboardInteractions } from '../utils/interactions'; -import { getDashboardSceneFor, getVizPanelKeyForPanelId } from '../utils/utils'; +import { getVizPanelKeyForPanelId } from '../utils/utils'; import { createVariablesForDashboard, createVariablesForSnapshot } from '../utils/variables'; import { getAngularPanelMigrationHandler } from './angularMigration'; @@ -267,7 +267,6 @@ export function createDashboardSceneFromDashboardModel(oldModel: DashboardModel, grid: new SceneGridLayout({ isLazy: !(dto.preload || contextSrv.user.authenticatedBy === 'render'), children: createSceneObjectsForPanels(oldModel.panels), - $behaviors: [trackIfEmpty], }), }), $timeRange: new SceneTimeRange({ @@ -426,20 +425,6 @@ export const convertOldSnapshotToScenesSnapshot = (panel: PanelModel) => { } }; -function trackIfEmpty(grid: SceneGridLayout) { - getDashboardSceneFor(grid).setState({ isEmpty: grid.state.children.length === 0 }); - - const sub = grid.subscribeToState((n, p) => { - if (n.children.length !== p.children.length || n.children !== p.children) { - getDashboardSceneFor(grid).setState({ isEmpty: n.children.length === 0 }); - } - }); - - return () => { - sub.unsubscribe(); - }; -} - function getDashboardInteractionCallback(uid: string, title: string) { return (e: SceneInteractionProfileEvent) => { let interactionType = ''; From 4c52abb6b47bd4dadebbb705b8f922bf7e84e6b3 Mon Sep 17 00:00:00 2001 From: Haris Rozajac <58232930+harisrozajac@users.noreply.github.com> Date: Thu, 6 Feb 2025 07:33:06 -0700 Subject: [PATCH 17/34] Dashboard Schema V2: Introduce __legacyStringValue and deprecate string type for query prop in QueryVariableSpec (#99716) * Introduce __legacyStringValue and deprecate string type for query * Fix tests * Fix tests * remove default * kind should default to default ds if variable doesn't have ds field * lint * getDefaultDataSourceRef should not return undefined --- .../dashboard/v2alpha0/dashboard.schema.cue | 2 +- .../src/schema/dashboard/v2alpha0/examples.ts | 8 +++++++- .../src/schema/dashboard/v2alpha0/types.gen.ts | 4 ++-- ...ansformSceneToSaveModelSchemaV2.test.ts.snap | 8 +++++++- .../sceneVariablesSetToVariables.test.ts | 7 ++++++- .../sceneVariablesSetToVariables.ts | 11 ++++++++--- .../transformSaveModelSchemaV2ToScene.ts | 9 +++++---- .../transformSceneToSaveModelSchemaV2.test.ts | 5 ++++- .../transformSceneToSaveModelSchemaV2.ts | 17 ++++++++--------- .../serialization/transformToV2TypesUtils.ts | 3 +++ .../dashboard-scene/v2schema/test-helpers.ts | 2 +- .../dashboard/api/ResponseTransformers.test.ts | 3 ++- .../dashboard/api/ResponseTransformers.ts | 16 ++++++++++++---- 13 files changed, 66 insertions(+), 29 deletions(-) diff --git a/packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.schema.cue b/packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.schema.cue index 170094040c1..e49511ddddd 100644 --- a/packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.schema.cue +++ b/packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.schema.cue @@ -651,7 +651,7 @@ QueryVariableSpec: { skipUrlSync: bool | *false description?: string datasource?: DataSourceRef - query: string | DataQueryKind | *"" + query: DataQueryKind regex: string | *"" sort: VariableSort definition?: string diff --git a/packages/grafana-schema/src/schema/dashboard/v2alpha0/examples.ts b/packages/grafana-schema/src/schema/dashboard/v2alpha0/examples.ts index 340ffeeef12..40b091f29dd 100644 --- a/packages/grafana-schema/src/schema/dashboard/v2alpha0/examples.ts +++ b/packages/grafana-schema/src/schema/dashboard/v2alpha0/examples.ts @@ -344,7 +344,13 @@ export const handyTestingSchema: DashboardV2Spec = { multi: true, name: 'queryVar', options: [], - query: 'query1', + query: { + kind: 'prometheus', + spec: { + expr: 'test-query', + refId: 'A', + }, + }, refresh: 'onDashboardLoad', regex: 'regex1', skipUrlSync: false, diff --git a/packages/grafana-schema/src/schema/dashboard/v2alpha0/types.gen.ts b/packages/grafana-schema/src/schema/dashboard/v2alpha0/types.gen.ts index 0e41bb7fedc..641cb9173cb 100644 --- a/packages/grafana-schema/src/schema/dashboard/v2alpha0/types.gen.ts +++ b/packages/grafana-schema/src/schema/dashboard/v2alpha0/types.gen.ts @@ -960,7 +960,7 @@ export interface QueryVariableSpec { skipUrlSync: boolean; description?: string; datasource?: DataSourceRef; - query: string | DataQueryKind; + query: DataQueryKind; regex: string; sort: VariableSort; definition?: string; @@ -977,7 +977,7 @@ export const defaultQueryVariableSpec = (): QueryVariableSpec => ({ hide: "dontHide", refresh: "never", skipUrlSync: false, - query: "", + query: defaultDataQueryKind(), regex: "", sort: "disabled", options: [], diff --git a/public/app/features/dashboard-scene/serialization/__snapshots__/transformSceneToSaveModelSchemaV2.test.ts.snap b/public/app/features/dashboard-scene/serialization/__snapshots__/transformSceneToSaveModelSchemaV2.test.ts.snap index a62ba3c1daf..7695bba0ab6 100644 --- a/public/app/features/dashboard-scene/serialization/__snapshots__/transformSceneToSaveModelSchemaV2.test.ts.snap +++ b/public/app/features/dashboard-scene/serialization/__snapshots__/transformSceneToSaveModelSchemaV2.test.ts.snap @@ -222,7 +222,13 @@ exports[`transformSceneToSaveModelSchemaV2 should transform scene to save model "multi": true, "name": "queryVar", "options": [], - "query": "query1", + "query": { + "kind": "prometheus", + "spec": { + "expr": "label_values(node_boot_time_seconds)", + "refId": "A", + }, + }, "refresh": "onDashboardLoad", "regex": "regex1", "skipUrlSync": false, diff --git a/public/app/features/dashboard-scene/serialization/sceneVariablesSetToVariables.test.ts b/public/app/features/dashboard-scene/serialization/sceneVariablesSetToVariables.test.ts index 2cbb9a03550..d7514139230 100644 --- a/public/app/features/dashboard-scene/serialization/sceneVariablesSetToVariables.test.ts +++ b/public/app/features/dashboard-scene/serialization/sceneVariablesSetToVariables.test.ts @@ -775,7 +775,12 @@ describe('sceneVariablesSetToVariables', () => { "multi": true, "name": "test", "options": [], - "query": "query", + "query": { + "kind": "fake-std", + "spec": { + "__legacyStringValue": "query", + }, + }, "refresh": "onDashboardLoad", "regex": "", "skipUrlSync": false, diff --git a/public/app/features/dashboard-scene/serialization/sceneVariablesSetToVariables.ts b/public/app/features/dashboard-scene/serialization/sceneVariablesSetToVariables.ts index bb9c410c7ec..c7d86f4c910 100644 --- a/public/app/features/dashboard-scene/serialization/sceneVariablesSetToVariables.ts +++ b/public/app/features/dashboard-scene/serialization/sceneVariablesSetToVariables.ts @@ -27,6 +27,7 @@ import { transformVariableRefreshToEnum, transformVariableHideToEnum, transformSortVariableToEnum, + LEGACY_STRING_VALUE_KEY, } from './transformToV2TypesUtils'; /** * Converts a SceneVariables object into an array of VariableModel objects. @@ -269,16 +270,20 @@ export function sceneVariablesSetToSchemaV2Variables( if (transformVariableRefreshToEnum(variable.state.refresh) === 'never' || keepQueryOptions) { options = variableValueOptionsToVariableOptions(variable.state); } - //query: DataQueryKind | string; const query = variable.state.query; let dataQuery: DataQueryKind | string; if (typeof query !== 'string') { dataQuery = { - kind: getDataQueryKind(query), + kind: variable.state.datasource?.type ?? getDataQueryKind(query), spec: getDataQuerySpec(query), }; } else { - dataQuery = query; + dataQuery = { + kind: variable.state.datasource?.type ?? getDataQueryKind(query), + spec: { + [LEGACY_STRING_VALUE_KEY]: query, + }, + }; } const queryVariable: QueryVariableKind = { kind: 'QueryVariable', diff --git a/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts b/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts index fd82cc7c161..61ed7495a8c 100644 --- a/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts +++ b/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts @@ -100,6 +100,7 @@ import { transformVariableHideToEnumV1, transformVariableRefreshToEnumV1, } from './transformToV1TypesUtils'; +import { LEGACY_STRING_VALUE_KEY } from './transformToV2TypesUtils'; const DEFAULT_DATASOURCE = 'default'; @@ -635,12 +636,12 @@ function createSceneVariableFromVariableModel(variable: TypedVariableModelV2): S } function getDataQueryForVariable(variable: QueryVariableKind) { - return typeof variable.spec.query !== 'string' - ? { + return LEGACY_STRING_VALUE_KEY in variable.spec.query.spec + ? (variable.spec.query.spec[LEGACY_STRING_VALUE_KEY] ?? '') + : { ...variable.spec.query.spec, refId: variable.spec.query.spec.refId ?? 'A', - } - : (variable.spec.query ?? ''); + }; } export function getCurrentValueForOldIntervalModel(variable: IntervalVariableKind, intervals: string[]): string { diff --git a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.test.ts b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.test.ts index 43121f54d8b..c87a4a0a687 100644 --- a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.test.ts +++ b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.test.ts @@ -225,7 +225,10 @@ describe('transformSceneToSaveModelSchemaV2', () => { hide: VariableHideV1.hideLabel, value: 'value1', text: 'text1', - query: 'query1', + query: { + expr: 'label_values(node_boot_time_seconds)', + refId: 'A', + }, definition: 'definition1', datasource: { uid: 'datasource1', type: 'prometheus' }, sort: VariableSortV1.alphabeticalDesc, diff --git a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.ts b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.ts index 67cae374214..928ce92733f 100644 --- a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.ts +++ b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.ts @@ -403,8 +403,11 @@ function getVizPanelQueries(vizPanel: VizPanel): PanelQueryKind[] { return queries; } -export function getDataQueryKind(query: SceneDataQuery): string { - // If the query has a datasource, use the datasource type, otherwise return empty kind +export function getDataQueryKind(query: SceneDataQuery | string): string { + if (typeof query === 'string') { + return getDefaultDataSourceRef()?.type ?? ''; + } + return query.datasource?.type ?? getDefaultDataSourceRef()?.type ?? ''; } @@ -616,19 +619,15 @@ export function getAnnotationQueryKind(annotationQuery: AnnotationQuery): string } } -export function getDefaultDataSourceRef(): DataSourceRef | undefined { +export function getDefaultDataSourceRef(): DataSourceRef { // we need to return the default datasource configured in the BootConfig const defaultDatasource = config.bootData.settings.defaultDatasource; // get default datasource type - const dsList = config.bootData.settings.datasources ?? {}; + const dsList = config.bootData.settings.datasources; const ds = dsList[defaultDatasource]; - if (ds) { - return { type: ds.meta.id, uid: ds.name }; // in the datasource list from bootData "id" is the type - } - - return undefined; + return { type: ds.meta.id, uid: ds.name }; // in the datasource list from bootData "id" is the type } // Function to know if the dashboard transformed is a valid DashboardV2Spec diff --git a/public/app/features/dashboard-scene/serialization/transformToV2TypesUtils.ts b/public/app/features/dashboard-scene/serialization/transformToV2TypesUtils.ts index ca7c056d752..582b6b4cfcf 100644 --- a/public/app/features/dashboard-scene/serialization/transformToV2TypesUtils.ts +++ b/public/app/features/dashboard-scene/serialization/transformToV2TypesUtils.ts @@ -19,6 +19,9 @@ import { FieldColorModeId as FieldColorModeIdV2, } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha0'; +// used for QueryVariableKind's query prop - in schema V2 we've deprecated string type and support only DataQuery +export const LEGACY_STRING_VALUE_KEY = '__legacyStringValue'; + export function transformCursorSynctoEnum(cursorSync?: DashboardCursorSyncV1): DashboardCursorSync { switch (cursorSync) { case 0: diff --git a/public/app/features/dashboard-scene/v2schema/test-helpers.ts b/public/app/features/dashboard-scene/v2schema/test-helpers.ts index 9c37f1df5d5..9f901bf67e4 100644 --- a/public/app/features/dashboard-scene/v2schema/test-helpers.ts +++ b/public/app/features/dashboard-scene/v2schema/test-helpers.ts @@ -53,7 +53,7 @@ export function validateVariable< } if (sceneVariable instanceof QueryVariable && variableKind.kind === 'QueryVariable') { expect(sceneVariable?.state.datasource).toBe(variableKind.spec.datasource); - expect(sceneVariable?.state.query).toBe(variableKind.spec.query); + expect(sceneVariable?.state.query).toEqual(variableKind.spec.query.spec); } if (sceneVariable instanceof CustomVariable && variableKind.kind === 'CustomVariable') { expect(sceneVariable?.state.query).toBe(variableKind.spec.query); diff --git a/public/app/features/dashboard/api/ResponseTransformers.test.ts b/public/app/features/dashboard/api/ResponseTransformers.test.ts index 3a677bf658e..1dfbd14eb04 100644 --- a/public/app/features/dashboard/api/ResponseTransformers.test.ts +++ b/public/app/features/dashboard/api/ResponseTransformers.test.ts @@ -19,6 +19,7 @@ import { } from 'app/features/apiserver/types'; import { getDefaultDataSourceRef } from 'app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2'; import { + LEGACY_STRING_VALUE_KEY, transformVariableHideToEnum, transformVariableRefreshToEnum, } from 'app/features/dashboard-scene/serialization/transformToV2TypesUtils'; @@ -915,7 +916,7 @@ describe('ResponseTransformers', () => { expect(v2.spec.datasource).toEqual(v1.datasource); if (typeof v1.query === 'string') { - expect(v2.spec.query).toEqual(v1.query); + expect(v2.spec.query.spec[LEGACY_STRING_VALUE_KEY]).toEqual(v1.query); } else { expect(v2.spec.query).toEqual({ kind: v1.datasource?.type, diff --git a/public/app/features/dashboard/api/ResponseTransformers.ts b/public/app/features/dashboard/api/ResponseTransformers.ts index 535265f606d..1b72d6b3c08 100644 --- a/public/app/features/dashboard/api/ResponseTransformers.ts +++ b/public/app/features/dashboard/api/ResponseTransformers.ts @@ -62,6 +62,7 @@ import { transformVariableRefreshToEnumV1, } from 'app/features/dashboard-scene/serialization/transformToV1TypesUtils'; import { + LEGACY_STRING_VALUE_KEY, transformCursorSynctoEnum, transformDataTopic, transformSortVariableToEnum, @@ -504,8 +505,12 @@ function getVariables(vars: TypedVariableModel[]): DashboardV2Spec['variables'] let query = v.query || {}; if (typeof query === 'string') { - console.error('Query variable query is a string. It needs to extend DataQuery.'); - query = {}; + console.warn( + 'Query variable query is a string which is deprecated in the schema v2. It should extend DataQuery' + ); + query = { + [LEGACY_STRING_VALUE_KEY]: query, + }; } const qv: QueryVariableKind = { @@ -527,7 +532,7 @@ function getVariables(vars: TypedVariableModel[]): DashboardV2Spec['variables'] query: { kind: v.datasource?.type || getDefaultDatasourceType(), spec: { - ...query, + ...v.query, }, }, }, @@ -708,7 +713,10 @@ function getVariablesV1(vars: DashboardV2Spec['variables']): VariableModel[] { ...commonProperties, current: v.spec.current, options: v.spec.options, - query: typeof v.spec.query === 'string' ? v.spec.query : v.spec.query.spec, + query: + LEGACY_STRING_VALUE_KEY in v.spec.query.spec + ? v.spec.query.spec[LEGACY_STRING_VALUE_KEY] + : v.spec.query.spec, datasource: v.spec.datasource, sort: transformSortVariableToEnumV1(v.spec.sort), refresh: transformVariableRefreshToEnumV1(v.spec.refresh), From 21bfdd445f54e62a006a9f6552f5465d1a4fb1a3 Mon Sep 17 00:00:00 2001 From: Nathan Marrs Date: Thu, 6 Feb 2025 08:01:11 -0700 Subject: [PATCH 18/34] Image Renderer: Minor refactor cleanup (#100089) --- pkg/services/rendering/rendering.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/pkg/services/rendering/rendering.go b/pkg/services/rendering/rendering.go index 744f5e09e18..89370542de1 100644 --- a/pkg/services/rendering/rendering.go +++ b/pkg/services/rendering/rendering.go @@ -424,7 +424,7 @@ func (rs *RenderingService) getNewFilePath(rt RenderType) (string, error) { // getGrafanaCallbackURL creates a URL to send to the image rendering as callback for rendering a Grafana resource func (rs *RenderingService) getGrafanaCallbackURL(path string) string { - if rs.Cfg.RendererUrl != "" { + if rs.Cfg.RendererUrl != "" || rs.Cfg.RendererCallbackUrl != "" { // The backend rendering service can potentially be remote. // So we need to use the root_url to ensure the rendering service // can reach this Grafana instance. @@ -433,11 +433,6 @@ func (rs *RenderingService) getGrafanaCallbackURL(path string) string { return fmt.Sprintf("%s%s&render=1", rs.Cfg.RendererCallbackUrl, path) } - if rs.Cfg.RendererCallbackUrl != "" { - // &render=1 signals to the legacy redirect layer to - return fmt.Sprintf("%s%s&render=1", rs.Cfg.RendererCallbackUrl, path) - } - protocol := rs.Cfg.Protocol switch protocol { case setting.HTTPScheme: From 05ea450dd28606e104f8c36a042237015084a16c Mon Sep 17 00:00:00 2001 From: Andres Martinez Gotor Date: Thu, 6 Feb 2025 16:01:50 +0100 Subject: [PATCH 19/34] Drone: Add apps directory for backend tests (#100204) --- .drone.yml | 4 +++- scripts/drone/events/pr.star | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.drone.yml b/.drone.yml index b00daa6bcaf..f48a891415e 100644 --- a/.drone.yml +++ b/.drone.yml @@ -489,6 +489,7 @@ trigger: - public/app/plugins/**/plugin.json - docs/sources/setup-grafana/configure-grafana/feature-toggles/** - devenv/** + - apps/** type: docker volumes: - host: @@ -598,6 +599,7 @@ trigger: - public/app/plugins/**/plugin.json - devenv/** - .bingo/** + - apps/** type: docker volumes: - host: @@ -5599,6 +5601,6 @@ kind: secret name: gcr_credentials --- kind: signature -hmac: abd48dfdec08a719b414fba06b06dfc97560df31d81751cd833b5fdf5b7f9937 +hmac: e1f198d994216d163d6a710aef2f6091572eb624081c550a5db42208ef37827b ... diff --git a/scripts/drone/events/pr.star b/scripts/drone/events/pr.star index 9a57ec4cc53..7d42325bef3 100644 --- a/scripts/drone/events/pr.star +++ b/scripts/drone/events/pr.star @@ -116,6 +116,7 @@ def pr_pipelines(): "public/app/plugins/**/plugin.json", "docs/sources/setup-grafana/configure-grafana/feature-toggles/**", "devenv/**", + "apps/**", ], ), ver_mode, @@ -134,6 +135,7 @@ def pr_pipelines(): "public/app/plugins/**/plugin.json", "devenv/**", ".bingo/**", + "apps/**", ], ), ver_mode, From 1bf53e7a5fb9020d6d7b64fed360e9e4c6dfdab9 Mon Sep 17 00:00:00 2001 From: Haris Rozajac <58232930+harisrozajac@users.noreply.github.com> Date: Thu, 6 Feb 2025 08:33:18 -0700 Subject: [PATCH 20/34] Dashboard Schema V2: E2E setup (#99843) * basic setup * update CODEOWNERS * update name * add temp test that ensures we are loading schema V2 json in the UI * update language * test with yarn cache and combine steps * revert combine * remove commented out code * Run current dashboard suite, make workflow optional * make job always succeed * Remove temp v2 suite * don't run on draft PRs * command for old arch --- .github/CODEOWNERS | 1 + .github/workflows/run-schema-v2-e2e.yml | 44 +++++++++++++++++++++++++ e2e/cypress/support/e2e.js | 5 +++ e2e/run-suite | 19 +++++++++++ package.json | 3 +- 5 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/run-schema-v2-e2e.yml diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index cd0bde5129f..8d87433f7b9 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -791,6 +791,7 @@ embed.go @grafana/grafana-as-code /.github/workflows/publish-kinds-release.yml @grafana/platform-monitoring /.github/workflows/verify-kinds.yml @grafana/platform-monitoring /.github/workflows/dashboards-issue-add-label.yml @grafana/dashboards-squad +/.github/workflows/run-schema-v2-e2e.yml @grafana/dashboards-squad /.github/workflows/ephemeral-instances-pr-comment.yml @grafana/grafana-backend-services-squad /.github/workflows/create-security-patch-from-security-mirror.yml @grafana/grafana-developer-enablement-squad /.github/workflows/core-plugins-build-and-release.yml @grafana/plugins-platform-frontend @grafana/plugins-platform-backend diff --git a/.github/workflows/run-schema-v2-e2e.yml b/.github/workflows/run-schema-v2-e2e.yml new file mode 100644 index 00000000000..8b55aa4c430 --- /dev/null +++ b/.github/workflows/run-schema-v2-e2e.yml @@ -0,0 +1,44 @@ +name: Run dashboard schema v2 e2e + +on: + push: + branches: + - main + pull_request: + branches: + - '**' + +env: + ARCH: linux-amd64 + +jobs: + dashboard-schema-v2-e2e: + runs-on: ubuntu-latest + continue-on-error: true + if: github.event.pull_request.draft == false + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Pin Go version to mod file + uses: actions/setup-go@v5 + with: + go-version-file: 'go.mod' + - run: go version + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: 'yarn' + - name: Install dependencies + run: yarn install --immutable + - name: Build grafana + run: make build + - name: Install Cypress dependencies + uses: cypress-io/github-action@v6 + with: + runTests: false + - name: Run dashboard scenes e2e + run: yarn e2e:schema-v2 || echo "Test failed but marking as success since schema V2 is behind a feature flag and should not block PRs" + + - name: Always succeed # This is a workaround to make the job pass even if the previous step fails + if: failure() + run: exit 0 \ No newline at end of file diff --git a/e2e/cypress/support/e2e.js b/e2e/cypress/support/e2e.js index 3e03d0f4344..ddd50a58844 100644 --- a/e2e/cypress/support/e2e.js +++ b/e2e/cypress/support/e2e.js @@ -50,4 +50,9 @@ beforeEach(() => { cy.logToConsole('disabling dashboardScene feature toggle in localstorage'); cy.setLocalStorage('grafana.featureToggles', 'dashboardScene=false'); } + + if (Cypress.env('useV2DashboardsAPI')) { + cy.logToConsole('enabling v2 dashboards API in localstorage'); + cy.setLocalStorage('grafana.featureToggles', 'useV2DashboardsAPI=true'); + } }); diff --git a/e2e/run-suite b/e2e/run-suite index 11569b43982..bc0eebd1567 100755 --- a/e2e/run-suite +++ b/e2e/run-suite @@ -28,6 +28,7 @@ declare -A env=( testFilesForSingleSuite="*.spec.ts" rootForEnterpriseSuite="./e2e/extensions-suite" rootForOldArch="./e2e/old-arch" +rootForDashboardsSchemaV2="./e2e/dashboards-suite" declare -A cypressConfig=( [screenshotsFolder]=./e2e/"${args[0]}"/screenshots @@ -111,6 +112,24 @@ case "$1" in cypressConfig[video]=${args[1]} env[DISABLE_SCENES]=true ;; + "dashboards-schema-v2") + env[useV2DashboardsAPI]=true + cypressConfig[specPattern]=$rootForDashboardsSchemaV2/$testFilesForSingleSuite + cypressConfig[video]=false + case "$2" in + "debug") + echo -e "Debug mode" + env[SLOWMO]=1 + PARAMS="--no-exit" + enterpriseSuite=$(basename "${args[2]}") + ;; + "dev") + echo "Dev mode" + CMD="cypress open" + enterpriseSuite=$(basename "${args[2]}") + ;; + esac + ;; "enterprise-smtp") env[SMTP_PLUGIN_ENABLED]=true cypressConfig[specPattern]=./e2e/extensions/enterprise/smtp-suite/$testFilesForSingleSuite diff --git a/package.json b/package.json index d87d9050539..5d19e8cf0d2 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,8 @@ "build:stats": "NODE_ENV=production webpack --progress --config scripts/webpack/webpack.stats.js", "dev": "NODE_ENV=dev nx exec -- webpack --config scripts/webpack/webpack.dev.js", "e2e": "./e2e/start-and-run-suite", - "e2e:scenes": "./e2e/start-and-run-suite scenes", + "e2e:old-arch": "./e2e/start-and-run-suite old-arch", + "e2e:schema-v2": "./e2e/start-and-run-suite dashboards-schema-v2", "e2e:debug": "./e2e/start-and-run-suite debug", "e2e:dev": "./e2e/start-and-run-suite dev", "e2e:benchmark:live": "./e2e/start-and-run-suite benchmark live", From 126396399e2fe485da63fde580d3003fbbf9e451 Mon Sep 17 00:00:00 2001 From: Karl Persson <23356117+kalleep@users.noreply.github.com> Date: Thu, 6 Feb 2025 16:40:07 +0100 Subject: [PATCH 21/34] Folder+Dashboard: Resolve parent folders as service in search (#100185) Resolve parent folders as service to guarantee that we can fetch the title --- pkg/services/dashboards/service/dashboard_service.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pkg/services/dashboards/service/dashboard_service.go b/pkg/services/dashboards/service/dashboard_service.go index efbd0787a45..ac0a3504f9f 100644 --- a/pkg/services/dashboards/service/dashboard_service.go +++ b/pkg/services/dashboards/service/dashboard_service.go @@ -1249,13 +1249,17 @@ func (dr *DashboardServiceImpl) FindDashboards(ctx context.Context, query *dashb finalResults := make([]dashboards.DashboardSearchProjection, len(response.Hits)) // Create a small runtime cache for folders to avoid extra calls to the folder service foldersMap := make(map[string]*folder.Folder) + serviceCtx, serviceIdent := identity.WithServiceIdentity(ctx, query.OrgId) for i, hit := range response.Hits { f, ok := foldersMap[hit.Folder] if !ok { - f, err = dr.folderService.Get(ctx, &folder.GetFolderQuery{ + // We can get search result where user don't have access to parents. If that happens this thi + // will fail if we call it as the requesting user. To resolve this we call this as the service so we can + // garantuee that we can fetch the parent. + f, err = dr.folderService.Get(serviceCtx, &folder.GetFolderQuery{ UID: &hit.Folder, OrgID: query.OrgId, - SignedInUser: query.SignedInUser, + SignedInUser: serviceIdent, }) if err != nil { return nil, err From 0035ed8a5e070385b50099328bd93a665045564d Mon Sep 17 00:00:00 2001 From: Sarah Zinger Date: Thu, 6 Feb 2025 11:05:30 -0500 Subject: [PATCH 22/34] Add log line for unexpected queries in ds querier (#100147) --- pkg/registry/apis/query/query.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/registry/apis/query/query.go b/pkg/registry/apis/query/query.go index 95a27508b5a..f3675514a25 100644 --- a/pkg/registry/apis/query/query.go +++ b/pkg/registry/apis/query/query.go @@ -160,6 +160,7 @@ func (r *queryREST) Connect(connectCtx context.Context, name string, _ runtime.O // Actually run the query rsp, err := b.execute(ctx, req) if err != nil { + b.log.Error("hit unexpected error while executing query, this will show as an unhandled k8s status error", "err", err) responder.Error(err) return } From f8509273cbe9838d60efd7693bf6c56d7902e61e Mon Sep 17 00:00:00 2001 From: Josh Hunt Date: Thu, 6 Feb 2025 16:14:38 +0000 Subject: [PATCH 23/34] Slider: Fix text input box being too wide (#100138) * Fix Input width from className not being respected * Also use width prop in Slider --- packages/grafana-ui/src/components/Input/Input.tsx | 10 ++++++---- packages/grafana-ui/src/components/Slider/Slider.tsx | 1 + packages/grafana-ui/src/components/Slider/styles.ts | 1 - 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/grafana-ui/src/components/Input/Input.tsx b/packages/grafana-ui/src/components/Input/Input.tsx index 09b113cf76b..563adfa9772 100644 --- a/packages/grafana-ui/src/components/Input/Input.tsx +++ b/packages/grafana-ui/src/components/Input/Input.tsx @@ -60,19 +60,21 @@ export const Input = forwardRef((props, ref) => { // if a better solution is found. const isInAutoSizeInput = useContext(AutoSizeInputContext); const accessoriesWidth = (prefixRect.width || 0) + (suffixRect.width || 0); - const finalWidth = isInAutoSizeInput && width ? width + accessoriesWidth / 8 : width; + const autoSizeWidth = isInAutoSizeInput && width ? width + accessoriesWidth / 8 : undefined; const theme = useTheme2(); // Don't pass the width prop, as this causes an unnecessary amount of Emotion calls when auto sizing - const styles = getInputStyles({ theme, invalid: !!invalid }); + const styles = getInputStyles({ theme, invalid: !!invalid, width: autoSizeWidth ? undefined : width }); const suffix = suffixProp || (loading && ); return (
{!!addonBefore &&
{addonBefore}
} @@ -130,7 +132,7 @@ export const getInputStyles = stylesFactory(({ theme, invalid = false, width }: css({ label: 'input-wrapper', display: 'flex', - width: width ? theme.spacing(width) : '100%', // Not used in Input, as this causes performance issues with auto sizing + width: width ? theme.spacing(width) : '100%', height: theme.spacing(theme.components.height.md), borderRadius: theme.shape.radius.default, '&:hover': { diff --git a/packages/grafana-ui/src/components/Slider/Slider.tsx b/packages/grafana-ui/src/components/Slider/Slider.tsx index 5da049a3d0c..c677d402dc9 100644 --- a/packages/grafana-ui/src/components/Slider/Slider.tsx +++ b/packages/grafana-ui/src/components/Slider/Slider.tsx @@ -109,6 +109,7 @@ export const Slider = ({ Date: Thu, 6 Feb 2025 17:16:30 +0100 Subject: [PATCH 24/34] Authz: client cache (#100195) * Reduce client permissions cache for authz client * Adjust server cache ttl --- pkg/services/authz/client.go | 12 ++++++++++++ pkg/services/authz/rbac/service.go | 10 +++++----- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/pkg/services/authz/client.go b/pkg/services/authz/client.go index 412a4e2acb9..506155df380 100644 --- a/pkg/services/authz/client.go +++ b/pkg/services/authz/client.go @@ -124,6 +124,10 @@ func newInProcLegacyClient(server *rbac.Service, tracer tracing.Tracer) (authlib authzlib.WithGrpcConnectionClientOption(channel), authzlib.WithDisableAccessTokenClientOption(), authzlib.WithTracerClientOption(tracer), + authzlib.WithCacheClientOption(cache.NewLocalCache(cache.Config{ + Expiry: 30 * time.Second, + CleanupInterval: 2 * time.Minute, + })), ) } @@ -147,6 +151,10 @@ func newGrpcLegacyClient(authCfg *Cfg, tracer tracing.Tracer) (authlib.AccessCli grpc.WithStreamInterceptor(clientInterceptor.StreamClientInterceptor), ), authzlib.WithTracerClientOption(tracer), + authzlib.WithCacheClientOption(cache.NewLocalCache(cache.Config{ + Expiry: 30 * time.Second, + CleanupInterval: 2 * time.Minute, + })), // TODO: remove this once access tokens are supported on-prem authzlib.WithDisableAccessTokenClientOption(), ) @@ -181,6 +189,10 @@ func newCloudLegacyClient(authCfg *Cfg, tracer tracing.Tracer) (authlib.AccessCl grpc.WithUnaryInterceptor(clientInterceptor.UnaryClientInterceptor), grpc.WithStreamInterceptor(clientInterceptor.StreamClientInterceptor), ), + authzlib.WithCacheClientOption(cache.NewLocalCache(cache.Config{ + Expiry: 30 * time.Second, + CleanupInterval: 2 * time.Minute, + })), authzlib.WithTracerClientOption(tracer), ) if err != nil { diff --git a/pkg/services/authz/rbac/service.go b/pkg/services/authz/rbac/service.go index 26bd352903c..349ccd53558 100644 --- a/pkg/services/authz/rbac/service.go +++ b/pkg/services/authz/rbac/service.go @@ -31,10 +31,10 @@ import ( ) const ( - shortCacheTTL = 1 * time.Minute - shortCleanupInterval = 5 * time.Minute - longCacheTTL = 5 * time.Minute - longCleanupInterval = 10 * time.Minute + shortCacheTTL = 30 * time.Second + shortCleanupInterval = 2 * time.Minute + longCacheTTL = 2 * time.Minute + longCleanupInterval = 4 * time.Minute ) type Service struct { @@ -82,7 +82,7 @@ func NewService( idCache: newCacheWrap[store.UserIdentifiers](cache, logger, longCacheTTL), permCache: newCacheWrap[map[string]bool](cache, logger, shortCacheTTL), teamCache: newCacheWrap[[]int64](cache, logger, shortCacheTTL), - basicRoleCache: newCacheWrap[store.BasicRole](cache, logger, longCacheTTL), + basicRoleCache: newCacheWrap[store.BasicRole](cache, logger, shortCacheTTL), folderCache: newCacheWrap[map[string]FolderNode](cache, logger, shortCacheTTL), sf: new(singleflight.Group), } From ccb0e9222afe2acf20efda0ffb38314616b3b5ac Mon Sep 17 00:00:00 2001 From: Matthew Jacobson Date: Thu, 6 Feb 2025 11:29:43 -0500 Subject: [PATCH 25/34] Alerting: Upgrade grafana/alerting to use EmbeddedContents (#99983) * Upgrade grafana/alerting to include EmbeddedContents for email images --- go.mod | 2 +- go.sum | 4 +-- go.work.sum | 3 +++ pkg/services/ngalert/notifier/email_test.go | 28 ++++++++++++++------- pkg/services/ngalert/notifier/sender.go | 28 ++++++++++++++------- pkg/storage/unified/apistore/go.mod | 2 +- pkg/storage/unified/apistore/go.sum | 4 +-- pkg/storage/unified/resource/go.mod | 2 +- pkg/storage/unified/resource/go.sum | 4 +-- 9 files changed, 50 insertions(+), 27 deletions(-) diff --git a/go.mod b/go.mod index 82a3b4de7e3..079a83598d0 100644 --- a/go.mod +++ b/go.mod @@ -71,7 +71,7 @@ require ( github.com/googleapis/gax-go/v2 v2.14.1 // @grafana/grafana-backend-group github.com/gorilla/mux v1.8.1 // @grafana/grafana-backend-group github.com/gorilla/websocket v1.5.3 // @grafana/grafana-app-platform-squad - github.com/grafana/alerting v0.0.0-20250130152446-d49e2e0b7d65 // @grafana/alerting-backend + github.com/grafana/alerting v0.0.0-20250205180645-995709fe8d64 // @grafana/alerting-backend github.com/grafana/authlib v0.0.0-20250204100101-00a7f40e4029 // @grafana/identity-access-team github.com/grafana/authlib/types v0.0.0-20250120145936-5f0e28e7a87c // @grafana/identity-access-team github.com/grafana/dataplane/examples v0.0.1 // @grafana/observability-metrics diff --git a/go.sum b/go.sum index efdc65bf13c..5c2c8b5537c 100644 --- a/go.sum +++ b/go.sum @@ -1508,8 +1508,8 @@ github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7Fsg github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grafana/alerting v0.0.0-20250130152446-d49e2e0b7d65 h1:dmsycYQzl5JexuV8UxQpT3B79maSvhiIahid4/tezAM= -github.com/grafana/alerting v0.0.0-20250130152446-d49e2e0b7d65/go.mod h1:QsnoKX/iYZxA4Cv+H+wC7uxutBD8qi8ZW5UJvD2TYmU= +github.com/grafana/alerting v0.0.0-20250205180645-995709fe8d64 h1:J3PIK9OL3ZHPypYHlcK+nBREwYL3ROZ3fJyNMsTYlpk= +github.com/grafana/alerting v0.0.0-20250205180645-995709fe8d64/go.mod h1:QsnoKX/iYZxA4Cv+H+wC7uxutBD8qi8ZW5UJvD2TYmU= github.com/grafana/authlib v0.0.0-20250204100101-00a7f40e4029 h1:0C0a1FEkecxDk60su4uuOozqBzqz/4nmfNFSxXnCViY= github.com/grafana/authlib v0.0.0-20250204100101-00a7f40e4029/go.mod h1:/gYfphsNu9v1qYWXxpv1NSvMEMSwvdf8qb8YlgwIRl8= github.com/grafana/authlib/types v0.0.0-20250120145936-5f0e28e7a87c h1:b0sPDtt33uFdmvUJjSCld3kwE2E49dUvevuUDSJsEuo= diff --git a/go.work.sum b/go.work.sum index 14d84965e7b..9fc55608511 100644 --- a/go.work.sum +++ b/go.work.sum @@ -1516,6 +1516,9 @@ github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB7 github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grafana/alerting v0.0.0-20250115195200-209e052dba64/go.mod h1:QsnoKX/iYZxA4Cv+H+wC7uxutBD8qi8ZW5UJvD2TYmU= +github.com/grafana/alerting v0.0.0-20250129195454-3e5b80036b7a/go.mod h1:QsnoKX/iYZxA4Cv+H+wC7uxutBD8qi8ZW5UJvD2TYmU= +github.com/grafana/alerting v0.0.0-20250205180645-995709fe8d64 h1:J3PIK9OL3ZHPypYHlcK+nBREwYL3ROZ3fJyNMsTYlpk= +github.com/grafana/alerting v0.0.0-20250205180645-995709fe8d64/go.mod h1:QsnoKX/iYZxA4Cv+H+wC7uxutBD8qi8ZW5UJvD2TYmU= github.com/grafana/authlib v0.0.0-20250120144156-d6737a7dc8f5/go.mod h1:V63rh3udd7sqXJeaG+nGUmViwVnM/bY6t8U9Tols2GU= github.com/grafana/authlib v0.0.0-20250120145936-5f0e28e7a87c/go.mod h1:/gYfphsNu9v1qYWXxpv1NSvMEMSwvdf8qb8YlgwIRl8= github.com/grafana/authlib/types v0.0.0-20250120144156-d6737a7dc8f5/go.mod h1:qYjSd1tmJiuVoSICp7Py9/zD54O9uQQA3wuM6Gg4DFM= diff --git a/pkg/services/ngalert/notifier/email_test.go b/pkg/services/ngalert/notifier/email_test.go index beb0251e17d..70e2a8543af 100644 --- a/pkg/services/ngalert/notifier/email_test.go +++ b/pkg/services/ngalert/notifier/email_test.go @@ -222,16 +222,26 @@ func (e emailSender) SendWebhook(ctx context.Context, cmd *receivers.SendWebhook } func (e emailSender) SendEmail(ctx context.Context, cmd *receivers.SendEmailSettings) error { + sendEmailCommand := notifications.SendEmailCommand{ + To: cmd.To, + SingleEmail: cmd.SingleEmail, + Template: cmd.Template, + Subject: cmd.Subject, + Data: cmd.Data, + ReplyTo: cmd.ReplyTo, + EmbeddedFiles: cmd.EmbeddedFiles, + } + if len(cmd.EmbeddedContents) > 0 { + sendEmailCommand.EmbeddedContents = make([]notifications.EmbeddedContent, len(cmd.EmbeddedContents)) + for i, ec := range cmd.EmbeddedContents { + sendEmailCommand.EmbeddedContents[i] = notifications.EmbeddedContent{ + Name: ec.Name, + Content: ec.Content, + } + } + } return e.ns.SendEmailCommandHandlerSync(ctx, ¬ifications.SendEmailCommandSync{ - SendEmailCommand: notifications.SendEmailCommand{ - To: cmd.To, - SingleEmail: cmd.SingleEmail, - Template: cmd.Template, - Subject: cmd.Subject, - Data: cmd.Data, - ReplyTo: cmd.ReplyTo, - EmbeddedFiles: cmd.EmbeddedFiles, - }, + SendEmailCommand: sendEmailCommand, }) } diff --git a/pkg/services/ngalert/notifier/sender.go b/pkg/services/ngalert/notifier/sender.go index be2f58b523d..67e68b2abea 100644 --- a/pkg/services/ngalert/notifier/sender.go +++ b/pkg/services/ngalert/notifier/sender.go @@ -27,15 +27,25 @@ func (s sender) SendWebhook(ctx context.Context, cmd *receivers.SendWebhookSetti } func (s sender) SendEmail(ctx context.Context, cmd *receivers.SendEmailSettings) error { + sendEmailCommand := notifications.SendEmailCommand{ + To: cmd.To, + SingleEmail: cmd.SingleEmail, + Template: cmd.Template, + Subject: cmd.Subject, + Data: cmd.Data, + ReplyTo: cmd.ReplyTo, + EmbeddedFiles: cmd.EmbeddedFiles, + } + if len(cmd.EmbeddedContents) > 0 { + sendEmailCommand.EmbeddedContents = make([]notifications.EmbeddedContent, len(cmd.EmbeddedContents)) + for i, ec := range cmd.EmbeddedContents { + sendEmailCommand.EmbeddedContents[i] = notifications.EmbeddedContent{ + Name: ec.Name, + Content: ec.Content, + } + } + } return s.ns.SendEmailCommandHandlerSync(ctx, ¬ifications.SendEmailCommandSync{ - SendEmailCommand: notifications.SendEmailCommand{ - To: cmd.To, - SingleEmail: cmd.SingleEmail, - Template: cmd.Template, - Subject: cmd.Subject, - Data: cmd.Data, - ReplyTo: cmd.ReplyTo, - EmbeddedFiles: cmd.EmbeddedFiles, - }, + SendEmailCommand: sendEmailCommand, }) } diff --git a/pkg/storage/unified/apistore/go.mod b/pkg/storage/unified/apistore/go.mod index 2ac8214df01..58e48dafc0d 100644 --- a/pkg/storage/unified/apistore/go.mod +++ b/pkg/storage/unified/apistore/go.mod @@ -175,7 +175,7 @@ require ( github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect github.com/googleapis/gax-go/v2 v2.14.1 // indirect github.com/gorilla/mux v1.8.1 // indirect - github.com/grafana/alerting v0.0.0-20250130152446-d49e2e0b7d65 // indirect + github.com/grafana/alerting v0.0.0-20250205180645-995709fe8d64 // indirect github.com/grafana/authlib v0.0.0-20250204100101-00a7f40e4029 // indirect github.com/grafana/dataplane/sdata v0.0.9 // indirect github.com/grafana/dskit v0.0.0-20241105154643-a6b453a88040 // indirect diff --git a/pkg/storage/unified/apistore/go.sum b/pkg/storage/unified/apistore/go.sum index 3c8ae34e004..05f1f0f36b5 100644 --- a/pkg/storage/unified/apistore/go.sum +++ b/pkg/storage/unified/apistore/go.sum @@ -557,8 +557,8 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grafana/alerting v0.0.0-20250130152446-d49e2e0b7d65 h1:dmsycYQzl5JexuV8UxQpT3B79maSvhiIahid4/tezAM= -github.com/grafana/alerting v0.0.0-20250130152446-d49e2e0b7d65/go.mod h1:QsnoKX/iYZxA4Cv+H+wC7uxutBD8qi8ZW5UJvD2TYmU= +github.com/grafana/alerting v0.0.0-20250205180645-995709fe8d64 h1:J3PIK9OL3ZHPypYHlcK+nBREwYL3ROZ3fJyNMsTYlpk= +github.com/grafana/alerting v0.0.0-20250205180645-995709fe8d64/go.mod h1:QsnoKX/iYZxA4Cv+H+wC7uxutBD8qi8ZW5UJvD2TYmU= github.com/grafana/authlib v0.0.0-20250204100101-00a7f40e4029 h1:0C0a1FEkecxDk60su4uuOozqBzqz/4nmfNFSxXnCViY= github.com/grafana/authlib v0.0.0-20250204100101-00a7f40e4029/go.mod h1:/gYfphsNu9v1qYWXxpv1NSvMEMSwvdf8qb8YlgwIRl8= github.com/grafana/authlib/types v0.0.0-20250120145936-5f0e28e7a87c h1:b0sPDtt33uFdmvUJjSCld3kwE2E49dUvevuUDSJsEuo= diff --git a/pkg/storage/unified/resource/go.mod b/pkg/storage/unified/resource/go.mod index 53a5f528af5..0b5cbb0b2db 100644 --- a/pkg/storage/unified/resource/go.mod +++ b/pkg/storage/unified/resource/go.mod @@ -115,7 +115,7 @@ require ( github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect github.com/googleapis/gax-go/v2 v2.14.1 // indirect github.com/gorilla/mux v1.8.1 // indirect - github.com/grafana/alerting v0.0.0-20250130152446-d49e2e0b7d65 // indirect + github.com/grafana/alerting v0.0.0-20250205180645-995709fe8d64 // indirect github.com/grafana/dataplane/sdata v0.0.9 // indirect github.com/grafana/grafana-app-sdk/logging v0.30.0 // indirect github.com/grafana/grafana-aws-sdk v0.31.5 // indirect diff --git a/pkg/storage/unified/resource/go.sum b/pkg/storage/unified/resource/go.sum index 17017755d74..96686090205 100644 --- a/pkg/storage/unified/resource/go.sum +++ b/pkg/storage/unified/resource/go.sum @@ -413,8 +413,8 @@ github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2z github.com/gorilla/mux v1.7.1/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= -github.com/grafana/alerting v0.0.0-20250130152446-d49e2e0b7d65 h1:dmsycYQzl5JexuV8UxQpT3B79maSvhiIahid4/tezAM= -github.com/grafana/alerting v0.0.0-20250130152446-d49e2e0b7d65/go.mod h1:QsnoKX/iYZxA4Cv+H+wC7uxutBD8qi8ZW5UJvD2TYmU= +github.com/grafana/alerting v0.0.0-20250205180645-995709fe8d64 h1:J3PIK9OL3ZHPypYHlcK+nBREwYL3ROZ3fJyNMsTYlpk= +github.com/grafana/alerting v0.0.0-20250205180645-995709fe8d64/go.mod h1:QsnoKX/iYZxA4Cv+H+wC7uxutBD8qi8ZW5UJvD2TYmU= github.com/grafana/authlib v0.0.0-20250204100101-00a7f40e4029 h1:0C0a1FEkecxDk60su4uuOozqBzqz/4nmfNFSxXnCViY= github.com/grafana/authlib v0.0.0-20250204100101-00a7f40e4029/go.mod h1:/gYfphsNu9v1qYWXxpv1NSvMEMSwvdf8qb8YlgwIRl8= github.com/grafana/authlib/types v0.0.0-20250120145936-5f0e28e7a87c h1:b0sPDtt33uFdmvUJjSCld3kwE2E49dUvevuUDSJsEuo= From 1467d4b3e3a783cefbd53dff3b525da65eb6909c Mon Sep 17 00:00:00 2001 From: Will Assis <35489495+gassiss@users.noreply.github.com> Date: Thu, 6 Feb 2025 13:30:47 -0300 Subject: [PATCH 26/34] [search] Legacy search fallback support legacy query params (#99765) * add support for deleted query param * support tag query param in modes 2 and below * handle dashboardIds * hhandle dashboardUIDs * handle folderUIDs query param * handle page query param when hitting legacy storage * handle sort query param * handle type query param * re-enable search fallback * remove folder search workaround and fix /api/search to return both folders and dashboards when no title or type is provided --------- Co-authored-by: Stephanie Hingtgen --- pkg/apis/dashboard/v0alpha1/register.go | 1 + .../dashboard/legacysearcher/search_client.go | 169 ++++- pkg/registry/apis/dashboard/search.go | 48 +- pkg/registry/apis/dashboard/search_test.go | 7 +- pkg/services/apiserver/client/client.go | 10 +- .../dashboards/service/dashboard_service.go | 71 +- .../service/dashboard_service_test.go | 13 + pkg/services/search/service.go | 15 - pkg/storage/unified/resource/go.mod | 2 +- pkg/storage/unified/resource/resource.pb.go | 645 +++++++++--------- pkg/storage/unified/resource/resource.proto | 50 +- pkg/storage/unified/resource/search.go | 33 + pkg/storage/unified/resource/search_client.go | 26 +- 13 files changed, 646 insertions(+), 444 deletions(-) diff --git a/pkg/apis/dashboard/v0alpha1/register.go b/pkg/apis/dashboard/v0alpha1/register.go index 9423f9ef195..e163aa01257 100644 --- a/pkg/apis/dashboard/v0alpha1/register.go +++ b/pkg/apis/dashboard/v0alpha1/register.go @@ -14,6 +14,7 @@ import ( const ( GROUP = "dashboard.grafana.app" VERSION = "v0alpha1" + RESOURCE = "dashboards" APIVERSION = GROUP + "/" + VERSION ) diff --git a/pkg/registry/apis/dashboard/legacysearcher/search_client.go b/pkg/registry/apis/dashboard/legacysearcher/search_client.go index 17aa5aa16a9..bd2e4a896a8 100644 --- a/pkg/registry/apis/dashboard/legacysearcher/search_client.go +++ b/pkg/registry/apis/dashboard/legacysearcher/search_client.go @@ -2,12 +2,18 @@ package legacysearcher import ( "context" + "encoding/json" "fmt" + "strconv" "strings" claims "github.com/grafana/authlib/types" "github.com/grafana/grafana/pkg/apimachinery/identity" + "github.com/grafana/grafana/pkg/apimachinery/utils" + "github.com/grafana/grafana/pkg/apis/dashboard" + folderv0alpha1 "github.com/grafana/grafana/pkg/apis/folder/v0alpha1" "github.com/grafana/grafana/pkg/services/dashboards" + "github.com/grafana/grafana/pkg/services/search" "github.com/grafana/grafana/pkg/services/sqlstore/searchstore" "github.com/grafana/grafana/pkg/storage/unified/resource" "google.golang.org/grpc" @@ -28,29 +34,93 @@ func (c *DashboardSearchClient) Search(ctx context.Context, req *resource.Resour return nil, err } - if req.Query == "*" { - req.Query = "" + // the "*"s will be added in the k8s handler in dashboard_service.go in order to make search work + // in modes 3+. These "*"s will break the legacy sql query so we need to remove them here + if strings.Contains(req.Query, "*") { + req.Query = strings.ReplaceAll(req.Query, "*", "") } // TODO add missing support for the following query params: - // - tag - // - starred (won't support) - // - page (check) - // - type - // - sort - // - deleted + // - folderIds (won't support, must use folderUIDs) // - permission - // - dashboardIds - // - dashboardUIDs - // - folderIds - // - folderUIDs - // - sort (default by title) query := &dashboards.FindPersistedDashboardsQuery{ - Title: req.Query, - Limit: req.Limit, - // FolderUIDs: req.FolderUIDs, - Type: searchstore.TypeDashboard, + Title: req.Query, + Limit: req.Limit, + Page: req.Page, SignedInUser: user, + IsDeleted: req.IsDeleted, + } + + var queryType string + if req.Options.Key.Resource == dashboard.DASHBOARD_RESOURCE { + queryType = searchstore.TypeDashboard + } else if req.Options.Key.Resource == folderv0alpha1.RESOURCE { + queryType = searchstore.TypeFolder + } else { + return nil, fmt.Errorf("bad type request") + } + + if len(req.Federated) > 1 { + return nil, fmt.Errorf("bad type request") + } + + if len(req.Federated) == 1 && + ((req.Federated[0].Resource == dashboard.DASHBOARD_RESOURCE && queryType == searchstore.TypeFolder) || + (req.Federated[0].Resource == folderv0alpha1.RESOURCE && queryType == searchstore.TypeDashboard)) { + queryType = "" // makes the legacy store search across both + } + + if queryType != "" { + query.Type = queryType + } + + // technically, there exists the ability to register multiple ways of sorting using the legacy database + // see RegisterSortOption in pkg/services/search/sorting.go + // however, it doesn't look like we are taking advantage of that. And since by default the legacy + // sql will sort by title ascending, we only really need to handle the "alpha-desc" case + if req.SortBy != nil { + for _, sort := range req.SortBy { + if sort.Field == "title" && sort.Desc { + query.Sort = search.SortAlphaDesc + } + } + } + // handle deprecated dashboardIds query param + for _, field := range req.Options.Labels { + if field.Key == utils.LabelKeyDeprecatedInternalID { + values := field.GetValues() + dashboardIds := make([]int64, len(values)) + for i, id := range values { + if n, err := strconv.ParseInt(id, 10, 64); err == nil { + dashboardIds[i] = n + } + } + + query.DashboardIds = dashboardIds + } + } + + for _, field := range req.Options.Fields { + switch field.Key { + case resource.SEARCH_FIELD_TAGS: + query.Tags = field.GetValues() + case resource.SEARCH_FIELD_NAME: + query.DashboardUIDs = field.GetValues() + query.DashboardIds = nil + case resource.SEARCH_FIELD_FOLDER: + vals := field.GetValues() + folders := make([]string, len(vals)) + + for i, val := range vals { + if val == "" { + folders[i] = "general" + } else { + folders[i] = val + } + } + + query.FolderUIDs = folders + } } // TODO need to test this @@ -75,26 +145,73 @@ func (c *DashboardSearchClient) Search(ctx context.Context, req *resource.Resour Columns: []*resource.ResourceTableColumnDefinition{ searchFields.Field(resource.SEARCH_FIELD_TITLE), searchFields.Field(resource.SEARCH_FIELD_FOLDER), - // searchFields.Field(resource.SEARCH_FIELD_TAGS), + searchFields.Field(resource.SEARCH_FIELD_TAGS), }, }, } - for _, dashboard := range res { + hits := formatQueryResult(res) + + for _, dashboard := range hits { + tags, err := json.Marshal(dashboard.Tags) + if err != nil { + return nil, err + } + list.Results.Rows = append(list.Results.Rows, &resource.ResourceTableRow{ - Key: &resource.ResourceKey{ - Namespace: "default", - Group: "dashboard.grafana.app", - Resource: "dashboards", - Name: dashboard.UID, - }, - Cells: [][]byte{[]byte(dashboard.Title), []byte(dashboard.FolderUID)}, // TODO add tag + Key: getResourceKey(dashboard, req.Options.Key.Namespace), + Cells: [][]byte{[]byte(dashboard.Title), []byte(dashboard.FolderUID), tags}, }) } return list, nil } +func getResourceKey(item *dashboards.DashboardSearchProjection, namespace string) *resource.ResourceKey { + if item.IsFolder { + return &resource.ResourceKey{ + Namespace: namespace, + Group: folderv0alpha1.GROUP, + Resource: folderv0alpha1.RESOURCE, + Name: item.UID, + } + } + + return &resource.ResourceKey{ + Namespace: namespace, + Group: dashboard.GROUP, + Resource: dashboard.DASHBOARD_RESOURCE, + Name: item.UID, + } +} + +func formatQueryResult(res []dashboards.DashboardSearchProjection) []*dashboards.DashboardSearchProjection { + hitList := make([]*dashboards.DashboardSearchProjection, 0) + hits := make(map[string]*dashboards.DashboardSearchProjection) + + for _, item := range res { + key := fmt.Sprintf("%s-%d", item.UID, item.OrgID) + hit, exists := hits[key] + if !exists { + hit = &dashboards.DashboardSearchProjection{ + UID: item.UID, + Title: item.Title, + FolderUID: item.FolderUID, + Tags: []string{}, + IsFolder: item.IsFolder, + } + hitList = append(hitList, hit) + hits[key] = hit + } + + if len(item.Term) > 0 { + hit.Tags = append(hit.Tags, item.Term) + } + } + + return hitList +} + func (c *DashboardSearchClient) GetStats(ctx context.Context, req *resource.ResourceStatsRequest, opts ...grpc.CallOption) (*resource.ResourceStatsResponse, error) { info, err := claims.ParseNamespace(req.Namespace) if err != nil { diff --git a/pkg/registry/apis/dashboard/search.go b/pkg/registry/apis/dashboard/search.go index fc170a36783..4d9b8137c85 100644 --- a/pkg/registry/apis/dashboard/search.go +++ b/pkg/registry/apis/dashboard/search.go @@ -18,7 +18,9 @@ import ( "k8s.io/kube-openapi/pkg/validation/spec" "github.com/grafana/grafana/pkg/apimachinery/identity" + "github.com/grafana/grafana/pkg/apis/dashboard" dashboardv0alpha1 "github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1" + folderv0alpha1 "github.com/grafana/grafana/pkg/apis/folder/v0alpha1" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/services/apiserver/builder" dashboardsearch "github.com/grafana/grafana/pkg/services/dashboards/service/search" @@ -199,7 +201,7 @@ func (s *SearchHandler) DoSortable(w http.ResponseWriter, r *http.Request) { const rootFolder = "general" -//nolint:gocyclo +// nolint:gocyclo func (s *SearchHandler) DoSearch(w http.ResponseWriter, r *http.Request) { ctx, span := s.tracer.Start(r.Context(), "dashboard.search") defer span.End() @@ -231,6 +233,7 @@ func (s *SearchHandler) DoSearch(w http.ResponseWriter, r *http.Request) { Query: queryParams.Get("query"), Limit: int64(limit), Offset: int64(offset), + Page: int64(offset), // on modes 0-2 (legacy) we use "Page" instead of "Offset" Explain: queryParams.Has("explain") && queryParams.Get("explain") != "false", } fields := []string{"title", "folder", "tags"} @@ -262,10 +265,10 @@ func (s *SearchHandler) DoSearch(w http.ResponseWriter, r *http.Request) { switch len(types) { case 0: // When no type specified, search for dashboards - searchRequest.Options.Key, err = asResourceKey(user.GetNamespace(), "dashboards") + searchRequest.Options.Key, err = asResourceKey(user.GetNamespace(), dashboard.DASHBOARD_RESOURCE) // Currently a search query is across folders and dashboards - if searchRequest.Query != "" { - federate, err = asResourceKey(user.GetNamespace(), "folders") + if err == nil { + federate, err = asResourceKey(user.GetNamespace(), folderv0alpha1.RESOURCE) } case 1: searchRequest.Options.Key, err = asResourceKey(user.GetNamespace(), types[0]) @@ -301,8 +304,7 @@ func (s *SearchHandler) DoSearch(w http.ResponseWriter, r *http.Request) { } // The facet term fields - facets, ok := queryParams["facet"] - if ok { + if facets, ok := queryParams["facet"]; ok { searchRequest.Facet = make(map[string]*resource.ResourceSearchRequest_Facet) for _, v := range facets { searchRequest.Facet[v] = &resource.ResourceSearchRequest_Facet{ @@ -313,8 +315,7 @@ func (s *SearchHandler) DoSearch(w http.ResponseWriter, r *http.Request) { } // The tags filter - tags, ok := queryParams["tag"] - if ok { + if tags, ok := queryParams["tag"]; ok { searchRequest.Options.Fields = []*resource.Requirement{{ Key: "tags", Operator: "=", @@ -323,8 +324,7 @@ func (s *SearchHandler) DoSearch(w http.ResponseWriter, r *http.Request) { } // The names filter - names, ok := queryParams["name"] - if ok { + if names, ok := queryParams["name"]; ok { if searchRequest.Options.Fields == nil { searchRequest.Options.Fields = []*resource.Requirement{} } @@ -366,30 +366,10 @@ func (s *SearchHandler) write(w http.ResponseWriter, obj any) { // Given a namespace and type convert it to a search key func asResourceKey(ns string, k string) (*resource.ResourceKey, error) { - if ns == "" { - return nil, apierrors.NewBadRequest("missing namespace") + key, err := resource.AsResourceKey(ns, k) + if err != nil { + return nil, apierrors.NewBadRequest(err.Error()) } - switch k { - case "folders", "folder": - return &resource.ResourceKey{ - Namespace: ns, - Group: "folder.grafana.app", - Resource: "folders", - }, nil - case "dashboards", "dashboard": - return &resource.ResourceKey{ - Namespace: ns, - Group: dashboardv0alpha1.GROUP, - Resource: "dashboards", - }, nil - // NOT really supported in the dashboard search UI, but useful for manual testing - case "playlist", "playlists": - return &resource.ResourceKey{ - Namespace: ns, - Group: "playlist.grafana.app", - Resource: "playlists", - }, nil - } - return nil, apierrors.NewBadRequest("unknown resource type") + return key, nil } diff --git a/pkg/registry/apis/dashboard/search_test.go b/pkg/registry/apis/dashboard/search_test.go index 83e46a6e6c2..d48a6e7a802 100644 --- a/pkg/registry/apis/dashboard/search_test.go +++ b/pkg/registry/apis/dashboard/search_test.go @@ -9,19 +9,17 @@ import ( "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1" + "github.com/grafana/grafana/pkg/apiserver/rest" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/services/user" + "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/storage/unified/resource" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "google.golang.org/grpc" ) -/* -Search Fallback was returning both Folders and Dashboards which resulted -in issues with rendering the Folder UI. Also, filters are not implemented -yet. For those reasons, we will be disabling Search Fallback for now func TestSearchFallback(t *testing.T) { t.Run("should hit legacy search handler on mode 0", func(t *testing.T) { mockClient := &MockClient{} @@ -179,7 +177,6 @@ func TestSearchFallback(t *testing.T) { } }) } -*/ func TestSearchHandler(t *testing.T) { // Create a mock client diff --git a/pkg/services/apiserver/client/client.go b/pkg/services/apiserver/client/client.go index 2f45e426bc3..9aaf573b7c8 100644 --- a/pkg/services/apiserver/client/client.go +++ b/pkg/services/apiserver/client/client.go @@ -179,10 +179,12 @@ func (h *k8sHandler) Search(ctx context.Context, orgID int64, in *resource.Resou in.Options = &resource.ListOptions{} } - in.Options.Key = &resource.ResourceKey{ - Namespace: h.GetNamespace(orgID), - Group: h.gvr.Group, - Resource: h.gvr.Resource, + if in.Options.Key == nil { + in.Options.Key = &resource.ResourceKey{ + Namespace: h.GetNamespace(orgID), + Group: h.gvr.Group, + Resource: h.gvr.Resource, + } } return h.searcher.Search(ctx, in) diff --git a/pkg/services/dashboards/service/dashboard_service.go b/pkg/services/dashboards/service/dashboard_service.go index ac0a3504f9f..f6b732b426f 100644 --- a/pkg/services/dashboards/service/dashboard_service.go +++ b/pkg/services/dashboards/service/dashboard_service.go @@ -27,7 +27,9 @@ import ( "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/apimachinery/utils" - "github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1" + "github.com/grafana/grafana/pkg/apis/dashboard" + dashboardv0alpha1 "github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1" + folderv0alpha1 "github.com/grafana/grafana/pkg/apis/folder/v0alpha1" "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/metrics" @@ -93,7 +95,7 @@ func ProvideDashboardServiceImpl( restConfigProvider apiserver.RestConfigProvider, userService user.Service, unified resource.ResourceClient, quotaService quota.Service, orgService org.Service, publicDashboardService publicdashboards.ServiceWrapper, ) (*DashboardServiceImpl, error) { - k8sHandler := client.NewK8sHandler(cfg, request.GetNamespaceMapper(cfg), v0alpha1.DashboardResourceInfo.GroupVersionResource(), restConfigProvider, unified, dashboardStore, userService) + k8sHandler := client.NewK8sHandler(cfg, request.GetNamespaceMapper(cfg), dashboardv0alpha1.DashboardResourceInfo.GroupVersionResource(), restConfigProvider, unified, dashboardStore, userService) dashSvc := &DashboardServiceImpl{ cfg: cfg, @@ -1266,7 +1268,7 @@ func (dr *DashboardServiceImpl) FindDashboards(ctx context.Context, query *dashb } foldersMap[hit.Folder] = f } - finalResults[i] = dashboards.DashboardSearchProjection{ + result := dashboards.DashboardSearchProjection{ ID: hit.Field.GetNestedInt64(search.DASHBOARD_LEGACY_ID), UID: hit.Name, OrgID: query.OrgId, @@ -1277,6 +1279,12 @@ func (dr *DashboardServiceImpl) FindDashboards(ctx context.Context, query *dashb FolderTitle: f.Title, Tags: hit.Tags, } + + if hit.Resource == folderv0alpha1.RESOURCE { + result.IsFolder = true + } + + finalResults[i] = result } return finalResults, nil @@ -1644,7 +1652,7 @@ func (dr *DashboardServiceImpl) listDashboardsThroughK8s(ctx context.Context, or return dashboards, nil } -func (dr *DashboardServiceImpl) searchDashboardsThroughK8sRaw(ctx context.Context, query *dashboards.FindPersistedDashboardsQuery) (*v0alpha1.SearchResults, error) { +func (dr *DashboardServiceImpl) searchDashboardsThroughK8sRaw(ctx context.Context, query *dashboards.FindPersistedDashboardsQuery) (*dashboardv0alpha1.SearchResults, error) { request := &resource.ResourceSearchRequest{ Options: &resource.ListOptions{ Fields: []*resource.Requirement{}, @@ -1722,8 +1730,55 @@ func (dr *DashboardServiceImpl) searchDashboardsThroughK8sRaw(ctx context.Contex request.Options.Fields = append(request.Options.Fields, req...) } - if query.Limit > 0 { - request.Limit = query.Limit + if query.IsDeleted { + request.IsDeleted = query.IsDeleted + } + + if query.Limit < 1 { + query.Limit = 1000 + } + + if query.Page < 1 { + query.Page = 1 + } + + request.Limit = query.Limit + request.Page = query.Page + request.Offset = query.Page - 1 // bleve's offset is 0 indexed + + namespace := dr.k8sclient.GetNamespace(query.OrgId) + var err error + var federate *resource.ResourceKey + switch query.Type { + case "": + // When no type specified, search for dashboards + request.Options.Key, err = resource.AsResourceKey(namespace, dashboard.DASHBOARD_RESOURCE) + // Currently a search query is across folders and dashboards + if err == nil { + federate, err = resource.AsResourceKey(namespace, folderv0alpha1.RESOURCE) + } + case searchstore.TypeDashboard, searchstore.TypeAnnotation: + request.Options.Key, err = resource.AsResourceKey(namespace, dashboard.DASHBOARD_RESOURCE) + case searchstore.TypeFolder, searchstore.TypeAlertFolder: + request.Options.Key, err = resource.AsResourceKey(namespace, folderv0alpha1.RESOURCE) + default: + err = fmt.Errorf("bad type request") + } + + if err != nil { + return nil, err + } + + if federate != nil { + request.Federated = []*resource.ResourceKey{federate} + } + + // technically, there exists the ability to register multiple ways of sorting using the legacy database + // see RegisterSortOption in pkg/services/search/sorting.go + // however, it doesn't look like we are taking advantage of that. And since by default the legacy + // sql will sort by title ascending, we only really need to handle the "alpha-desc" case + if query.Sort.Name == "alpha-desc" { + request.SortBy = append(request.SortBy, &resource.ResourceSearchRequest_Sort{Field: resource.SEARCH_FIELD_TITLE, Desc: true}) } res, err := dr.k8sclient.Search(ctx, query.OrgId, request) @@ -1770,7 +1825,7 @@ func (dr *DashboardServiceImpl) searchProvisionedDashboardsThroughK8s(ctx contex var mu sync.Mutex g, ctx := errgroup.WithContext(ctx) for _, h := range searchResults.Hits { - func(hit v0alpha1.DashboardHit) { + func(hit dashboardv0alpha1.DashboardHit) { g.Go(func() error { out, err := dr.k8sclient.Get(ctx, hit.Name, query.OrgId, v1.GetOptions{}) if err != nil { @@ -2000,7 +2055,7 @@ func LegacySaveCommandToUnstructured(cmd *dashboards.SaveDashboardCommand, names finalObj.Object["spec"] = obj finalObj.SetName(uid) finalObj.SetNamespace(namespace) - finalObj.SetGroupVersionKind(v0alpha1.DashboardResourceInfo.GroupVersionKind()) + finalObj.SetGroupVersionKind(dashboardv0alpha1.DashboardResourceInfo.GroupVersionKind()) meta, err := utils.MetaAccessor(&finalObj) if err != nil { diff --git a/pkg/services/dashboards/service/dashboard_service_test.go b/pkg/services/dashboards/service/dashboard_service_test.go index 58cc555bfd3..5e9167c1f50 100644 --- a/pkg/services/dashboards/service/dashboard_service_test.go +++ b/pkg/services/dashboards/service/dashboard_service_test.go @@ -333,6 +333,7 @@ func TestGetDashboard(t *testing.T) { OrgID: 1, } ctx, k8sCliMock := setupK8sDashboardTests(service) + k8sCliMock.On("GetNamespace", mock.Anything, mock.Anything).Return("default") dashboardUnstructured := unstructured.Unstructured{Object: map[string]any{ "metadata": map[string]any{ "name": "uid", @@ -538,6 +539,7 @@ func TestGetProvisionedDashboardData(t *testing.T) { t.Run("Should use Kubernetes client if feature flags are enabled and get from relevant org", func(t *testing.T) { ctx, k8sCliMock := setupK8sDashboardTests(service) + k8sCliMock.On("GetNamespace", mock.Anything, mock.Anything).Return("default") k8sCliMock.On("Get", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&unstructured.Unstructured{Object: map[string]any{ "metadata": map[string]any{ "name": "uid", @@ -636,6 +638,7 @@ func TestGetProvisionedDashboardDataByDashboardID(t *testing.T) { t.Run("Should use Kubernetes client if feature flags are enabled and get from whatever org it is in", func(t *testing.T) { ctx, k8sCliMock := setupK8sDashboardTests(service) + k8sCliMock.On("GetNamespace", mock.Anything, mock.Anything).Return("default") k8sCliMock.On("Get", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&unstructured.Unstructured{Object: map[string]any{ "metadata": map[string]any{ "name": "uid", @@ -725,6 +728,7 @@ func TestGetProvisionedDashboardDataByDashboardUID(t *testing.T) { t.Run("Should use Kubernetes client if feature flags are enabled", func(t *testing.T) { ctx, k8sCliMock := setupK8sDashboardTests(service) + k8sCliMock.On("GetNamespace", mock.Anything, mock.Anything).Return("default") k8sCliMock.On("Get", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&unstructured.Unstructured{Object: map[string]any{ "metadata": map[string]any{ "name": "uid", @@ -812,6 +816,7 @@ func TestDeleteOrphanedProvisionedDashboards(t *testing.T) { t.Run("Should use Kubernetes client if feature flags are enabled, delete across all orgs, but only delete file based provisioned dashboards", func(t *testing.T) { _, k8sCliMock := setupK8sDashboardTests(service) + k8sCliMock.On("GetNamespace", mock.Anything, mock.Anything).Return("default") k8sCliMock.On("Delete", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) fakeStore.On("CleanupAfterDelete", mock.Anything, &dashboards.DeleteDashboardCommand{UID: "uid", OrgID: 1}).Return(nil).Once() fakeStore.On("CleanupAfterDelete", mock.Anything, &dashboards.DeleteDashboardCommand{UID: "uid3", OrgID: 2}).Return(nil).Once() @@ -1044,6 +1049,7 @@ func TestGetDashboardsByPluginID(t *testing.T) { t.Run("Should use Kubernetes client if feature flags are enabled", func(t *testing.T) { ctx, k8sCliMock := setupK8sDashboardTests(service) + k8sCliMock.On("GetNamespace", mock.Anything, mock.Anything).Return("default") k8sCliMock.On("Get", mock.Anything, "uid", mock.Anything, mock.Anything, mock.Anything).Return(uidUnstructured, nil) k8sCliMock.On("GetUserFromMeta", mock.Anything, mock.Anything).Return(&user.User{}, nil) k8sCliMock.On("Search", mock.Anything, mock.Anything, mock.MatchedBy(func(req *resource.ResourceSearchRequest) bool { @@ -1263,6 +1269,7 @@ func TestDeleteDashboard(t *testing.T) { t.Run("If UID is not passed in, it should retrieve that first", func(t *testing.T) { ctx, k8sCliMock := setupK8sDashboardTests(service) + k8sCliMock.On("GetNamespace", mock.Anything, mock.Anything).Return("default") k8sCliMock.On("Delete", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil).Once() fakeStore.On("CleanupAfterDelete", mock.Anything, mock.Anything).Return(nil).Once() fakePublicDashboardService.On("DeleteByDashboardUIDs", mock.Anything, mock.Anything, mock.Anything).Return(nil).Once() @@ -1394,6 +1401,7 @@ func TestSearchDashboards(t *testing.T) { t.Run("Should use Kubernetes client if feature flags are enabled", func(t *testing.T) { ctx, k8sCliMock := setupK8sDashboardTests(service) + k8sCliMock.On("GetNamespace", mock.Anything, mock.Anything).Return("default") k8sCliMock.On("Search", mock.Anything, mock.Anything, mock.Anything).Return(&resource.ResourceSearchResponse{ Results: &resource.ResourceTable{ Columns: []*resource.ResourceTableColumnDefinition{ @@ -1513,6 +1521,7 @@ func TestGetDashboards(t *testing.T) { t.Run("Should use Kubernetes client if feature flags are enabled", func(t *testing.T) { ctx, k8sCliMock := setupK8sDashboardTests(service) + k8sCliMock.On("GetNamespace", mock.Anything, mock.Anything).Return("default") k8sCliMock.On("Get", mock.Anything, "uid1", mock.Anything, mock.Anything, mock.Anything).Return(uid1Unstructured, nil) k8sCliMock.On("Get", mock.Anything, "uid2", mock.Anything, mock.Anything, mock.Anything).Return(uid2Unstructured, nil) k8sCliMock.On("GetUserFromMeta", mock.Anything, mock.Anything).Return(&user.User{}, nil) @@ -1595,6 +1604,7 @@ func TestGetDashboardUIDByID(t *testing.T) { t.Run("Should use Kubernetes client if feature flags are enabled", func(t *testing.T) { ctx, k8sCliMock := setupK8sDashboardTests(service) + k8sCliMock.On("GetNamespace", mock.Anything, mock.Anything).Return("default") k8sCliMock.On("Search", mock.Anything, mock.Anything, mock.Anything).Return(&resource.ResourceSearchResponse{ Results: &resource.ResourceTable{ Columns: []*resource.ResourceTableColumnDefinition{ @@ -1892,6 +1902,7 @@ func TestCountInFolders(t *testing.T) { t.Run("Should use Kubernetes client if feature flags are enabled", func(t *testing.T) { ctx, k8sCliMock := setupK8sDashboardTests(service) + k8sCliMock.On("GetNamespace", mock.Anything, mock.Anything).Return("default") k8sCliMock.On("Search", mock.Anything, mock.Anything, mock.Anything).Return(dashs, nil).Once() result, err := service.CountInFolders(ctx, 1, []string{"folder1"}, &user.SignedInUser{}) require.NoError(t, err) @@ -1906,6 +1917,7 @@ func TestSearchDashboardsThroughK8sRaw(t *testing.T) { query := &dashboards.FindPersistedDashboardsQuery{ OrgId: 1, } + k8sCliMock.On("GetNamespace", mock.Anything, mock.Anything).Return("default") k8sCliMock.On("Search", mock.Anything, mock.Anything, mock.Anything).Return(&resource.ResourceSearchResponse{ Results: &resource.ResourceTable{ Columns: []*resource.ResourceTableColumnDefinition{ @@ -1972,6 +1984,7 @@ func TestSearchProvisionedDashboardsThroughK8sRaw(t *testing.T) { }, "spec": map[string]any{}, }} + k8sCliMock.On("GetNamespace", mock.Anything, mock.Anything).Return("default") k8sCliMock.On("Search", mock.Anything, mock.Anything, mock.Anything).Return(&resource.ResourceSearchResponse{ Results: &resource.ResourceTable{ Columns: []*resource.ResourceTableColumnDefinition{ diff --git a/pkg/services/search/service.go b/pkg/services/search/service.go index 1b48756ea07..137cf29db12 100644 --- a/pkg/services/search/service.go +++ b/pkg/services/search/service.go @@ -11,7 +11,6 @@ import ( "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/folder" "github.com/grafana/grafana/pkg/services/search/model" - "github.com/grafana/grafana/pkg/services/sqlstore/searchstore" "github.com/grafana/grafana/pkg/services/star" "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/setting" @@ -114,20 +113,6 @@ func (s *SearchService) SearchHandler(ctx context.Context, query *Query) (model. dashboardQuery.Sort = sortOpt } - // if folders are stored in unified storage, we need to use the folder service to query for folders - if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesFoldersServiceV2) && (query.Type == searchstore.TypeFolder || query.Type == searchstore.TypeAlertFolder) { - hits, err := s.folderService.SearchFolders(ctx, folder.SearchFoldersQuery{ - OrgID: query.OrgId, - UIDs: query.FolderUIDs, - IDs: query.FolderIds, - Title: query.Title, - Limit: query.Limit, - SignedInUser: query.SignedInUser, - }) - - return sortedHits(hits), err - } - hits, err := s.dashboardService.SearchDashboards(ctx, &dashboardQuery) if err != nil { return nil, err diff --git a/pkg/storage/unified/resource/go.mod b/pkg/storage/unified/resource/go.mod index 0b5cbb0b2db..450b1f37813 100644 --- a/pkg/storage/unified/resource/go.mod +++ b/pkg/storage/unified/resource/go.mod @@ -17,6 +17,7 @@ require ( github.com/grafana/grafana v11.4.0-00010101000000-000000000000+incompatible github.com/grafana/grafana-plugin-sdk-go v0.263.0 github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250121113133-e747350fee2d + github.com/grafana/grafana/pkg/apiserver v0.0.0-20250121113133-e747350fee2d github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.2.0 github.com/hashicorp/golang-lru/v2 v2.0.7 github.com/prometheus/client_golang v1.20.5 @@ -120,7 +121,6 @@ require ( github.com/grafana/grafana-app-sdk/logging v0.30.0 // indirect github.com/grafana/grafana-aws-sdk v0.31.5 // indirect github.com/grafana/grafana-azure-sdk-go/v2 v2.1.6 // indirect - github.com/grafana/grafana/pkg/apiserver v0.0.0-20250121113133-e747350fee2d // indirect github.com/grafana/otel-profiling-go v0.5.1 // indirect github.com/grafana/pyroscope-go/godeltaprof v0.1.8 // indirect github.com/grafana/sqlds/v4 v4.1.3 // indirect diff --git a/pkg/storage/unified/resource/resource.pb.go b/pkg/storage/unified/resource/resource.pb.go index 9a41f4c4e5a..0e72212de6f 100644 --- a/pkg/storage/unified/resource/resource.pb.go +++ b/pkg/storage/unified/resource/resource.pb.go @@ -1814,7 +1814,9 @@ type ResourceSearchRequest struct { // the return fields (empty will return everything) Fields []string `protobuf:"bytes,8,rep,name=fields,proto3" json:"fields,omitempty"` // explain each result (added to the each row) - Explain bool `protobuf:"varint,9,opt,name=explain,proto3" json:"explain,omitempty"` + Explain bool `protobuf:"varint,9,opt,name=explain,proto3" json:"explain,omitempty"` + IsDeleted bool `protobuf:"varint,10,opt,name=is_deleted,json=isDeleted,proto3" json:"is_deleted,omitempty"` + Page int64 `protobuf:"varint,11,opt,name=page,proto3" json:"page,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -1912,6 +1914,20 @@ func (x *ResourceSearchRequest) GetExplain() bool { return false } +func (x *ResourceSearchRequest) GetIsDeleted() bool { + if x != nil { + return x.IsDeleted + } + return false +} + +func (x *ResourceSearchRequest) GetPage() int64 { + if x != nil { + return x.Page + } + return 0 +} + type ResourceSearchResponse struct { state protoimpl.MessageState `protogen:"open.v1"` // Error details @@ -3810,7 +3826,7 @@ var file_resource_proto_rawDesc = []byte{ 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, - 0x22, 0xbb, 0x04, 0x0a, 0x15, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x61, + 0x22, 0xee, 0x04, 0x0a, 0x15, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2f, 0x0a, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x70, 0x74, 0x69, 0x6f, @@ -3833,321 +3849,324 @@ var file_resource_proto_rawDesc = []byte{ 0x61, 0x63, 0x65, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x78, 0x70, 0x6c, 0x61, 0x69, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, - 0x78, 0x70, 0x6c, 0x61, 0x69, 0x6e, 0x1a, 0x30, 0x0a, 0x04, 0x53, 0x6f, 0x72, 0x74, 0x12, 0x14, - 0x0a, 0x05, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x66, - 0x69, 0x65, 0x6c, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x65, 0x73, 0x63, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x04, 0x64, 0x65, 0x73, 0x63, 0x1a, 0x33, 0x0a, 0x05, 0x46, 0x61, 0x63, 0x65, + 0x78, 0x70, 0x6c, 0x61, 0x69, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x69, 0x73, 0x5f, 0x64, 0x65, 0x6c, + 0x65, 0x74, 0x65, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x69, 0x73, 0x44, 0x65, + 0x6c, 0x65, 0x74, 0x65, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x67, 0x65, 0x18, 0x0b, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x04, 0x70, 0x61, 0x67, 0x65, 0x1a, 0x30, 0x0a, 0x04, 0x53, 0x6f, 0x72, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x05, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x1a, 0x5f, 0x0a, - 0x0a, 0x46, 0x61, 0x63, 0x65, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, - 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x3b, 0x0a, - 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x72, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x46, 0x61, - 0x63, 0x65, 0x74, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xea, - 0x04, 0x0a, 0x16, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x61, 0x72, 0x63, - 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2b, 0x0a, 0x05, 0x65, 0x72, 0x72, - 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, - 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x27, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, - 0x31, 0x0a, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x17, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, - 0x74, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x68, 0x69, 0x74, 0x73, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x48, 0x69, 0x74, - 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x71, 0x75, 0x65, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x73, 0x74, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x01, 0x52, 0x09, 0x71, 0x75, 0x65, 0x72, 0x79, 0x43, 0x6f, 0x73, 0x74, - 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x61, 0x78, 0x5f, 0x73, 0x63, 0x6f, 0x72, 0x65, 0x18, 0x06, 0x20, - 0x01, 0x28, 0x01, 0x52, 0x08, 0x6d, 0x61, 0x78, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x12, 0x41, 0x0a, - 0x05, 0x66, 0x61, 0x63, 0x65, 0x74, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x72, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x46, - 0x61, 0x63, 0x65, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x05, 0x66, 0x61, 0x63, 0x65, 0x74, - 0x1a, 0x8f, 0x01, 0x0a, 0x05, 0x46, 0x61, 0x63, 0x65, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x69, - 0x65, 0x6c, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x66, 0x69, 0x65, 0x6c, 0x64, - 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6e, - 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, - 0x12, 0x40, 0x0a, 0x05, 0x74, 0x65, 0x72, 0x6d, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x2a, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, + 0x52, 0x05, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x65, 0x73, 0x63, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x64, 0x65, 0x73, 0x63, 0x1a, 0x33, 0x0a, 0x05, 0x46, + 0x61, 0x63, 0x65, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x05, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, + 0x6d, 0x69, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, + 0x1a, 0x5f, 0x0a, 0x0a, 0x46, 0x61, 0x63, 0x65, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, + 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, + 0x12, 0x3b, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x25, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x2e, 0x46, 0x61, 0x63, 0x65, 0x74, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, + 0x01, 0x22, 0xea, 0x04, 0x0a, 0x16, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, + 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2b, 0x0a, 0x05, + 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x75, + 0x6c, 0x74, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x27, 0x0a, 0x03, 0x6b, 0x65, 0x79, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x03, 0x6b, + 0x65, 0x79, 0x12, 0x31, 0x0a, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x07, 0x72, 0x65, + 0x73, 0x75, 0x6c, 0x74, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x68, + 0x69, 0x74, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x74, 0x6f, 0x74, 0x61, 0x6c, + 0x48, 0x69, 0x74, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x71, 0x75, 0x65, 0x72, 0x79, 0x5f, 0x63, 0x6f, + 0x73, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x01, 0x52, 0x09, 0x71, 0x75, 0x65, 0x72, 0x79, 0x43, + 0x6f, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x61, 0x78, 0x5f, 0x73, 0x63, 0x6f, 0x72, 0x65, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x01, 0x52, 0x08, 0x6d, 0x61, 0x78, 0x53, 0x63, 0x6f, 0x72, 0x65, + 0x12, 0x41, 0x0a, 0x05, 0x66, 0x61, 0x63, 0x65, 0x74, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x2b, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x2e, 0x54, 0x65, 0x72, 0x6d, 0x46, 0x61, 0x63, 0x65, 0x74, 0x52, 0x05, 0x74, 0x65, 0x72, - 0x6d, 0x73, 0x1a, 0x35, 0x0a, 0x09, 0x54, 0x65, 0x72, 0x6d, 0x46, 0x61, 0x63, 0x65, 0x74, 0x12, - 0x12, 0x0a, 0x04, 0x74, 0x65, 0x72, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, - 0x65, 0x72, 0x6d, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x1a, 0x60, 0x0a, 0x0a, 0x46, 0x61, 0x63, - 0x65, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x3c, 0x0a, 0x05, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x61, 0x72, - 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x46, 0x61, 0x63, 0x65, 0x74, - 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x78, 0x0a, 0x1c, 0x4c, - 0x69, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x4f, 0x62, 0x6a, - 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x0f, 0x6e, - 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, - 0x6b, 0x65, 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, - 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0xda, 0x02, 0x0a, 0x1d, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, - 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x42, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, - 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, - 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, - 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x6e, - 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, - 0x6b, 0x65, 0x6e, 0x12, 0x2b, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x45, 0x72, - 0x72, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, - 0x1a, 0x9f, 0x01, 0x0a, 0x04, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x2d, 0x0a, 0x06, 0x6f, 0x62, 0x6a, - 0x65, 0x63, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4b, 0x65, 0x79, - 0x52, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x12, 0x0a, 0x04, - 0x68, 0x61, 0x73, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, - 0x12, 0x12, 0x0a, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, - 0x74, 0x69, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, 0x06, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x66, 0x6f, - 0x6c, 0x64, 0x65, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x66, 0x6f, 0x6c, 0x64, - 0x65, 0x72, 0x22, 0x51, 0x0a, 0x1d, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x73, - 0x69, 0x74, 0x6f, 0x72, 0x79, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, - 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x94, 0x02, 0x0a, 0x1e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x52, - 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4c, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, - 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x36, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x2e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, - 0x72, 0x79, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x52, - 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x12, 0x2b, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x05, 0x65, 0x72, - 0x72, 0x6f, 0x72, 0x1a, 0x77, 0x0a, 0x0d, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x43, - 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, - 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, - 0x74, 0x6f, 0x72, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, - 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, - 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x2e, 0x0a, 0x12, - 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x22, 0xab, 0x01, 0x0a, - 0x13, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x43, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2b, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, - 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x4f, 0x0a, 0x0d, 0x53, 0x65, 0x72, - 0x76, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, - 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x53, 0x45, 0x52, 0x56, 0x49, - 0x4e, 0x47, 0x10, 0x01, 0x12, 0x0f, 0x0a, 0x0b, 0x4e, 0x4f, 0x54, 0x5f, 0x53, 0x45, 0x52, 0x56, - 0x49, 0x4e, 0x47, 0x10, 0x02, 0x12, 0x13, 0x0a, 0x0f, 0x53, 0x45, 0x52, 0x56, 0x49, 0x43, 0x45, - 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x03, 0x22, 0x87, 0x02, 0x0a, 0x0d, 0x52, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x41, 0x0a, 0x07, - 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, - 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x44, 0x65, 0x66, 0x69, - 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x07, 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x73, 0x12, - 0x2e, 0x0a, 0x04, 0x72, 0x6f, 0x77, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, - 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x6f, 0x77, 0x52, 0x04, 0x72, 0x6f, 0x77, 0x73, 0x12, - 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, - 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, - 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x0f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, - 0x6f, 0x6e, 0x12, 0x30, 0x0a, 0x14, 0x72, 0x65, 0x6d, 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x5f, - 0x69, 0x74, 0x65, 0x6d, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x12, 0x72, 0x65, 0x6d, 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x49, 0x74, 0x65, 0x6d, 0x43, - 0x6f, 0x75, 0x6e, 0x74, 0x22, 0xf1, 0x04, 0x0a, 0x1d, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x44, 0x65, 0x66, 0x69, - 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x46, 0x0a, 0x04, 0x74, 0x79, - 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x32, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x61, 0x62, 0x6c, - 0x65, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x44, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x6f, - 0x6e, 0x2e, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, - 0x70, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x69, 0x73, 0x5f, 0x61, 0x72, 0x72, 0x61, 0x79, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x69, 0x73, 0x41, 0x72, 0x72, 0x61, 0x79, 0x12, 0x20, 0x0a, - 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, - 0x52, 0x0a, 0x0a, 0x70, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x69, 0x65, 0x73, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x6f, 0x6c, 0x75, - 0x6d, 0x6e, 0x44, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x50, 0x72, 0x6f, - 0x70, 0x65, 0x72, 0x74, 0x69, 0x65, 0x73, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, - 0x69, 0x65, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x18, - 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x1a, - 0xae, 0x01, 0x0a, 0x0a, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x69, 0x65, 0x73, 0x12, 0x23, - 0x0a, 0x0d, 0x75, 0x6e, 0x69, 0x71, 0x75, 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x75, 0x6e, 0x69, 0x71, 0x75, 0x65, 0x56, 0x61, 0x6c, - 0x75, 0x65, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x72, 0x65, 0x65, 0x5f, 0x74, 0x65, 0x78, 0x74, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x66, 0x72, 0x65, 0x65, 0x54, 0x65, 0x78, 0x74, - 0x12, 0x1e, 0x0a, 0x0a, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x61, 0x62, 0x6c, 0x65, - 0x12, 0x19, 0x0a, 0x08, 0x6e, 0x6f, 0x74, 0x5f, 0x6e, 0x75, 0x6c, 0x6c, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x07, 0x6e, 0x6f, 0x74, 0x4e, 0x75, 0x6c, 0x6c, 0x12, 0x23, 0x0a, 0x0d, 0x64, - 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x05, 0x20, 0x01, - 0x28, 0x0c, 0x52, 0x0c, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, - 0x22, 0x95, 0x01, 0x0a, 0x0a, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, - 0x10, 0x0a, 0x0c, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x10, - 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x53, 0x54, 0x52, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x0b, 0x0a, - 0x07, 0x42, 0x4f, 0x4f, 0x4c, 0x45, 0x41, 0x4e, 0x10, 0x02, 0x12, 0x09, 0x0a, 0x05, 0x49, 0x4e, - 0x54, 0x33, 0x32, 0x10, 0x03, 0x12, 0x09, 0x0a, 0x05, 0x49, 0x4e, 0x54, 0x36, 0x34, 0x10, 0x04, - 0x12, 0x09, 0x0a, 0x05, 0x46, 0x4c, 0x4f, 0x41, 0x54, 0x10, 0x05, 0x12, 0x0a, 0x0a, 0x06, 0x44, - 0x4f, 0x55, 0x42, 0x4c, 0x45, 0x10, 0x06, 0x12, 0x08, 0x0a, 0x04, 0x44, 0x41, 0x54, 0x45, 0x10, - 0x07, 0x12, 0x0d, 0x0a, 0x09, 0x44, 0x41, 0x54, 0x45, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x10, 0x08, - 0x12, 0x0a, 0x0a, 0x06, 0x42, 0x49, 0x4e, 0x41, 0x52, 0x59, 0x10, 0x09, 0x12, 0x0a, 0x0a, 0x06, - 0x4f, 0x42, 0x4a, 0x45, 0x43, 0x54, 0x10, 0x0a, 0x22, 0x94, 0x01, 0x0a, 0x10, 0x52, 0x65, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x6f, 0x77, 0x12, 0x27, 0x0a, - 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4b, 0x65, - 0x79, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x0f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, - 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x65, 0x6c, 0x6c, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0c, - 0x52, 0x05, 0x63, 0x65, 0x6c, 0x6c, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, - 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x22, - 0x64, 0x0a, 0x0e, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x27, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, - 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, - 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x56, 0x65, - 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x69, 0x0a, 0x0f, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2b, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, - 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x05, - 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x0f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, - 0x22, 0xd3, 0x01, 0x0a, 0x0e, 0x50, 0x75, 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x31, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x08, 0x72, 0x65, - 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x37, 0x0a, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1f, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x2e, 0x50, 0x75, 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x52, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, - 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x54, 0x79, - 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x1c, 0x0a, 0x06, 0x4d, 0x65, 0x74, 0x68, - 0x6f, 0x64, 0x12, 0x08, 0x0a, 0x04, 0x47, 0x52, 0x50, 0x43, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, - 0x48, 0x54, 0x54, 0x50, 0x10, 0x01, 0x22, 0xc1, 0x01, 0x0a, 0x0f, 0x50, 0x75, 0x74, 0x42, 0x6c, - 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2b, 0x0a, 0x05, 0x65, 0x72, - 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, - 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x69, 0x64, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x69, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x73, - 0x69, 0x7a, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x12, - 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, - 0x61, 0x73, 0x68, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x69, 0x6d, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, - 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6d, 0x69, 0x6d, 0x65, 0x54, 0x79, 0x70, 0x65, - 0x12, 0x18, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x07, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, 0x74, 0x22, 0x98, 0x01, 0x0a, 0x0e, 0x47, - 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x31, 0x0a, - 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x76, 0x65, 0x72, - 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x72, 0x65, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x28, 0x0a, 0x10, 0x6d, - 0x75, 0x73, 0x74, 0x5f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x6d, 0x75, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x78, 0x79, - 0x42, 0x79, 0x74, 0x65, 0x73, 0x22, 0x89, 0x01, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, - 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2b, 0x0a, 0x05, 0x65, 0x72, 0x72, - 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, - 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x74, - 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, - 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, - 0x65, 0x2a, 0x33, 0x0a, 0x14, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x56, 0x65, 0x72, - 0x73, 0x69, 0x6f, 0x6e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x12, 0x10, 0x0a, 0x0c, 0x4e, 0x6f, 0x74, - 0x4f, 0x6c, 0x64, 0x65, 0x72, 0x54, 0x68, 0x61, 0x6e, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x45, - 0x78, 0x61, 0x63, 0x74, 0x10, 0x01, 0x32, 0xad, 0x03, 0x0a, 0x0d, 0x52, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x35, 0x0a, 0x04, 0x52, 0x65, 0x61, 0x64, - 0x12, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x61, 0x64, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x2e, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x3b, 0x0a, 0x06, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x12, 0x17, 0x2e, 0x72, 0x65, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x43, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x06, - 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x17, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x18, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, - 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x06, 0x44, 0x65, 0x6c, - 0x65, 0x74, 0x65, 0x12, 0x17, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x44, - 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x72, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x07, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, - 0x65, 0x12, 0x18, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, - 0x74, 0x6f, 0x72, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x72, 0x65, - 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x35, 0x0a, 0x04, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x15, - 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, - 0x05, 0x57, 0x61, 0x74, 0x63, 0x68, 0x12, 0x16, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x2e, 0x57, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, - 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x57, 0x61, 0x74, 0x63, 0x68, 0x45, - 0x76, 0x65, 0x6e, 0x74, 0x30, 0x01, 0x32, 0xa9, 0x01, 0x0a, 0x0d, 0x52, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x4b, 0x0a, 0x06, 0x53, 0x65, 0x61, 0x72, - 0x63, 0x68, 0x12, 0x1f, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, - 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x08, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, - 0x73, 0x12, 0x1e, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x1f, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x32, 0xe8, 0x01, 0x0a, 0x0f, 0x52, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, - 0x79, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x6b, 0x0a, 0x16, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x52, - 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, - 0x12, 0x27, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x43, 0x6f, 0x75, 0x6e, + 0x65, 0x2e, 0x46, 0x61, 0x63, 0x65, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x05, 0x66, 0x61, + 0x63, 0x65, 0x74, 0x1a, 0x8f, 0x01, 0x0a, 0x05, 0x46, 0x61, 0x63, 0x65, 0x74, 0x12, 0x14, 0x0a, + 0x05, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x66, 0x69, + 0x65, 0x6c, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x69, 0x73, + 0x73, 0x69, 0x6e, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x6d, 0x69, 0x73, 0x73, + 0x69, 0x6e, 0x67, 0x12, 0x40, 0x0a, 0x05, 0x74, 0x65, 0x72, 0x6d, 0x73, 0x18, 0x04, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x54, 0x65, 0x72, 0x6d, 0x46, 0x61, 0x63, 0x65, 0x74, 0x52, 0x05, + 0x74, 0x65, 0x72, 0x6d, 0x73, 0x1a, 0x35, 0x0a, 0x09, 0x54, 0x65, 0x72, 0x6d, 0x46, 0x61, 0x63, + 0x65, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x65, 0x72, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x04, 0x74, 0x65, 0x72, 0x6d, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x1a, 0x60, 0x0a, 0x0a, + 0x46, 0x61, 0x63, 0x65, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, + 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x3c, 0x0a, 0x05, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x72, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, + 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x46, 0x61, + 0x63, 0x65, 0x74, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x78, + 0x0a, 0x1c, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, + 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x26, + 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, + 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, + 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, + 0x61, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, + 0x70, 0x61, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0xda, 0x02, 0x0a, 0x1d, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x4f, 0x62, 0x6a, 0x65, 0x63, - 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x72, 0x65, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x2e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x73, 0x69, - 0x74, 0x6f, 0x72, 0x79, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x68, 0x0a, 0x15, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x73, - 0x69, 0x74, 0x6f, 0x72, 0x79, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x12, 0x26, 0x2e, 0x72, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, - 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, - 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x4f, 0x62, - 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x8b, 0x01, - 0x0a, 0x09, 0x42, 0x6c, 0x6f, 0x62, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x3e, 0x0a, 0x07, 0x50, - 0x75, 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x12, 0x18, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x2e, 0x50, 0x75, 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x19, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x50, 0x75, 0x74, 0x42, - 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x07, 0x47, - 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x12, 0x18, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x19, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x47, 0x65, 0x74, 0x42, - 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x57, 0x0a, 0x0b, 0x44, - 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x73, 0x12, 0x48, 0x0a, 0x09, 0x49, 0x73, - 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x79, 0x12, 0x1c, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x42, 0x0a, 0x05, 0x69, 0x74, + 0x65, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x72, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, + 0x6f, 0x72, 0x79, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x2e, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x12, 0x26, + 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, + 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, + 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x2b, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x05, 0x65, 0x72, + 0x72, 0x6f, 0x72, 0x1a, 0x9f, 0x01, 0x0a, 0x04, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x2d, 0x0a, 0x06, + 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x4b, 0x65, 0x79, 0x52, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x70, + 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, + 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, + 0x61, 0x73, 0x68, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x16, 0x0a, + 0x06, 0x66, 0x6f, 0x6c, 0x64, 0x65, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x66, + 0x6f, 0x6c, 0x64, 0x65, 0x72, 0x22, 0x51, 0x0a, 0x1d, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, + 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, + 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, + 0x70, 0x61, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x94, 0x02, 0x0a, 0x1e, 0x43, 0x6f, 0x75, + 0x6e, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x4f, 0x62, 0x6a, 0x65, + 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4c, 0x0a, 0x05, 0x69, + 0x74, 0x65, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x36, 0x2e, 0x72, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x73, + 0x69, 0x74, 0x6f, 0x72, 0x79, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x43, 0x6f, 0x75, + 0x6e, 0x74, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x12, 0x2b, 0x0a, 0x05, 0x65, 0x72, 0x72, + 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, + 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x1a, 0x77, 0x0a, 0x0d, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x72, 0x65, 0x70, 0x6f, 0x73, + 0x69, 0x74, 0x6f, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x70, + 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x1a, 0x0a, + 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, + 0x2e, 0x0a, 0x12, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x22, + 0xab, 0x01, 0x0a, 0x13, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x43, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2b, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x39, 0x5a, 0x37, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, - 0x6f, 0x6d, 0x2f, 0x67, 0x72, 0x61, 0x66, 0x61, 0x6e, 0x61, 0x2f, 0x67, 0x72, 0x61, 0x66, 0x61, - 0x6e, 0x61, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2f, 0x75, - 0x6e, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x62, - 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x53, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x4f, 0x0a, 0x0d, + 0x53, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x0b, 0x0a, + 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x53, 0x45, + 0x52, 0x56, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x0f, 0x0a, 0x0b, 0x4e, 0x4f, 0x54, 0x5f, 0x53, + 0x45, 0x52, 0x56, 0x49, 0x4e, 0x47, 0x10, 0x02, 0x12, 0x13, 0x0a, 0x0f, 0x53, 0x45, 0x52, 0x56, + 0x49, 0x43, 0x45, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x03, 0x22, 0x87, 0x02, + 0x0a, 0x0d, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x12, + 0x41, 0x0a, 0x07, 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x27, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x44, + 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x07, 0x63, 0x6f, 0x6c, 0x75, 0x6d, + 0x6e, 0x73, 0x12, 0x2e, 0x0a, 0x04, 0x72, 0x6f, 0x77, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x1a, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x6f, 0x77, 0x52, 0x04, 0x72, 0x6f, + 0x77, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, + 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, + 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x56, 0x65, + 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x30, 0x0a, 0x14, 0x72, 0x65, 0x6d, 0x61, 0x69, 0x6e, 0x69, + 0x6e, 0x67, 0x5f, 0x69, 0x74, 0x65, 0x6d, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x12, 0x72, 0x65, 0x6d, 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x49, 0x74, + 0x65, 0x6d, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0xf1, 0x04, 0x0a, 0x1d, 0x52, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x44, + 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x46, 0x0a, + 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x32, 0x2e, 0x72, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, + 0x61, 0x62, 0x6c, 0x65, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x44, 0x65, 0x66, 0x69, 0x6e, 0x69, + 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x52, + 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x69, 0x73, 0x5f, 0x61, 0x72, 0x72, 0x61, + 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x69, 0x73, 0x41, 0x72, 0x72, 0x61, 0x79, + 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, + 0x6f, 0x6e, 0x12, 0x52, 0x0a, 0x0a, 0x70, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x69, 0x65, 0x73, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x43, + 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x44, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x2e, + 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x69, 0x65, 0x73, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x70, + 0x65, 0x72, 0x74, 0x69, 0x65, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, + 0x74, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, + 0x74, 0x79, 0x1a, 0xae, 0x01, 0x0a, 0x0a, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x69, 0x65, + 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x75, 0x6e, 0x69, 0x71, 0x75, 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x75, 0x6e, 0x69, 0x71, 0x75, 0x65, + 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x72, 0x65, 0x65, 0x5f, 0x74, + 0x65, 0x78, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x66, 0x72, 0x65, 0x65, 0x54, + 0x65, 0x78, 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x61, 0x62, 0x6c, + 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x61, + 0x62, 0x6c, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x6e, 0x6f, 0x74, 0x5f, 0x6e, 0x75, 0x6c, 0x6c, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x6e, 0x6f, 0x74, 0x4e, 0x75, 0x6c, 0x6c, 0x12, 0x23, + 0x0a, 0x0d, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0c, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x56, 0x61, + 0x6c, 0x75, 0x65, 0x22, 0x95, 0x01, 0x0a, 0x0a, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x54, 0x79, + 0x70, 0x65, 0x12, 0x10, 0x0a, 0x0c, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x54, 0x59, + 0x50, 0x45, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x53, 0x54, 0x52, 0x49, 0x4e, 0x47, 0x10, 0x01, + 0x12, 0x0b, 0x0a, 0x07, 0x42, 0x4f, 0x4f, 0x4c, 0x45, 0x41, 0x4e, 0x10, 0x02, 0x12, 0x09, 0x0a, + 0x05, 0x49, 0x4e, 0x54, 0x33, 0x32, 0x10, 0x03, 0x12, 0x09, 0x0a, 0x05, 0x49, 0x4e, 0x54, 0x36, + 0x34, 0x10, 0x04, 0x12, 0x09, 0x0a, 0x05, 0x46, 0x4c, 0x4f, 0x41, 0x54, 0x10, 0x05, 0x12, 0x0a, + 0x0a, 0x06, 0x44, 0x4f, 0x55, 0x42, 0x4c, 0x45, 0x10, 0x06, 0x12, 0x08, 0x0a, 0x04, 0x44, 0x41, + 0x54, 0x45, 0x10, 0x07, 0x12, 0x0d, 0x0a, 0x09, 0x44, 0x41, 0x54, 0x45, 0x5f, 0x54, 0x49, 0x4d, + 0x45, 0x10, 0x08, 0x12, 0x0a, 0x0a, 0x06, 0x42, 0x49, 0x4e, 0x41, 0x52, 0x59, 0x10, 0x09, 0x12, + 0x0a, 0x0a, 0x06, 0x4f, 0x42, 0x4a, 0x45, 0x43, 0x54, 0x10, 0x0a, 0x22, 0x94, 0x01, 0x0a, 0x10, + 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x6f, 0x77, + 0x12, 0x27, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, + 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x4b, 0x65, 0x79, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x56, 0x65, 0x72, + 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x65, 0x6c, 0x6c, 0x73, 0x18, 0x03, 0x20, + 0x03, 0x28, 0x0c, 0x52, 0x05, 0x63, 0x65, 0x6c, 0x6c, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x62, + 0x6a, 0x65, 0x63, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x6f, 0x62, 0x6a, 0x65, + 0x63, 0x74, 0x22, 0x64, 0x0a, 0x0e, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x27, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x29, 0x0a, + 0x10, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, + 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x69, 0x0a, 0x0f, 0x52, 0x65, 0x73, 0x74, + 0x6f, 0x72, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2b, 0x0a, 0x05, 0x65, + 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, + 0x74, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x0f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, + 0x69, 0x6f, 0x6e, 0x22, 0xd3, 0x01, 0x0a, 0x0e, 0x50, 0x75, 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x31, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x52, + 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x37, 0x0a, 0x06, 0x6d, 0x65, 0x74, + 0x68, 0x6f, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1f, 0x2e, 0x72, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x2e, 0x50, 0x75, 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x52, 0x06, 0x6d, 0x65, 0x74, 0x68, + 0x6f, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, + 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, + 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x1c, 0x0a, 0x06, 0x4d, + 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x08, 0x0a, 0x04, 0x47, 0x52, 0x50, 0x43, 0x10, 0x00, 0x12, + 0x08, 0x0a, 0x04, 0x48, 0x54, 0x54, 0x50, 0x10, 0x01, 0x22, 0xc1, 0x01, 0x0a, 0x0f, 0x50, 0x75, + 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2b, 0x0a, + 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x65, 0x73, + 0x75, 0x6c, 0x74, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x69, + 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x69, 0x64, 0x12, 0x10, 0x0a, 0x03, + 0x75, 0x72, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x12, + 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x73, 0x69, + 0x7a, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x69, 0x6d, 0x65, 0x5f, 0x74, + 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6d, 0x69, 0x6d, 0x65, 0x54, + 0x79, 0x70, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, 0x74, 0x18, 0x07, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, 0x74, 0x22, 0x98, 0x01, + 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x31, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, + 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x72, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x28, + 0x0a, 0x10, 0x6d, 0x75, 0x73, 0x74, 0x5f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x5f, 0x62, 0x79, 0x74, + 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x6d, 0x75, 0x73, 0x74, 0x50, 0x72, + 0x6f, 0x78, 0x79, 0x42, 0x79, 0x74, 0x65, 0x73, 0x22, 0x89, 0x01, 0x0a, 0x0f, 0x47, 0x65, 0x74, + 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2b, 0x0a, 0x05, + 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x75, + 0x6c, 0x74, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x21, 0x0a, 0x0c, 0x63, + 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x14, + 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x2a, 0x33, 0x0a, 0x14, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x12, 0x10, 0x0a, 0x0c, + 0x4e, 0x6f, 0x74, 0x4f, 0x6c, 0x64, 0x65, 0x72, 0x54, 0x68, 0x61, 0x6e, 0x10, 0x00, 0x12, 0x09, + 0x0a, 0x05, 0x45, 0x78, 0x61, 0x63, 0x74, 0x10, 0x01, 0x32, 0xad, 0x03, 0x0a, 0x0d, 0x52, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x35, 0x0a, 0x04, 0x52, + 0x65, 0x61, 0x64, 0x12, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, + 0x65, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x72, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x06, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x12, 0x17, 0x2e, 0x72, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x3b, 0x0a, 0x06, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x17, 0x2e, 0x72, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x55, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x06, + 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x17, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x18, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, + 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x07, 0x52, 0x65, 0x73, + 0x74, 0x6f, 0x72, 0x65, 0x12, 0x18, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, + 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, + 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, + 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x35, 0x0a, 0x04, 0x4c, 0x69, 0x73, + 0x74, 0x12, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x4c, 0x69, 0x73, + 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x37, 0x0a, 0x05, 0x57, 0x61, 0x74, 0x63, 0x68, 0x12, 0x16, 0x2e, 0x72, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x2e, 0x57, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x14, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x57, 0x61, 0x74, + 0x63, 0x68, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x30, 0x01, 0x32, 0xa9, 0x01, 0x0a, 0x0d, 0x52, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x4b, 0x0a, 0x06, 0x53, + 0x65, 0x61, 0x72, 0x63, 0x68, 0x12, 0x1f, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x08, 0x47, 0x65, 0x74, 0x53, + 0x74, 0x61, 0x74, 0x73, 0x12, 0x1e, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, + 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, + 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xe8, 0x01, 0x0a, 0x0f, 0x52, 0x65, 0x70, 0x6f, 0x73, 0x69, + 0x74, 0x6f, 0x72, 0x79, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x6b, 0x0a, 0x16, 0x43, 0x6f, 0x75, + 0x6e, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x4f, 0x62, 0x6a, 0x65, + 0x63, 0x74, 0x73, 0x12, 0x27, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x43, + 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x4f, 0x62, + 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x72, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x70, + 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x68, 0x0a, 0x15, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, + 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x12, + 0x26, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, + 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, + 0x79, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x32, 0x8b, 0x01, 0x0a, 0x09, 0x42, 0x6c, 0x6f, 0x62, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x3e, + 0x0a, 0x07, 0x50, 0x75, 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x12, 0x18, 0x2e, 0x72, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x2e, 0x50, 0x75, 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x50, + 0x75, 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3e, + 0x0a, 0x07, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x12, 0x18, 0x2e, 0x72, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x47, + 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x57, + 0x0a, 0x0b, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x73, 0x12, 0x48, 0x0a, + 0x09, 0x49, 0x73, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x79, 0x12, 0x1c, 0x2e, 0x72, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, + 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x39, 0x5a, 0x37, 0x67, 0x69, 0x74, 0x68, 0x75, + 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x72, 0x61, 0x66, 0x61, 0x6e, 0x61, 0x2f, 0x67, 0x72, + 0x61, 0x66, 0x61, 0x6e, 0x61, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, + 0x65, 0x2f, 0x75, 0x6e, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/pkg/storage/unified/resource/resource.proto b/pkg/storage/unified/resource/resource.proto index ae7d067ff5a..2624e36dc94 100644 --- a/pkg/storage/unified/resource/resource.proto +++ b/pkg/storage/unified/resource/resource.proto @@ -225,9 +225,9 @@ enum ResourceVersionMatch { } message ListRequest { - enum Source { + enum Source { STORE = 0; // the standard place - HISTORY = 1; + HISTORY = 1; TRASH = 2; } @@ -248,7 +248,7 @@ message ListRequest { ListOptions options = 5; // Select values from history or trash - Source source = 6; + Source source = 6; } message ListResponse { @@ -329,7 +329,7 @@ message ResourceStatsRequest { // NOTE, this query may need to federate across a few storage instances repeated string kinds = 2; - // Limit the stats within a folder (not recursive!) + // Limit the stats within a folder (not recursive!) string folder = 3; } @@ -356,7 +356,7 @@ message ResourceSearchRequest { string field = 1; bool desc = 2; // defaults to ascending } - + message Facet { string field = 1; int64 limit = 2; @@ -370,8 +370,8 @@ message ResourceSearchRequest { // To search additional resource types, add additional keys to this list // NOTE: queries will only support federation across kinds with common fields - repeated ResourceKey federated = 2; - + repeated ResourceKey federated = 2; + // When a query exists, it is parsed and used to influence // query string for chosen implementation (currently just bleve) // The score is only relevant when a query exists @@ -394,6 +394,10 @@ message ResourceSearchRequest { // explain each result (added to the each row) bool explain = 9; + + bool is_deleted = 10; + + int64 page = 11; } message ResourceSearchResponse { @@ -406,7 +410,7 @@ message ResourceSearchResponse { // Top term stats repeated TermFacet terms = 4; // numeric range - // date range facets + // date range facets } message TermFacet { @@ -444,7 +448,7 @@ message ListRepositoryObjectsRequest { // Namespace (tenant) string namespace = 2; - + // The name of the repository string name = 3; } @@ -456,16 +460,16 @@ message ListRepositoryObjectsResponse { // Hash for the resource string path = 2; - + // Verification hash from the origin string hash = 3; - + // Change time from the origin int64 time = 5; // Title inside the payload string title = 6; - + // The name of the folder in metadata string folder = 7; } @@ -484,7 +488,7 @@ message ListRepositoryObjectsResponse { message CountRepositoryObjectsRequest { // Namespace (tenant) string namespace = 1; - + // The name of the repository // empty to count across all repositories string name = 2; @@ -559,16 +563,16 @@ message ResourceTableColumnDefinition { STRING = 1; BOOLEAN = 2; INT32 = 3; - INT64 = 4; - FLOAT = 5; - DOUBLE = 6; - DATE = 7; - DATE_TIME = 8; - BINARY = 9; + INT64 = 4; + FLOAT = 5; + DOUBLE = 6; + DATE = 7; + DATE_TIME = 8; + BINARY = 9; OBJECT = 10; // map[string]any } - // These values are not part of standard k8s format + // These values are not part of standard k8s format // however these are useful when indexing and analyzing results message Properties { // All values in this columns should be unique @@ -618,7 +622,7 @@ message ResourceTableRow { // The resource version for the given values int64 resource_version = 2; - // Cells will be as wide as the column definitions array + // Cells will be as wide as the column definitions array // Numeric values will be encoded using big endian bytes // All arrays will be JSON encoded repeated bytes cells = 3; @@ -638,7 +642,7 @@ message ResourceTableRow { message RestoreRequest { // Full key must be set ResourceKey key = 1; - + // The resource version to restore int64 resource_version = 2; } @@ -759,7 +763,7 @@ service ResourceIndex { rpc GetStats(ResourceStatsRequest) returns (ResourceStatsResponse); } -// Query repository info from the search index. +// Query repository info from the search index. // Results access control is based on access to the repository *not* the items service RepositoryIndex { // Describe how many resources of each type exist within a repository diff --git a/pkg/storage/unified/resource/search.go b/pkg/storage/unified/resource/search.go index f0d18960dc5..dc990d499eb 100644 --- a/pkg/storage/unified/resource/search.go +++ b/pkg/storage/unified/resource/search.go @@ -10,6 +10,8 @@ import ( "sync" "time" + dashboardv0alpha1 "github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1" + folderv0alpha1 "github.com/grafana/grafana/pkg/apis/folder/v0alpha1" "github.com/hashicorp/golang-lru/v2/expirable" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" @@ -651,3 +653,34 @@ func (s *builderCache) get(ctx context.Context, key NamespacedResource) (Documen } return s.defaultBuilder, nil } + +// AsResourceKey converts the given namespace and type to a search key +func AsResourceKey(ns string, t string) (*ResourceKey, error) { + if ns == "" { + return nil, fmt.Errorf("missing namespace") + } + switch t { + case "folders", "folder": + return &ResourceKey{ + Namespace: ns, + Group: folderv0alpha1.GROUP, + Resource: folderv0alpha1.RESOURCE, + }, nil + case "dashboards", "dashboard": + return &ResourceKey{ + Namespace: ns, + Group: dashboardv0alpha1.GROUP, + Resource: dashboardv0alpha1.RESOURCE, + }, nil + + // NOT really supported in the dashboard search UI, but useful for manual testing + case "playlist", "playlists": + return &ResourceKey{ + Namespace: ns, + Group: "playlist.grafana.app", + Resource: "playlists", + }, nil + } + + return nil, fmt.Errorf("unknown resource type") +} diff --git a/pkg/storage/unified/resource/search_client.go b/pkg/storage/unified/resource/search_client.go index a9c9c95ef9d..43e0faa7b1e 100644 --- a/pkg/storage/unified/resource/search_client.go +++ b/pkg/storage/unified/resource/search_client.go @@ -1,24 +1,20 @@ package resource import ( + "github.com/grafana/grafana/pkg/apiserver/rest" "github.com/grafana/grafana/pkg/setting" ) -// Search Fallback was returning both Folders and Dashboards which resulted -// in issues with rendering the Folder UI. Also, filters are not implemented -// yet. For those reasons, we will be disabling Search Fallback for now func NewSearchClient(cfg *setting.Cfg, unifiedStorageConfigKey string, unifiedClient ResourceIndexClient, legacyClient ResourceIndexClient) ResourceIndexClient { - // config, ok := cfg.UnifiedStorage[unifiedStorageConfigKey] - // if !ok { - // return legacyClient - // } + config, ok := cfg.UnifiedStorage[unifiedStorageConfigKey] + if !ok { + return legacyClient + } - // switch config.DualWriterMode { - // case rest.Mode0, rest.Mode1, rest.Mode2: - // return legacyClient - // default: - // return unifiedClient - // } - - return unifiedClient + switch config.DualWriterMode { + case rest.Mode0, rest.Mode1, rest.Mode2: + return legacyClient + default: + return unifiedClient + } } From 677060862c2ebe8001f55c3e5bdb8f1583632609 Mon Sep 17 00:00:00 2001 From: Tobias Skarhed <1438972+tskarhed@users.noreply.github.com> Date: Thu, 6 Feb 2025 17:34:52 +0100 Subject: [PATCH 27/34] Combobox: Fix list not being virtualized initially in some cases (#100188) * Combobox: Set arbitrary initial max size * Remove ? * Set initial values to 0 --- .../src/components/Combobox/useComboboxFloat.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/grafana-ui/src/components/Combobox/useComboboxFloat.ts b/packages/grafana-ui/src/components/Combobox/useComboboxFloat.ts index 83eb2631a62..f5803afdf2e 100644 --- a/packages/grafana-ui/src/components/Combobox/useComboboxFloat.ts +++ b/packages/grafana-ui/src/components/Combobox/useComboboxFloat.ts @@ -22,7 +22,10 @@ export const useComboboxFloat = (items: Array>, const inputRef = useRef(null); const floatingRef = useRef(null); const scrollRef = useRef(null); - const [popoverMaxSize, setPopoverMaxSize] = useState<{ width: number; height: number } | undefined>(undefined); + const [popoverMaxSize, setPopoverMaxSize] = useState<{ width: number; height: number }>({ + width: 0, + height: 0, + }); // set initial values to prevent infinite size, briefly removing the list virtualization const scrollbarWidth = useMemo(() => getScrollbarWidth(), []); @@ -72,10 +75,10 @@ export const useComboboxFloat = (items: Array>, const floatStyles = { ...floatingStyles, width: longestItemWidth, - maxWidth: popoverMaxSize?.width, + maxWidth: popoverMaxSize.width, minWidth: inputRef.current?.offsetWidth, - maxHeight: popoverMaxSize?.height, + maxHeight: popoverMaxSize.height, }; return { inputRef, floatingRef, scrollRef, floatStyles }; From 2b3ccfe5b95dd93167cf4c732551f3a0c8b0aaea Mon Sep 17 00:00:00 2001 From: Victor Marin <36818606+mdvictor@users.noreply.github.com> Date: Thu, 6 Feb 2025 18:37:28 +0200 Subject: [PATCH 28/34] DynamicDashboards: Revert unnecessary empty page on new layouts (#100218) Revert unnecessary empty page on new layouts --- .../ResponsiveGridLayoutManager.tsx | 20 +------------------ .../scene/layout-rows/RowsLayoutManager.tsx | 10 ---------- 2 files changed, 1 insertion(+), 29 deletions(-) diff --git a/public/app/features/dashboard-scene/scene/layout-responsive-grid/ResponsiveGridLayoutManager.tsx b/public/app/features/dashboard-scene/scene/layout-responsive-grid/ResponsiveGridLayoutManager.tsx index f936d8617eb..91fcbec0a48 100644 --- a/public/app/features/dashboard-scene/scene/layout-responsive-grid/ResponsiveGridLayoutManager.tsx +++ b/public/app/features/dashboard-scene/scene/layout-responsive-grid/ResponsiveGridLayoutManager.tsx @@ -1,16 +1,8 @@ import { SelectableValue } from '@grafana/data'; -import { - SceneComponentProps, - SceneCSSGridLayout, - SceneObjectBase, - SceneObjectState, - useSceneObjectState, - VizPanel, -} from '@grafana/scenes'; +import { SceneComponentProps, SceneCSSGridLayout, SceneObjectBase, SceneObjectState, VizPanel } from '@grafana/scenes'; import { Select } from '@grafana/ui'; import { t } from 'app/core/internationalization'; import { OptionsPaneItemDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor'; -import DashboardEmpty from 'app/features/dashboard/dashgrid/DashboardEmpty'; import { dashboardSceneGraph } from '../../utils/dashboardSceneGraph'; import { getDashboardSceneFor, getGridItemKeyForPanelId, getVizPanelKeyForPanelId } from '../../utils/utils'; @@ -148,16 +140,6 @@ export class ResponsiveGridLayoutManager public activateRepeaters(): void {} public static Component = ({ model }: SceneComponentProps) => { - const { children } = useSceneObjectState(model.state.layout, { shouldActivateOrKeepAlive: true }); - const dashboard = getDashboardSceneFor(model); - - // If we are top level layout and have no children, show empty state - if (model.parent === dashboard && children.length === 0) { - return ( - - ); - } - return ; }; } diff --git a/public/app/features/dashboard-scene/scene/layout-rows/RowsLayoutManager.tsx b/public/app/features/dashboard-scene/scene/layout-rows/RowsLayoutManager.tsx index 1aafc0adeb2..dc9154d3b2b 100644 --- a/public/app/features/dashboard-scene/scene/layout-rows/RowsLayoutManager.tsx +++ b/public/app/features/dashboard-scene/scene/layout-rows/RowsLayoutManager.tsx @@ -12,10 +12,8 @@ import { } from '@grafana/scenes'; import { useStyles2 } from '@grafana/ui'; import { t } from 'app/core/internationalization'; -import DashboardEmpty from 'app/features/dashboard/dashgrid/DashboardEmpty'; import { isClonedKey } from '../../utils/clone'; -import { getDashboardSceneFor } from '../../utils/utils'; import { DashboardScene } from '../DashboardScene'; import { DashboardGridItem } from '../layout-default/DashboardGridItem'; import { DefaultGridLayoutManager } from '../layout-default/DefaultGridLayoutManager'; @@ -197,14 +195,6 @@ export class RowsLayoutManager extends SceneObjectBase i public static Component = ({ model }: SceneComponentProps) => { const { rows } = model.useState(); const styles = useStyles2(getStyles); - const dashboard = getDashboardSceneFor(model); - - // If we are top level layout and have no children, show empty state - if (model.parent === dashboard && rows.length === 0) { - return ( - - ); - } return (
From a9ce930634dc707efe33f7225ce21f57787ab623 Mon Sep 17 00:00:00 2001 From: Alexander Akhmetov Date: Thu, 6 Feb 2025 18:09:43 +0100 Subject: [PATCH 29/34] Alerting: Promote alertingSaveStateCompressed flag to public preview (#99935) --- .../set-up/performance-limitations/index.md | 8 +++++++- .../configure-grafana/feature-toggles/index.md | 2 +- pkg/services/featuremgmt/registry.go | 3 ++- pkg/services/featuremgmt/toggles_gen.csv | 2 +- pkg/services/featuremgmt/toggles_gen.json | 16 ++++++++++------ 5 files changed, 21 insertions(+), 10 deletions(-) diff --git a/docs/sources/alerting/set-up/performance-limitations/index.md b/docs/sources/alerting/set-up/performance-limitations/index.md index 69becbad450..b8524e4c8a7 100644 --- a/docs/sources/alerting/set-up/performance-limitations/index.md +++ b/docs/sources/alerting/set-up/performance-limitations/index.md @@ -61,7 +61,13 @@ For more information, refer to [this GitHub issue](https://github.com/grafana/gr If you have a high number of alert instances, it can happen that the load on the database gets very high, as each state transition of an alert instance is saved in the database. -This can be prevented by writing to the database periodically. For this the feature flag `alertingSaveStatePeriodic` needs +### Compressed alert state + +When the `alertingSaveStateCompressed` feature toggle is enabled, Grafana saves the alert rule state in a compressed form, reducing database overhead for alerts with many instances. + +### Save state periodically + +High load can be also prevented by writing to the database periodically. For this the feature flag `alertingSaveStatePeriodic` needs to be enabled. By default, it saves the states every 5 minutes to the database and on each shutdown. The periodic interval can also be configured using the `state_periodic_save_interval` configuration flag. During this process, Grafana deletes all existing alert instances from the database and then writes the entire current set of instances back in batches in a single transacton. diff --git a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md index 9cd7fe7e1a7..13646ef8948 100644 --- a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md +++ b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md @@ -114,6 +114,7 @@ Most [generally available](https://grafana.com/docs/release-life-cycle/#general- | `canvasPanelPanZoom` | Allow pan and zoom in canvas panel | | `regressionTransformation` | Enables regression analysis transformation | | `onPremToCloudMigrations` | Enable the Grafana Migration Assistant, which helps you easily migrate on-prem resources, such as dashboards, folders, and data source configurations, to your Grafana Cloud stack. | +| `alertingSaveStateCompressed` | Enables the compressed protobuf-based alert state storage | | `ssoSettingsSAML` | Use the new SSO Settings API to configure the SAML connector | | `azureMonitorPrometheusExemplars` | Allows configuration of Azure Monitor as a data source that can provide Prometheus exemplars | | `ssoSettingsLDAP` | Use the new SSO Settings API to configure LDAP | @@ -188,7 +189,6 @@ Experimental features might be changed or removed without prior notice. | `tableSharedCrosshair` | Enables shared crosshair in table panel | | `kubernetesFeatureToggles` | Use the kubernetes API for feature toggle management in the frontend | | `newFolderPicker` | Enables the nested folder picker without having nested folders enabled | -| `alertingSaveStateCompressed` | Enables the compressed protobuf-based alert state storage | | `scopeApi` | In-development feature flag for the scope api using the app platform. | | `sqlExpressions` | Enables using SQL and DuckDB functions as Expressions. | | `nodeGraphDotLayout` | Changed the layout algorithm for the node graph | diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index 5589a2cdb6b..ac2eaffc940 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -1023,9 +1023,10 @@ var ( { Name: "alertingSaveStateCompressed", Description: "Enables the compressed protobuf-based alert state storage", - Stage: FeatureStageExperimental, + Stage: FeatureStagePublicPreview, FrontendOnly: false, Owner: grafanaAlertingSquad, + Expression: "false", }, { Name: "scopeApi", diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index 5ee9c0c2f1f..e88c9c3d176 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -134,7 +134,7 @@ newFolderPicker,experimental,@grafana/grafana-frontend-platform,false,false,true jitterAlertRulesWithinGroups,preview,@grafana/alerting-squad,false,true,false onPremToCloudMigrations,preview,@grafana/grafana-operator-experience-squad,false,false,false alertingSaveStatePeriodic,privatePreview,@grafana/alerting-squad,false,false,false -alertingSaveStateCompressed,experimental,@grafana/alerting-squad,false,false,false +alertingSaveStateCompressed,preview,@grafana/alerting-squad,false,false,false scopeApi,experimental,@grafana/grafana-app-platform-squad,false,false,false promQLScope,GA,@grafana/oss-big-tent,false,false,false logQLScope,privatePreview,@grafana/observability-logs,false,false,false diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json index 970b8edf66f..607d0581270 100644 --- a/pkg/services/featuremgmt/toggles_gen.json +++ b/pkg/services/featuremgmt/toggles_gen.json @@ -373,23 +373,27 @@ { "metadata": { "name": "alertingSaveStateCompressed", - "resourceVersion": "1737472824047", + "resourceVersion": "1738604435531", "creationTimestamp": "2025-01-17T18:17:20Z", "annotations": { - "grafana.app/updatedTimestamp": "2025-01-21 15:20:24.047499 +0000 UTC" + "grafana.app/updatedTimestamp": "2025-02-03 17:40:35.53174 +0000 UTC" } }, "spec": { "description": "Enables the compressed protobuf-based alert state storage", - "stage": "experimental", - "codeowner": "@grafana/alerting-squad" + "stage": "preview", + "codeowner": "@grafana/alerting-squad", + "expression": "false" } }, { "metadata": { "name": "alertingSaveStatePeriodic", - "resourceVersion": "1718727528075", - "creationTimestamp": "2024-01-23T16:03:30Z" + "resourceVersion": "1738604155684", + "creationTimestamp": "2024-01-23T16:03:30Z", + "annotations": { + "grafana.app/updatedTimestamp": "2025-02-03 17:35:55.684572 +0000 UTC" + } }, "spec": { "description": "Writes the state periodically to the database, asynchronous to rule evaluation", From f4426e22bfc419b21d7c45dba9911d835c7999ce Mon Sep 17 00:00:00 2001 From: Sam Jewell <2903904+samjewell@users.noreply.github.com> Date: Thu, 6 Feb 2025 17:34:35 +0000 Subject: [PATCH 30/34] SQL Expressions: Bump go-mysql-server dependency to `main` (#100222) Bump go-mysql-server dependency to `main` This is so we receive a few bug fixes - fixes to these issues: - https://github.com/dolthub/dolt/issues/8807 - https://github.com/dolthub/dolt/issues/8735 - https://github.com/dolthub/dolt/issues/8724 --- go.mod | 4 +- go.sum | 8 +- go.work.sum | 123 +++++++++++++++++++++++++++- pkg/storage/unified/apistore/go.mod | 4 +- pkg/storage/unified/apistore/go.sum | 8 +- pkg/storage/unified/resource/go.sum | 8 +- 6 files changed, 137 insertions(+), 18 deletions(-) diff --git a/go.mod b/go.mod index 079a83598d0..800bd9c565f 100644 --- a/go.mod +++ b/go.mod @@ -41,8 +41,8 @@ require ( github.com/centrifugal/centrifuge v0.33.3 // @grafana/grafana-app-platform-squad github.com/crewjam/saml v0.4.13 // @grafana/identity-access-team github.com/dlmiddlecote/sqlstats v1.0.2 // @grafana/grafana-backend-group - github.com/dolthub/go-mysql-server v0.19.0 // @grafana/grafana-datasources-core-services - github.com/dolthub/vitess v0.0.0-20241211024425-b00987f7ba54 // @grafana/grafana-datasources-core-services + github.com/dolthub/go-mysql-server v0.19.1-0.20250206012855-c216e59c21a7 // @grafana/grafana-datasources-core-services + github.com/dolthub/vitess v0.0.0-20250123002143-3b45b8cacbfa // @grafana/grafana-datasources-core-services github.com/fatih/color v1.17.0 // @grafana/grafana-backend-group github.com/fullstorydev/grpchan v1.1.1 // @grafana/grafana-backend-group github.com/gchaincl/sqlhooks v1.3.0 // @grafana/grafana-search-and-storage diff --git a/go.sum b/go.sum index 5c2c8b5537c..294f7fd4289 100644 --- a/go.sum +++ b/go.sum @@ -1061,14 +1061,14 @@ github.com/dolthub/flatbuffers/v23 v23.3.3-dh.2 h1:u3PMzfF8RkKd3lB9pZ2bfn0qEG+1G github.com/dolthub/flatbuffers/v23 v23.3.3-dh.2/go.mod h1:mIEZOHnFx4ZMQeawhw9rhsj+0zwQj7adVsnBX7t+eKY= github.com/dolthub/go-icu-regex v0.0.0-20241215010122-db690dd53c90 h1:Sni8jrP0sy/w9ZYXoff4g/ixe+7bFCZlfCqXKJSU+zM= github.com/dolthub/go-icu-regex v0.0.0-20241215010122-db690dd53c90/go.mod h1:ylU4XjUpsMcvl/BKeRRMXSH7e7WBrPXdSLvnRJYrxEA= -github.com/dolthub/go-mysql-server v0.19.0 h1:NdcXyGt9v7m4sQOahU+ss++iyPy4Q3viuVvbnn3rUTQ= -github.com/dolthub/go-mysql-server v0.19.0/go.mod h1:elfIatfq2fkU5lqTBrTcpL0RcHZOgYPE8EzBD7yQFiY= +github.com/dolthub/go-mysql-server v0.19.1-0.20250206012855-c216e59c21a7 h1:b9ygz2zMlMv2AcJ9mLL0b60tJ9Qd+bzfsAY5ZDTu9VY= +github.com/dolthub/go-mysql-server v0.19.1-0.20250206012855-c216e59c21a7/go.mod h1:jYEJ8tNkA7K3k39X8iMqaX3MSMmViRgh222JSLHDgVc= github.com/dolthub/jsonpath v0.0.2-0.20240227200619-19675ab05c71 h1:bMGS25NWAGTEtT5tOBsCuCrlYnLRKpbJVJkDbrTRhwQ= github.com/dolthub/jsonpath v0.0.2-0.20240227200619-19675ab05c71/go.mod h1:2/2zjLQ/JOOSbbSboojeg+cAwcRV0fDLzIiWch/lhqI= github.com/dolthub/maphash v0.1.0 h1:bsQ7JsF4FkkWyrP3oCnFJgrCUAFbFf3kOl4L/QxPDyQ= github.com/dolthub/maphash v0.1.0/go.mod h1:gkg4Ch4CdCDu5h6PMriVLawB7koZ+5ijb9puGMV50a4= -github.com/dolthub/vitess v0.0.0-20241211024425-b00987f7ba54 h1:nzBnC0Rt1gFtscJEz4veYd/mazZEdbdmed+tujdaKOo= -github.com/dolthub/vitess v0.0.0-20241211024425-b00987f7ba54/go.mod h1:1gQZs/byeHLMSul3Lvl3MzioMtOW1je79QYGyi2fd70= +github.com/dolthub/vitess v0.0.0-20250123002143-3b45b8cacbfa h1:kyoPzxViSXAyqfO0Mab7Qo1UogFIrxZKKyBU6kBOl+E= +github.com/dolthub/vitess v0.0.0-20250123002143-3b45b8cacbfa/go.mod h1:1gQZs/byeHLMSul3Lvl3MzioMtOW1je79QYGyi2fd70= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= 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= diff --git a/go.work.sum b/go.work.sum index 9fc55608511..ff30236f289 100644 --- a/go.work.sum +++ b/go.work.sum @@ -1044,6 +1044,7 @@ github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53 h1:sR+/8Yb4s github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno= github.com/CloudyKit/jet/v6 v6.2.0 h1:EpcZ6SR9n28BUGtNJSvlBqf90IpjeFr36Tizxhn/oME= github.com/CloudyKit/jet/v6 v6.2.0/go.mod h1:d3ypHeIRNo2+XyqnGA8s+aphtcVpjP5hPwP/Lzo7Ro4= +github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/DataDog/datadog-go v3.2.0+incompatible h1:qSG2N4FghB1He/r2mFrWKCaL7dXCilEuNEeAn20fdD4= github.com/DataDog/sketches-go v1.4.6 h1:acd5fb+QdUzGrosfNLwrIhqyrbMORpvBy7mE+vHlT3I= github.com/DataDog/sketches-go v1.4.6/go.mod h1:7Y8GN8Jf66DLyDhc94zuWA3uHEt/7ttt8jHOBWWrSOg= @@ -1062,6 +1063,7 @@ github.com/IBM/go-sdk-core/v5 v5.17.4 h1:VGb9+mRrnS2HpHZFM5hy4J6ppIWnwNrw0G+tLSg github.com/IBM/go-sdk-core/v5 v5.17.4/go.mod h1:KsAAI7eStAWwQa4F96MLy+whYSh39JzNjklZRbN/8ns= github.com/IBM/ibm-cos-sdk-go v1.11.0 h1:Jp55NLN3OvBwucMGpP5wNybyjncsmTZ9+GPHai/1cE8= github.com/IBM/ibm-cos-sdk-go v1.11.0/go.mod h1:FnWOym0CvrPM0nHoXvceClOEvGVXecPpmVIO5RFjlFk= +github.com/IBM/sarama v1.43.1/go.mod h1:GG5q1RURtDNPz8xxJs3mgX6Ytak8Z9eLhAkJPObe2xE= github.com/IBM/sarama v1.43.2 h1:HABeEqRUh32z8yzY2hGB/j8mHSzC/HA9zlEjqFNCzSw= github.com/IBM/sarama v1.43.2/go.mod h1:Kyo4WkF24Z+1nz7xeVUFWIuKVV8RS3wM8mkvPKMdXFQ= github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c h1:RGWPOewvKIROun94nF7v2cua9qP+thov/7M50KEoeSU= @@ -1141,14 +1143,20 @@ github.com/apache/arrow/go/v14 v14.0.2 h1:N8OkaJEOfI3mEZt07BIkvo4sC6XDbL+48MBPWO github.com/apache/arrow/go/v14 v14.0.2/go.mod h1:u3fgh3EdgN/YQ8cVQRguVW3R+seMybFg8QBQ5LU+eBY= github.com/apache/arrow/go/v15 v15.0.2 h1:60IliRbiyTWCWjERBCkO1W4Qun9svcYoZrSLcyOsMLE= github.com/apache/arrow/go/v15 v15.0.2/go.mod h1:DGXsR3ajT524njufqf95822i+KTh+yea1jass9YXgjA= +github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.17.0/go.mod h1:OLxhMRJxomX+1I/KUw03qoV3mMz16BwaKI+d4fPBx7Q= github.com/apparentlymart/go-dump v0.0.0-20180507223929-23540a00eaa3 h1:ZSTrOEhiM5J5RFxEaFvMZVEAM1KvT1YzbEOwB2EAGjA= github.com/apparentlymart/go-dump v0.0.0-20180507223929-23540a00eaa3/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e h1:QEF07wC0T1rKkctt1RINW/+RMTVmiwxETico2l3gxJA= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6 h1:G1bPvciwNyF7IUmKXNt9Ak3m6u9DE1rF+RmtIkBpVdA= github.com/armon/go-metrics v0.3.9/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= +github.com/armon/go-metrics v0.4.0/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= +github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= +github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= +github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.40.45/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q= github.com/aws/aws-sdk-go v1.51.25/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= +github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= github.com/aws/aws-sdk-go-v2 v1.9.1/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4= github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.8.1 h1:w/fPGB0t5rWwA43mux4e9ozFSH5zF1moQemlA131PWc= github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.8.1/go.mod h1:CM+19rL1+4dFWnOQKwDc7H1KwXTz+h61oUSHyhV0b3o= @@ -1193,8 +1201,11 @@ github.com/bytedance/sonic v1.10.0-rc3 h1:uNSnscRapXTwUgTyOF0GVljYD08p9X/Lbr9Mwe github.com/bytedance/sonic v1.10.0-rc3/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= github.com/campoy/embedmd v1.0.0 h1:V4kI2qTJJLf4J29RzI/MAt2c3Bl4dQSYPuflzwFH2hY= github.com/campoy/embedmd v1.0.0/go.mod h1:oxyr9RCiSXg0M3VJ3ks0UGfp98BpSSGr0kpiX3MzVl8= +github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= github.com/casbin/casbin/v2 v2.37.0 h1:/poEwPSovi4bTOcP752/CsTQiRz2xycyVKFG7GUhbDw= github.com/casbin/casbin/v2 v2.37.0/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg= +github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= +github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMrBo8f1j86j5WHzznCCQxV/b8g= github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0= @@ -1225,6 +1236,7 @@ github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible h1:C29Ae4G5GtYyY github.com/circonus-labs/circonusllhist v0.1.3 h1:TJH+oke8D16535+jHExHj4nQvzlZrj7ug5D7I/orNUA= github.com/clbanning/mxj v1.8.4 h1:HuhwZtbyvyOw+3Z1AowPkU87JkJUSv751ELWaiTpj8I= github.com/clbanning/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng= +github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI= github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe h1:QQ3GSy+MqSHxm/d8nCtnAiZdYFd45cYZPs8vOOIYKfk= github.com/cncf/xds/go v0.0.0-20230428030218-4003588d1b74/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= @@ -1232,6 +1244,7 @@ github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20231109132714-523115ebc101/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa/go.mod h1:x/1Gn8zydmfq8dk6e9PdstVsDgu9RuyIIJqAaF//0IM= github.com/cockroachdb/cockroach-go v0.0.0-20181001143604-e0a95dfd547c h1:2zRrJWIt/f9c9HhNHAgrRgq0San5gRRUJTBXLkchal0= +github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/cockroachdb/datadriven v1.0.2 h1:H9MtNqVoVhvd9nCBwOyDjUEdZCREqbIdCJD93PBm/jA= github.com/cockroachdb/datadriven v1.0.2/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd h1:qMd81Ts1T2OTKmB4acZcyKaMtRnY5Y44NuXGX2GFJ1w= @@ -1252,8 +1265,10 @@ github.com/coreos/etcd v3.3.27+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc github.com/coreos/go-etcd v2.0.0+incompatible h1:bXhRBIXoTm9BYHS3gE0TtQuyNZyeEMux2sDi4oo5YOo= github.com/coreos/go-oidc v2.2.1+incompatible h1:mh48q/BqXqgjVHpy2ZY7WnWAbenxRjsz9N1i1YxjHAk= github.com/coreos/go-oidc v2.2.1+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= +github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU= github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/coreos/pkg v0.0.0-20220810130054-c7d1c02cb6cf h1:GOPo6vn/vTN+3IwZBvXX0y5doJfSC7My0cdzelyOCsQ= github.com/coreos/pkg v0.0.0-20220810130054-c7d1c02cb6cf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/couchbase/ghistogram v0.1.0 h1:b95QcQTCzjTUocDXp/uMgSNQi8oj1tGwnJ4bODWZnps= @@ -1262,6 +1277,7 @@ github.com/couchbase/moss v0.2.0 h1:VCYrMzFwEryyhRSeI+/b3tRBSeTpi/8gn5Kf6dxqn+o= github.com/couchbase/moss v0.2.0/go.mod h1:9MaHIaRuy9pvLPUJxB8sh8OrLfyDczECVL37grCIubs= github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk= github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= @@ -1300,6 +1316,8 @@ github.com/dave/rebecca v0.9.1/go.mod h1:N6XYdMD/OKw3lkF3ywh8Z6wPGuwNFDNtWYEMFWE github.com/dchest/uniuri v1.2.0 h1:koIcOUdrTIivZgSLhHQvKgqdWZq5d7KdMEWF1Ud6+5g= github.com/dchest/uniuri v1.2.0/go.mod h1:fSzm4SLHzNZvWLvWJew423PhAzkpNQYq+uNLq4kxhkY= github.com/denisenkom/go-mssqldb v0.0.0-20190515213511-eb9f6a1743f3 h1:tkum0XDgfR0jcVVXuTsYv/erY2NnEDqwRojbxR1rBYA= +github.com/denisenkom/go-mssqldb v0.10.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20190329191031-25c5027a8c7b h1:Yqiad0+sloMPdd/0Fg22actpFx0dekpzt1xJmVNVkU0= github.com/dhui/dktest v0.3.0 h1:kwX5a7EkLcjo7VpsPQSYJcKGbXBXdjI9FGjuUj1jn6I= github.com/digitalocean/godo v1.113.0/go.mod h1:Z2mTP848Vi3IXXl5YbPekUgr4j4tOePomA+OE1Ag98w= @@ -1328,6 +1346,7 @@ github.com/dolthub/swiss v0.2.1/go.mod h1:8AhKZZ1HK7g18j7v7k6c5cYIGEZJcPn0ARsai8 github.com/drone/funcmap v0.0.0-20220929084810-72602997d16f h1:/jEs7lulqVO2u1+XI5rW4oFwIIusxuDOVKD9PAzlW2E= github.com/drone/funcmap v0.0.0-20220929084810-72602997d16f/go.mod h1:nDRkX7PHq+p39AD5/usv3KZMerxZTYU/9rfLS5IDspU= github.com/drone/signal v1.0.0 h1:NrnM2M/4yAuU/tXs6RP1a1ZfxnaHwYkd0kJurA1p6uI= +github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dvyukov/go-fuzz v0.0.0-20210103155950-6a8e9d1f2415 h1:q1oJaUPdmpDm/VyXosjgPgr6wS7c5iV2p0PwJD73bUI= github.com/dvyukov/go-fuzz v0.0.0-20210103155950-6a8e9d1f2415/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw= github.com/eapache/go-resiliency v1.6.0 h1:CqGDTLtpwuWKn6Nj3uNUdflaq+/kIPsg0gfNzHton30= @@ -1347,6 +1366,7 @@ github.com/elastic/go-windows v1.0.1/go.mod h1:FoVvqWSun28vaDQPbj2Elfc0JahhPB7WQ github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633 h1:H2pdYOb3KQ1/YsqVWoWNLQO+fusocsw354rqGTZtAgw= github.com/emicklei/go-restful/v3 v3.8.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/emicklei/proto v1.10.0/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A= +github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= github.com/envoyproxy/go-control-plane v0.11.1-0.20230524094728-9239064ad72f/go.mod h1:sfYdkwUW4BA3PbKjySwjJy+O4Pu0h62rlqCMHNk+K+Q= github.com/envoyproxy/go-control-plane v0.11.1/go.mod h1:uhMcXKCQMEJHiAb0w+YGefQLaTEw+YhGluxZkrTmD0g= github.com/envoyproxy/go-control-plane v0.12.0/go.mod h1:ZBTaoJ23lqITozF0M6G4/IragXCQKCnYbmlmtHvwRG0= @@ -1370,6 +1390,8 @@ github.com/fluent/fluent-bit-go v0.0.0-20230731091245-a7a013e2473c h1:yKN46XJHYC github.com/fluent/fluent-bit-go v0.0.0-20230731091245-a7a013e2473c/go.mod h1:L92h+dgwElEyUuShEwjbiHjseW410WIcNz+Bjutc8YQ= github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8= github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk= +github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= +github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= github.com/franela/goblin v0.0.0-20210519012713-85d372ac71e2 h1:cZqz+yOJ/R64LcKjNQOdARott/jP7BnUQ9Ah7KaZCvw= github.com/franela/goblin v0.0.0-20210519012713-85d372ac71e2/go.mod h1:VzmDKDJVZI3aJmnRI9VjAn9nJ8qPPsN1fqzr9dqInIo= github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8 h1:a9ENSRDFBUPkJ5lCgVZh26+ZbGyoVJG7yb5SSzF5H54= @@ -1399,6 +1421,7 @@ github.com/go-fonts/liberation v0.3.2/go.mod h1:N0QsDLVUQPy3UYg9XAc3Uh3UDMp2Z7M1 github.com/go-fonts/stix v0.1.0 h1:UlZlgrvvmT/58o573ot7NFw0vZasZ5I6bcIft/oMdgg= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1 h1:QbL/5oDUmRBzO9/Z7Seo6zf912W/a6Sr4Eu0G/3Jho0= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4 h1:WtGNWLvXpe6ZudgnXrq0barxBImvnnJoMEhXAzcbM0I= +github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= github.com/go-kit/kit v0.12.0/go.mod h1:lHd+EkCZPIwYItmGDDRdhinkzX2A1sj+M9biaEaizzs= github.com/go-kit/kit v0.13.0 h1:OoneCcHKHQ03LfBpoQCUfCluwd2Vt3ohz+kvbJneZAU= github.com/go-kit/kit v0.13.0/go.mod h1:phqEHMMUbyrCFCTgH48JueqrM3md2HcAZ8N3XE4FKDg= @@ -1451,11 +1474,13 @@ github.com/gocraft/dbr/v2 v2.7.2/go.mod h1:5bCqyIXO5fYn3jEp/L06QF4K1siFdhxChMjdN github.com/godbus/dbus/v5 v5.0.4 h1:9349emZab16e7zQvpmsbtjc18ykshndd8y2PG3sgJbA= github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= +github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= 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/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ= github.com/golang/glog v1.2.0/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= @@ -1480,6 +1505,7 @@ github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= github.com/google/pprof v0.0.0-20240416155748-26353dc0451f/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= +github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA= github.com/google/renameio/v2 v2.0.0 h1:UifI23ZTGY8Tt29JbYFiuyIU3eX+RNFtUwefq9qAhxg= @@ -1512,13 +1538,14 @@ github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= +github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grafana/alerting v0.0.0-20250115195200-209e052dba64/go.mod h1:QsnoKX/iYZxA4Cv+H+wC7uxutBD8qi8ZW5UJvD2TYmU= +github.com/grafana/alerting v0.0.0-20250117230852-a5e8136407d4/go.mod h1:QsnoKX/iYZxA4Cv+H+wC7uxutBD8qi8ZW5UJvD2TYmU= github.com/grafana/alerting v0.0.0-20250129195454-3e5b80036b7a/go.mod h1:QsnoKX/iYZxA4Cv+H+wC7uxutBD8qi8ZW5UJvD2TYmU= -github.com/grafana/alerting v0.0.0-20250205180645-995709fe8d64 h1:J3PIK9OL3ZHPypYHlcK+nBREwYL3ROZ3fJyNMsTYlpk= -github.com/grafana/alerting v0.0.0-20250205180645-995709fe8d64/go.mod h1:QsnoKX/iYZxA4Cv+H+wC7uxutBD8qi8ZW5UJvD2TYmU= github.com/grafana/authlib v0.0.0-20250120144156-d6737a7dc8f5/go.mod h1:V63rh3udd7sqXJeaG+nGUmViwVnM/bY6t8U9Tols2GU= github.com/grafana/authlib v0.0.0-20250120145936-5f0e28e7a87c/go.mod h1:/gYfphsNu9v1qYWXxpv1NSvMEMSwvdf8qb8YlgwIRl8= github.com/grafana/authlib/types v0.0.0-20250120144156-d6737a7dc8f5/go.mod h1:qYjSd1tmJiuVoSICp7Py9/zD54O9uQQA3wuM6Gg4DFM= @@ -1527,6 +1554,8 @@ 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/grafana-app-sdk v0.29.0/go.mod h1:XLt308EmK6kvqPlzjUyXxbwZKEk2vur/eiypUNDay5I= +github.com/grafana/grafana-azure-sdk-go/v2 v2.1.5/go.mod h1:i0uiuu9/sMFBJnpFbjvviH0KOZzdWkti9Q9Ck1HkFWM= +github.com/grafana/grafana-plugin-sdk-go v0.262.0/go.mod h1:U43Cnrj/9DNYyvFcNdeUWNjMXTKNB0jcTcQGpWKd2gw= github.com/grafana/grafana/apps/advisor v0.0.0-20250121115006-c1eac9f9973f/go.mod h1:goSDiy3jtC2cp8wjpPZdUHRENcoSUHae1/Px/MDfddA= github.com/grafana/grafana/pkg/promlib v0.0.7/go.mod h1:rnwJXCA2xRwb7F27NB35iO/JsLL/H/+eVXECk/hrEhQ= github.com/grafana/regexp v0.0.0-20221122212121-6b5c0a4cb7fd/go.mod h1:M5qHK+eWfAv8VR/265dIuEpL3fNfeC21tXXp9itM24A= @@ -1535,6 +1564,7 @@ github.com/grafana/tail v0.0.0-20230510142333-77b18831edf0/go.mod h1:7t5XR+2IA8P github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0/go.mod h1:qmOFXW2epJhM0qSnUUYpldc7gVz2KMQwJ/QYCDIa7XU= @@ -1546,18 +1576,26 @@ github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 h1 github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8= github.com/hamba/avro/v2 v2.27.0 h1:IAM4lQ0VzUIKBuo4qlAiLKfqALSrFC+zi1iseTtbBKU= github.com/hamba/avro/v2 v2.27.0/go.mod h1:jN209lopfllfrz7IGoZErlDz+AyUJ3vrBePQFZwYf5I= +github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= github.com/hashicorp/consul/api v1.10.1/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M= +github.com/hashicorp/consul/api v1.14.0/go.mod h1:bcaw5CSZ7NE9qfOfKCI1xb7ZKjzu/MyvQkCLTfqLqxQ= github.com/hashicorp/consul/api v1.28.2/go.mod h1:KyzqzgMEya+IZPcD65YFoOVAgPpbfERu4I/tzG6/ueE= +github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/consul/sdk v0.10.0/go.mod h1:yPkX5Q6CsxTFMjQQDJwzeNmUUF5NUGGbrDsv9wTb8cw= github.com/hashicorp/consul/sdk v0.16.0/go.mod h1:7pxqqhqoaPqnBnzXD1StKed62LqJeClzVsUEy85Zr0A= github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-hclog v0.16.2/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-hclog v1.2.2/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-immutable-radix v1.3.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack/v2 v2.1.1 h1:xQEY9yB2wnHitoSzk/B9UjXWRQ67QKu5AOm8aFp8N3I= github.com/hashicorp/go-msgpack/v2 v2.1.1/go.mod h1:upybraOAblm4S7rx0+jeNy+CWWhzywQsSRV5033mMu4= github.com/hashicorp/go-retryablehttp v0.7.1/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= github.com/hashicorp/go-retryablehttp v0.7.4/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= github.com/hashicorp/go-syslog v1.0.0 h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE= +github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go.net v0.0.1 h1:sNCoNyDEvN1xa+X0baata4RdcpKwcMS6DH+xwfqPgjw= @@ -1566,6 +1604,8 @@ github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY= github.com/hashicorp/mdns v1.0.4 h1:sY0CMhFmjIPDMlTB+HfymFHCaYLhgifZ0QhjaYKD/UQ= github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= +github.com/hashicorp/memberlist v0.3.1/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= +github.com/hashicorp/memberlist v0.4.0/go.mod h1:yvyXLpo0QaGE59Y7hDTsTzDD25JYBZ4mHgHUZ8lrOI0= github.com/hashicorp/nomad/api v0.0.0-20240418183417-ea5f2f6748c7 h1:pjE59CS2C9Bg+Xby0ROrnZSSBWtKwx3Sf9gqsrvIFSA= github.com/hashicorp/nomad/api v0.0.0-20240418183417-ea5f2f6748c7/go.mod h1:svtxn6QnrQ69P23VvIWMR34tg3vmwLz4UdUzm1dSCgE= github.com/hashicorp/raft v1.7.0 h1:4u24Qn6lQ6uwziM++UgsyiT64Q8GyRn43CV41qPiz1o= @@ -1573,6 +1613,7 @@ github.com/hashicorp/raft v1.7.0/go.mod h1:N1sKh6Vn47mrWvEArQgILTyng8GoDRNYlgKyK github.com/hashicorp/raft-wal v0.4.1 h1:aU8XZ6x8R9BAIB/83Z1dTDtXvDVmv9YVYeXxd/1QBSA= github.com/hashicorp/raft-wal v0.4.1/go.mod h1:A6vP5o8hGOs1LHfC1Okh9xPwWDcmb6Vvuz/QyqUXlOE= github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= +github.com/hashicorp/serf v0.10.0/go.mod h1:bXN03oZc5xlH46k/K1qTrpXb9ERKyY1/i/N5mxvgrZw= github.com/heroku/x v0.0.61 h1:yfoAAtnFWSFZj+UlS+RZL/h8QYEp1R4wHVEg0G+Hwh4= github.com/heroku/x v0.0.61/go.mod h1:C7xYbpMdond+s6L5VpniDUSVPRwm3kZum1o7XiD5ZHk= github.com/hetznercloud/hcloud-go/v2 v2.7.2 h1:UlE7n1GQZacCfyjv9tDVUN7HZfOXErPIfM/M039u9A0= @@ -1580,6 +1621,7 @@ github.com/hetznercloud/hcloud-go/v2 v2.7.2/go.mod h1:49tIV+pXRJTUC7fbFZ03s45LKq github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= +github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/hudl/fargo v1.4.0 h1:ZDDILMbB37UlAVLlWcJ2Iz1XuahZZTDZfdCKeclfq2s= github.com/hudl/fargo v1.4.0/go.mod h1:9Ai6uvFy5fQNq6VPKtg+Ceq1+eTY4nKUlR2JElEOcDo= github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI= @@ -1592,6 +1634,7 @@ github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/influxdata/influxdb v1.7.6 h1:8mQ7A/V+3noMGCt/P9pD09ISaiz9XvgCk303UYA3gcs= github.com/influxdata/influxdb v1.7.7 h1:UvNzAPfBrKMENVbQ4mr4ccA9sW+W1Ihl0Yh1s0BiVAg= +github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= github.com/influxdata/influxdb1-client v0.0.0-20200827194710-b269163b24ab h1:HqW4xhhynfjrtEiiSGcQUd6vrK23iMam1FO8rI7mwig= github.com/influxdata/influxdb1-client v0.0.0-20200827194710-b269163b24ab/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= github.com/influxdata/tdigest v0.0.2-0.20210216194612-fc98d27c9e8b h1:i44CesU68ZBRvtCjBi3QSosCIKrjmMbYlQMFAwVLds4= @@ -1622,12 +1665,14 @@ github.com/jhump/gopoet v0.1.0 h1:gYjOPnzHd2nzB37xYQZxj4EIQNpBrBskRqQQ3q4ZgSg= github.com/jhump/goprotoc v0.5.0 h1:Y1UgUX+txUznfqcGdDef8ZOVlyQvnV0pKWZH08RmZuo= github.com/jmattheis/goverter v1.4.0 h1:SrboBYMpGkj1XSgFhWwqzdP024zIa1+58YzUm+0jcBE= github.com/jmattheis/goverter v1.4.0/go.mod h1:iVIl/4qItWjWj2g3vjouGoYensJbRqDHpzlEVMHHFeY= +github.com/jmoiron/sqlx v1.3.4/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ= github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901 h1:rp+c0RAYOWj8l6qbCUTSiRLG/iKnW3K3/QfPPuSsBt4= github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901/go.mod h1:Z86h9688Y0wesXCyonoVr47MasHilkuLMqGhRZ4Hpak= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/jon-whit/go-grpc-prometheus v1.4.0 h1:/wmpGDJcLXuEjXryWhVYEGt9YBRhtLwFEN7T+Flr8sw= github.com/jon-whit/go-grpc-prometheus v1.4.0/go.mod h1:iTPm+Iuhh3IIqR0iGZ91JJEg5ax6YQEe1I0f6vtBuao= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/joncrlsn/dque v0.0.0-20211108142734-c2ef48c5192a h1:sfe532Ipn7GX0V6mHdynBk393rDmqgI0QmjLK7ct7TU= github.com/joncrlsn/dque v0.0.0-20211108142734-c2ef48c5192a/go.mod h1:dNKs71rs2VJGBAmttu7fouEsRQlRjxy0p1Sx+T5wbpY= github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o= @@ -1660,7 +1705,9 @@ github.com/kisielk/errcheck v1.5.0 h1:e8esj/e4R+SAOwFwN+n3zr0nYeCyeweozKfO23MvHz github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg= github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46 h1:veS9QfglfvqAw2e+eeNT/SbGySq8ajECXJ9e4fPoLhY= github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= +github.com/klauspost/compress v1.14.4/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= @@ -1696,6 +1743,9 @@ github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/leodido/ragel-machinery v0.0.0-20190525184631-5f46317e436b h1:11UHH39z1RhZ5dc4y4r/4koJo6IYFgTRMe/LlwRTEw0= github.com/leodido/ragel-machinery v0.0.0-20190525184631-5f46317e436b/go.mod h1:WZxr2/6a/Ar9bMDc2rN/LJrE/hF6bXE4LPyDSIxwAfg= +github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= +github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/linode/linodego v1.32.0 h1:OmZzB3iON6uu84VtLFf64uKmAQqJJarvmsVguroioPI= github.com/linode/linodego v1.32.0/go.mod h1:y8GDP9uLVH4jTB9qyrgw79qfKdYJmNCGUOJmfuiOcmI= github.com/logrusorgru/aurora/v3 v3.0.0 h1:R6zcoZZbvVcGMvDCKo45A9U/lzYyzl5NfYIvznmDfE4= @@ -1706,12 +1756,16 @@ github.com/lyft/protoc-gen-star v0.6.1 h1:erE0rdztuaDq3bpGifD95wfoPrSZc95nGA6tbi github.com/lyft/protoc-gen-star/v2 v2.0.3/go.mod h1:amey7yeodaJhXSbf/TlLvWiqQfLOSpEk//mLlc+axEk= github.com/lyft/protoc-gen-star/v2 v2.0.4-0.20230330145011-496ad1ac90a4 h1:sIXJOMrYnQZJu7OB7ANSF4MYri2fTEGIsRLz6LwI4xE= github.com/lyft/protoc-gen-star/v2 v2.0.4-0.20230330145011-496ad1ac90a4/go.mod h1:amey7yeodaJhXSbf/TlLvWiqQfLOSpEk//mLlc+axEk= +github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/mailgun/raymond/v2 v2.0.48 h1:5dmlB680ZkFG2RN/0lvTAghrSxIESeu9/2aeDqACtjw= github.com/mailgun/raymond/v2 v2.0.48/go.mod h1:lsgvL50kgt1ylcFJYZiULi5fjPBkkhNfj4KA0W54Z18= github.com/matryer/moq v0.3.3 h1:pScMH9VyrdT4S93yiLpVyU8rCDqGQr24uOyBxmktG5Q= github.com/matryer/moq v0.3.3/go.mod h1:RJ75ZZZD71hejp39j4crZLsEDszGk6iH4v4YsWFKH4s= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-sqlite3 v1.14.7/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= @@ -1739,6 +1793,7 @@ github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/gox v0.4.0 h1:lfGJxY7ToLJQjHHwi0EX6uYBdK78egf954SQl13PQJc= github.com/mitchellh/iochan v1.0.0 h1:C+X3KsSTLFVBr/tK1eYN/vs4rJcvsiLU338UhYPJWeY= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.4.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mithrandie/readline-csvq v1.3.0 h1:VTJEOGouJ8j27jJCD4kBBbNTxM0OdBvE1aY1tMhlqE8= github.com/mithrandie/readline-csvq v1.3.0/go.mod h1:FKyYqDgf/G4SNov7SMFXRWO6LQLXIOeTog/NB97FZl0= @@ -1756,15 +1811,24 @@ github.com/mostynb/go-grpc-compression v1.2.3 h1:42/BKWMy0KEJGSdWvzqIyOZ95YcR9mL github.com/mostynb/go-grpc-compression v1.2.3/go.mod h1:AghIxF3P57umzqM9yz795+y1Vjs47Km/Y2FE6ouQ7Lg= github.com/nakagami/firebirdsql v0.0.0-20190310045651-3c02a58cfed8 h1:P48LjvUQpTReR3TQRbxSeSBsMXzfK0uol7eRcr7VBYQ= github.com/natessilva/dag v0.0.0-20180124060714-7194b8dcc5c4 h1:dnMxwus89s86tI8rcGVp2HwZzlz7c5o92VOy7dSckBQ= +github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= +github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= github.com/nats-io/jwt v1.2.2 h1:w3GMTO969dFg+UOKTmmyuu7IGdusK+7Ytlt//OYH/uU= github.com/nats-io/jwt v1.2.2/go.mod h1:/xX356yQA6LuXI9xWW7mZNpxgF2mBmGecH+Fj34sP5Q= github.com/nats-io/jwt/v2 v2.0.3 h1:i/O6cmIsjpcQyWDYNcq2JyZ3/VTF8SJ4JWluI5OhpvI= github.com/nats-io/jwt/v2 v2.0.3/go.mod h1:VRP+deawSXyhNjXmxPCHskrR6Mq50BqpEI5SEcNiGlY= +github.com/nats-io/jwt/v2 v2.2.1-0.20220330180145-442af02fd36a/go.mod h1:0tqz9Hlu6bCBFLWAASKhE5vUA4c24L9KPUUgvwumE/k= +github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= github.com/nats-io/nats-server/v2 v2.5.0 h1:wsnVaaXH9VRSg+A2MVg5Q727/CqxnmPLGFQ3YZYKTQg= github.com/nats-io/nats-server/v2 v2.5.0/go.mod h1:Kj86UtrXAL6LwYRA6H4RqzkHhK0Vcv2ZnKD5WbQ1t3g= +github.com/nats-io/nats-server/v2 v2.8.4/go.mod h1:8zZa+Al3WsESfmgSs98Fi06dRWLH5Bnq90m5bKD/eT4= +github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= github.com/nats-io/nats.go v1.12.1/go.mod h1:BPko4oXsySz4aSWeFgOHLZs3G4Jq4ZAyE6/zMCxRT6w= +github.com/nats-io/nats.go v1.15.0/go.mod h1:BPko4oXsySz4aSWeFgOHLZs3G4Jq4ZAyE6/zMCxRT6w= github.com/nats-io/nats.go v1.34.0 h1:fnxnPCNiwIG5w08rlMcEKTUw4AV/nKyGCOJE8TdhSPk= github.com/nats-io/nats.go v1.34.0/go.mod h1:Ubdu4Nh9exXdSz0RVWRFBbRfrbSxOYd26oF0wkWclB8= +github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= github.com/nats-io/nkeys v0.2.0/go.mod h1:XdZpAbhgyyODYqjTawOnIOI7VlbKSarI9Gfy1tqEu/s= github.com/nats-io/nkeys v0.3.0/go.mod h1:gvUNGjVcM2IPr5rCsRsC6Wb3Hr2CQAm08dsxtV6A5y4= github.com/nats-io/nkeys v0.4.7 h1:RwNJbbIdYCoClSDNY7QVKZlyb/wfT6ugvFCiKy6vDvI= @@ -1779,6 +1843,8 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWb github.com/nsf/jsondiff v0.0.0-20230430225905-43f6cf3098c1 h1:dOYG7LS/WK00RWZc8XGgcUTlTxpp3mKhdR2Q9z9HbXM= github.com/nsf/jsondiff v0.0.0-20230430225905-43f6cf3098c1/go.mod h1:mpRZBD8SJ55OIICQ3iWH0Yz3cjzA61JdqMLoWXeB2+8= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= +github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= @@ -1797,6 +1863,7 @@ github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3Ro github.com/onsi/ginkgo/v2 v2.9.7/go.mod h1:cxrmXWykAwTwhQsJOPfdIDiJ+l2RYq7U8hFU+M/1uw0= github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM= github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o= +github.com/onsi/ginkgo/v2 v2.20.1/go.mod h1:lG9ey2Z29hR41WMVthyJBGUBcBhGOtoPF2VFMvBXFCI= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY= @@ -1816,6 +1883,7 @@ github.com/onsi/gomega v1.27.7/go.mod h1:1p8OOlwo2iUUDsHnOrjE5UKYJ+e3W8eQ3qSlRah github.com/onsi/gomega v1.27.8/go.mod h1:2J8vzI/s+2shY9XHRApDkdgPo1TKT7P2u6fXeJKFnNQ= github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= +github.com/onsi/gomega v1.34.2/go.mod h1:v1xfxRgk0KIsG+QOdm7p8UosrOzPYRo60fd3B/1Dukc= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/open-telemetry/opentelemetry-collector-contrib/exporter/kafkaexporter v0.102.0 h1:R70PpK14trQfL/Vj5oAiGRqX09s2gOWuf6t1Ae5fevQ= @@ -1863,6 +1931,12 @@ github.com/openfga/api/proto v0.0.0-20240905181937-3583905f61a6/go.mod h1:gil5LB github.com/openfga/api/proto v0.0.0-20240906203051-102620ef2a66/go.mod h1:gil5LBD8tSdFQbUkCQdnXsoeU9kDJdJgbGdHkgJfcd0= github.com/openfga/language/pkg/go v0.2.0-beta.2.0.20240926131254-992b301a003f/go.mod h1:ll/hN6kS4EE6B/7J/PbZqac9Nuv7ZHpI+Jfh36JLrbs= github.com/openfga/openfga v1.6.2/go.mod h1:jzbEpheazf6MFjtanQt1rpxexSRzfa9057F7JlkMv2I= +github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= +github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= +github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= +github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= github.com/openzipkin/zipkin-go v0.2.5/go.mod h1:KpXfKdgRDnnhsxw4pNIH9Md5lyFqKUa4YDFlwRYAMyE= github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde h1:x0TT0RDC7UhAVbbWWBzr41ElhJx5tXPWkIHA2HWPRuw= github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0= @@ -1872,6 +1946,7 @@ github.com/oschwald/maxminddb-golang v1.13.0 h1:R8xBorY71s84yO06NgTmQvqvTvlS/bnY github.com/oschwald/maxminddb-golang v1.13.0/go.mod h1:BU0z8BfFVhi1LQaonTwwGQlsHUEu9pWNdMfmq4ztm0o= github.com/ovh/go-ovh v1.4.3 h1:Gs3V823zwTFpzgGLZNI6ILS4rmxZgJwJCz54Er9LwD0= github.com/ovh/go-ovh v1.4.3/go.mod h1:AkPXVtgwB6xlKblMjRKJJmjRp+ogrE7fz2lVgcQY8SY= +github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= github.com/parquet-go/parquet-go v0.23.0 h1:dyEU5oiHCtbASyItMCD2tXtT2nPmoPbKpqf0+nnGrmk= github.com/parquet-go/parquet-go v0.23.0/go.mod h1:MnwbUcFHU6uBYMymKAlPPAw9yh3kE1wWl6Gl1uLdkNk= github.com/paulmach/orb v0.11.1 h1:3koVegMC4X/WeiXYz9iswopaTwMem53NzTJuTF20JzU= @@ -1883,6 +1958,7 @@ github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30 h1:BHT1/DKsYDGkUgQ2 github.com/pborman/uuid v1.2.0 h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= github.com/performancecopilot/speed/v4 v4.0.0 h1:VxEDCmdkfbQYDlcr/GC9YoN9PQ6p8ulk9xVsepYy9ZY= github.com/performancecopilot/speed/v4 v4.0.0/go.mod h1:qxrSyuDGrTOWfV+uKRFhfxw6h/4HXRGUiZiufxo49BM= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= @@ -1918,6 +1994,7 @@ github.com/prometheus/prometheus v0.52.0 h1:f7kHJgr7+zShpWdTCeKqbCWR7nKTScgLYQwR github.com/prometheus/prometheus v0.52.0/go.mod h1:3z74cVsmVH0iXOR5QBjB7Pa6A0KJeEAK5A6UsmAFb1g= github.com/prometheus/statsd_exporter v0.26.0 h1:SQl3M6suC6NWQYEzOvIv+EF6dAMYEqIuZy+o4H9F5Ig= github.com/prometheus/statsd_exporter v0.26.0/go.mod h1:GXFLADOmBTVDrHc7b04nX8ooq3azG61pnECNqT7O5DM= +github.com/rabbitmq/amqp091-go v1.2.0/go.mod h1:ogQDLSOACsLPsIq0NpbtiifNZi2YOz0VTJ0kHRghqbM= github.com/rabbitmq/amqp091-go v1.9.0 h1:qrQtyzB4H8BQgEuJwhmVQqVHB9O4+MNDJCCAcpc3Aoo= 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= @@ -1936,6 +2013,7 @@ github.com/ryanuber/columnize v2.1.2+incompatible h1:C89EOx/XBWwIXl8wm8OPJBd7kPF github.com/sagikazarmark/crypt v0.19.0 h1:WMyLTjHBo64UvNcWqpzY3pbZTYgnemZU8FBZigKc42E= github.com/sagikazarmark/crypt v0.19.0/go.mod h1:c6vimRziqqERhtSe0MhIvzE1w54FrCHtrXb5NH/ja78= github.com/samuel/go-zookeeper v0.0.0-20190810000440-0ceca61e4d75 h1:cA+Ubq9qEVIQhIWvP2kNuSZ2CmnfBJFSRq+kO1pu2cc= +github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= github.com/scaleway/scaleway-sdk-go v1.0.0-beta.26 h1:F+GIVtGqCFxPxO46ujf8cEOP574MBoRm3gNbPXECbxs= github.com/scaleway/scaleway-sdk-go v1.0.0-beta.26/go.mod h1:fCa7OJZ/9DRTnOKmxvT6pn+LPWUptQAmHF/SBJUGEcg= @@ -1953,21 +2031,26 @@ github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFt github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= github.com/shoenig/test v1.7.1 h1:UJcjSAI3aUKx52kfcfhblgyhZceouhvvs3OYdWgn+PY= github.com/shoenig/test v1.7.1/go.mod h1:UxJ6u/x2v/TNs/LoLxBNJRV9DiwBBKYxXSyczsBHFoI= +github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= +github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad h1:fiWzISvDn0Csy5H0iwgAuJGQTUpVfEMJJd4nRFXogbc= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stoewer/parquet-cli v0.0.7 h1:rhdZODIbyMS3twr4OM3am8BPPT5pbfMcHLH93whDM5o= github.com/stoewer/parquet-cli v0.0.7/go.mod h1:bskxHdj8q3H1EmfuCqjViFoeO3NEvs5lzZAQvI8Nfjk= github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/amqp v1.0.0 h1:kuuDrUJFZL1QYL9hUNuCxNObNzB0bV/ZG5jV3RWAQgo= github.com/streadway/amqp v1.0.0/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= github.com/streadway/handy v0.0.0-20200128134331-0f66f006fb2e h1:mOtuXaRAbVZsxAHVdPR3IjfmN8T1h2iczJLynhLybf8= github.com/streadway/handy v0.0.0-20200128134331-0f66f006fb2e/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= @@ -1998,6 +2081,7 @@ github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFA github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= +github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tursodatabase/libsql-client-go v0.0.0-20240902231107-85af5b9d094d h1:dOMI4+zEbDI37KGb0TI44GUAwxHF9cMsIoDTJ7UmgfU= github.com/tursodatabase/libsql-client-go v0.0.0-20240902231107-85af5b9d094d/go.mod h1:l8xTsYB90uaVdMHXMCxKKLSgw5wLYBwBKKefNIUnm9s= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926 h1:G3dpKMzFDjgEh2q1Z7zUUtKa8ViPtH+ocF0bE0g00O8= @@ -2011,6 +2095,7 @@ github.com/twmb/franz-go/plugin/kprom v1.1.0 h1:grGeIJbm4llUBF8jkDjTb/b8rKllWSXj github.com/twmb/franz-go/plugin/kprom v1.1.0/go.mod h1:cTDrPMSkyrO99LyGx3AtiwF9W6+THHjZrkDE2+TEBIU= github.com/uber-go/atomic v1.4.0 h1:yOuPqEq4ovnhEjpHmfFwsqBXDYbQeT6Nb0bwD6XnD5o= github.com/uber-go/atomic v1.4.0/go.mod h1:/Ct5t2lcmbJ4OSe/waGBoaVvVqtO0bmtfVNex1PFV8g= +github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/vburenin/ifacemaker v1.2.1 h1:3Vq8B/bfBgjWTkv+jDg4dVL1KHt3k1K4lO7XRxYA2sk= @@ -2041,6 +2126,7 @@ github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17 github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/xhit/go-str2duration v1.2.0 h1:BcV5u025cITWxEQKGWr1URRzrcXtu7uk8+luz3Yuhwc= github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77 h1:ESFSdwYZvkeru3RtdrYueztKhOBCSAAzS4Gf+k0tEow= github.com/ydb-platform/ydb-go-genproto v0.0.0-20240528144234-5d5a685e41f7 h1:nL8XwD6fSst7xFUirkaWJmE7kM0CdWRYgu6+YQer1d4= github.com/ydb-platform/ydb-go-genproto v0.0.0-20240528144234-5d5a685e41f7/go.mod h1:Er+FePu1dNUieD+XTMDduGpQuCPssK5Q4BjF+IIXJ3I= @@ -2064,6 +2150,9 @@ go.einride.tech/aip v0.66.0 h1:XfV+NQX6L7EOYK11yoHHFtndeaWh3KbD9/cN/6iWEt8= go.einride.tech/aip v0.66.0/go.mod h1:qAhMsfT7plxBX+Oy7Huol6YUvZ0ZzdUz26yZsQwfl1M= go.einride.tech/aip v0.68.0 h1:4seM66oLzTpz50u4K1zlJyOXQ3tCzcJN7I22tKkjipw= go.einride.tech/aip v0.68.0/go.mod h1:7y9FF8VtPWqpxuAxl0KQWqaULxW4zFIesD6zF5RIHHg= +go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738 h1:VcrIfasaLFkyjk6KNlXQSzO+B0fZcnECiDrKJsfxka0= +go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= @@ -2072,6 +2161,7 @@ go.etcd.io/gofail v0.1.0 h1:XItAMIhOojXFQMgrxjnd2EIIHun/d5qL0Pf7FzVTkFg= go.etcd.io/gofail v0.1.0/go.mod h1:VZBCXYGZhHAinaBiiqYvuDynvahNsAyLFwB3kEHKz1M= go.mongodb.org/mongo-driver v1.11.4/go.mod h1:PTSz5yu21bkT/wXpkS7WR5f0ddqw5quethTUn9WM+2g= go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c= +go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opentelemetry.io/collector v0.102.1 h1:M/ciCcReQsSDYG9bJ2Qwqk7pQILDJ2bM/l0MdeCAvJE= go.opentelemetry.io/collector v0.102.1/go.mod h1:yF1lDRgL/Eksb4/LUnkMjvLvHHpi6wqBVlzp+dACnPM= go.opentelemetry.io/collector/component v0.102.1 h1:66z+LN5dVCXhvuVKD1b56/3cYLK+mtYSLIwlskYA9IQ= @@ -2227,7 +2317,9 @@ go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v8 go.opentelemetry.io/proto/otlp v1.1.0/go.mod h1:GpBHCBWiqvVLDqmHZsoMM3C5ySeKTC7ej/RNTae6MdY= go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= go.opentelemetry.io/proto/otlp v1.4.0/go.mod h1:PPBWZIP98o2ElSqI35IHfu7hIhSwvc5N38Jw8pXuGFY= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8= go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0= @@ -2235,11 +2327,15 @@ go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= golang.org/x/arch v0.4.0 h1:A8WCeEWhLwPBKNbFi5Wv5UTCBx5zzubnXDlMOFAzFMc= golang.org/x/arch v0.4.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= @@ -2247,6 +2343,7 @@ golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210915214749-c084706c2272/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220511200225-c6db032c6c88/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I= @@ -2264,6 +2361,7 @@ golang.org/x/exp v0.0.0-20220328175248-053ad81199eb/go.mod h1:lgLbSvA5ygNOMpwM/9 golang.org/x/exp v0.0.0-20230206171751-46f607a40771/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= +golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63/go.mod h1:0v4NqG35kSWCMzLaMeX+IQrlSnVE/bqGSyC2cz/9Le8= golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= @@ -2291,6 +2389,7 @@ golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= @@ -2299,6 +2398,7 @@ golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210917221730-978cfadd31cf/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211123203042-d83791d6bcd9/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ= golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= @@ -2332,6 +2432,7 @@ golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190130150945-aca44879d564/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -2344,6 +2445,7 @@ golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220406163625-3f8b81556e12/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220823224334-20c2bfdbfe24/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= @@ -2373,12 +2475,17 @@ golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= @@ -2427,8 +2534,11 @@ google.golang.org/api v0.187.0/go.mod h1:KIHlTc4x7N7gKKuVsdmfBXN13yEEWXWFURWY6SB google.golang.org/api v0.196.0/go.mod h1:g9IL21uGkYgvQ5BZg6BAtoGJQIm8r6EgaAbpNey5wBE= google.golang.org/api v0.197.0/go.mod h1:AuOuo20GoQ331nq7DquGHlU6d+2wN2fZ8O0ta60nRNw= google.golang.org/api v0.211.0/go.mod h1:XOloB4MXFH4UTlQSGuNUxw0UT74qdENK8d6JNsXKLi0= +google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= +google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= +google.golang.org/genproto v0.0.0-20190926190326-7ee9db18f195/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= google.golang.org/genproto v0.0.0-20210917145530-b395a37504d4/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/genproto v0.0.0-20230526161137-0005af68ea54/go.mod h1:zqTuNwFlFRsw5zIts5VnzLQxSRqh+CGOTVMlYbY0Eyk= google.golang.org/genproto v0.0.0-20230526203410-71b5a4ffd15e/go.mod h1:zqTuNwFlFRsw5zIts5VnzLQxSRqh+CGOTVMlYbY0Eyk= @@ -2550,6 +2660,9 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576/go. google.golang.org/genproto/googleapis/rpc v0.0.0-20241219192143-6b3ec007d9bb/go.mod h1:lcTa1sDdWEIHMWlITnIczmw5w60CF9ffkb8Z+DVmmjA= google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8/go.mod h1:lcTa1sDdWEIHMWlITnIczmw5w60CF9ffkb8Z+DVmmjA= google.golang.org/genproto/googleapis/rpc v0.0.0-20250106144421-5f5ef82da422/go.mod h1:3ENsm/5D1mzDyhpzeRi1NR784I0BcofWBoSc5QqqMK4= +google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= google.golang.org/grpc v1.52.3/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY= google.golang.org/grpc v1.56.1/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= google.golang.org/grpc v1.56.2/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= @@ -2581,6 +2694,8 @@ google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojt google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= google.golang.org/protobuf v1.36.0/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= +gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0 h1:0vLT13EuvQ0hNvakwLuFZ/jYrLp5F3kcWHXdRggjCE8= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/gcfg.v1 v1.2.3 h1:m8OOJ4ccYHnx2f4gQwpno8nAX5OGOh7RLaaz0pj3Ogs= @@ -2605,11 +2720,13 @@ howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM= howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g= k8s.io/api v0.29.3/go.mod h1:y2yg2NTyHUUkIoTC+phinTnEa3KFM6RZ3szxt014a80= k8s.io/apimachinery v0.29.3/go.mod h1:hx/S4V2PNW4OMg3WizRrHutyB5la0iCUbZym+W0EQIU= +k8s.io/apiserver v0.32.0/go.mod h1:HFh+dM1/BE/Hm4bS4nTXHVfN6Z6tFIZPi649n83b4Ag= k8s.io/client-go v0.29.3/go.mod h1:tkDisCvgPfiRpxGnOORfkljmS+UrW+WtXAy2fTvXJB0= k8s.io/code-generator v0.32.0 h1:s0lNN8VSWny8LBz5t5iy7MCdgwdOhdg7vAGVxvS+VWU= k8s.io/code-generator v0.32.0/go.mod h1:b7Q7KMZkvsYFy72A79QYjiv4aTz3GvW0f1T3UfhFq4s= k8s.io/code-generator v0.32.1 h1:4lw1kFNDuFYXquTkB7Sl5EwPMUP2yyW9hh6BnFfRZFY= k8s.io/code-generator v0.32.1/go.mod h1:zaILfm00CVyP/6/pJMJ3zxRepXkxyDfUV5SNG4CjZI4= +k8s.io/component-base v0.32.0/go.mod h1:JLG2W5TUxUu5uDyKiH2R/7NnxJo1HlPoRIIbVLkK5eM= k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6 h1:4s3/R4+OYYYUKptXPhZKjQ04WJ6EhQQVFdjOFvCazDk= k8s.io/gengo v0.0.0-20230829151522-9cce18d56c01 h1:pWEwq4Asjm4vjW7vcsmijwBhOr1/shsbSYiWXmNGlks= k8s.io/gengo v0.0.0-20230829151522-9cce18d56c01/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= @@ -2623,6 +2740,7 @@ k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/klog/v2 v2.110.1/go.mod h1:YGtd1984u+GgbuZ7e08/yBuAfKLSO0+uR1Fhi6ExXjo= k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/kms v0.32.0/go.mod h1:Bk2evz/Yvk0oVrvm4MvZbgq8BD34Ksxs2SRHn4/UiOM= k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA= k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= @@ -2677,3 +2795,4 @@ sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ih sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= sigs.k8s.io/structured-merge-diff/v4 v4.4.2/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4= sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= +sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= diff --git a/pkg/storage/unified/apistore/go.mod b/pkg/storage/unified/apistore/go.mod index 58e48dafc0d..5b5d4913d87 100644 --- a/pkg/storage/unified/apistore/go.mod +++ b/pkg/storage/unified/apistore/go.mod @@ -119,9 +119,9 @@ require ( github.com/docker/go-units v0.5.0 // indirect github.com/dolthub/flatbuffers/v23 v23.3.3-dh.2 // indirect github.com/dolthub/go-icu-regex v0.0.0-20241215010122-db690dd53c90 // indirect - github.com/dolthub/go-mysql-server v0.19.0 // indirect + github.com/dolthub/go-mysql-server v0.19.1-0.20250206012855-c216e59c21a7 // indirect github.com/dolthub/jsonpath v0.0.2-0.20240227200619-19675ab05c71 // indirect - github.com/dolthub/vitess v0.0.0-20241211024425-b00987f7ba54 // indirect + github.com/dolthub/vitess v0.0.0-20250123002143-3b45b8cacbfa // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/elazarl/goproxy v1.3.0 // indirect github.com/emicklei/go-restful/v3 v3.11.0 // indirect diff --git a/pkg/storage/unified/apistore/go.sum b/pkg/storage/unified/apistore/go.sum index 05f1f0f36b5..0ec94d6bec8 100644 --- a/pkg/storage/unified/apistore/go.sum +++ b/pkg/storage/unified/apistore/go.sum @@ -316,12 +316,12 @@ github.com/dolthub/flatbuffers/v23 v23.3.3-dh.2 h1:u3PMzfF8RkKd3lB9pZ2bfn0qEG+1G github.com/dolthub/flatbuffers/v23 v23.3.3-dh.2/go.mod h1:mIEZOHnFx4ZMQeawhw9rhsj+0zwQj7adVsnBX7t+eKY= github.com/dolthub/go-icu-regex v0.0.0-20241215010122-db690dd53c90 h1:Sni8jrP0sy/w9ZYXoff4g/ixe+7bFCZlfCqXKJSU+zM= github.com/dolthub/go-icu-regex v0.0.0-20241215010122-db690dd53c90/go.mod h1:ylU4XjUpsMcvl/BKeRRMXSH7e7WBrPXdSLvnRJYrxEA= -github.com/dolthub/go-mysql-server v0.19.0 h1:NdcXyGt9v7m4sQOahU+ss++iyPy4Q3viuVvbnn3rUTQ= -github.com/dolthub/go-mysql-server v0.19.0/go.mod h1:elfIatfq2fkU5lqTBrTcpL0RcHZOgYPE8EzBD7yQFiY= +github.com/dolthub/go-mysql-server v0.19.1-0.20250206012855-c216e59c21a7 h1:b9ygz2zMlMv2AcJ9mLL0b60tJ9Qd+bzfsAY5ZDTu9VY= +github.com/dolthub/go-mysql-server v0.19.1-0.20250206012855-c216e59c21a7/go.mod h1:jYEJ8tNkA7K3k39X8iMqaX3MSMmViRgh222JSLHDgVc= github.com/dolthub/jsonpath v0.0.2-0.20240227200619-19675ab05c71 h1:bMGS25NWAGTEtT5tOBsCuCrlYnLRKpbJVJkDbrTRhwQ= 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-20241211024425-b00987f7ba54 h1:nzBnC0Rt1gFtscJEz4veYd/mazZEdbdmed+tujdaKOo= -github.com/dolthub/vitess v0.0.0-20241211024425-b00987f7ba54/go.mod h1:1gQZs/byeHLMSul3Lvl3MzioMtOW1je79QYGyi2fd70= +github.com/dolthub/vitess v0.0.0-20250123002143-3b45b8cacbfa h1:kyoPzxViSXAyqfO0Mab7Qo1UogFIrxZKKyBU6kBOl+E= +github.com/dolthub/vitess v0.0.0-20250123002143-3b45b8cacbfa/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= diff --git a/pkg/storage/unified/resource/go.sum b/pkg/storage/unified/resource/go.sum index 96686090205..71ad28afea0 100644 --- a/pkg/storage/unified/resource/go.sum +++ b/pkg/storage/unified/resource/go.sum @@ -214,12 +214,12 @@ github.com/dolthub/flatbuffers/v23 v23.3.3-dh.2 h1:u3PMzfF8RkKd3lB9pZ2bfn0qEG+1G github.com/dolthub/flatbuffers/v23 v23.3.3-dh.2/go.mod h1:mIEZOHnFx4ZMQeawhw9rhsj+0zwQj7adVsnBX7t+eKY= github.com/dolthub/go-icu-regex v0.0.0-20241215010122-db690dd53c90 h1:Sni8jrP0sy/w9ZYXoff4g/ixe+7bFCZlfCqXKJSU+zM= github.com/dolthub/go-icu-regex v0.0.0-20241215010122-db690dd53c90/go.mod h1:ylU4XjUpsMcvl/BKeRRMXSH7e7WBrPXdSLvnRJYrxEA= -github.com/dolthub/go-mysql-server v0.19.0 h1:NdcXyGt9v7m4sQOahU+ss++iyPy4Q3viuVvbnn3rUTQ= -github.com/dolthub/go-mysql-server v0.19.0/go.mod h1:elfIatfq2fkU5lqTBrTcpL0RcHZOgYPE8EzBD7yQFiY= +github.com/dolthub/go-mysql-server v0.19.1-0.20250206012855-c216e59c21a7 h1:b9ygz2zMlMv2AcJ9mLL0b60tJ9Qd+bzfsAY5ZDTu9VY= +github.com/dolthub/go-mysql-server v0.19.1-0.20250206012855-c216e59c21a7/go.mod h1:jYEJ8tNkA7K3k39X8iMqaX3MSMmViRgh222JSLHDgVc= github.com/dolthub/jsonpath v0.0.2-0.20240227200619-19675ab05c71 h1:bMGS25NWAGTEtT5tOBsCuCrlYnLRKpbJVJkDbrTRhwQ= 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-20241211024425-b00987f7ba54 h1:nzBnC0Rt1gFtscJEz4veYd/mazZEdbdmed+tujdaKOo= -github.com/dolthub/vitess v0.0.0-20241211024425-b00987f7ba54/go.mod h1:1gQZs/byeHLMSul3Lvl3MzioMtOW1je79QYGyi2fd70= +github.com/dolthub/vitess v0.0.0-20250123002143-3b45b8cacbfa h1:kyoPzxViSXAyqfO0Mab7Qo1UogFIrxZKKyBU6kBOl+E= +github.com/dolthub/vitess v0.0.0-20250123002143-3b45b8cacbfa/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= From c8609b8a6165eded00aea0c7dc7bf26702c4528f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Philippe=20Qu=C3=A9m=C3=A9ner?= Date: Thu, 6 Feb 2025 22:23:00 +0100 Subject: [PATCH 31/34] feat(unified-storage): keep tags on reduce of dashboards (#100230) --- pkg/registry/apis/dashboard/large.go | 2 +- pkg/registry/apis/dashboard/large_test.go | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/pkg/registry/apis/dashboard/large.go b/pkg/registry/apis/dashboard/large.go index 88a5eb2d56d..9530b247d2f 100644 --- a/pkg/registry/apis/dashboard/large.go +++ b/pkg/registry/apis/dashboard/large.go @@ -31,7 +31,7 @@ func NewDashboardLargeObjectSupport(scheme *runtime.Scheme) *apistore.BasicLarge dash.Spec = spec dash.SetManagedFields(nil) // this could be bigger than the object! - keep := []string{"title", "description", "schemaVersion"} + keep := []string{"title", "description", "tags", "schemaVersion"} for _, k := range keep { v, ok := old[k] if ok { diff --git a/pkg/registry/apis/dashboard/large_test.go b/pkg/registry/apis/dashboard/large_test.go index a1f1094a935..45510304a65 100644 --- a/pkg/registry/apis/dashboard/large_test.go +++ b/pkg/registry/apis/dashboard/large_test.go @@ -54,7 +54,8 @@ func TestLargeDashboardSupport(t *testing.T) { require.NoError(t, err) require.JSONEq(t, `{ "schemaVersion": 33, - "title": "Panel tests - All panels" + "title": "Panel tests - All panels", + "tags": ["gdev","panel-tests","all-panels"] }`, string(small)) // Now make it big again From 8e3327a446f9a9319f61aa55e28c66d7587c3ee2 Mon Sep 17 00:00:00 2001 From: Jev Forsberg <46619047+baldm0mma@users.noreply.github.com> Date: Thu, 6 Feb 2025 15:23:51 -0700 Subject: [PATCH 32/34] Chore: Update grabpl version to v3.1.2 (#100157) baldm0mma/ update grabpl version --- .drone.yml | 18 +++++++++--------- scripts/drone/variables.star | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.drone.yml b/.drone.yml index f48a891415e..fe72be97168 100644 --- a/.drone.yml +++ b/.drone.yml @@ -646,7 +646,7 @@ steps: name: identify-runner - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.1.1/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.1.2/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -1099,7 +1099,7 @@ steps: path: /github-app - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.1.1/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.1.2/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -2065,7 +2065,7 @@ steps: name: identify-runner - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.1.1/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.1.2/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -2594,7 +2594,7 @@ services: steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.1.1/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.1.2/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -3152,7 +3152,7 @@ services: steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.1.1/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.1.2/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -3372,7 +3372,7 @@ steps: name: identify-runner - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.1.1/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.1.2/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -3504,7 +3504,7 @@ steps: name: identify-runner - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.1.1/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.1.2/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -4869,7 +4869,7 @@ services: steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.1.1/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.1.2/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -5601,6 +5601,6 @@ kind: secret name: gcr_credentials --- kind: signature -hmac: e1f198d994216d163d6a710aef2f6091572eb624081c550a5db42208ef37827b +hmac: a11d1b70585920d1086c0b98ef6f797365e91e5b31799061609cb6dbead074fa ... diff --git a/scripts/drone/variables.star b/scripts/drone/variables.star index 01147f6df17..6eeb6cfa03c 100644 --- a/scripts/drone/variables.star +++ b/scripts/drone/variables.star @@ -2,7 +2,7 @@ global variables """ -grabpl_version = "v3.1.1" +grabpl_version = "v3.1.2" golang_version = "1.23.5" # nodejs_version should match what's in ".nvmrc", but without the v prefix. From 74b2b5fb190dd298e341b6171038374fcbe39418 Mon Sep 17 00:00:00 2001 From: Jev Forsberg <46619047+baldm0mma@users.noreply.github.com> Date: Thu, 6 Feb 2025 15:58:56 -0700 Subject: [PATCH 33/34] Chore: Update drone.yml signature (#100236) baldm0mma/update drone signature --- .drone.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.drone.yml b/.drone.yml index fe72be97168..12e432f896a 100644 --- a/.drone.yml +++ b/.drone.yml @@ -5601,6 +5601,6 @@ kind: secret name: gcr_credentials --- kind: signature -hmac: a11d1b70585920d1086c0b98ef6f797365e91e5b31799061609cb6dbead074fa +hmac: 39572225832c2de6e648b6a4d66a36d212777390bf6fd4643f9cfaad21182df3 ... From d16f2315a4fa95bbb8a4f94192cb1be0aad3097a Mon Sep 17 00:00:00 2001 From: Brendan O'Handley Date: Thu, 6 Feb 2025 18:26:25 -0600 Subject: [PATCH 34/34] Explore metrics: Always check that custom var is present for otel dep env migration (#100233) --- .../trails/migrations/otelDeploymentEnvironment.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/public/app/features/trails/migrations/otelDeploymentEnvironment.ts b/public/app/features/trails/migrations/otelDeploymentEnvironment.ts index 969c95bf662..517ce88ce2c 100644 --- a/public/app/features/trails/migrations/otelDeploymentEnvironment.ts +++ b/public/app/features/trails/migrations/otelDeploymentEnvironment.ts @@ -32,8 +32,15 @@ export function migrateOtelDeploymentEnvironment(trail: DataTrail, urlParams: Ur ) { return; } - // if there is no dep env, does not need to be migrated - if (!deploymentEnv) { + + // check that there is a deployment environment variable value to migrate + // in some cases the deployment environment may not present + // but due to this change it is now always present and the value is undefined + // https://github.com/grafana/scenes/pull/1033 + if ( + !deploymentEnv || + (Array.isArray(deploymentEnv) && deploymentEnv.length > 0 && deploymentEnv[0] === 'undefined') + ) { return; }