From adbc5b2b8813c1ae2b888fe2b726acf72991e935 Mon Sep 17 00:00:00 2001 From: Gilles De Mey Date: Tue, 11 Mar 2025 12:51:48 +0100 Subject: [PATCH 001/141] Alerting: Hide "unauthorized" warning for anonymous users (#101811) * remove nav analytics * revert * Remove new user check for alerting navigation tracking * Delete Analytics.test.ts --- .../alerting/unified/Analytics.test.ts | 41 ------------------- .../features/alerting/unified/Analytics.ts | 24 +---------- 2 files changed, 1 insertion(+), 64 deletions(-) delete mode 100644 public/app/features/alerting/unified/Analytics.test.ts diff --git a/public/app/features/alerting/unified/Analytics.test.ts b/public/app/features/alerting/unified/Analytics.test.ts deleted file mode 100644 index 47575f755df..00000000000 --- a/public/app/features/alerting/unified/Analytics.test.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { dateTime } from '@grafana/data'; -import { getBackendSrv } from '@grafana/runtime'; - -import { USER_CREATION_MIN_DAYS, isNewUser } from './Analytics'; - -jest.mock('@grafana/runtime', () => ({ - ...jest.requireActual('@grafana/runtime'), - getBackendSrv: jest.fn().mockReturnValue({ - get: jest.fn(), - }), -})); - -describe('isNewUser', function () { - it('should return true if the user has been created within the last week', async () => { - const newUser = { - id: 1, - createdAt: dateTime().subtract(6, 'days'), - }; - - getBackendSrv().get = jest.fn().mockResolvedValue(newUser); - - const isNew = await isNewUser(); - expect(isNew).toBe(true); - expect(getBackendSrv().get).toHaveBeenCalledTimes(1); - expect(getBackendSrv().get).toHaveBeenCalledWith('/api/user'); - }); - - it('should return false if the user has been created prior to the last two weeks', async () => { - const oldUser = { - id: 2, - createdAt: dateTime().subtract(USER_CREATION_MIN_DAYS, 'days'), - }; - - getBackendSrv().get = jest.fn().mockResolvedValue(oldUser); - - const isNew = await isNewUser(); - expect(isNew).toBe(false); - expect(getBackendSrv().get).toHaveBeenCalledTimes(1); - expect(getBackendSrv().get).toHaveBeenCalledWith('/api/user'); - }); -}); diff --git a/public/app/features/alerting/unified/Analytics.ts b/public/app/features/alerting/unified/Analytics.ts index e546d215574..559e7808d96 100644 --- a/public/app/features/alerting/unified/Analytics.ts +++ b/public/app/features/alerting/unified/Analytics.ts @@ -1,7 +1,6 @@ import { isEmpty } from 'lodash'; -import { dateTime } from '@grafana/data'; -import { createMonitoringLogger, getBackendSrv } from '@grafana/runtime'; +import { createMonitoringLogger } from '@grafana/runtime'; import { config, reportInteraction } from '@grafana/runtime/src'; import { contextSrv } from 'app/core/core'; @@ -13,8 +12,6 @@ import { FilterType } from './components/rules/central-state-history/EventListSc import { RulesFilter, getSearchFilterFromQuery } from './search/rulesSearchParser'; import { RuleFormType } from './types/rule-form'; -export const USER_CREATION_MIN_DAYS = 7; - export const LogMessages = { filterByLabel: 'filtering alert instances by label', loadedList: 'loaded Alert Rules list', @@ -152,21 +149,6 @@ function getRulerRulesMetadata(rulerRules: RulerRulesConfigDTO) { }; } -export async function isNewUser() { - try { - const { createdAt } = await getBackendSrv().get(`/api/user`); - - const limitDateForNewUser = dateTime().subtract(USER_CREATION_MIN_DAYS, 'days'); - const userCreationDate = dateTime(createdAt); - - const isNew = limitDateForNewUser.isBefore(userCreationDate); - - return isNew; - } catch { - return true; //if no date is returned, we assume the user is new to prevent tracking actions - } -} - export const trackRuleListNavigation = async ( props: AlertRuleTrackingProps = { grafana_version: config.buildInfo.version, @@ -174,10 +156,6 @@ export const trackRuleListNavigation = async ( user_id: contextSrv.user.id, } ) => { - const isNew = await isNewUser(); - if (isNew) { - return; - } reportInteraction('grafana_alerting_navigation', props); }; From 062a0e7212f495330408359fee7c42e2c6839abc Mon Sep 17 00:00:00 2001 From: Joey <90795735+joey-grafana@users.noreply.github.com> Date: Tue, 11 Mar 2025 12:43:02 +0000 Subject: [PATCH 002/141] Tempo: fallback for intrinsic tags (#101677) * Add intrinsics fallback * Add test * Update tests * Prettier * Remove extra uniq --- .../tempo/SearchTraceQLEditor/utils.test.ts | 11 +++++++++-- .../tempo/SearchTraceQLEditor/utils.ts | 18 +++++++++++------- .../datasource/tempo/language_provider.test.ts | 9 +++++++-- 3 files changed, 27 insertions(+), 11 deletions(-) diff --git a/public/app/plugins/datasource/tempo/SearchTraceQLEditor/utils.test.ts b/public/app/plugins/datasource/tempo/SearchTraceQLEditor/utils.test.ts index e76cdf168e6..57c2dadc2b1 100644 --- a/public/app/plugins/datasource/tempo/SearchTraceQLEditor/utils.test.ts +++ b/public/app/plugins/datasource/tempo/SearchTraceQLEditor/utils.test.ts @@ -3,12 +3,14 @@ import { uniq } from 'lodash'; import { TraceqlFilter, TraceqlSearchScope } from '../dataquery.gen'; import { TempoDatasource } from '../datasource'; import TempoLanguageProvider from '../language_provider'; +import { intrinsics } from '../traceql/traceql'; import { filterToQuerySection, generateQueryFromAdHocFilters, getAllTags, getFilteredTags, + getIntrinsicTags, getTagsByScope, getUnscopedTags, } from './utils'; @@ -94,7 +96,7 @@ describe('gets correct tags', () => { it('for all tags', () => { const tags = getAllTags(v2Tags); - expect(tags).toEqual(['cluster', 'container', 'db', 'duration', 'kind', 'name', 'status']); + expect(tags).toEqual(uniq(['cluster', 'container', 'db', 'duration', 'kind', 'name', 'status'].concat(intrinsics))); }); it('for tags by resource scope', () => { @@ -106,6 +108,11 @@ describe('gets correct tags', () => { const tags = getTagsByScope(v2Tags, TraceqlSearchScope.Span); expect(tags).toEqual(['db']); }); + + it('for intrinsic tags', () => { + const tags = getIntrinsicTags(v2Tags); + expect(tags).toEqual(testIntrinsics); + }); }); describe('filterToQuerySection returns the correct query section for a filter', () => { @@ -179,7 +186,7 @@ describe('filterToQuerySection returns the correct query section for a filter', }); export const emptyTags = []; -export const testIntrinsics = ['duration', 'kind', 'name', 'status']; +export const testIntrinsics = uniq(['duration', 'kind', 'name', 'status'].concat(intrinsics)); export const v1Tags = ['bar', 'foo']; export const v2Tags = [ { diff --git a/public/app/plugins/datasource/tempo/SearchTraceQLEditor/utils.ts b/public/app/plugins/datasource/tempo/SearchTraceQLEditor/utils.ts index e1a599e99b3..5b4313ce6f2 100644 --- a/public/app/plugins/datasource/tempo/SearchTraceQLEditor/utils.ts +++ b/public/app/plugins/datasource/tempo/SearchTraceQLEditor/utils.ts @@ -7,6 +7,7 @@ import { VariableFormatID } from '@grafana/schema'; import { TraceqlFilter, TraceqlSearchScope } from '../dataquery.gen'; import { getEscapedSpanNames } from '../datasource'; import TempoLanguageProvider from '../language_provider'; +import { intrinsics } from '../traceql/traceql'; import { Scope } from '../types'; export const interpolateFilters = (filters: TraceqlFilter[], scopedVars?: ScopedVars) => { @@ -132,13 +133,16 @@ export const getUnscopedTags = (scopes: Scope[]) => { }; export const getIntrinsicTags = (scopes: Scope[]) => { - return uniq( - scopes - .map((scope: Scope) => - scope.name && scope.name === TraceqlSearchScope.Intrinsic && scope.tags ? scope.tags : [] - ) - .flat() - ); + let tags = scopes + .map((scope: Scope) => (scope.name && scope.name === TraceqlSearchScope.Intrinsic && scope.tags ? scope.tags : [])) + .flat(); + + // Add the default intrinsic tags to the list of tags. + // This is needed because the /api/v2/search/tags API + // may not always return all the default intrinsic tags + // but generally has the most up to date list. + tags = uniq(tags.concat(intrinsics)); + return tags; }; export const getAllTags = (scopes: Scope[]) => { diff --git a/public/app/plugins/datasource/tempo/language_provider.test.ts b/public/app/plugins/datasource/tempo/language_provider.test.ts index 9bd44f33bed..7aff5eb7b18 100644 --- a/public/app/plugins/datasource/tempo/language_provider.test.ts +++ b/public/app/plugins/datasource/tempo/language_provider.test.ts @@ -1,7 +1,10 @@ +import { uniq } from 'lodash'; + import { v1Tags, v2Tags } from './SearchTraceQLEditor/utils.test'; import { TraceqlSearchScope } from './dataquery.gen'; import { TempoDatasource } from './datasource'; import TempoLanguageProvider from './language_provider'; +import { intrinsics } from './traceql/traceql'; import { Scope } from './types'; describe('Language_provider', () => { @@ -15,7 +18,7 @@ describe('Language_provider', () => { it('for API v2 intrinsic tags', async () => { const lp = setup(undefined, v2Tags); const tags = lp.getMetricsSummaryTags(TraceqlSearchScope.Intrinsic); - expect(tags).toEqual(['duration', 'kind', 'name', 'status']); + expect(tags).toEqual(uniq(['duration', 'kind', 'name', 'status'].concat(intrinsics))); }); it('for API v2 resource tags', async () => { @@ -105,7 +108,9 @@ describe('Language_provider', () => { it('for API v2 tags', async () => { const lp = setup(undefined, v2Tags); const tags = lp.getAutocompleteTags(); - expect(tags).toEqual(['cluster', 'container', 'db', 'duration', 'kind', 'name', 'status']); + expect(tags).toEqual( + uniq(['cluster', 'container', 'db', 'duration', 'kind', 'name', 'status'].concat(intrinsics)) + ); }); }); From bbab62ce399c349e314148fd9603f15830a7ecd1 Mon Sep 17 00:00:00 2001 From: Steve Simpson Date: Tue, 11 Mar 2025 13:45:16 +0100 Subject: [PATCH 003/141] Alerting: Select remote write path dependent on metrics backend type. (#101891) The remote write path differs based on whether the data source is actually Prometheus, Mimir, Cortex, or an older version of Cortex. We do not want users to have to specify the path, so this change determines the path as best it can. It may be in the future we have to make this configurable per-datasource to cater for setups where it's impossible to determine the correct path. --- conf/defaults.ini | 3 - conf/sample.ini | 3 - .../fakes/fake_datasource_service.go | 11 ++- pkg/services/ngalert/ngalert.go | 8 +- .../ngalert/schedule/recording_rule_test.go | 11 ++- .../ngalert/writer/datasourcewriter.go | 85 +++++++++++++++-- .../ngalert/writer/datasourcewriter_test.go | 95 +++++++++++++++++-- pkg/services/ngalert/writer/testing.go | 12 +-- pkg/setting/setting_unified_alerting.go | 28 +++--- 9 files changed, 199 insertions(+), 57 deletions(-) diff --git a/conf/defaults.ini b/conf/defaults.ini index a216bb0792a..ee703f76d2a 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -1527,9 +1527,6 @@ timeout = 10s # Only has effect if the grafanaManagedRecordRulesDatasources feature toggle is enabled. default_datasource_uid = -# Suffix to apply to the data source URL for remote write requests. -remote_write_path_suffix = /push - # Optional custom headers to include in recording rule write requests. [recording_rules.custom_headers] # exampleHeader = exampleValue diff --git a/conf/sample.ini b/conf/sample.ini index 21ef0ad88c6..207fa535410 100644 --- a/conf/sample.ini +++ b/conf/sample.ini @@ -1509,9 +1509,6 @@ timeout = 30s # Only has effect if the grafanaManagedRecordRulesDatasources feature toggle is enabled. default_datasource_uid = -# Suffix to apply to the data source URL for remote write requests. -remote_write_path_suffix = /push - # Optional custom headers to include in recording rule write requests. [recording_rules.custom_headers] # exampleHeader = exampleValue diff --git a/pkg/services/datasources/fakes/fake_datasource_service.go b/pkg/services/datasources/fakes/fake_datasource_service.go index f117f7b4af8..43a71852c43 100644 --- a/pkg/services/datasources/fakes/fake_datasource_service.go +++ b/pkg/services/datasources/fakes/fake_datasource_service.go @@ -74,11 +74,12 @@ func (s *FakeDataSourceService) AddDataSource(ctx context.Context, cmd *datasour s.lastID = int64(len(s.DataSources) - 1) } dataSource := &datasources.DataSource{ - ID: s.lastID + 1, - Name: cmd.Name, - Type: cmd.Type, - UID: cmd.UID, - OrgID: cmd.OrgID, + ID: s.lastID + 1, + Name: cmd.Name, + Type: cmd.Type, + UID: cmd.UID, + OrgID: cmd.OrgID, + JsonData: cmd.JsonData, } s.DataSources = append(s.DataSources, dataSource) return dataSource, nil diff --git a/pkg/services/ngalert/ngalert.go b/pkg/services/ngalert/ngalert.go index 9f4a62656f6..690c58c291b 100644 --- a/pkg/services/ngalert/ngalert.go +++ b/pkg/services/ngalert/ngalert.go @@ -760,14 +760,12 @@ func createRecordingWriter(featureToggles featuremgmt.FeatureToggles, settings s if settings.Enabled { if featureToggles.IsEnabledGlobally(featuremgmt.FlagGrafanaManagedRecordingRulesDatasources) { cfg := writer.DatasourceWriterConfig{ - Timeout: settings.Timeout, - DefaultDatasourceUID: settings.DefaultDatasourceUID, - RemoteWritePathSuffix: settings.RemoteWritePathSuffix, + Timeout: settings.Timeout, + DefaultDatasourceUID: settings.DefaultDatasourceUID, } logger.Info("Setting up remote write using data sources", - "timeout", cfg.Timeout, "default_datasource_uid", cfg.DefaultDatasourceUID, - "remote_write_path_suffix", cfg.RemoteWritePathSuffix) + "timeout", cfg.Timeout, "default_datasource_uid", cfg.DefaultDatasourceUID) return writer.NewDatasourceWriter(cfg, datasourceService, httpClientProvider, clock, logger, m), nil } else { diff --git a/pkg/services/ngalert/schedule/recording_rule_test.go b/pkg/services/ngalert/schedule/recording_rule_test.go index 6979d8469b7..a3f2a90cbae 100644 --- a/pkg/services/ngalert/schedule/recording_rule_test.go +++ b/pkg/services/ngalert/schedule/recording_rule_test.go @@ -18,6 +18,7 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient" + "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/expr" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/services/datasources" @@ -574,15 +575,15 @@ func setupDatasourceWriter(t *testing.T, target *writer.TestRemoteWriteTarget, r dss := &dsfakes.FakeDataSourceService{} p1, _ := dss.AddDataSource(context.Background(), &datasources.AddDataSourceCommand{ - UID: dsUID, - Type: datasources.DS_PROMETHEUS, + UID: dsUID, + Type: datasources.DS_PROMETHEUS, + JsonData: simplejson.MustJson([]byte(`{"prometheusType":"Prometheus"}`)), }) p1.URL = target.DatasourceURL() cfg := writer.DatasourceWriterConfig{ - Timeout: time.Second * 5, - DefaultDatasourceUID: "", - RemoteWritePathSuffix: writer.RemoteWriteSuffix, + Timeout: time.Second * 5, + DefaultDatasourceUID: "", } return writer.NewDatasourceWriter(cfg, dss, provider, clock.NewMock(), diff --git a/pkg/services/ngalert/writer/datasourcewriter.go b/pkg/services/ngalert/writer/datasourcewriter.go index a69b157c80b..9be600ae157 100644 --- a/pkg/services/ngalert/writer/datasourcewriter.go +++ b/pkg/services/ngalert/writer/datasourcewriter.go @@ -5,6 +5,8 @@ import ( "errors" "fmt" "net/url" + "path" + "strings" "time" "github.com/benbjohnson/clock" @@ -35,9 +37,6 @@ type DatasourceWriterConfig struct { // This exists to cater for upgrading from old versions of Grafana, where rule // definitions may not have a target data source specified. DefaultDatasourceUID string - - // RemoteWritePathSuffix is the path suffix for remote write, normally /push. - RemoteWritePathSuffix string } type DatasourceWriter struct { @@ -78,6 +77,73 @@ func (w *DatasourceWriter) decrypt(ds *datasources.DataSource) (map[string]strin return decryptedJsonData, err } +func getPrometheusType(ds *datasources.DataSource) string { + if ds.JsonData == nil { + return "" + } + jsonData := ds.JsonData.Get("prometheusType") + if jsonData == nil { + return "" + } + str, err := jsonData.String() + if err != nil { + return "" + } + return str +} + +func getRemoteWriteURL(ds *datasources.DataSource) (*url.URL, error) { + u, err := url.Parse(ds.URL) + if err != nil { + return nil, err + } + + if getPrometheusType(ds) == "Prometheus" { + return u.JoinPath("/api/v1/write"), nil + } + + // All other cases assume Mimir/Cortex, as these systems are much more likely to be + // used as a remote write target, where as Prometheus does not recommend it. + + // Mimir/Cortex are more complicated, as Grafana has to be configured with the + // base URL for where the Prometheus API is located, e.g. /api/prom or /prometheus. + // + // - For "legacy" routes, /push is located on the same level as /api/v1/query. + // + // For example: + // Grafana will be configured with /api/prom + // The query API is at /api/prom/api/v1/query + // The push API is at /api/prom/push + // + // - For "new" routes, /push is located at the Mimir root, not Prometheus root. + // + // For example: + // Grafana will be configured with e.g. /prometheus + // The query API is at /prometheus/api/v1/query + // But push API is at /push + // + // Unfortunately, the prefixes can also be configured, + + cleanPath := path.Clean(u.Path) + + // If the suffix is /api/prom, assume Mimir/Cortex with legacy routes. + if strings.HasSuffix(cleanPath, "/api/prom") { + u.Path = path.Join(u.Path, "/push") + return u, nil + } + + // If the suffix is /prometheus, assume Mimir/Cortex with new routes. + if strings.HasSuffix(cleanPath, "/prometheus") { + u.Path = path.Join(path.Dir(u.Path), "/api/v1/push") + return u, nil + } + + // The user has configured an unknown prefix, so fall back to taking + // the host as the Mimir root. This is less than ideal. + u.Path = "/api/v1/push" + return u, nil +} + func (w *DatasourceWriter) makeWriter(ctx context.Context, orgID int64, dsUID string) (*PrometheusWriter, error) { ds, err := w.datasources.GetDataSource(ctx, &datasources.GetDataSourceQuery{ UID: dsUID, @@ -101,13 +167,11 @@ func (w *DatasourceWriter) makeWriter(ctx context.Context, orgID int64, dsUID st return nil, err } - u, err := url.Parse(is.URL) + u, err := getRemoteWriteURL(ds) if err != nil { return nil, err } - u = u.JoinPath(w.cfg.RemoteWritePathSuffix) - cfg := PrometheusWriterConfig{ URL: u.String(), HTTPOptions: httpclient.Options{ @@ -121,6 +185,15 @@ func (w *DatasourceWriter) makeWriter(ctx context.Context, orgID int64, dsUID st return nil, err } + w.l.Debug("Created Prometheus remote writer", + "datasource_uid", dsUID, + "type", ds.Type, + "prometheusType", getPrometheusType(ds), + "url", cfg.URL, + "tls", cfg.HTTPOptions.TLS != nil, + "basic_auth", cfg.HTTPOptions.BasicAuth != nil, + "timeout", cfg.Timeout) + return NewPrometheusWriter( cfg, w.httpClientProvider, diff --git a/pkg/services/ngalert/writer/datasourcewriter_test.go b/pkg/services/ngalert/writer/datasourcewriter_test.go index 6727d352997..165f735d10d 100644 --- a/pkg/services/ngalert/writer/datasourcewriter_test.go +++ b/pkg/services/ngalert/writer/datasourcewriter_test.go @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/infra/httpclient" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/services/datasources" @@ -43,16 +44,20 @@ func setupDataSources(t *testing.T) *testDataSources { }) p1, _ := res.AddDataSource(context.Background(), &datasources.AddDataSourceCommand{ - UID: "prom-1", - Type: datasources.DS_PROMETHEUS, + UID: "prom-1", + Type: datasources.DS_PROMETHEUS, + JsonData: simplejson.MustJson([]byte(`{"prometheusType":"Prometheus"}`)), }) - p1.URL = res.prom1.srv.URL + "/api/v1" + p1.URL = res.prom1.srv.URL + res.prom1.ExpectedPath = "/api/v1/write" p2, _ := res.AddDataSource(context.Background(), &datasources.AddDataSourceCommand{ - UID: "prom-2", - Type: datasources.DS_PROMETHEUS, + UID: "prom-2", + Type: datasources.DS_PROMETHEUS, + JsonData: simplejson.MustJson([]byte(`{"prometheusType":"Mimir"}`)), }) - p2.URL = res.prom2.srv.URL + "/api/v1" + p2.URL = res.prom2.srv.URL + "/api/prom" + res.prom2.ExpectedPath = "/api/prom/push" // Add a non-Prometheus datasource. _, _ = res.AddDataSource(context.Background(), &datasources.AddDataSourceCommand{ @@ -70,9 +75,8 @@ func TestDatasourceWriter(t *testing.T) { datasources := setupDataSources(t) cfg := DatasourceWriterConfig{ - Timeout: time.Second * 5, - DefaultDatasourceUID: "prom-2", - RemoteWritePathSuffix: "/write", + Timeout: time.Second * 5, + DefaultDatasourceUID: "prom-2", } met := metrics.NewRemoteWriterMetrics(prometheus.NewRegistry()) @@ -117,3 +121,76 @@ func TestDatasourceWriter(t *testing.T) { require.NoError(t, err) }) } + +func TestDatasourceWriterGetRemoteWriteURL(t *testing.T) { + tc := []struct { + name string + ds datasources.DataSource + url string + }{ + { + "prometheus", + datasources.DataSource{ + JsonData: simplejson.MustJson([]byte(`{"prometheusType":"Prometheus"}`)), + URL: "http://example.com", + }, + "http://example.com/api/v1/write", + }, + { + "prometheus with prefix", + datasources.DataSource{ + JsonData: simplejson.MustJson([]byte(`{"prometheusType":"Prometheus"}`)), + URL: "http://example.com/myprom", + }, + "http://example.com/myprom/api/v1/write", + }, + { + "mimir/cortex legacy routes", + datasources.DataSource{ + JsonData: simplejson.MustJson([]byte(`{"prometheusType":"Anything"}`)), + URL: "http://example.com/api/prom", + }, + "http://example.com/api/prom/push", + }, + { + "mimir/cortex legacy routes with prefix", + datasources.DataSource{ + JsonData: simplejson.MustJson([]byte(`{"prometheusType":"Anything"}`)), + URL: "http://example.com/myprom/api/prom", + }, + "http://example.com/myprom/api/prom/push", + }, + { + "mimir/cortex new routes", + datasources.DataSource{ + JsonData: simplejson.MustJson([]byte(`{"prometheusType":"Anything"}`)), + URL: "http://example.com/prometheus", + }, + "http://example.com/api/v1/push", + }, + { + "mimir/cortex new routes with prefix", + datasources.DataSource{ + JsonData: simplejson.MustJson([]byte(`{"prometheusType":"Anything"}`)), + URL: "http://example.com/mymimir/prometheus", + }, + "http://example.com/mymimir/api/v1/push", + }, + { + "mimir/cortex with unknown suffix", + datasources.DataSource{ + JsonData: simplejson.MustJson([]byte(`{"prometheusType":"Anything"}`)), + URL: "http://example.com/foo/bar", + }, + "http://example.com/api/v1/push", + }, + } + + for _, tt := range tc { + t.Run(tt.name, func(t *testing.T) { + res, err := getRemoteWriteURL(&tt.ds) + require.NoError(t, err) + require.Equal(t, tt.url, res.String()) + }) + } +} diff --git a/pkg/services/ngalert/writer/testing.go b/pkg/services/ngalert/writer/testing.go index 1996e712247..2100c9cfc7a 100644 --- a/pkg/services/ngalert/writer/testing.go +++ b/pkg/services/ngalert/writer/testing.go @@ -12,10 +12,7 @@ import ( "github.com/stretchr/testify/require" ) -const RemoteWritePrefix = "/api/v1" -const RemoteWriteSuffix = "/write" - -const RemoteWriteEndpoint = RemoteWritePrefix + RemoteWriteSuffix +const RemoteWriteEndpoint = "/api/v1/write" type TestRemoteWriteTarget struct { srv *httptest.Server @@ -23,6 +20,8 @@ type TestRemoteWriteTarget struct { mtx sync.Mutex RequestsCount int LastRequestBody string + + ExpectedPath string } func NewTestRemoteWriteTarget(t *testing.T) *TestRemoteWriteTarget { @@ -31,10 +30,11 @@ func NewTestRemoteWriteTarget(t *testing.T) *TestRemoteWriteTarget { target := &TestRemoteWriteTarget{ RequestsCount: 0, LastRequestBody: "", + ExpectedPath: RemoteWriteEndpoint, } handler := func(w http.ResponseWriter, r *http.Request) { - if r.URL.Path != RemoteWriteEndpoint { + if r.URL.Path != target.ExpectedPath { require.Fail(t, "Received unexpected request for endpoint %s", r.URL.Path) } @@ -63,7 +63,7 @@ func (s *TestRemoteWriteTarget) Close() { } func (s *TestRemoteWriteTarget) DatasourceURL() string { - return s.srv.URL + RemoteWritePrefix + return s.srv.URL } func (s *TestRemoteWriteTarget) ClientSettings() setting.RecordingRuleSettings { diff --git a/pkg/setting/setting_unified_alerting.go b/pkg/setting/setting_unified_alerting.go index bf359c34855..33f002cb571 100644 --- a/pkg/setting/setting_unified_alerting.go +++ b/pkg/setting/setting_unified_alerting.go @@ -132,14 +132,13 @@ type UnifiedAlertingSettings struct { } type RecordingRuleSettings struct { - Enabled bool - URL string - BasicAuthUsername string - BasicAuthPassword string - CustomHeaders map[string]string - Timeout time.Duration - DefaultDatasourceUID string - RemoteWritePathSuffix string + Enabled bool + URL string + BasicAuthUsername string + BasicAuthPassword string + CustomHeaders map[string]string + Timeout time.Duration + DefaultDatasourceUID string } // RemoteAlertmanagerSettings contains the configuration needed @@ -437,13 +436,12 @@ func (cfg *Cfg) ReadUnifiedAlertingSettings(iniFile *ini.File) error { rr := iniFile.Section("recording_rules") uaCfgRecordingRules := RecordingRuleSettings{ - Enabled: rr.Key("enabled").MustBool(false), - URL: rr.Key("url").MustString(""), - BasicAuthUsername: rr.Key("basic_auth_username").MustString(""), - BasicAuthPassword: rr.Key("basic_auth_password").MustString(""), - Timeout: rr.Key("timeout").MustDuration(defaultRecordingRequestTimeout), - DefaultDatasourceUID: rr.Key("default_datasource_uid").MustString(""), - RemoteWritePathSuffix: rr.Key("remote_write_path_suffix").MustString("/push"), + Enabled: rr.Key("enabled").MustBool(false), + URL: rr.Key("url").MustString(""), + BasicAuthUsername: rr.Key("basic_auth_username").MustString(""), + BasicAuthPassword: rr.Key("basic_auth_password").MustString(""), + Timeout: rr.Key("timeout").MustDuration(defaultRecordingRequestTimeout), + DefaultDatasourceUID: rr.Key("default_datasource_uid").MustString(""), } rrHeaders := iniFile.Section("recording_rules.custom_headers") From 2712686a368a18ec788e601d38c9235ac6e658bd Mon Sep 17 00:00:00 2001 From: Alex Bikfalvi Date: Tue, 11 Mar 2025 13:45:26 +0100 Subject: [PATCH 004/141] feat(datasource/Tempo): Instrument Tempo query latency measurements (#101285) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: Instrument Tempo query latency measurements Add comprehensive latency tracking and reporting for Tempo queries using reportInteraction: - Add latency measurements for TraceQL metrics queries - Add latency measurements for TraceID queries - Add latency measurements for TraceQL search queries - Track both streaming and non-streaming query performance - Include success/error states and relevant metadata in reports - Measure latency in milliseconds for more precise tracking This instrumentation will help monitor query performance and identify potential bottlenecks in trace queries. Signed-off-by: Alex Bikfalvi * fixup! feat: Instrument Tempo query latency measurements Signed-off-by: Alex Bikfalvi * prettier fix --------- Signed-off-by: Alex Bikfalvi Co-authored-by: André Pereira --- .../plugins/datasource/tempo/datasource.ts | 216 +++++++++++++++++- 1 file changed, 210 insertions(+), 6 deletions(-) diff --git a/public/app/plugins/datasource/tempo/datasource.ts b/public/app/plugins/datasource/tempo/datasource.ts index ee8727ce74f..de07cc54e45 100644 --- a/public/app/plugins/datasource/tempo/datasource.ts +++ b/public/app/plugins/datasource/tempo/datasource.ts @@ -1,6 +1,6 @@ import { groupBy } from 'lodash'; import { EMPTY, forkJoin, from, lastValueFrom, merge, Observable, of } from 'rxjs'; -import { catchError, concatMap, map, mergeMap, toArray } from 'rxjs/operators'; +import { catchError, concatMap, finalize, map, mergeMap, toArray } from 'rxjs/operators'; import semver from 'semver'; import { @@ -97,6 +97,16 @@ interface ServiceMapQueryResponseWithRates { edges: DataFrame; } +interface TempoQueryMetrics { + success: boolean; + streaming?: boolean; + latencyMs: number; + query?: string; + error?: string; + statusCode?: number; + statusText?: string; +} + export class TempoDatasource extends DataSourceWithBackend { tracesToLogs?: TraceToLogsOptions; serviceMap?: { @@ -363,8 +373,7 @@ export class TempoDatasource extends DataSourceWithBackend { + reportTempoQueryMetrics('grafana_traces_traceql_response', options, { + success: true, + streaming: false, + latencyMs: Math.round(performance.now() - startTime), // rounded to nearest millisecond + query: queryFromFilters ?? '', + }); return { data: formatTraceQLResponse( response.data.traces, @@ -442,6 +458,15 @@ export class TempoDatasource extends DataSourceWithBackend { + reportTempoQueryMetrics('grafana_traces_traceql_response', options, { + success: false, + streaming: false, + latencyMs: Math.round(performance.now() - startTime), // rounded to nearest millisecond + query: queryFromFilters ?? '', + error: getErrorMessage(err.message), + statusCode: err.status, + statusText: err.statusText, + }); return of({ error: { message: getErrorMessage(err.data.message) }, data: [] }); }) ) @@ -569,7 +594,11 @@ export class TempoDatasource extends DataSourceWithBackend, targets: TempoQuery[]): Observable { + handleTraceIdQuery( + options: DataQueryRequest, + targets: TempoQuery[], + query: string + ): Observable { const validTargets = targets .filter((t) => t.query) .map((t): TempoQuery => ({ ...t, query: t.query?.trim(), queryType: 'traceId' })); @@ -577,13 +606,41 @@ export class TempoDatasource extends DataSourceWithBackend { if (response.error) { + reportTempoQueryMetrics('grafana_traces_traceID_response', options, { + success: false, + streaming: false, + latencyMs: Math.round(performance.now() - startTime), // rounded to nearest millisecond + query: query ?? '', + error: getErrorMessage(response.error.message), + statusCode: response.error.status, + statusText: response.error.statusText, + }); return response; } + reportTempoQueryMetrics('grafana_traces_traceID_response', options, { + success: true, + streaming: false, + latencyMs: Math.round(performance.now() - startTime), // rounded to nearest millisecond + query: query ?? '', + }); return transformTrace(response, this.instanceSettings, this.nodeGraph?.enabled); + }), + catchError((error) => { + reportTempoQueryMetrics('grafana_traces_traceID_response', options, { + success: false, + streaming: false, + latencyMs: Math.round(performance.now() - startTime), // rounded to nearest millisecond + query: query ?? '', + error: getErrorMessage(error.message), + statusCode: error.status, + statusText: error.statusText, + }); + throw error; }) ); } @@ -595,6 +652,7 @@ export class TempoDatasource extends DataSourceWithBackend => { + const startTime = performance.now(); if (this.isStreamingSearchEnabled()) { return this.handleStreamingQuery(options, targets.traceql, queryValue); } else { @@ -606,11 +664,26 @@ export class TempoDatasource extends DataSourceWithBackend { + reportTempoQueryMetrics('grafana_traces_traceql_response', options, { + success: true, + streaming: false, + latencyMs: Math.round(performance.now() - startTime), // rounded to nearest millisecond + query: queryValue ?? '', + }); return { data: formatTraceQLResponse(response.data.traces, this.instanceSettings, targets.traceql[0].tableType), }; }), catchError((err) => { + reportTempoQueryMetrics('grafana_traces_traceql_response', options, { + success: false, + streaming: false, + latencyMs: Math.round(performance.now() - startTime), // rounded to nearest millisecond + query: queryValue ?? '', + error: getErrorMessage(err.message), + statusCode: err.status, + statusText: err.statusText, + }); return of({ error: { message: getErrorMessage(err.data.message) }, data: [] }); }) ); @@ -619,7 +692,8 @@ export class TempoDatasource extends DataSourceWithBackend, - targets: TempoQuery[] + targets: TempoQuery[], + query: string ): Observable { const validTargets = targets .filter((t) => t.query) @@ -630,12 +704,28 @@ export class TempoDatasource extends DataSourceWithBackend { + reportTempoQueryMetrics('grafana_traces_traceql_metrics_response', options, { + success: true, + streaming: false, + latencyMs: Math.round(performance.now() - startTime), // rounded to nearest millisecond + query: query ?? '', + }); return enhanceTraceQlMetricsResponse(response, this.instanceSettings); }), catchError((err) => { + reportTempoQueryMetrics('grafana_traces_traceql_metrics_response', options, { + success: false, + streaming: false, + latencyMs: Math.round(performance.now() - startTime), // rounded to nearest millisecond + query: query ?? '', + error: getErrorMessage(err.data.message), + statusCode: err.status, + statusText: err.statusText, + }); return of({ error: { message: getErrorMessage(err.data.message) }, data: [] }); }) ); @@ -659,6 +749,7 @@ export class TempoDatasource extends DataSourceWithBackend { if (!response.data.summaries) { + reportTempoQueryMetrics('grafana_traces_metrics_summary_response', options, { + success: false, + streaming: false, + latencyMs: Math.round(performance.now() - startTime), // rounded to nearest millisecond + query: query ?? '', + error: getErrorMessage(`No summary data for '${groupBy}'.`), + }); return { error: { message: getErrorMessage(`No summary data for '${groupBy}'.`), @@ -678,6 +776,13 @@ export class TempoDatasource extends DataSourceWithBackend summary.series.length > 0); if (!hasSeries) { + reportTempoQueryMetrics('grafana_traces_metrics_summary_response', options, { + success: false, + streaming: false, + latencyMs: Math.round(performance.now() - startTime), // rounded to nearest millisecond + query: query ?? '', + error: getErrorMessage(`No series data. Ensure you are using an up to date version of Tempo`), + }); return { error: { message: getErrorMessage(`No series data. Ensure you are using an up to date version of Tempo`), @@ -685,11 +790,26 @@ export class TempoDatasource extends DataSourceWithBackend { + reportTempoQueryMetrics('grafana_traces_metrics_summary_response', options, { + success: false, + streaming: false, + latencyMs: Math.round(performance.now() - startTime), // rounded to nearest millisecond + query: query ?? '', + error: getErrorMessage(error.data.message), + statusCode: error.status, + statusText: error.statusText, + }); return of({ error: { message: getErrorMessage(error.data.message) }, data: emptyResponse, @@ -709,6 +829,7 @@ export class TempoDatasource extends DataSourceWithBackend doTempoSearchStreaming( @@ -718,6 +839,28 @@ export class TempoDatasource extends DataSourceWithBackend { + reportTempoQueryMetrics('grafana_traces_traceql_response', options, { + success: false, + streaming: true, + latencyMs: Math.round(performance.now() - startTime), // rounded to nearest millisecond + query: query ?? '', + error: getErrorMessage(error.data.message), + statusCode: error.status, + statusText: error.statusText, + }); + // Re-throw the error to maintain the error chain + throw error; + }), + finalize(() => { + reportTempoQueryMetrics('grafana_traces_traceql_response', options, { + success: true, + streaming: true, + query: query ?? '', + latencyMs: Math.round(performance.now() - startTime), // rounded to nearest millisecond + }); + }) ); } @@ -732,6 +875,7 @@ export class TempoDatasource extends DataSourceWithBackend doTempoMetricsStreaming( @@ -740,6 +884,28 @@ export class TempoDatasource extends DataSourceWithBackend { + reportTempoQueryMetrics('grafana_traces_traceql_metrics_response', options, { + success: false, + streaming: true, + latencyMs: Math.round(performance.now() - startTime), // rounded to nearest millisecond + query: query ?? '', + error: getErrorMessage(error.data.message), + statusCode: error.status, + statusText: error.statusText, + }); + // Re-throw the error to maintain the error chain + throw error; + }), + finalize(() => { + reportTempoQueryMetrics('grafana_traces_traceql_metrics_response', options, { + success: true, + streaming: true, + query: query ?? '', + latencyMs: Math.round(performance.now() - startTime), // rounded to nearest millisecond + }); + }) ); } @@ -1442,6 +1608,44 @@ function getServiceGraphViewDataFrames( return df; } +/** + * Reports metrics for Tempo query interactions. + * + * @param options - The data query request options containing app and other context + * @param metrics - Object containing metrics to report: + * - success: Whether the query was successful + * - streaming: (optional) Whether streaming was used + * - latencyMs: Query execution time in milliseconds + * - query: (optional) The query string that was executed + * - error: (optional) Error message if query failed + * - statusCode: (optional) HTTP status code if query failed + * - statusText: (optional) HTTP status text if query failed + * @param interactionName - (optional) Name of the interaction to report. + * Defaults to 'grafana_traces_traceql_response' + * + * @example + * ```typescript + * reportTempoQueryMetrics(options, { + * success: true, + * streaming: true, + * latencyMs: Math.round(performance.now() - startTime), + * query: 'my query' + * }); + * ``` + */ +function reportTempoQueryMetrics( + interactionName: string, + options: DataQueryRequest, + metrics: TempoQueryMetrics +) { + reportInteraction(interactionName, { + datasourceType: 'tempo', + app: options.app ?? '', + grafana_version: config.buildInfo.version, + ...metrics, + }); +} + export function buildExpr( metric: { expr: string; params: string[]; topk?: number }, extraParams: string, From 0519cfa66d6697aa9cc12a0063a01aefb9918423 Mon Sep 17 00:00:00 2001 From: Ed Poole Date: Tue, 11 Mar 2025 13:09:08 +0000 Subject: [PATCH 005/141] Fix/theme gradients (#101934) * Brighten the DesertBloom gradient * Adjust gradient values so they're consistently rgba --- .../grafana-data/src/themes/themeDefinitions/desertbloom.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/grafana-data/src/themes/themeDefinitions/desertbloom.ts b/packages/grafana-data/src/themes/themeDefinitions/desertbloom.ts index 0d8ce37e045..8c08ca75da1 100644 --- a/packages/grafana-data/src/themes/themeDefinitions/desertbloom.ts +++ b/packages/grafana-data/src/themes/themeDefinitions/desertbloom.ts @@ -61,9 +61,10 @@ const desertBloomTheme: NewThemeOptions = { 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%)', + brandHorizontal: 'linear-gradient(270deg,rgba(255, 111, 97, 1) 0%, rgba(255, 167, 58, 1) 100%)', + brandVertical: 'linear-gradient(0deg, rgba(255, 111, 97, 1) 0%, rgba(255, 167, 58, 1) 100%)', }, contrastThreshold: 3, hoverFactor: 0.03, From 6b2c73141df74430eb72f1e6b6c166f5f896e715 Mon Sep 17 00:00:00 2001 From: Tom Ratcliffe Date: Tue, 11 Mar 2025 13:13:00 +0000 Subject: [PATCH 006/141] Alerting: Improve clarity of recording rule creation (#100700) * Add description below group and namespace fields to make creation clearer * Make DS managed recording rules clearer * Change link for recording rule on empty state to Grafana managed * Tweak empty state * Tidy up logic for display of recording rule buttons * Update .betterer.results --- .betterer.results | 6 +- .../rule-editor/GroupAndNamespaceFields.tsx | 15 +++- .../unified/components/rules/CloudRules.tsx | 5 +- .../unified/components/rules/NoRulesCTA.tsx | 69 ++++++++++++++++--- public/locales/en-US/grafana.json | 2 + 5 files changed, 82 insertions(+), 15 deletions(-) diff --git a/.betterer.results b/.betterer.results index b03dac144aa..4df170e7dc1 100644 --- a/.betterer.results +++ b/.betterer.results @@ -2387,10 +2387,8 @@ exports[`better eslint`] = { ], "public/app/features/alerting/unified/components/rules/CloudRules.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "2"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "3"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "4"] + [0, 0, 0, "No untranslated strings. Wrap text with ", "1"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "2"] ], "public/app/features/alerting/unified/components/rules/EditRuleGroupModal.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], diff --git a/public/app/features/alerting/unified/components/rule-editor/GroupAndNamespaceFields.tsx b/public/app/features/alerting/unified/components/rule-editor/GroupAndNamespaceFields.tsx index dee970cf598..6bbac094cbb 100644 --- a/public/app/features/alerting/unified/components/rule-editor/GroupAndNamespaceFields.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/GroupAndNamespaceFields.tsx @@ -45,6 +45,10 @@ export const GroupAndNamespaceFields = ({ rulesSourceName }: Props) => { @@ -71,7 +75,16 @@ export const GroupAndNamespaceFields = ({ rulesSourceName }: Props) => { }} /> - + ( - New recording rule + + New data source-managed recording rule + ); } diff --git a/public/app/features/alerting/unified/components/rules/NoRulesCTA.tsx b/public/app/features/alerting/unified/components/rules/NoRulesCTA.tsx index 38a4da4ce30..f33db5d9962 100644 --- a/public/app/features/alerting/unified/components/rules/NoRulesCTA.tsx +++ b/public/app/features/alerting/unified/components/rules/NoRulesCTA.tsx @@ -1,8 +1,65 @@ -import { EmptyState, LinkButton, Stack, TextLink } from '@grafana/ui'; -import { Trans } from 'app/core/internationalization'; +import { config } from '@grafana/runtime'; +import { Dropdown, EmptyState, LinkButton, Menu, MenuItem, Stack, TextLink } from '@grafana/ui'; +import { Trans, t } from 'app/core/internationalization'; import { useRulesAccess } from '../../utils/accessControlHooks'; +const RecordingRulesButtons = () => { + const { canCreateGrafanaRules, canCreateCloudRules } = useRulesAccess(); + const grafanaRecordingRulesEnabled = config.featureToggles.grafanaManagedRecordingRules; + const canCreateAll = canCreateGrafanaRules && canCreateCloudRules && grafanaRecordingRulesEnabled; + + // User can create Grafana and DS-managed recording rules, show a dropdown + if (canCreateAll) { + return ( + + + + + } + > + + New recording rule + + + ); + } + + // ...Otherwise, just show the buttons for each type of recording rule + // (this will just be one or the other) + return ( + <> + {canCreateGrafanaRules && grafanaRecordingRulesEnabled && ( + + + New Grafana-managed recording rule + + + )} + {canCreateCloudRules && ( + + + New data source-managed recording rule + + + )} + + ); +}; + export const NoRulesSplash = () => { const { canCreateGrafanaRules, canCreateCloudRules } = useRulesAccess(); const canCreateAnything = canCreateGrafanaRules || canCreateCloudRules; @@ -14,17 +71,13 @@ export const NoRulesSplash = () => { variant="call-to-action" button={ canCreateAnything ? ( - + {canCreateAnything && ( New alert rule )} - {canCreateCloudRules && ( - - New recording rule - - )} + ) : null } diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index ee429b59821..2deedc01267 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -379,6 +379,8 @@ "list-view": { "empty": { "new-alert-rule": "New alert rule", + "new-ds-managed-recording-rule": "New data source-managed recording rule", + "new-grafana-recording-rule": "New Grafana-managed recording rule", "new-recording-rule": "New recording rule", "provisioning": "You can also define rules through file provisioning or Terraform. <2>Learn more" }, From c74a5fcbedba9d0c4717ce0348065da3cb487d4f Mon Sep 17 00:00:00 2001 From: Will Browne Date: Tue, 11 Mar 2025 14:24:20 +0000 Subject: [PATCH 007/141] Chore: Avoid simplejson usage in `xorm` module (#101943) avoid simplejson usage --- pkg/util/xorm/go.mod | 2 -- pkg/util/xorm/go.sum | 4 ---- pkg/util/xorm/xorm_test.go | 7 +++---- 3 files changed, 3 insertions(+), 10 deletions(-) diff --git a/pkg/util/xorm/go.mod b/pkg/util/xorm/go.mod index 310a1bbc263..fb55683da79 100644 --- a/pkg/util/xorm/go.mod +++ b/pkg/util/xorm/go.mod @@ -5,7 +5,6 @@ go 1.23.7 require ( cloud.google.com/go/spanner v1.75.0 github.com/googleapis/go-sql-spanner v1.11.1 - github.com/grafana/grafana v5.4.5+incompatible github.com/mattn/go-sqlite3 v1.14.22 github.com/stretchr/testify v1.10.0 xorm.io/builder v0.3.6 @@ -23,7 +22,6 @@ require ( cloud.google.com/go/monitoring v1.23.0 // indirect github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.2 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 // indirect - github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect diff --git a/pkg/util/xorm/go.sum b/pkg/util/xorm/go.sum index 7febbb40654..f93449ff49b 100644 --- a/pkg/util/xorm/go.sum +++ b/pkg/util/xorm/go.sum @@ -632,8 +632,6 @@ github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kd github.com/apache/arrow/go/v10 v10.0.1/go.mod h1:YvhnlEePVnBS4+0z3fhPfUy7W1Ikj0Ih0vcRo/gZ1M0= github.com/apache/arrow/go/v11 v11.0.0/go.mod h1:Eg5OsL5H+e299f7u5ssuXsuHQVEGC4xei5aX110hRiI= github.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU= -github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= -github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -834,8 +832,6 @@ github.com/googleapis/go-sql-spanner v1.11.1 h1:z3ThtKV5HFvaNv9UGc26+ggS+lS0dsCA github.com/googleapis/go-sql-spanner v1.11.1/go.mod h1:fuA5q4yMS3SZiVfRr5bvksPNk7zUn/irbQW62H/ffZw= github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= -github.com/grafana/grafana v5.4.5+incompatible h1:xNuhSBxLgwDwesuQIAhQu1QCk6tD0TAghKHE36/hxrs= -github.com/grafana/grafana v5.4.5+incompatible/go.mod h1:U8QyUclJHj254BFcuw45p6sg7eeGYX44qn1ShYo5rGE= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3/go.mod h1:o//XUCC/F+yRGJoPO/VU0GSB0f8Nhgmxx0VIRUvaC0w= diff --git a/pkg/util/xorm/xorm_test.go b/pkg/util/xorm/xorm_test.go index 4dee7eb8294..8b8d5aeb03b 100644 --- a/pkg/util/xorm/xorm_test.go +++ b/pkg/util/xorm/xorm_test.go @@ -1,12 +1,11 @@ package xorm import ( + "encoding/json" "testing" _ "github.com/mattn/go-sqlite3" "github.com/stretchr/testify/require" - - "github.com/grafana/grafana/pkg/components/simplejson" ) func TestBasicOperationsWithSqlite(t *testing.T) { @@ -38,7 +37,7 @@ func testBasicOperations(t *testing.T, eng *Engine) { require.NoError(t, err) require.NotZero(t, obj.Id) - obj.Json = simplejson.MustJson([]byte(`{"test": "test", "key": null}`)) + obj.Json = json.RawMessage(`{"test": "test", "key": null}`) _, err = sess.Update(obj) require.NoError(t, err) }) @@ -47,5 +46,5 @@ func testBasicOperations(t *testing.T, eng *Engine) { type TestStruct struct { Id int64 Comment string - Json *simplejson.Json + Json json.RawMessage } From d9cb6e632dfb9c36e9e72ce65959f260a60c69d5 Mon Sep 17 00:00:00 2001 From: Matthew Thorning Date: Tue, 11 Mar 2025 14:31:43 +0000 Subject: [PATCH 008/141] Navigation: Add the `IsNew` badge to the IRM menu item (#101926) add the `IsNew` badge to the IRM menu item --- pkg/services/navtree/navtreeimpl/applinks.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/services/navtree/navtreeimpl/applinks.go b/pkg/services/navtree/navtreeimpl/applinks.go index 4c71eb150ed..c3a385fd67d 100644 --- a/pkg/services/navtree/navtreeimpl/applinks.go +++ b/pkg/services/navtree/navtreeimpl/applinks.go @@ -239,6 +239,9 @@ func (s *ServiceImpl) addPluginToSection(c *contextmodel.ReqContext, treeRoot *n alertsAndIncidentsChildren = append(alertsAndIncidentsChildren, alertingNode) treeRoot.RemoveSection(alertingNode) } + if appLink.Id == "plugin-page-grafana-irm-app" { + appLink.IsNew = true + } alertsAndIncidentsChildren = append(alertsAndIncidentsChildren, appLink) treeRoot.AddSection(&navtree.NavLink{ Text: "Alerts & IRM", From 82610288b1170c0c7565c8e4b9134b8f6593c1eb Mon Sep 17 00:00:00 2001 From: Yulia Shanyrova Date: Tue, 11 Mar 2025 15:51:25 +0100 Subject: [PATCH 009/141] Plugins: Move raiseanissueurl from plugin object to plugin details (#101428) * move raiseanissueurl from plugin object to plugin details * updated the test for PluginDetailsPane; --- public/app/features/plugins/admin/api.ts | 1 + .../components/PluginDetailsPanel.test.tsx | 32 +++++++++++++++++++ .../admin/components/PluginDetailsPanel.tsx | 10 ++++-- public/app/features/plugins/admin/helpers.ts | 5 --- public/app/features/plugins/admin/types.ts | 3 +- 5 files changed, 42 insertions(+), 9 deletions(-) diff --git a/public/app/features/plugins/admin/api.ts b/public/app/features/plugins/admin/api.ts index f79968d47ae..4520989603f 100644 --- a/public/app/features/plugins/admin/api.ts +++ b/public/app/features/plugins/admin/api.ts @@ -39,6 +39,7 @@ export async function getPluginDetails(id: string): Promise=9.0.0', statusContext: 'stable', @@ -118,4 +134,20 @@ describe('PluginDetailsPanel', () => { const panel = screen.getByTestId('plugin-details-panel'); expect(panel).toHaveStyle({ width: '300px' }); }); + + it('should render license, documentation, repository, raise issue links', () => { + render(); + const repositoryLink = screen.getByText('Repository'); + const licenseLink = screen.getByText('License'); + const documentationLink = screen.getByText('Documentation'); + const raiseIssueLink = screen.getByText('Raise issue'); + expect(repositoryLink).toBeInTheDocument(); + expect(repositoryLink).toHaveAttribute('href', 'https://github.com/grafana/test-plugin'); + expect(licenseLink).toBeInTheDocument(); + expect(licenseLink).toHaveAttribute('href', 'https://github.com/grafana/test-plugin/blob/main/LICENSE'); + expect(documentationLink).toBeInTheDocument(); + expect(documentationLink).toHaveAttribute('href', 'https://test-plugin.com/docs'); + expect(raiseIssueLink).toBeInTheDocument(); + expect(raiseIssueLink).toHaveAttribute('href', 'https://github.com/grafana/test-plugin/issues/new'); + }); }); diff --git a/public/app/features/plugins/admin/components/PluginDetailsPanel.tsx b/public/app/features/plugins/admin/components/PluginDetailsPanel.tsx index 7d7612dc814..8440b37c76d 100644 --- a/public/app/features/plugins/admin/components/PluginDetailsPanel.tsx +++ b/public/app/features/plugins/admin/components/PluginDetailsPanel.tsx @@ -99,8 +99,14 @@ export function PluginDetailsPanel(props: Props): React.ReactElement | null { Repository )} - {plugin.raiseAnIssueUrl && ( - + {plugin.details?.raiseAnIssueUrl && ( + Raise an issue )} diff --git a/public/app/features/plugins/admin/helpers.ts b/public/app/features/plugins/admin/helpers.ts index 28386a73cbd..8b0bfe80cfd 100644 --- a/public/app/features/plugins/admin/helpers.ts +++ b/public/app/features/plugins/admin/helpers.ts @@ -122,7 +122,6 @@ export function mapRemoteToCatalog(plugin: RemotePlugin, error?: PluginError): C versionSignatureType, versionSignedByOrgName, url, - raiseAnIssueUrl, } = plugin; const isDisabled = !!error || isDisabledSecretsPlugin(typeCode); @@ -161,7 +160,6 @@ export function mapRemoteToCatalog(plugin: RemotePlugin, error?: PluginError): C isFullyInstalled: isDisabled, latestVersion: plugin.version, url, - raiseAnIssueUrl, }; } @@ -178,7 +176,6 @@ export function mapLocalToCatalog(plugin: LocalPlugin, error?: PluginError): Cat hasUpdate, accessControl, angularDetected, - raiseAnIssueUrl, } = plugin; const isDisabled = !!error || isDisabledSecretsPlugin(type); @@ -213,7 +210,6 @@ export function mapLocalToCatalog(plugin: LocalPlugin, error?: PluginError): Cat isFullyInstalled: true, iam: plugin.iam, latestVersion: plugin.latestVersion, - raiseAnIssueUrl, }; } @@ -278,7 +274,6 @@ export function mapToCatalogPlugin(local?: LocalPlugin, remote?: RemotePlugin, e iam: local?.iam, latestVersion: local?.latestVersion || remote?.version || '', url: remote?.url || '', - raiseAnIssueUrl: remote?.raiseAnIssueUrl || local?.raiseAnIssueUrl, }; } diff --git a/public/app/features/plugins/admin/types.ts b/public/app/features/plugins/admin/types.ts index 96e1d2451d2..4476c824216 100644 --- a/public/app/features/plugins/admin/types.ts +++ b/public/app/features/plugins/admin/types.ts @@ -65,7 +65,6 @@ export interface CatalogPlugin extends WithAccessControlMetadata { iam?: IdentityAccessManagement; isProvisioned?: boolean; url?: string; - raiseAnIssueUrl?: string; } export interface CatalogPluginDetails { @@ -83,6 +82,7 @@ export interface CatalogPluginDetails { lastCommitDate?: string; licenseUrl?: string; documentationUrl?: string; + raiseAnIssueUrl?: string; signatureType?: PluginSignatureType; signature?: PluginSignatureStatus; } @@ -197,7 +197,6 @@ export type LocalPlugin = WithAccessControlMetadata & { dependencies: PluginDependencies; angularDetected: boolean; iam?: IdentityAccessManagement; - raiseAnIssueUrl?: string; }; interface IdentityAccessManagement { From c8c17683ed9fdca0270086504db3258edd1b2998 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laura=20Fern=C3=A1ndez?= Date: Tue, 11 Mar 2025 15:55:30 +0100 Subject: [PATCH 010/141] ThemeDemo: Use `Combobox` instead of `Select` (#101947) --- packages/grafana-ui/src/components/ThemeDemos/ThemeDemo.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/grafana-ui/src/components/ThemeDemos/ThemeDemo.tsx b/packages/grafana-ui/src/components/ThemeDemos/ThemeDemo.tsx index ba7146bcf23..0e6315899df 100644 --- a/packages/grafana-ui/src/components/ThemeDemos/ThemeDemo.tsx +++ b/packages/grafana-ui/src/components/ThemeDemos/ThemeDemo.tsx @@ -9,6 +9,7 @@ import { useTheme2 } from '../../themes/ThemeContext'; import { allButtonVariants, Button } from '../Button'; import { Card } from '../Card/Card'; import { CollapsableSection } from '../Collapse/CollapsableSection'; +import { Combobox } from '../Combobox/Combobox'; import { Field } from '../Forms/Field'; import { InlineField } from '../Forms/InlineField'; import { InlineFieldRow } from '../Forms/InlineFieldRow'; @@ -17,7 +18,6 @@ import { Icon } from '../Icon/Icon'; import { Input } from '../Input/Input'; import { BackgroundColor, BorderColor, Box, BoxShadow } from '../Layout/Box/Box'; import { Stack } from '../Layout/Stack/Stack'; -import { Select } from '../Select/Select'; import { Switch } from '../Switch/Switch'; import { Text, TextProps } from '../Text/Text'; @@ -150,8 +150,8 @@ export const ThemeDemo = () => { - - {}} /> + {}} /> ); From 5bfe046da957d32cfd57f0dd50d27c7eb28befef Mon Sep 17 00:00:00 2001 From: Pepe Cano <825430+ppcano@users.noreply.github.com> Date: Tue, 11 Mar 2025 15:58:25 +0100 Subject: [PATCH 013/141] docs(alerting): clarify behaviour when provisioning the policy tree (#101937) --- .../export-alerting-resources/index.md | 6 +----- .../file-provisioning/index.md | 6 +----- .../terraform-provisioning/index.md | 6 +----- docs/sources/shared/alerts/alerting_provisioning.md | 2 ++ docs/sources/shared/alerts/warning-provisioning-tree.md | 9 +++++++++ 5 files changed, 14 insertions(+), 15 deletions(-) create mode 100644 docs/sources/shared/alerts/warning-provisioning-tree.md diff --git a/docs/sources/alerting/set-up/provision-alerting-resources/export-alerting-resources/index.md b/docs/sources/alerting/set-up/provision-alerting-resources/export-alerting-resources/index.md index 3a6f3576452..a7fac56ebe9 100644 --- a/docs/sources/alerting/set-up/provision-alerting-resources/export-alerting-resources/index.md +++ b/docs/sources/alerting/set-up/provision-alerting-resources/export-alerting-resources/index.md @@ -197,11 +197,7 @@ However, you can export it by manually copying the content and name of the notif All notification policies are provisioned through a single resource: the root of the notification policy tree. -{{% admonition type="warning" %}} - -Since the policy tree is a single resource, provisioning it overwrites a policy tree created through any other means. - -{{< /admonition >}} +{{< docs/shared lookup="alerts/warning-provisioning-tree.md" source="grafana" version="" >}} To export the notification policy tree from the Grafana UI, complete the following steps. diff --git a/docs/sources/alerting/set-up/provision-alerting-resources/file-provisioning/index.md b/docs/sources/alerting/set-up/provision-alerting-resources/file-provisioning/index.md index 850600cc074..f7cb4a0e174 100644 --- a/docs/sources/alerting/set-up/provision-alerting-resources/file-provisioning/index.md +++ b/docs/sources/alerting/set-up/provision-alerting-resources/file-provisioning/index.md @@ -702,11 +702,7 @@ Create or reset the notification policy tree using provisioning files in your Gr In Grafana, the entire notification policy tree is considered a single, large resource. Add new specific policies as sub-policies under the root policy. Since specific policies may depend on each other, you cannot provision subsets of the policy tree; the entire tree must be defined in a single place. -{{% admonition type="warning" %}} - -Since the policy tree is a single resource, provisioning it will overwrite a policy tree created through any other means. - -{{< /admonition >}} +{{< docs/shared lookup="alerts/warning-provisioning-tree.md" source="grafana" version="" >}} 1. Find the notification policy tree in Grafana. 1. [Export](ref:export_policies) and download a provisioning file for your notification policy tree. diff --git a/docs/sources/alerting/set-up/provision-alerting-resources/terraform-provisioning/index.md b/docs/sources/alerting/set-up/provision-alerting-resources/terraform-provisioning/index.md index 0821f5ec0b4..2a5f5d97211 100644 --- a/docs/sources/alerting/set-up/provision-alerting-resources/terraform-provisioning/index.md +++ b/docs/sources/alerting/set-up/provision-alerting-resources/terraform-provisioning/index.md @@ -341,11 +341,7 @@ In this section, we'll create Terraform configurations for each alerting resourc [Notification policies](ref:notification-policy) defines how to route alert instances to your contact points. -{{% admonition type="warning" %}} - -Since the policy tree is a single resource, provisioning the `grafana_notification_policy` resource will overwrite a policy tree created through any other means. - -{{< /admonition >}} +{{< docs/shared lookup="alerts/warning-provisioning-tree.md" source="grafana" version="" >}} 1. Find the default notification policy tree. Alternatively, consider writing the resource in code as demonstrated in the example below. diff --git a/docs/sources/shared/alerts/alerting_provisioning.md b/docs/sources/shared/alerts/alerting_provisioning.md index 134bd086f97..40d3c72622d 100644 --- a/docs/sources/shared/alerts/alerting_provisioning.md +++ b/docs/sources/shared/alerts/alerting_provisioning.md @@ -1386,6 +1386,8 @@ Status: Conflict ### Sets the notification policy tree. (_RoutePutPolicyTree_) +{{< docs/shared lookup="alerts/warning-provisioning-tree.md" source="grafana" version="" >}} + ``` PUT /api/v1/provisioning/policies ``` diff --git a/docs/sources/shared/alerts/warning-provisioning-tree.md b/docs/sources/shared/alerts/warning-provisioning-tree.md new file mode 100644 index 00000000000..36ecf9b0fa6 --- /dev/null +++ b/docs/sources/shared/alerts/warning-provisioning-tree.md @@ -0,0 +1,9 @@ +--- +title: 'Warning Provisioning Tree' +--- + +{{% admonition type="warning" %}} + +Since the policy tree is a single resource, provisioning it will overwrite all policies in the notification policy tree. However, it does not affect internal policies created when alert rules directly select a contact point. + +{{< /admonition >}} From f6f6ae449615cfb866aed4afe53dc4a0cde83f25 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Tue, 11 Mar 2025 16:27:17 +0100 Subject: [PATCH 014/141] Zanzana: Update docs with subresources description (#101948) * Zanzana: Update docs with subresources description * clarify resource name --- pkg/services/authz/zanzana/schema/README.md | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/pkg/services/authz/zanzana/schema/README.md b/pkg/services/authz/zanzana/schema/README.md index 4462e0f18e0..3a41e96a11a 100644 --- a/pkg/services/authz/zanzana/schema/README.md +++ b/pkg/services/authz/zanzana/schema/README.md @@ -5,8 +5,8 @@ Here's some notes about [OpenFGA authorization model](https://openfga.dev/docs/m ## GroupResource level permissions A relation to a group_resource object grants access to all objects of the GroupResource. -They take the form of `{ “user”: “user:1”, relation: “read”, object:”group_resource:dashboard.grafana.app/dashboard” }`. This -example would grant `user:1` access to all `dashboard.grafana.app/dashboard` in the namespace. +They take the form of `{ “user”: “user:1”, relation: “read”, object:”group_resource:dashboard.grafana.app/dashboards” }`. This +example would grant `user:1` access to all `dashboard.grafana.app/dashboards` in the namespace. ## Folder level permissions @@ -20,11 +20,19 @@ This context holds all GroupResources in a list e.g. `{ "group_resources": ["das ## Resource level permissions -Most of our resource should use the generic resource type. +Most of our resource should use the generic resource type. -To grant a user direct access to a specific resource we store `{ “user”: “user:1”, relation: “read”, object:”resource:dashboard.grafana.app/dashboard/” }` with additional context. +To grant a user direct access to a specific resource we store `{ “user”: “user:1”, relation: “read”, object:”resource:dashboard.grafana.app/dashboards/” }` with additional context. This context store the GroupResource. `{ "group_resource": "dashboard.grafana.app/dashboards" }`. This is required so we can filter them out for list requests. +## Subresources + +Subresources enable more granular permissions for the resources. Example might be access to public dashboards or access to dashboard settings. + +To grant a user access to the subresource of the specific resource we store following tuple: `{ “user”: “user:1”, relation: “read”, object:”resource:dashboard.grafana.app/dashboards//” }` with additional context `{ "group_resource": "dashboard.grafana.app/dashboards/" }` + +It's also possible to grant user access to all subresources for specific resource type. It can be done with following tuple: `{ “user”: “user:1”, relation: “read”, object:”resource:dashboard.grafana.app/dashboards/” }`. + ## Managed permissions In the RBAC model managed permissions stored as a special "managed" role permissions. OpenFGA model allows to assign permissions directly to users, so it produces following tuples: @@ -58,4 +66,3 @@ type folder ``` According to the schema, user can get `read` access to folder if it has `read` relation granted directly to the folder or its parent folders. - From 13d1f0259762a9fb212b252ee7463cae606b008f Mon Sep 17 00:00:00 2001 From: Esteban Beltran Date: Tue, 11 Mar 2025 09:40:15 -0600 Subject: [PATCH 015/141] Frontend Sandbox: Do not perform authenticated queries for non authenticated users (#101946) * Do not perform authenticated queries for non authenticated users * Empty commit --- .../sandbox/sandbox_plugin_loader_registry.test.ts | 9 +++++++++ .../plugins/sandbox/sandbox_plugin_loader_registry.ts | 5 +++++ 2 files changed, 14 insertions(+) diff --git a/public/app/features/plugins/sandbox/sandbox_plugin_loader_registry.test.ts b/public/app/features/plugins/sandbox/sandbox_plugin_loader_registry.test.ts index 8138baf6b6f..9e1f8a11b0d 100644 --- a/public/app/features/plugins/sandbox/sandbox_plugin_loader_registry.test.ts +++ b/public/app/features/plugins/sandbox/sandbox_plugin_loader_registry.test.ts @@ -1,5 +1,6 @@ import { PluginMeta, PluginSignatureStatus, PluginSignatureType } from '@grafana/data'; import { config } from '@grafana/runtime'; +import { contextSrv } from 'app/core/services/context_srv'; import { getPluginDetails } from '../admin/api'; import { CatalogPluginDetails } from '../admin/types'; @@ -30,6 +31,7 @@ jest.mock('../admin/api', () => ({ const getPluginSettingsMock = jest.mocked(getPluginSettings); const getPluginDetailsMock = jest.mocked(getPluginDetails); +const mockContextSrv = jest.mocked(contextSrv); const fakePluginSettings: PluginMeta = { id: 'test-plugin', @@ -45,6 +47,7 @@ describe('Sandbox eligibility checks', () => { jest.clearAllMocks(); getPluginDetailsMock.mockReset(); getPluginSettingsMock.mockReset(); + mockContextSrv.isSignedIn = true; // restore default check setSandboxEnabledCheck(isPluginFrontendSandboxEnabled); @@ -63,6 +66,12 @@ describe('Sandbox eligibility checks', () => { expect(result).toBe(false); }); + test('isPluginFrontendSandboxEligible returns false for unsigned users', async () => { + mockContextSrv.isSignedIn = false; + const isEligible = await isPluginFrontendSandboxEligible({ pluginId: 'test-plugin' }); + expect(isEligible).toBe(false); + }); + test('shouldLoadPluginInFrontendSandbox returns false when feature toggle is off', async () => { config.featureToggles.pluginsFrontendSandbox = false; const result = await shouldLoadPluginInFrontendSandbox({ pluginId: 'test-plugin' }); diff --git a/public/app/features/plugins/sandbox/sandbox_plugin_loader_registry.ts b/public/app/features/plugins/sandbox/sandbox_plugin_loader_registry.ts index 2dbef3be1e1..6001734a905 100644 --- a/public/app/features/plugins/sandbox/sandbox_plugin_loader_registry.ts +++ b/public/app/features/plugins/sandbox/sandbox_plugin_loader_registry.ts @@ -1,5 +1,6 @@ import { PluginSignatureType } from '@grafana/data'; import { config } from '@grafana/runtime'; +import { contextSrv } from 'app/core/core'; import { getPluginDetails } from '../admin/api'; import { getPluginSettings } from '../pluginSettings'; @@ -62,6 +63,10 @@ export async function isPluginFrontendSandboxEligible({ return false; } + if (!contextSrv.isSignedIn) { + return false; + } + // grafana signature and internal plugins are not allowed in the sandbox return isPluginSignatureEligibleForSandbox({ pluginId }); } From 59d87fe3f1c0dd59353e6176bf55bd4a3c37c0bf Mon Sep 17 00:00:00 2001 From: owensmallwood Date: Tue, 11 Mar 2025 10:15:58 -0600 Subject: [PATCH 016/141] Unified Storage: Use match all query instead of wildcard for not-in requirement query (#101953) use match all query insteaed of wildcard --- pkg/storage/unified/search/bleve.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/storage/unified/search/bleve.go b/pkg/storage/unified/search/bleve.go index 630c9832315..322abff0516 100644 --- a/pkg/storage/unified/search/bleve.go +++ b/pkg/storage/unified/search/bleve.go @@ -803,7 +803,7 @@ func requirementQuery(req *resource.Requirement, prefix string) (query.Query, *r boolQuery.AddMustNot(mustNotQueries...) // must still have a value - notEmptyQuery := bleve.NewWildcardQuery("*") + notEmptyQuery := bleve.NewMatchAllQuery() boolQuery.AddMust(notEmptyQuery) return boolQuery, nil From 7e4beb2074ae23e083312218c1177af35c815d80 Mon Sep 17 00:00:00 2001 From: Yuri Tseretyan Date: Tue, 11 Mar 2025 12:40:44 -0400 Subject: [PATCH 017/141] Alerting: API to return deleted rules (#101429) --- pkg/services/ngalert/api/api_ruler.go | 20 ++++ pkg/services/ngalert/api/persist.go | 1 + pkg/services/ngalert/store/alert_rule.go | 34 +++++++ pkg/services/ngalert/store/alert_rule_test.go | 57 +++++++++++ pkg/services/ngalert/tests/fakes/rules.go | 13 +++ pkg/tests/api/alerting/api_ruler_test.go | 98 +++++++++++++++++++ pkg/tests/api/alerting/testing.go | 10 ++ 7 files changed, 233 insertions(+) diff --git a/pkg/services/ngalert/api/api_ruler.go b/pkg/services/ngalert/api/api_ruler.go index 2d88ae849e1..e5ece7154e3 100644 --- a/pkg/services/ngalert/api/api_ruler.go +++ b/pkg/services/ngalert/api/api_ruler.go @@ -261,6 +261,26 @@ func (srv RulerSrv) RouteGetRulesGroupConfig(c *contextmodel.ReqContext, namespa // RouteGetRulesConfig returns all alert rules that are available to the current user func (srv RulerSrv) RouteGetRulesConfig(c *contextmodel.ReqContext) response.Response { + if strings.ToLower(c.Query("deleted")) == "true" { + if !srv.featureManager.IsEnabledGlobally(featuremgmt.FlagAlertRuleRestore) { + return ErrResp(http.StatusBadRequest, errors.New("restore of deleted rules is not enabled"), "") + } + if !c.SignedInUser.HasRole(identity.RoleAdmin) { + return ErrResp(http.StatusForbidden, errors.New("only admins can get deleted rules"), "") + } + rules, err := srv.store.ListDeletedRules(c.Req.Context(), c.SignedInUser.GetOrgID()) + if err != nil { + return ErrResp(http.StatusInternalServerError, err, "failed to get deleted rules") + } + result := apimodels.NamespaceConfigResponse{} + if len(rules) > 0 { + result[""] = []apimodels.GettableRuleGroupConfig{ + toGettableRuleGroupConfig("", rules, map[string]ngmodels.Provenance{}, srv.resolveUserIdToNameFn(c.Req.Context())), + } + } + return response.JSON(http.StatusOK, result) + } + namespaceMap, err := srv.store.GetUserVisibleNamespaces(c.Req.Context(), c.SignedInUser.GetOrgID(), c.SignedInUser) if err != nil { return ErrResp(http.StatusInternalServerError, err, "failed to get namespaces visible to the user") diff --git a/pkg/services/ngalert/api/persist.go b/pkg/services/ngalert/api/persist.go index b169be59c07..30b79b666dd 100644 --- a/pkg/services/ngalert/api/persist.go +++ b/pkg/services/ngalert/api/persist.go @@ -23,6 +23,7 @@ type RuleStore interface { GetAlertRuleByUID(ctx context.Context, query *ngmodels.GetAlertRuleByUIDQuery) (*ngmodels.AlertRule, error) GetAlertRulesGroupByRuleUID(ctx context.Context, query *ngmodels.GetAlertRulesGroupByRuleUIDQuery) ([]*ngmodels.AlertRule, error) ListAlertRules(ctx context.Context, query *ngmodels.ListAlertRulesQuery) (ngmodels.RulesGroup, error) + ListDeletedRules(ctx context.Context, orgID int64) ([]*ngmodels.AlertRule, error) // InsertAlertRules will insert all alert rules passed into the function // and return the map of uuid to id. diff --git a/pkg/services/ngalert/store/alert_rule.go b/pkg/services/ngalert/store/alert_rule.go index 47ab409c63a..d4fb0202a78 100644 --- a/pkg/services/ngalert/store/alert_rule.go +++ b/pkg/services/ngalert/store/alert_rule.go @@ -236,6 +236,40 @@ func (st DBstore) GetAlertRuleVersions(ctx context.Context, orgID int64, guid st return alertRules, nil } +// ListDeletedRules retrieves a list of deleted alert rules for the specified organization ID from the database. +// It ensures that only the latest version of each rule is included and filters out invalid or duplicated versions. +// Returns a slice of *models.AlertRule or an error if the operation fails. +func (st DBstore) ListDeletedRules(ctx context.Context, orgID int64) ([]*ngmodels.AlertRule, error) { + alertRules := make([]*ngmodels.AlertRule, 0) + err := st.SQLStore.WithDbSession(ctx, func(sess *db.Session) error { + // take only the latest versions of each rule by GUID + rows, err := sess.Table(alertRuleVersion{}).Where("rule_org_id = ? AND rule_uid = ''", orgID).Rows(alertRuleVersion{}) + if err != nil { + return err + } + // Deserialize each rule separately in case any of them contain invalid JSON. + for rows.Next() { + rule := new(alertRuleVersion) + err = rows.Scan(rule) + if err != nil { + st.Logger.Error("Invalid rule version found in DB store, ignoring it", "func", "GetAlertRuleVersions", "error", err) + continue + } + converted, err := alertRuleToModelsAlertRule(alertRuleVersionToAlertRule(*rule), st.Logger) + if err != nil { + st.Logger.Error("Invalid rule found in DB store, cannot convert, ignoring it", "func", "GetAlertRuleVersions", "error", err, "version_id", rule.ID) + continue + } + alertRules = append(alertRules, &converted) + } + return nil + }) + if err != nil { + return nil, err + } + return alertRules, nil +} + // GetRuleByID retrieves models.AlertRule by ID. // It returns models.ErrAlertRuleNotFound if no alert rule is found for the provided ID. func (st DBstore) GetRuleByID(ctx context.Context, query ngmodels.GetAlertRuleByIDQuery) (result *ngmodels.AlertRule, err error) { diff --git a/pkg/services/ngalert/store/alert_rule_test.go b/pkg/services/ngalert/store/alert_rule_test.go index 2b6d7d76971..c3e94e7e806 100644 --- a/pkg/services/ngalert/store/alert_rule_test.go +++ b/pkg/services/ngalert/store/alert_rule_test.go @@ -1954,6 +1954,63 @@ func TestIntegration_ListAlertRules(t *testing.T) { }) } +func TestIntegration_ListDeletedRules(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test") + } + cfg := setting.NewCfg() + cfg.UnifiedAlerting = setting.UnifiedAlertingSettings{ + BaseInterval: 1 * time.Second, + RuleVersionRecordLimit: -1, + } + sqlStore := db.InitTestDB(t) + folderService := setupFolderService(t, sqlStore, cfg, featuremgmt.WithFeatures()) + b := &fakeBus{} + store := createTestStore(sqlStore, folderService, &logtest.Fake{}, cfg.UnifiedAlerting, b) + store.FeatureToggles = featuremgmt.WithFeatures(featuremgmt.FlagAlertRuleRestore) + + orgID := int64(1) + gen := models.RuleGen + gen = gen.With(gen.WithIntervalMatching(store.Cfg.BaseInterval), gen.WithOrgID(orgID)) + + result, err := store.InsertAlertRules(context.Background(), &models.AlertingUserUID, []models.AlertRule{gen.Generate()}) + require.NoError(t, err) + rule, err := store.GetAlertRuleByUID(context.Background(), &models.GetAlertRuleByUIDQuery{UID: result[0].UID}) + require.NoError(t, err) + + rule2 := models.CopyRule(rule, gen.WithTitle(util.GenerateShortUID())) + err = store.UpdateAlertRules(context.Background(), &models.AlertingUserUID, []models.UpdateRule{ + { + Existing: rule, + New: *rule2, + }, + }) + require.NoError(t, err) + rule2, err = store.GetAlertRuleByUID(context.Background(), &models.GetAlertRuleByUIDQuery{UID: result[0].UID}) + require.NoError(t, err) + + versions, err := store.GetAlertRuleVersions(context.Background(), orgID, rule.GUID) + require.NoError(t, err) + require.Len(t, versions, 2) + + t.Run("should not return if rule is not deleted", func(t *testing.T) { + list, err := store.ListDeletedRules(context.Background(), orgID) + require.NoError(t, err) + require.Empty(t, list) + }) + + err = store.DeleteAlertRulesByUID(context.Background(), orgID, &models.AlertingUserUID, rule.UID) + require.NoError(t, err) + + t.Run("should return the last deleted rule", func(t *testing.T) { + list, err := store.ListDeletedRules(context.Background(), orgID) + require.NoError(t, err) + require.Len(t, list, 1) + assert.Empty(t, list[0].UID) + assert.Empty(t, rule2.Diff(list[0], "ID", "UID", "DashboardUID", "PanelID")) + }) +} + func createTestStore( sqlStore db.DB, folderService folder.Service, diff --git a/pkg/services/ngalert/tests/fakes/rules.go b/pkg/services/ngalert/tests/fakes/rules.go index 10bc282e8a8..24d9959a0e2 100644 --- a/pkg/services/ngalert/tests/fakes/rules.go +++ b/pkg/services/ngalert/tests/fakes/rules.go @@ -24,6 +24,7 @@ type RuleStore struct { // OrgID -> RuleGroup -> Namespace -> Rules Rules map[int64][]*models.AlertRule History map[string][]*models.AlertRule + Deleted map[int64][]*models.AlertRule Hook func(cmd any) error // use Hook if you need to intercept some query and return an error RecordedOps []any Folders map[int64][]*folder.Folder @@ -460,3 +461,15 @@ func (f *RuleStore) GetAlertRuleVersions(_ context.Context, orgID int64, guid st return f.History[guid], nil } + +func (f *RuleStore) ListDeletedRules(_ context.Context, orgID int64) ([]*models.AlertRule, error) { + f.mtx.Lock() + defer f.mtx.Unlock() + defer func() { + f.RecordedOps = append(f.RecordedOps, GenericRecordedQuery{Name: "ListDeletedRules", Params: []any{orgID}}) + }() + if err := f.Hook(orgID); err != nil { + return nil, err + } + return f.Deleted[orgID], nil +} diff --git a/pkg/tests/api/alerting/api_ruler_test.go b/pkg/tests/api/alerting/api_ruler_test.go index 9f03a070f53..3cdf2d2a20f 100644 --- a/pkg/tests/api/alerting/api_ruler_test.go +++ b/pkg/tests/api/alerting/api_ruler_test.go @@ -6,6 +6,7 @@ import ( "encoding/json" "fmt" "io" + "maps" "math/rand" "net/http" "path" @@ -15,6 +16,7 @@ import ( "time" "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" "github.com/grafana/grafana-plugin-sdk-go/data" "github.com/prometheus/alertmanager/pkg/labels" @@ -4645,6 +4647,102 @@ func TestIntegrationRuleVersions(t *testing.T) { }) } +func TestIntegrationRuleSoftDelete(t *testing.T) { + testinfra.SQLiteIntegrationTest(t) + + // Setup Grafana and its Database + dir, p := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{ + DisableLegacyAlerting: true, + EnableUnifiedAlerting: true, + EnableQuota: true, + DisableAnonymous: true, + AppModeProduction: true, + EnableFeatureToggles: []string{featuremgmt.FlagAlertRuleRestore}, + }) + + grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, p) + + createUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{ + DefaultOrgRole: string(org.RoleAdmin), + Password: "admin", + Login: "admin", + }) + + createUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{ + DefaultOrgRole: string(org.RoleEditor), + Password: "password", + Login: "editor", + }) + + adminClient := newAlertingApiClient(grafanaListedAddr, "admin", "admin") + editorClient := newAlertingApiClient(grafanaListedAddr, "editor", "password") + + deleted, status, data := adminClient.GetDeletedRulesWithStatus(t) + requireStatusCode(t, http.StatusOK, status, data) + require.Emptyf(t, deleted, "Expected empty list of deleted rules, got %v", deleted) + + // Create the namespace we'll save our alerts to. + adminClient.CreateFolder(t, "folder1", "folder1") + + var group apimodels.RuleGroupConfigResponse + { // create rules and some history + postGroupRaw, err := testData.ReadFile(path.Join("test-data", "rulegroup-1-post.json")) + require.NoError(t, err) + var group1 apimodels.PostableRuleGroupConfig + require.NoError(t, json.Unmarshal(postGroupRaw, &group1)) + + // Create rule under folder1 + response := adminClient.PostRulesGroup(t, "folder1", &group1) + require.NotEmptyf(t, response.Created, "Expected created to be set") + + // create some versions of the rule + for i := 0; i < 3; i++ { + groups, status := adminClient.GetRulesGroup(t, "folder1", group1.Name) + require.Equal(t, http.StatusAccepted, status) + group1 = convertGettableRuleGroupToPostable(groups.GettableRuleGroupConfig) + group1.Rules[0].Annotations[util.GenerateShortUID()] = util.GenerateShortUID() + _ = adminClient.PostRulesGroup(t, "folder1", &group1) + } + group, status = adminClient.GetRulesGroup(t, "folder1", group1.Name) + require.Equal(t, http.StatusAccepted, status) + } + + // deleting group by using editor user + status, body := editorClient.DeleteRulesGroup(t, "folder1", group.Name) + require.Equalf(t, http.StatusAccepted, status, "failed to delete group. Response: %s", body) + + t.Run("should see deleted rules", func(t *testing.T) { + rules, status, raw := adminClient.GetDeletedRulesWithStatus(t) + requireStatusCode(t, http.StatusOK, status, raw) + + require.Containsf(t, rules, "", "All rules should be in empty folder but got %v", slices.Collect(maps.Keys(rules))) + require.Lenf(t, rules[""], 1, "All deleted rules should be in single group but got %d", len(rules[""])) + require.Equalf(t, "", rules[""][0].Name, "All deleted rules should be in empty group but got %v", rules[""][0].Name) + + require.Len(t, rules[""][0].Rules, len(group.Rules)) + require.Empty(t, cmp.Diff(group.Rules, rules[""][0].Rules, cmpopts.IgnoreFields(apimodels.GettableGrafanaRule{}, "UID", "Version", "Updated", "UpdatedBy"))) + rule := rules[""][0].Rules[0] + require.Equalf(t, "editor", rule.GrafanaManagedAlert.UpdatedBy.Name, "Field 'UpdatedBy' should be set by editor but got %v ", rule.GrafanaManagedAlert.UpdatedBy) + }) + + t.Run("only admin should be able to see deleted rules", func(t *testing.T) { + t.Run("editor", func(t *testing.T) { + _, status, raw := editorClient.GetDeletedRulesWithStatus(t) + requireStatusCode(t, http.StatusForbidden, status, raw) + }) + t.Run("viewer", func(t *testing.T) { + createUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{ + DefaultOrgRole: string(org.RoleViewer), + Password: "password", + Login: "viewer", + }) + client := newAlertingApiClient(grafanaListedAddr, "viewer", "password") + _, status, raw := client.GetDeletedRulesWithStatus(t) + requireStatusCode(t, http.StatusForbidden, status, raw) + }) + }) +} + func newTestingRuleConfig(t *testing.T) apimodels.PostableRuleGroupConfig { interval, err := model.ParseDuration("1m") require.NoError(t, err) diff --git a/pkg/tests/api/alerting/testing.go b/pkg/tests/api/alerting/testing.go index f613d7a5547..25a25fd4e58 100644 --- a/pkg/tests/api/alerting/testing.go +++ b/pkg/tests/api/alerting/testing.go @@ -647,6 +647,16 @@ func (a apiClient) GetAllRulesWithStatus(t *testing.T) (apimodels.NamespaceConfi return result, resp.StatusCode, b } +func (a apiClient) GetDeletedRulesWithStatus(t *testing.T) (apimodels.NamespaceConfigResponse, int, string) { + t.Helper() + req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/api/ruler/grafana/api/v1/rules", a.url), nil) + require.NoError(t, err) + q := req.URL.Query() + q.Add("deleted", "true") + req.URL.RawQuery = q.Encode() + return sendRequestJSON[apimodels.NamespaceConfigResponse](t, req, http.StatusOK) +} + func (a apiClient) ExportRulesWithStatus(t *testing.T, params *apimodels.AlertRulesExportParameters) (int, string) { t.Helper() u, err := url.Parse(fmt.Sprintf("%s/api/ruler/grafana/api/v1/export/rules", a.url)) From 42ae2fb02695281956e3787f7dbaa0cbb58e6d08 Mon Sep 17 00:00:00 2001 From: Will Assis <35489495+gassiss@users.noreply.github.com> Date: Tue, 11 Mar 2025 13:56:34 -0300 Subject: [PATCH 018/141] fix(unified-storage): add missing dashboard legacy_id when in legacy read mode (#101944) * add missing dashboard legacy_id when in modes 0-2 --- .../dashboard/legacysearcher/search_client.go | 8 +++++- .../legacysearcher/search_client_test.go | 27 ++++++++++++++++--- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/pkg/registry/apis/dashboard/legacysearcher/search_client.go b/pkg/registry/apis/dashboard/legacysearcher/search_client.go index 1f98dc3a1e1..c80919971c6 100644 --- a/pkg/registry/apis/dashboard/legacysearcher/search_client.go +++ b/pkg/registry/apis/dashboard/legacysearcher/search_client.go @@ -213,6 +213,11 @@ func (c *DashboardSearchClient) Search(ctx context.Context, req *resource.Resour searchFields.Field(resource.SEARCH_FIELD_TITLE), searchFields.Field(resource.SEARCH_FIELD_FOLDER), searchFields.Field(resource.SEARCH_FIELD_TAGS), + { + Name: unisearch.DASHBOARD_LEGACY_ID, + Type: resource.ResourceTableColumnDefinition_INT64, + Description: "Deprecated legacy id of the dashboard", + }, { Name: sortByField, Type: resource.ResourceTableColumnDefinition_INT64, @@ -270,7 +275,7 @@ func (c *DashboardSearchClient) Search(ctx context.Context, req *resource.Resour list.Results.Rows = append(list.Results.Rows, &resource.ResourceTableRow{ Key: getResourceKey(dashboard, req.Options.Key.Namespace), - Cells: [][]byte{[]byte(dashboard.Title), []byte(dashboard.FolderUID), tags, []byte(strconv.FormatInt(dashboard.SortMeta, 10))}, + Cells: [][]byte{[]byte(dashboard.Title), []byte(dashboard.FolderUID), tags, []byte(strconv.FormatInt(dashboard.ID, 10)), []byte(strconv.FormatInt(dashboard.SortMeta, 10))}, }) } @@ -306,6 +311,7 @@ func formatQueryResult(res []dashboards.DashboardSearchProjection) []*dashboards hit, exists := hits[key] if !exists { hit = &dashboards.DashboardSearchProjection{ + ID: item.ID, UID: item.UID, Title: item.Title, FolderUID: item.FolderUID, diff --git a/pkg/registry/apis/dashboard/legacysearcher/search_client_test.go b/pkg/registry/apis/dashboard/legacysearcher/search_client_test.go index 4b56d2e9b6f..ffbc973ba07 100644 --- a/pkg/registry/apis/dashboard/legacysearcher/search_client_test.go +++ b/pkg/registry/apis/dashboard/legacysearcher/search_client_test.go @@ -43,8 +43,8 @@ func TestDashboardSearchClient_Search(t *testing.T) { Type: "dash-db", // should set type based off of key Sort: sorter, }).Return([]dashboards.DashboardSearchProjection{ - {UID: "uid", Title: "Test Dashboard", FolderUID: "folder1", Term: "term"}, - {UID: "uid2", Title: "Test Dashboard2", FolderUID: "folder2"}, + {ID: 1, UID: "uid", Title: "Test Dashboard", FolderUID: "folder1", Term: "term"}, + {ID: 2, UID: "uid2", Title: "Test Dashboard2", FolderUID: "folder2"}, }, nil).Once() req := &resource.ResourceSearchRequest{ @@ -72,6 +72,11 @@ func TestDashboardSearchClient_Search(t *testing.T) { searchFields.Field(resource.SEARCH_FIELD_TITLE), searchFields.Field(resource.SEARCH_FIELD_FOLDER), searchFields.Field(resource.SEARCH_FIELD_TAGS), + { + Name: unisearch.DASHBOARD_LEGACY_ID, + Type: resource.ResourceTableColumnDefinition_INT64, + Description: "Deprecated legacy id of the dashboard", + }, { Name: "", // sort by should be empty if title is what we sorted by Type: resource.ResourceTableColumnDefinition_INT64, @@ -88,6 +93,7 @@ func TestDashboardSearchClient_Search(t *testing.T) { []byte("Test Dashboard"), []byte("folder1"), tags, + []byte("1"), []byte(strconv.FormatInt(0, 10)), }, }, @@ -101,6 +107,7 @@ func TestDashboardSearchClient_Search(t *testing.T) { []byte("Test Dashboard2"), []byte("folder2"), emptyTags, + []byte("2"), []byte(strconv.FormatInt(0, 10)), }, }, @@ -120,7 +127,7 @@ func TestDashboardSearchClient_Search(t *testing.T) { Type: "dash-db", Sort: sortOptionAsc, }).Return([]dashboards.DashboardSearchProjection{ - {UID: "uid", Title: "Test Dashboard", FolderUID: "folder", SortMeta: int64(50)}, + {ID: 1, UID: "uid", Title: "Test Dashboard", FolderUID: "folder", SortMeta: int64(50)}, }, nil).Once() req := &resource.ResourceSearchRequest{ @@ -145,6 +152,11 @@ func TestDashboardSearchClient_Search(t *testing.T) { searchFields.Field(resource.SEARCH_FIELD_TITLE), searchFields.Field(resource.SEARCH_FIELD_FOLDER), searchFields.Field(resource.SEARCH_FIELD_TAGS), + { + Name: unisearch.DASHBOARD_LEGACY_ID, + Type: resource.ResourceTableColumnDefinition_INT64, + Description: "Deprecated legacy id of the dashboard", + }, { Name: "views_total", Type: resource.ResourceTableColumnDefinition_INT64, @@ -161,6 +173,7 @@ func TestDashboardSearchClient_Search(t *testing.T) { []byte("Test Dashboard"), []byte("folder"), emptyTags, + []byte("1"), []byte(strconv.FormatInt(50, 10)), }, }, @@ -180,7 +193,7 @@ func TestDashboardSearchClient_Search(t *testing.T) { Type: "dash-db", Sort: sortOptionAsc, }).Return([]dashboards.DashboardSearchProjection{ - {UID: "uid", Title: "Test Dashboard", FolderUID: "folder", SortMeta: int64(2)}, + {ID: 1, UID: "uid", Title: "Test Dashboard", FolderUID: "folder", SortMeta: int64(2)}, }, nil).Once() req := &resource.ResourceSearchRequest{ @@ -205,6 +218,11 @@ func TestDashboardSearchClient_Search(t *testing.T) { searchFields.Field(resource.SEARCH_FIELD_TITLE), searchFields.Field(resource.SEARCH_FIELD_FOLDER), searchFields.Field(resource.SEARCH_FIELD_TAGS), + { + Name: unisearch.DASHBOARD_LEGACY_ID, + Type: resource.ResourceTableColumnDefinition_INT64, + Description: "Deprecated legacy id of the dashboard", + }, { Name: "errors_last_30_days", Type: resource.ResourceTableColumnDefinition_INT64, @@ -221,6 +239,7 @@ func TestDashboardSearchClient_Search(t *testing.T) { []byte("Test Dashboard"), []byte("folder"), emptyTags, + []byte("1"), []byte(strconv.FormatInt(2, 10)), }, }, From 7a3415148e579c102c0d0f171c0fb26fbe6ac58c Mon Sep 17 00:00:00 2001 From: Sam Jewell <2903904+samjewell@users.noreply.github.com> Date: Tue, 11 Mar 2025 17:14:33 +0000 Subject: [PATCH 019/141] SQL Expressions: Add cell-limit for input dataframes (#101700) * expr: Add row limit to SQL expressions Adds a configurable row limit to SQL expressions to prevent memory issues with large result sets. The limit is configured via the `sql_expression_row_limit` setting in the `[expressions]` section of grafana.ini, with a default of 100,000 rows. The limit is enforced by checking the total number of rows across all input tables before executing the SQL query. If the total exceeds the limit, the query fails with an error message indicating the limit was exceeded. * revert addition of newline * Switch to table-driven tests * Remove single-frame test-cases. We only need to test for the multi frame case. Single frame is a subset of the multi-frame case * Add helper function Simplify the way tests are set up and written * Support convention, that limit: 0 is no limit * Set the row-limit in one place only * Update default limit to 20k rows As per some discussion here: https://raintank-corp.slack.com/archives/C071A5XCFST/p1741611647001369?thread_ts=1740047619.804869&cid=C071A5XCFST * Test row-limit is applied from config Make sure we protect this from regressions This is perhaps a brittle test, somewhat coupled to the code here. But it's good enough to prevent regressions at least. * Add public documentation for the limit * Limit total number of cells instead of rows * Use named-return for totalRows As @kylebrandt requested during review of #101700 * Leave DF cells as zero values during limits tests When testing the cell limit we don't interact with the cell values at all, so we leave them at their zero values both to speed up tests, and to simplify and clarify that their values aren't used. * Set SQLCmd limit at object creation - don't mutate * Test that SQL node receives limit when built And that it receives it from the Grafana config * Improve TODO message for new Expression Parser * Fix failing test by always creating config on the Service --- .../setup-grafana/configure-grafana/_index.md | 4 + pkg/expr/graph.go | 2 +- pkg/expr/graph_test.go | 2 + pkg/expr/nodes.go | 4 +- pkg/expr/reader.go | 4 +- pkg/expr/service_test.go | 65 ++++++++ pkg/expr/sql_command.go | 38 ++++- pkg/expr/sql_command_test.go | 142 +++++++++++++++++- pkg/setting/setting.go | 4 + 9 files changed, 253 insertions(+), 12 deletions(-) diff --git a/docs/sources/setup-grafana/configure-grafana/_index.md b/docs/sources/setup-grafana/configure-grafana/_index.md index 5c7b92515ec..9f16b072e63 100644 --- a/docs/sources/setup-grafana/configure-grafana/_index.md +++ b/docs/sources/setup-grafana/configure-grafana/_index.md @@ -2753,6 +2753,10 @@ Set the default start of the week, valid values are: `saturday`, `sunday`, `mond Set this to `false` to disable expressions and hide them in the Grafana UI. Default is `true`. +#### `sql_expression_cell_limit` + +Set the maximum number of cells that can be passed to a SQL expression. Default is `100000`. + ### `[geomap]` This section controls the defaults settings for **Geomap Plugin**. diff --git a/pkg/expr/graph.go b/pkg/expr/graph.go index 6632a6b74c1..ae0ec6f9660 100644 --- a/pkg/expr/graph.go +++ b/pkg/expr/graph.go @@ -277,7 +277,7 @@ func (s *Service) buildGraph(req *Request) (*simple.DirectedGraph, error) { case TypeDatasourceNode: node, err = s.buildDSNode(dp, rn, req) case TypeCMDNode: - node, err = buildCMDNode(rn, s.features) + node, err = buildCMDNode(rn, s.features, s.cfg.SQLExpressionCellLimit) case TypeMLNode: if s.features.IsEnabledGlobally(featuremgmt.FlagMlExpressions) { node, err = s.buildMLNode(dp, rn, req) diff --git a/pkg/expr/graph_test.go b/pkg/expr/graph_test.go index fafca8f6876..dfa9f5f5b0a 100644 --- a/pkg/expr/graph_test.go +++ b/pkg/expr/graph_test.go @@ -8,6 +8,7 @@ import ( "github.com/grafana/grafana/pkg/services/datasources" "github.com/grafana/grafana/pkg/services/featuremgmt" + "github.com/grafana/grafana/pkg/setting" ) func TestServicebuildPipeLine(t *testing.T) { @@ -234,6 +235,7 @@ func TestServicebuildPipeLine(t *testing.T) { } s := Service{ features: featuremgmt.WithFeatures(featuremgmt.FlagExpressionParser), + cfg: setting.NewCfg(), } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/pkg/expr/nodes.go b/pkg/expr/nodes.go index 1159ef13d04..dea3b10e659 100644 --- a/pkg/expr/nodes.go +++ b/pkg/expr/nodes.go @@ -106,7 +106,7 @@ func (gn *CMDNode) Execute(ctx context.Context, now time.Time, vars mathexp.Vars return gn.Command.Execute(ctx, now, vars, s.tracer) } -func buildCMDNode(rn *rawNode, toggles featuremgmt.FeatureToggles) (*CMDNode, error) { +func buildCMDNode(rn *rawNode, toggles featuremgmt.FeatureToggles, sqlExpressionCellLimit int64) (*CMDNode, error) { commandType, err := GetExpressionCommandType(rn.Query) if err != nil { return nil, fmt.Errorf("invalid command type in expression '%v': %w", rn.RefID, err) @@ -163,7 +163,7 @@ func buildCMDNode(rn *rawNode, toggles featuremgmt.FeatureToggles) (*CMDNode, er case TypeThreshold: node.Command, err = UnmarshalThresholdCommand(rn, toggles) case TypeSQL: - node.Command, err = UnmarshalSQLCommand(rn) + node.Command, err = UnmarshalSQLCommand(rn, sqlExpressionCellLimit) default: return nil, fmt.Errorf("expression command type '%v' in expression '%v' not implemented", commandType, rn.RefID) } diff --git a/pkg/expr/reader.go b/pkg/expr/reader.go index ef18d9d8c2b..10a219f0692 100644 --- a/pkg/expr/reader.go +++ b/pkg/expr/reader.go @@ -134,7 +134,9 @@ func (h *ExpressionQueryReader) ReadQuery( err = iter.ReadVal(q) if err == nil { eq.Properties = q - eq.Command, err = NewSQLCommand(common.RefID, q.Expression) + // TODO: Cascade limit from Grafana config in this (new Expression Parser) branch of the code + cellLimit := 0 // zero means no limit + eq.Command, err = NewSQLCommand(common.RefID, q.Expression, int64(cellLimit)) } case QueryTypeThreshold: diff --git a/pkg/expr/service_test.go b/pkg/expr/service_test.go index 2fe6f6e1e9c..a343cdaf7b4 100644 --- a/pkg/expr/service_test.go +++ b/pkg/expr/service_test.go @@ -146,6 +146,71 @@ func TestDSQueryError(t *testing.T) { require.Equal(t, fp(42), res.Responses["C"].Frames[0].Fields[0].At(0)) } +func TestSQLExpressionCellLimitFromConfig(t *testing.T) { + tests := []struct { + name string + configCellLimit int64 + expectedLimit int64 + }{ + { + name: "should pass default cell limit (0) to SQL command", + configCellLimit: 0, + expectedLimit: 0, + }, + { + name: "should pass custom cell limit to SQL command", + configCellLimit: 5000, + expectedLimit: 5000, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create a request with an SQL expression + sqlQuery := Query{ + RefID: "A", + DataSource: dataSourceModel(), + JSON: json.RawMessage(`{ "datasource": { "uid": "__expr__", "type": "__expr__"}, "type": "sql", "expression": "SELECT 1 AS n" }`), + TimeRange: AbsoluteTimeRange{ + From: time.Time{}, + To: time.Time{}, + }, + } + + queries := []Query{sqlQuery} + + // Create service with specified cell limit + cfg := setting.NewCfg() + cfg.ExpressionsEnabled = true + cfg.SQLExpressionCellLimit = tt.configCellLimit + + features := featuremgmt.WithFeatures(featuremgmt.FlagSqlExpressions) + + // Create service with our configured limit + s := &Service{ + cfg: cfg, + features: features, + converter: &ResultConverter{ + Features: features, + }, + } + + req := &Request{Queries: queries, User: &user.SignedInUser{}} + + // Build the pipeline + pipeline, err := s.BuildPipeline(req) + require.NoError(t, err) + + node := pipeline[0] + cmdNode := node.(*CMDNode) + sqlCmd := cmdNode.Command.(*SQLCommand) + + // Verify the SQL command has the correct limit + require.Equal(t, tt.expectedLimit, sqlCmd.limit, "SQL command has incorrect cell limit") + }) + } +} + func fp(f float64) *float64 { return &f } diff --git a/pkg/expr/sql_command.go b/pkg/expr/sql_command.go index 069d2a39e91..0b4d7ab698e 100644 --- a/pkg/expr/sql_command.go +++ b/pkg/expr/sql_command.go @@ -19,10 +19,11 @@ type SQLCommand struct { query string varsToQuery []string refID string + limit int64 } // NewSQLCommand creates a new SQLCommand. -func NewSQLCommand(refID, rawSQL string) (*SQLCommand, error) { +func NewSQLCommand(refID, rawSQL string, limit int64) (*SQLCommand, error) { if rawSQL == "" { return nil, errutil.BadRequest("sql-missing-query", errutil.WithPublicMessage("missing SQL query")) @@ -40,15 +41,17 @@ func NewSQLCommand(refID, rawSQL string) (*SQLCommand, error) { if tables != nil { logger.Debug("REF tables", "tables", tables, "sql", rawSQL) } + return &SQLCommand{ query: rawSQL, varsToQuery: tables, refID: refID, + limit: limit, }, nil } // UnmarshalSQLCommand creates a SQLCommand from Grafana's frontend query. -func UnmarshalSQLCommand(rn *rawNode) (*SQLCommand, error) { +func UnmarshalSQLCommand(rn *rawNode, limit int64) (*SQLCommand, error) { if rn.TimeRange == nil { logger.Error("time range must be specified for refID", "refID", rn.RefID) return nil, fmt.Errorf("time range must be specified for refID %s", rn.RefID) @@ -65,7 +68,7 @@ func UnmarshalSQLCommand(rn *rawNode) (*SQLCommand, error) { return nil, fmt.Errorf("expected sql expression to be type string, but got type %T", expressionRaw) } - return NewSQLCommand(rn.RefID, expression) + return NewSQLCommand(rn.RefID, expression, limit) } // NeedsVars returns the variable names (refIds) that are dependencies @@ -91,12 +94,23 @@ func (gr *SQLCommand) Execute(ctx context.Context, now time.Time, vars mathexp.V allFrames = append(allFrames, frames...) } - rsp := mathexp.Results{} - - db := sql.DB{} + totalCells := totalCells(allFrames) + // limit of 0 or less means no limit (following convention) + if gr.limit > 0 && totalCells > gr.limit { + return mathexp.Results{}, + fmt.Errorf( + "SQL expression: total cell count across all input tables exceeds limit of %d. Total cells: %d", + gr.limit, + totalCells, + ) + } logger.Debug("Executing query", "query", gr.query, "frames", len(allFrames)) + + db := sql.DB{} frame, err := db.QueryFrames(ctx, gr.refID, gr.query, allFrames) + + rsp := mathexp.Results{} if err != nil { logger.Error("Failed to query frames", "error", err.Error()) rsp.Error = err @@ -121,3 +135,15 @@ func (gr *SQLCommand) Execute(ctx context.Context, now time.Time, vars mathexp.V func (gr *SQLCommand) Type() string { return TypeSQL.String() } + +func totalCells(frames []*data.Frame) (total int64) { + for _, frame := range frames { + if frame != nil { + // Calculate cells as rows × columns + rows := int64(frame.Rows()) + cols := int64(len(frame.Fields)) + total += rows * cols + } + } + return +} diff --git a/pkg/expr/sql_command_test.go b/pkg/expr/sql_command_test.go index 3e0c5527721..07387a46612 100644 --- a/pkg/expr/sql_command_test.go +++ b/pkg/expr/sql_command_test.go @@ -1,13 +1,21 @@ package expr import ( + "context" + "fmt" + "net/http" "strings" "testing" + "time" + + "github.com/grafana/grafana-plugin-sdk-go/data" + "github.com/grafana/grafana/pkg/expr/mathexp" + "github.com/stretchr/testify/require" + "go.opentelemetry.io/otel/trace" ) func TestNewCommand(t *testing.T) { - t.Skip() - cmd, err := NewSQLCommand("a", "select a from foo, bar") + cmd, err := NewSQLCommand("a", "select a from foo, bar", 0) if err != nil && strings.Contains(err.Error(), "feature is not enabled") { return } @@ -25,3 +33,133 @@ func TestNewCommand(t *testing.T) { return } } + +// Helper function for creating test data +func createFrameWithRowsAndCols(rows int, cols int) *data.Frame { + frame := data.NewFrame("dummy") + + for c := 0; c < cols; c++ { + values := make([]string, rows) + frame.Fields = append(frame.Fields, data.NewField(fmt.Sprintf("col%d", c), nil, values)) + } + + return frame +} + +func TestSQLCommandCellLimits(t *testing.T) { + tests := []struct { + name string + limit int64 + frames []*data.Frame + vars []string + expectError bool + errorContains string + }{ + { + name: "single (long) frame within cell limit", + limit: 10, + frames: []*data.Frame{ + createFrameWithRowsAndCols(10, 1), // 10 cells + }, + vars: []string{"foo"}, + }, + { + name: "single (wide) frame within cell limit", + limit: 10, + frames: []*data.Frame{ + createFrameWithRowsAndCols(1, 10), // 10 cells + }, + vars: []string{"foo"}, + }, + { + name: "multiple frames within cell limit", + limit: 12, + frames: []*data.Frame{ + createFrameWithRowsAndCols(2, 3), // 6 cells + createFrameWithRowsAndCols(2, 3), // 6 cells + }, + vars: []string{"foo", "bar"}, + }, + { + name: "single (long) frame exceeds cell limit", + limit: 9, + frames: []*data.Frame{ + createFrameWithRowsAndCols(10, 1), // 10 cells > 9 limit + }, + vars: []string{"foo"}, + expectError: true, + errorContains: "exceeds limit", + }, + { + name: "single (wide) frame exceeds cell limit", + limit: 9, + frames: []*data.Frame{ + createFrameWithRowsAndCols(1, 10), // 10 cells > 9 limit + }, + vars: []string{"foo"}, + expectError: true, + errorContains: "exceeds limit", + }, + { + name: "multiple frames exceed cell limit", + limit: 11, + frames: []*data.Frame{ + createFrameWithRowsAndCols(2, 3), // 6 cells + createFrameWithRowsAndCols(2, 3), // 6 cells + }, + vars: []string{"foo", "bar"}, + expectError: true, + errorContains: "exceeds limit", + }, + { + name: "limit of 0 means no limit: allow large frame", + limit: 0, + frames: []*data.Frame{ + createFrameWithRowsAndCols(200000, 1), // 200,000 cells + }, + vars: []string{"foo", "bar"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cmd, err := NewSQLCommand("a", "select a from foo, bar", tt.limit) + require.NoError(t, err, "Failed to create SQL command") + + vars := mathexp.Vars{} + + for i, frame := range tt.frames { + vars[tt.vars[i]] = mathexp.Results{ + Values: mathexp.Values{mathexp.TableData{Frame: frame}}, + } + } + + _, err = cmd.Execute(context.Background(), time.Now(), vars, &testTracer{}) + + if tt.expectError { + require.Error(t, err) + require.Contains(t, err.Error(), tt.errorContains) + } else { + require.NoError(t, err) + } + }) + } +} + +type testTracer struct { + trace.Tracer +} + +func (t *testTracer) Start(ctx context.Context, name string, s ...trace.SpanStartOption) (context.Context, trace.Span) { + return ctx, &testSpan{} +} +func (t *testTracer) Inject(context.Context, http.Header, trace.Span) { + +} + +type testSpan struct { + trace.Span +} + +func (ts *testSpan) End(opt ...trace.SpanEndOption) { +} diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index cc1b10b9054..c5805d8520d 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -419,6 +419,9 @@ type Cfg struct { // ExpressionsEnabled specifies whether expressions are enabled. ExpressionsEnabled bool + // SQLExpressionCellLimit is the maximum number of cells (rows × columns, across all frames) that can be accepted by a SQL expression. + SQLExpressionCellLimit int64 + ImageUploadProvider string // LiveMaxConnections is a maximum number of WebSocket connections to @@ -780,6 +783,7 @@ func (cfg *Cfg) readAnnotationSettings() error { func (cfg *Cfg) readExpressionsSettings() { expressions := cfg.Raw.Section("expressions") cfg.ExpressionsEnabled = expressions.Key("enabled").MustBool(true) + cfg.SQLExpressionCellLimit = expressions.Key("sql_expression_cell_limit").MustInt64(100000) } type AnnotationCleanupSettings struct { From e645a7d8ff3f5b31d1e04a1efcbce10d027cdefd Mon Sep 17 00:00:00 2001 From: Denis Vodopianov Date: Tue, 11 Mar 2025 18:25:52 +0100 Subject: [PATCH 020/141] Chore: update golang version in .drone.yaml (#101894) --- .drone.yml | 206 ++++++++++++++++---------------- public/api-enterprise-spec.json | 82 +++++++++++-- public/api-merged.json | 52 +++++++- public/openapi3.json | 52 +++++++- scripts/drone/variables.star | 2 +- 5 files changed, 276 insertions(+), 118 deletions(-) diff --git a/.drone.yml b/.drone.yml index dbcc1662f84..6340d739fd6 100644 --- a/.drone.yml +++ b/.drone.yml @@ -25,7 +25,7 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: compile-build-cmd - commands: - ./bin/build verify-drone @@ -75,7 +75,7 @@ steps: - go install github.com/bazelbuild/buildtools/buildifier@latest - buildifier --lint=warn -mode=check -r . depends_on: [] - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: lint-starlark trigger: event: @@ -437,7 +437,7 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-cue depends_on: [] - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: verify-gen-cue - commands: - '# It is required that generated jsonnet is committed and in sync with its inputs.' @@ -446,21 +446,21 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-jsonnet depends_on: [] - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: verify-gen-jsonnet - commands: - apk add --update make - make gen-go depends_on: - verify-gen-cue - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: wire-install - commands: - apk add --update build-base shared-mime-info shared-mime-info-lang - go list -f '{{.Dir}}/...' -m | xargs go test -short -covermode=atomic -timeout=5m depends_on: - wire-install - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: test-backend - commands: - apk add --update build-base @@ -469,7 +469,7 @@ steps: | grep -o '\(.*\)/' | sort -u) depends_on: - wire-install - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: test-backend-integration trigger: event: @@ -524,7 +524,7 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: compile-build-cmd - commands: - echo $(/usr/bin/github-app-external-token) > /github-app/token @@ -569,16 +569,16 @@ steps: - apk add --update make - make gen-go depends_on: [] - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: wire-install - commands: - go run scripts/modowners/modowners.go check go.mod - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: validate-modfile - commands: - apk add --update make - make swagger-validate - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: validate-openapi-spec trigger: event: @@ -655,7 +655,7 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: compile-build-cmd - commands: - '# It is required that code generated from Thema/CUE be committed and in sync @@ -665,7 +665,7 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-cue depends_on: [] - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: verify-gen-cue - commands: - '# It is required that generated jsonnet is committed and in sync with its inputs.' @@ -674,7 +674,7 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-jsonnet depends_on: [] - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: verify-gen-jsonnet - commands: - yarn install --immutable || yarn install --immutable @@ -712,7 +712,7 @@ steps: - /src/grafana-build artifacts -a targz:grafana:linux/amd64 -a targz:grafana:linux/arm64 -a targz:grafana:linux/arm/v7 -a docker:grafana:linux/amd64 -a docker:grafana:linux/amd64:ubuntu -a docker:grafana:linux/arm64 -a docker:grafana:linux/arm64:ubuntu -a docker:grafana:linux/arm/v7 - -a docker:grafana:linux/arm/v7:ubuntu --go-version=1.23.7 --yarn-cache=$$YARN_CACHE_FOLDER + -a docker:grafana:linux/arm/v7:ubuntu --go-version=1.24.1 --yarn-cache=$$YARN_CACHE_FOLDER --build-id=$$DRONE_BUILD_NUMBER --ubuntu-base=ubuntu:22.04 --alpine-base=alpine:3.21.3 --tag-format='{{ .version_base }}-{{ .buildID }}-{{ .arch }}' --ubuntu-tag-format='{{ .version_base }}-{{ .buildID }}-ubuntu-{{ .arch }}' --verify='false' --grafana-dir=$$PWD @@ -1110,7 +1110,7 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: compile-build-cmd - commands: - echo $DRONE_RUNNER_NAME @@ -1124,7 +1124,7 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-cue depends_on: [] - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: verify-gen-cue - commands: - '# It is required that generated jsonnet is committed and in sync with its inputs.' @@ -1133,14 +1133,14 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-jsonnet depends_on: [] - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: verify-gen-jsonnet - commands: - apk add --update make - make gen-go depends_on: - verify-gen-cue - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: wire-install - commands: - dockerize -wait tcp://postgres:5432 -timeout 120s @@ -1161,7 +1161,7 @@ steps: GRAFANA_TEST_DB: postgres PGPASSWORD: grafanatest POSTGRES_HOST: postgres - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: postgres-integration-tests - commands: - dockerize -wait tcp://mysql80:3306 -timeout 120s @@ -1182,7 +1182,7 @@ steps: environment: GRAFANA_TEST_DB: mysql MYSQL_HOST: mysql80 - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: mysql-8.0-integration-tests - commands: - dockerize -wait tcp://redis:6379 -timeout 120s @@ -1198,7 +1198,7 @@ steps: - wait-for-redis environment: REDIS_URL: redis://redis:6379/0 - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: redis-integration-tests - commands: - dockerize -wait tcp://memcached:11211 -timeout 120s @@ -1214,7 +1214,7 @@ steps: - wait-for-memcached environment: MEMCACHED_HOSTS: memcached:11211 - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: memcached-integration-tests - commands: - dockerize -wait tcp://mimir_backend:8080 -timeout 120s @@ -1230,7 +1230,7 @@ steps: environment: AM_TENANT_ID: test AM_URL: http://mimir_backend:8080 - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: remote-alertmanager-integration-tests trigger: event: @@ -1312,7 +1312,7 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-cue depends_on: [] - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: verify-gen-cue trigger: event: @@ -1433,7 +1433,7 @@ steps: && return 1; fi depends_on: - clone-enterprise - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: swagger-gen trigger: event: @@ -1538,7 +1538,7 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: compile-build-cmd - commands: - '# It is required that code generated from Thema/CUE be committed and in sync @@ -1549,7 +1549,7 @@ steps: - CODEGEN_VERIFY=1 make gen-cue depends_on: - clone-enterprise - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: verify-gen-cue - commands: - '# It is required that generated jsonnet is committed and in sync with its inputs.' @@ -1559,14 +1559,14 @@ steps: - CODEGEN_VERIFY=1 make gen-jsonnet depends_on: - clone-enterprise - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: verify-gen-jsonnet - commands: - apk add --update make - make gen-go depends_on: - verify-gen-cue - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: wire-install - commands: - apk add --update build-base @@ -1574,7 +1574,7 @@ steps: - go test -v -run=^$ -benchmem -timeout=1h -count=8 -bench=. ${GO_PACKAGES} depends_on: - wire-install - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: sqlite-benchmark-integration-tests - commands: - apk add --update build-base @@ -1586,7 +1586,7 @@ steps: GRAFANA_TEST_DB: postgres PGPASSWORD: grafanatest POSTGRES_HOST: postgres - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: postgres-benchmark-integration-tests - commands: - apk add --update build-base @@ -1597,7 +1597,7 @@ steps: environment: GRAFANA_TEST_DB: mysql MYSQL_HOST: mysql80 - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: mysql-8.0-benchmark-integration-tests trigger: event: @@ -1669,7 +1669,7 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-cue depends_on: [] - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: verify-gen-cue trigger: branch: main @@ -1852,7 +1852,7 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-cue depends_on: [] - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: verify-gen-cue - commands: - '# It is required that generated jsonnet is committed and in sync with its inputs.' @@ -1861,21 +1861,21 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-jsonnet depends_on: [] - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: verify-gen-jsonnet - commands: - apk add --update make - make gen-go depends_on: - verify-gen-cue - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: wire-install - commands: - apk add --update build-base shared-mime-info shared-mime-info-lang - go list -f '{{.Dir}}/...' -m | xargs go test -short -covermode=atomic -timeout=5m depends_on: - wire-install - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: test-backend - commands: - apk add --update build-base @@ -1884,7 +1884,7 @@ steps: | grep -o '\(.*\)/' | sort -u) depends_on: - wire-install - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: test-backend-integration trigger: branch: main @@ -1929,22 +1929,22 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: compile-build-cmd - commands: - apk add --update make - make gen-go depends_on: [] - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: wire-install - commands: - go run scripts/modowners/modowners.go check go.mod - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: validate-modfile - commands: - apk add --update make - make swagger-validate - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: validate-openapi-spec - commands: - ./bin/build verify-drone @@ -2076,7 +2076,7 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: compile-build-cmd - commands: - '# It is required that code generated from Thema/CUE be committed and in sync @@ -2086,7 +2086,7 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-cue depends_on: [] - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: verify-gen-cue - commands: - '# It is required that generated jsonnet is committed and in sync with its inputs.' @@ -2095,7 +2095,7 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-jsonnet depends_on: [] - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: verify-gen-jsonnet - commands: - yarn install --immutable || yarn install --immutable @@ -2132,7 +2132,7 @@ steps: - /src/grafana-build artifacts -a targz:grafana:linux/amd64 -a targz:grafana:linux/arm64 -a targz:grafana:linux/arm/v7 -a docker:grafana:linux/amd64 -a docker:grafana:linux/amd64:ubuntu -a docker:grafana:linux/arm64 -a docker:grafana:linux/arm64:ubuntu -a docker:grafana:linux/arm/v7 - -a docker:grafana:linux/arm/v7:ubuntu --go-version=1.23.7 --yarn-cache=$$YARN_CACHE_FOLDER + -a docker:grafana:linux/arm/v7:ubuntu --go-version=1.24.1 --yarn-cache=$$YARN_CACHE_FOLDER --build-id=$$DRONE_BUILD_NUMBER --ubuntu-base=ubuntu:22.04 --alpine-base=alpine:3.21.3 --tag-format='{{ .version_base }}-{{ .buildID }}-{{ .arch }}' --ubuntu-tag-format='{{ .version_base }}-{{ .buildID }}-ubuntu-{{ .arch }}' --verify='false' --grafana-dir=$$PWD @@ -2607,7 +2607,7 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: compile-build-cmd - commands: - echo $DRONE_RUNNER_NAME @@ -2621,7 +2621,7 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-cue depends_on: [] - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: verify-gen-cue - commands: - '# It is required that generated jsonnet is committed and in sync with its inputs.' @@ -2630,14 +2630,14 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-jsonnet depends_on: [] - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: verify-gen-jsonnet - commands: - apk add --update make - make gen-go depends_on: - verify-gen-cue - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: wire-install - commands: - dockerize -wait tcp://postgres:5432 -timeout 120s @@ -2658,7 +2658,7 @@ steps: GRAFANA_TEST_DB: postgres PGPASSWORD: grafanatest POSTGRES_HOST: postgres - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: postgres-integration-tests - commands: - dockerize -wait tcp://mysql80:3306 -timeout 120s @@ -2679,7 +2679,7 @@ steps: environment: GRAFANA_TEST_DB: mysql MYSQL_HOST: mysql80 - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: mysql-8.0-integration-tests - commands: - dockerize -wait tcp://redis:6379 -timeout 120s @@ -2695,7 +2695,7 @@ steps: - wait-for-redis environment: REDIS_URL: redis://redis:6379/0 - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: redis-integration-tests - commands: - dockerize -wait tcp://memcached:11211 -timeout 120s @@ -2711,7 +2711,7 @@ steps: - wait-for-memcached environment: MEMCACHED_HOSTS: memcached:11211 - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: memcached-integration-tests - commands: - dockerize -wait tcp://mimir_backend:8080 -timeout 120s @@ -2727,7 +2727,7 @@ steps: environment: AM_TENANT_ID: test AM_URL: http://mimir_backend:8080 - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: remote-alertmanager-integration-tests trigger: branch: main @@ -2996,7 +2996,7 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-cue depends_on: [] - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: verify-gen-cue - commands: - '# It is required that generated jsonnet is committed and in sync with its inputs.' @@ -3005,21 +3005,21 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-jsonnet depends_on: [] - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: verify-gen-jsonnet - commands: - apk add --update make - make gen-go depends_on: - verify-gen-cue - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: wire-install - commands: - apk add --update build-base shared-mime-info shared-mime-info-lang - go list -f '{{.Dir}}/...' -m | xargs go test -short -covermode=atomic -timeout=5m depends_on: - wire-install - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: test-backend - commands: - apk add --update build-base @@ -3028,7 +3028,7 @@ steps: | grep -o '\(.*\)/' | sort -u) depends_on: - wire-install - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: test-backend-integration trigger: branch: @@ -3071,22 +3071,22 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: compile-build-cmd - commands: - apk add --update make - make gen-go depends_on: [] - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: wire-install - commands: - go run scripts/modowners/modowners.go check go.mod - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: validate-modfile - commands: - apk add --update make - make swagger-validate - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: validate-openapi-spec trigger: branch: @@ -3165,7 +3165,7 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: compile-build-cmd - commands: - echo $DRONE_RUNNER_NAME @@ -3179,7 +3179,7 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-cue depends_on: [] - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: verify-gen-cue - commands: - '# It is required that generated jsonnet is committed and in sync with its inputs.' @@ -3188,14 +3188,14 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-jsonnet depends_on: [] - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: verify-gen-jsonnet - commands: - apk add --update make - make gen-go depends_on: - verify-gen-cue - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: wire-install - commands: - dockerize -wait tcp://postgres:5432 -timeout 120s @@ -3216,7 +3216,7 @@ steps: GRAFANA_TEST_DB: postgres PGPASSWORD: grafanatest POSTGRES_HOST: postgres - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: postgres-integration-tests - commands: - dockerize -wait tcp://mysql80:3306 -timeout 120s @@ -3237,7 +3237,7 @@ steps: environment: GRAFANA_TEST_DB: mysql MYSQL_HOST: mysql80 - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: mysql-8.0-integration-tests - commands: - dockerize -wait tcp://redis:6379 -timeout 120s @@ -3253,7 +3253,7 @@ steps: - wait-for-redis environment: REDIS_URL: redis://redis:6379/0 - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: redis-integration-tests - commands: - dockerize -wait tcp://memcached:11211 -timeout 120s @@ -3269,7 +3269,7 @@ steps: - wait-for-memcached environment: MEMCACHED_HOSTS: memcached:11211 - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: memcached-integration-tests - commands: - dockerize -wait tcp://mimir_backend:8080 -timeout 120s @@ -3285,7 +3285,7 @@ steps: environment: AM_TENANT_ID: test AM_URL: http://mimir_backend:8080 - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: remote-alertmanager-integration-tests trigger: branch: @@ -3385,7 +3385,7 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: compile-build-cmd - commands: - ./bin/build artifacts docker fetch --edition oss @@ -3517,7 +3517,7 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: compile-build-cmd - commands: - ./bin/build artifacts docker fetch --edition oss @@ -3658,7 +3658,7 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: compile-build-cmd - commands: - ./bin/build artifacts packages --artifacts-editions=oss --tag $${DRONE_TAG} --src-bucket @@ -3750,7 +3750,7 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: compile-build-cmd - commands: - yarn install --immutable || yarn install --immutable @@ -3850,7 +3850,7 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: compile-build-cmd - depends_on: - compile-build-cmd @@ -3947,7 +3947,7 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: compile-build-cmd - commands: - ./bin/build publish grafana-com --edition oss ${DRONE_TAG} @@ -4009,7 +4009,7 @@ steps: from_secret: grafana_api_key GCP_KEY_BASE64: from_secret: gcp_key_base64 - GO_VERSION: 1.23.7 + GO_VERSION: 1.24.1 GPG_PASSPHRASE: from_secret: packages_gpg_passphrase GPG_PRIVATE_KEY: @@ -4084,7 +4084,7 @@ steps: from_secret: grafana_api_key GCP_KEY_BASE64: from_secret: gcp_key_base64 - GO_VERSION: 1.23.7 + GO_VERSION: 1.24.1 GPG_PASSPHRASE: from_secret: packages_gpg_passphrase GPG_PRIVATE_KEY: @@ -4201,7 +4201,7 @@ steps: from_secret: grafana_api_key GCP_KEY_BASE64: from_secret: gcp_key_base64 - GO_VERSION: 1.23.7 + GO_VERSION: 1.24.1 GPG_PASSPHRASE: from_secret: packages_gpg_passphrase GPG_PRIVATE_KEY: @@ -4352,7 +4352,7 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-cue depends_on: [] - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: verify-gen-cue - commands: - '# It is required that generated jsonnet is committed and in sync with its inputs.' @@ -4361,21 +4361,21 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-jsonnet depends_on: [] - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: verify-gen-jsonnet - commands: - apk add --update make - make gen-go depends_on: - verify-gen-cue - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: wire-install - commands: - apk add --update build-base shared-mime-info shared-mime-info-lang - go list -f '{{.Dir}}/...' -m | xargs go test -short -covermode=atomic -timeout=5m depends_on: - wire-install - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: test-backend - commands: - apk add --update build-base @@ -4384,7 +4384,7 @@ steps: | grep -o '\(.*\)/' | sort -u) depends_on: - wire-install - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: test-backend-integration trigger: cron: @@ -4438,7 +4438,7 @@ steps: from_secret: grafana_api_key GCP_KEY_BASE64: from_secret: gcp_key_base64 - GO_VERSION: 1.23.7 + GO_VERSION: 1.24.1 GPG_PASSPHRASE: from_secret: packages_gpg_passphrase GPG_PRIVATE_KEY: @@ -4582,7 +4582,7 @@ steps: from_secret: grafana_api_key GCP_KEY_BASE64: from_secret: gcp_key_base64 - GO_VERSION: 1.23.7 + GO_VERSION: 1.24.1 GPG_PASSPHRASE: from_secret: packages_gpg_passphrase GPG_PRIVATE_KEY: @@ -4689,7 +4689,7 @@ steps: - export GITHUB_TOKEN=$(cat /github-app/token) - dagger run --silent /src/grafana-build artifacts -a $${ARTIFACTS} --grafana-ref=$${GRAFANA_REF} --enterprise-ref=$${ENTERPRISE_REF} --grafana-repo=$${GRAFANA_REPO} --version=$${VERSION} - --go-version=1.23.7 + --go-version=1.24.1 depends_on: - github-app-generate-token environment: @@ -4710,7 +4710,7 @@ steps: from_secret: grafana_api_key GCP_KEY_BASE64: from_secret: gcp_key_base64 - GO_VERSION: 1.23.7 + GO_VERSION: 1.24.1 GPG_PASSPHRASE: from_secret: packages_gpg_passphrase GPG_PRIVATE_KEY: @@ -4848,7 +4848,7 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-cue depends_on: [] - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: verify-gen-cue - commands: - '# It is required that generated jsonnet is committed and in sync with its inputs.' @@ -4857,14 +4857,14 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-jsonnet depends_on: [] - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: verify-gen-jsonnet - commands: - apk add --update make - make gen-go depends_on: - verify-gen-cue - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: wire-install - commands: - dockerize -wait tcp://postgres:5432 -timeout 120s @@ -4885,7 +4885,7 @@ steps: GRAFANA_TEST_DB: postgres PGPASSWORD: grafanatest POSTGRES_HOST: postgres - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: postgres-integration-tests - commands: - dockerize -wait tcp://mysql80:3306 -timeout 120s @@ -4906,7 +4906,7 @@ steps: environment: GRAFANA_TEST_DB: mysql MYSQL_HOST: mysql80 - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: mysql-8.0-integration-tests - commands: - dockerize -wait tcp://redis:6379 -timeout 120s @@ -4922,7 +4922,7 @@ steps: - wait-for-redis environment: REDIS_URL: redis://redis:6379/0 - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: redis-integration-tests - commands: - dockerize -wait tcp://memcached:11211 -timeout 120s @@ -4938,7 +4938,7 @@ steps: - wait-for-memcached environment: MEMCACHED_HOSTS: memcached:11211 - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: memcached-integration-tests - commands: - dockerize -wait tcp://mimir_backend:8080 -timeout 120s @@ -4954,7 +4954,7 @@ steps: environment: AM_TENANT_ID: test AM_URL: http://mimir_backend:8080 - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: remote-alertmanager-integration-tests trigger: event: @@ -5257,7 +5257,7 @@ steps: - commands: - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM docker:27-cli - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM alpine/git:2.40.1 - - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM golang:1.23.7-alpine + - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM golang:1.24.1-alpine - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM node:22.11.0-alpine - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM node:22-bookworm - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM google/cloud-sdk:431.0.0 @@ -5295,7 +5295,7 @@ steps: - commands: - trivy --exit-code 1 --severity HIGH,CRITICAL docker:27-cli - trivy --exit-code 1 --severity HIGH,CRITICAL alpine/git:2.40.1 - - trivy --exit-code 1 --severity HIGH,CRITICAL golang:1.23.7-alpine + - trivy --exit-code 1 --severity HIGH,CRITICAL golang:1.24.1-alpine - trivy --exit-code 1 --severity HIGH,CRITICAL node:22.11.0-alpine - trivy --exit-code 1 --severity HIGH,CRITICAL node:22-bookworm - trivy --exit-code 1 --severity HIGH,CRITICAL google/cloud-sdk:431.0.0 @@ -5564,6 +5564,6 @@ kind: secret name: gcr_credentials --- kind: signature -hmac: 33f2e5615dfd7889899f9f8f16f7716190fa637fe98f1efd7e29607f8946be7d +hmac: f55fddb4c6faf30b232ae778ec6c022c9f3d32955879e8a764c715642712c5ea ... diff --git a/public/api-enterprise-spec.json b/public/api-enterprise-spec.json index 3faeab0aa9a..a036e242e30 100644 --- a/public/api-enterprise-spec.json +++ b/public/api-enterprise-spec.json @@ -2779,6 +2779,7 @@ } }, "AnnotationActions": { + "description": "+k8s:deepcopy-gen=true", "type": "object", "properties": { "canAdd": { @@ -2853,6 +2854,7 @@ } }, "AnnotationPermission": { + "description": "+k8s:deepcopy-gen=true", "type": "object", "properties": { "dashboard": { @@ -3206,6 +3208,24 @@ "type": "string" } }, + "InhibitAnyPolicy": { + "description": "InhibitAnyPolicy and InhibitAnyPolicyZero indicate the presence and value\nof the inhibitAnyPolicy extension.\n\nThe value of InhibitAnyPolicy indicates the number of additional\ncertificates in the path after this certificate that may use the\nanyPolicy policy OID to indicate a match with any other policy.\n\nWhen parsing a certificate, a positive non-zero InhibitAnyPolicy means\nthat the field was specified, -1 means it was unset, and\nInhibitAnyPolicyZero being true mean that the field was explicitly set to\nzero. The case of InhibitAnyPolicy==0 with InhibitAnyPolicyZero==false\nshould be treated equivalent to -1 (unset).", + "type": "integer", + "format": "int64" + }, + "InhibitAnyPolicyZero": { + "description": "InhibitAnyPolicyZero indicates that InhibitAnyPolicy==0 should be\ninterpreted as an actual maximum path length of zero. Otherwise, that\ncombination is interpreted as InhibitAnyPolicy not being set.", + "type": "boolean" + }, + "InhibitPolicyMapping": { + "description": "InhibitPolicyMapping and InhibitPolicyMappingZero indicate the presence\nand value of the inhibitPolicyMapping field of the policyConstraints\nextension.\n\nThe value of InhibitPolicyMapping indicates the number of additional\ncertificates in the path after this certificate that may use policy\nmapping.\n\nWhen parsing a certificate, a positive non-zero InhibitPolicyMapping\nmeans that the field was specified, -1 means it was unset, and\nInhibitPolicyMappingZero being true mean that the field was explicitly\nset to zero. The case of InhibitPolicyMapping==0 with\nInhibitPolicyMappingZero==false should be treated equivalent to -1\n(unset).", + "type": "integer", + "format": "int64" + }, + "InhibitPolicyMappingZero": { + "description": "InhibitPolicyMappingZero indicates that InhibitPolicyMapping==0 should be\ninterpreted as an actual maximum path length of zero. Otherwise, that\ncombination is interpreted as InhibitAnyPolicy not being set.", + "type": "boolean" + }, "IsCA": { "type": "boolean" }, @@ -3270,19 +3290,26 @@ } }, "Policies": { - "description": "Policies contains all policy identifiers included in the certificate.\nIn Go 1.22, encoding/gob cannot handle and ignores this field.", + "description": "Policies contains all policy identifiers included in the certificate.\nSee CreateCertificate for context about how this field and the PolicyIdentifiers field\ninteract.\nIn Go 1.22, encoding/gob cannot handle and ignores this field.", "type": "array", "items": { "type": "string" } }, "PolicyIdentifiers": { - "description": "PolicyIdentifiers contains asn1.ObjectIdentifiers, the components\nof which are limited to int32. If a certificate contains a policy which\ncannot be represented by asn1.ObjectIdentifier, it will not be included in\nPolicyIdentifiers, but will be present in Policies, which contains all parsed\npolicy OIDs.", + "description": "PolicyIdentifiers contains asn1.ObjectIdentifiers, the components\nof which are limited to int32. If a certificate contains a policy which\ncannot be represented by asn1.ObjectIdentifier, it will not be included in\nPolicyIdentifiers, but will be present in Policies, which contains all parsed\npolicy OIDs.\nSee CreateCertificate for context about how this field and the Policies field\ninteract.", "type": "array", "items": { "$ref": "#/definitions/ObjectIdentifier" } }, + "PolicyMappings": { + "description": "PolicyMappings contains a list of policy mappings included in the certificate.", + "type": "array", + "items": { + "$ref": "#/definitions/PolicyMapping" + } + }, "PublicKey": {}, "PublicKeyAlgorithm": { "$ref": "#/definitions/PublicKeyAlgorithm" @@ -3322,6 +3349,15 @@ "format": "uint8" } }, + "RequireExplicitPolicy": { + "description": "RequireExplicitPolicy and RequireExplicitPolicyZero indicate the presence\nand value of the requireExplicitPolicy field of the policyConstraints\nextension.\n\nThe value of RequireExplicitPolicy indicates the number of additional\ncertificates in the path after this certificate before an explicit policy\nis required for the rest of the path. When an explicit policy is required,\neach subsequent certificate in the path must contain a required policy OID,\nor a policy OID which has been declared as equivalent through the policy\nmapping extension.\n\nWhen parsing a certificate, a positive non-zero RequireExplicitPolicy\nmeans that the field was specified, -1 means it was unset, and\nRequireExplicitPolicyZero being true mean that the field was explicitly\nset to zero. The case of RequireExplicitPolicy==0 with\nRequireExplicitPolicyZero==false should be treated equivalent to -1\n(unset).", + "type": "integer", + "format": "int64" + }, + "RequireExplicitPolicyZero": { + "description": "RequireExplicitPolicyZero indicates that RequireExplicitPolicy==0 should be\ninterpreted as an actual maximum path length of zero. Otherwise, that\ncombination is interpreted as InhibitAnyPolicy not being set.", + "type": "boolean" + }, "SerialNumber": { "type": "string" }, @@ -4047,6 +4083,9 @@ "annotationsPermissions": { "$ref": "#/definitions/AnnotationPermission" }, + "apiVersion": { + "type": "string" + }, "canAdmin": { "type": "boolean" }, @@ -4737,6 +4776,9 @@ "type": "integer", "format": "int64" }, + "managedBy": { + "$ref": "#/definitions/ManagerKind" + }, "orgId": { "type": "integer", "format": "int64" @@ -4752,10 +4794,6 @@ "$ref": "#/definitions/Folder" } }, - "repository": { - "description": "When the folder belongs to a repository\nNOTE: this is only populated when folders are managed by unified storage", - "type": "string" - }, "title": { "type": "string" }, @@ -4785,11 +4823,10 @@ "type": "integer", "format": "int64" }, - "parentUid": { - "type": "string" + "managedBy": { + "$ref": "#/definitions/ManagerKind" }, - "repository": { - "description": "When the folder belongs to a repository\nNOTE: this is only populated when folders are managed by unified storage", + "parentUid": { "type": "string" }, "title": { @@ -5536,6 +5573,11 @@ } } }, + "ManagerKind": { + "description": "It can be a user or a tool or a generic API client.\n+enum", + "type": "string", + "title": "ManagerKind is the type of manager, which is responsible for managing the resource." + }, "MassDeleteAnnotationsCmd": { "type": "object", "properties": { @@ -6175,6 +6217,20 @@ "$ref": "#/definitions/Playlist" } }, + "PolicyMapping": { + "type": "object", + "title": "PolicyMapping represents a policy mapping entry in the policyMappings extension.", + "properties": { + "IssuerDomainPolicy": { + "description": "IssuerDomainPolicy contains a policy OID the issuing certificate considers\nequivalent to SubjectDomainPolicy in the subject certificate.", + "type": "string" + }, + "SubjectDomainPolicy": { + "description": "SubjectDomainPolicy contains a OID the issuing certificate considers\nequivalent to IssuerDomainPolicy in the subject certificate.", + "type": "string" + } + } + }, "PostAnnotationsCmd": { "type": "object", "required": [ @@ -9946,6 +10002,12 @@ "type": "object" } }, + "notAcceptableError": { + "description": "NotAcceptableError is returned when the server cannot produce a response matching the accepted formats.", + "schema": { + "$ref": "#/definitions/ErrorResponseBody" + } + }, "notFoundError": { "description": "NotFoundError is returned when the requested resource was not found.", "schema": { diff --git a/public/api-merged.json b/public/api-merged.json index 873200a1fb7..44d88382eb6 100644 --- a/public/api-merged.json +++ b/public/api-merged.json @@ -13615,6 +13615,24 @@ "type": "string" } }, + "InhibitAnyPolicy": { + "description": "InhibitAnyPolicy and InhibitAnyPolicyZero indicate the presence and value\nof the inhibitAnyPolicy extension.\n\nThe value of InhibitAnyPolicy indicates the number of additional\ncertificates in the path after this certificate that may use the\nanyPolicy policy OID to indicate a match with any other policy.\n\nWhen parsing a certificate, a positive non-zero InhibitAnyPolicy means\nthat the field was specified, -1 means it was unset, and\nInhibitAnyPolicyZero being true mean that the field was explicitly set to\nzero. The case of InhibitAnyPolicy==0 with InhibitAnyPolicyZero==false\nshould be treated equivalent to -1 (unset).", + "type": "integer", + "format": "int64" + }, + "InhibitAnyPolicyZero": { + "description": "InhibitAnyPolicyZero indicates that InhibitAnyPolicy==0 should be\ninterpreted as an actual maximum path length of zero. Otherwise, that\ncombination is interpreted as InhibitAnyPolicy not being set.", + "type": "boolean" + }, + "InhibitPolicyMapping": { + "description": "InhibitPolicyMapping and InhibitPolicyMappingZero indicate the presence\nand value of the inhibitPolicyMapping field of the policyConstraints\nextension.\n\nThe value of InhibitPolicyMapping indicates the number of additional\ncertificates in the path after this certificate that may use policy\nmapping.\n\nWhen parsing a certificate, a positive non-zero InhibitPolicyMapping\nmeans that the field was specified, -1 means it was unset, and\nInhibitPolicyMappingZero being true mean that the field was explicitly\nset to zero. The case of InhibitPolicyMapping==0 with\nInhibitPolicyMappingZero==false should be treated equivalent to -1\n(unset).", + "type": "integer", + "format": "int64" + }, + "InhibitPolicyMappingZero": { + "description": "InhibitPolicyMappingZero indicates that InhibitPolicyMapping==0 should be\ninterpreted as an actual maximum path length of zero. Otherwise, that\ncombination is interpreted as InhibitAnyPolicy not being set.", + "type": "boolean" + }, "IsCA": { "type": "boolean" }, @@ -13679,19 +13697,26 @@ } }, "Policies": { - "description": "Policies contains all policy identifiers included in the certificate.\nIn Go 1.22, encoding/gob cannot handle and ignores this field.", + "description": "Policies contains all policy identifiers included in the certificate.\nSee CreateCertificate for context about how this field and the PolicyIdentifiers field\ninteract.\nIn Go 1.22, encoding/gob cannot handle and ignores this field.", "type": "array", "items": { "type": "string" } }, "PolicyIdentifiers": { - "description": "PolicyIdentifiers contains asn1.ObjectIdentifiers, the components\nof which are limited to int32. If a certificate contains a policy which\ncannot be represented by asn1.ObjectIdentifier, it will not be included in\nPolicyIdentifiers, but will be present in Policies, which contains all parsed\npolicy OIDs.", + "description": "PolicyIdentifiers contains asn1.ObjectIdentifiers, the components\nof which are limited to int32. If a certificate contains a policy which\ncannot be represented by asn1.ObjectIdentifier, it will not be included in\nPolicyIdentifiers, but will be present in Policies, which contains all parsed\npolicy OIDs.\nSee CreateCertificate for context about how this field and the Policies field\ninteract.", "type": "array", "items": { "$ref": "#/definitions/ObjectIdentifier" } }, + "PolicyMappings": { + "description": "PolicyMappings contains a list of policy mappings included in the certificate.", + "type": "array", + "items": { + "$ref": "#/definitions/PolicyMapping" + } + }, "PublicKey": {}, "PublicKeyAlgorithm": { "$ref": "#/definitions/PublicKeyAlgorithm" @@ -13731,6 +13756,15 @@ "format": "uint8" } }, + "RequireExplicitPolicy": { + "description": "RequireExplicitPolicy and RequireExplicitPolicyZero indicate the presence\nand value of the requireExplicitPolicy field of the policyConstraints\nextension.\n\nThe value of RequireExplicitPolicy indicates the number of additional\ncertificates in the path after this certificate before an explicit policy\nis required for the rest of the path. When an explicit policy is required,\neach subsequent certificate in the path must contain a required policy OID,\nor a policy OID which has been declared as equivalent through the policy\nmapping extension.\n\nWhen parsing a certificate, a positive non-zero RequireExplicitPolicy\nmeans that the field was specified, -1 means it was unset, and\nRequireExplicitPolicyZero being true mean that the field was explicitly\nset to zero. The case of RequireExplicitPolicy==0 with\nRequireExplicitPolicyZero==false should be treated equivalent to -1\n(unset).", + "type": "integer", + "format": "int64" + }, + "RequireExplicitPolicyZero": { + "description": "RequireExplicitPolicyZero indicates that RequireExplicitPolicy==0 should be\ninterpreted as an actual maximum path length of zero. Otherwise, that\ncombination is interpreted as InhibitAnyPolicy not being set.", + "type": "boolean" + }, "SerialNumber": { "type": "string" }, @@ -18116,6 +18150,20 @@ "$ref": "#/definitions/Playlist" } }, + "PolicyMapping": { + "type": "object", + "title": "PolicyMapping represents a policy mapping entry in the policyMappings extension.", + "properties": { + "IssuerDomainPolicy": { + "description": "IssuerDomainPolicy contains a policy OID the issuing certificate considers\nequivalent to SubjectDomainPolicy in the subject certificate.", + "type": "string" + }, + "SubjectDomainPolicy": { + "description": "SubjectDomainPolicy contains a OID the issuing certificate considers\nequivalent to IssuerDomainPolicy in the subject certificate.", + "type": "string" + } + } + }, "PostAnnotationsCmd": { "type": "object", "required": [ diff --git a/public/openapi3.json b/public/openapi3.json index 9d4c63135c3..ccfd199e14e 100644 --- a/public/openapi3.json +++ b/public/openapi3.json @@ -3676,6 +3676,24 @@ }, "type": "array" }, + "InhibitAnyPolicy": { + "description": "InhibitAnyPolicy and InhibitAnyPolicyZero indicate the presence and value\nof the inhibitAnyPolicy extension.\n\nThe value of InhibitAnyPolicy indicates the number of additional\ncertificates in the path after this certificate that may use the\nanyPolicy policy OID to indicate a match with any other policy.\n\nWhen parsing a certificate, a positive non-zero InhibitAnyPolicy means\nthat the field was specified, -1 means it was unset, and\nInhibitAnyPolicyZero being true mean that the field was explicitly set to\nzero. The case of InhibitAnyPolicy==0 with InhibitAnyPolicyZero==false\nshould be treated equivalent to -1 (unset).", + "format": "int64", + "type": "integer" + }, + "InhibitAnyPolicyZero": { + "description": "InhibitAnyPolicyZero indicates that InhibitAnyPolicy==0 should be\ninterpreted as an actual maximum path length of zero. Otherwise, that\ncombination is interpreted as InhibitAnyPolicy not being set.", + "type": "boolean" + }, + "InhibitPolicyMapping": { + "description": "InhibitPolicyMapping and InhibitPolicyMappingZero indicate the presence\nand value of the inhibitPolicyMapping field of the policyConstraints\nextension.\n\nThe value of InhibitPolicyMapping indicates the number of additional\ncertificates in the path after this certificate that may use policy\nmapping.\n\nWhen parsing a certificate, a positive non-zero InhibitPolicyMapping\nmeans that the field was specified, -1 means it was unset, and\nInhibitPolicyMappingZero being true mean that the field was explicitly\nset to zero. The case of InhibitPolicyMapping==0 with\nInhibitPolicyMappingZero==false should be treated equivalent to -1\n(unset).", + "format": "int64", + "type": "integer" + }, + "InhibitPolicyMappingZero": { + "description": "InhibitPolicyMappingZero indicates that InhibitPolicyMapping==0 should be\ninterpreted as an actual maximum path length of zero. Otherwise, that\ncombination is interpreted as InhibitAnyPolicy not being set.", + "type": "boolean" + }, "IsCA": { "type": "boolean" }, @@ -3740,19 +3758,26 @@ "type": "array" }, "Policies": { - "description": "Policies contains all policy identifiers included in the certificate.\nIn Go 1.22, encoding/gob cannot handle and ignores this field.", + "description": "Policies contains all policy identifiers included in the certificate.\nSee CreateCertificate for context about how this field and the PolicyIdentifiers field\ninteract.\nIn Go 1.22, encoding/gob cannot handle and ignores this field.", "items": { "type": "string" }, "type": "array" }, "PolicyIdentifiers": { - "description": "PolicyIdentifiers contains asn1.ObjectIdentifiers, the components\nof which are limited to int32. If a certificate contains a policy which\ncannot be represented by asn1.ObjectIdentifier, it will not be included in\nPolicyIdentifiers, but will be present in Policies, which contains all parsed\npolicy OIDs.", + "description": "PolicyIdentifiers contains asn1.ObjectIdentifiers, the components\nof which are limited to int32. If a certificate contains a policy which\ncannot be represented by asn1.ObjectIdentifier, it will not be included in\nPolicyIdentifiers, but will be present in Policies, which contains all parsed\npolicy OIDs.\nSee CreateCertificate for context about how this field and the Policies field\ninteract.", "items": { "$ref": "#/components/schemas/ObjectIdentifier" }, "type": "array" }, + "PolicyMappings": { + "description": "PolicyMappings contains a list of policy mappings included in the certificate.", + "items": { + "$ref": "#/components/schemas/PolicyMapping" + }, + "type": "array" + }, "PublicKey": {}, "PublicKeyAlgorithm": { "$ref": "#/components/schemas/PublicKeyAlgorithm" @@ -3792,6 +3817,15 @@ }, "type": "array" }, + "RequireExplicitPolicy": { + "description": "RequireExplicitPolicy and RequireExplicitPolicyZero indicate the presence\nand value of the requireExplicitPolicy field of the policyConstraints\nextension.\n\nThe value of RequireExplicitPolicy indicates the number of additional\ncertificates in the path after this certificate before an explicit policy\nis required for the rest of the path. When an explicit policy is required,\neach subsequent certificate in the path must contain a required policy OID,\nor a policy OID which has been declared as equivalent through the policy\nmapping extension.\n\nWhen parsing a certificate, a positive non-zero RequireExplicitPolicy\nmeans that the field was specified, -1 means it was unset, and\nRequireExplicitPolicyZero being true mean that the field was explicitly\nset to zero. The case of RequireExplicitPolicy==0 with\nRequireExplicitPolicyZero==false should be treated equivalent to -1\n(unset).", + "format": "int64", + "type": "integer" + }, + "RequireExplicitPolicyZero": { + "description": "RequireExplicitPolicyZero indicates that RequireExplicitPolicy==0 should be\ninterpreted as an actual maximum path length of zero. Otherwise, that\ncombination is interpreted as InhibitAnyPolicy not being set.", + "type": "boolean" + }, "SerialNumber": { "type": "string" }, @@ -8179,6 +8213,20 @@ }, "type": "array" }, + "PolicyMapping": { + "properties": { + "IssuerDomainPolicy": { + "description": "IssuerDomainPolicy contains a policy OID the issuing certificate considers\nequivalent to SubjectDomainPolicy in the subject certificate.", + "type": "string" + }, + "SubjectDomainPolicy": { + "description": "SubjectDomainPolicy contains a OID the issuing certificate considers\nequivalent to IssuerDomainPolicy in the subject certificate.", + "type": "string" + } + }, + "title": "PolicyMapping represents a policy mapping entry in the policyMappings extension.", + "type": "object" + }, "PostAnnotationsCmd": { "properties": { "dashboardId": { diff --git a/scripts/drone/variables.star b/scripts/drone/variables.star index c737ef65d61..51da1d676ba 100644 --- a/scripts/drone/variables.star +++ b/scripts/drone/variables.star @@ -3,7 +3,7 @@ global variables """ grabpl_version = "v3.1.2" -golang_version = "1.23.7" +golang_version = "1.24.1" # nodejs_version should match what's in ".nvmrc", but without the v prefix. nodejs_version = "22.11.0" From 4dbd1846c70c839fa71a06b7fb72d82d784aeb14 Mon Sep 17 00:00:00 2001 From: Ashley Harrison Date: Tue, 11 Mar 2025 17:28:36 +0000 Subject: [PATCH 021/141] Chore: bump codeql versions used in pr checks (#101957) * bump codeql versions used in pr checks * update supported versions * use glob syntax * wider glob --- .github/workflows/codeql-analysis.yml | 2 +- .github/workflows/pr-codeql-analysis-go.yml | 4 ++-- .github/workflows/pr-codeql-analysis-javascript.yml | 4 ++-- .github/workflows/pr-codeql-analysis-python.yml | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index c1f90ceb831..8c8b1abde50 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -8,7 +8,7 @@ name: "CodeQL" on: workflow_dispatch: push: - branches: [main, v1.8.x, v2.0.x, v2.1.x, v2.6.x, v3.0.x, v3.1.x, v4.0.x, v4.1.x, v4.2.x, v4.3.x, v4.4.x, v4.5.x, v4.6.x, v4.7.x, v5.0.x, v5.1.x, v5.2.x, v5.3.x, v5.4.x, v6.0.x, v6.1.x, v6.2.x, v6.3.x, v6.4.x, v6.5.x, v6.6.x, v6.7.x, v7.0.x, v7.1.x, v7.2.x] + branches: [main, v*.*.*] paths-ignore: - '**/*.cue' - '**/*.json' diff --git a/.github/workflows/pr-codeql-analysis-go.yml b/.github/workflows/pr-codeql-analysis-go.yml index ce9082f4400..46645b7fa3f 100644 --- a/.github/workflows/pr-codeql-analysis-go.yml +++ b/.github/workflows/pr-codeql-analysis-go.yml @@ -40,7 +40,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: languages: "go" @@ -50,4 +50,4 @@ jobs: make build-go - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/pr-codeql-analysis-javascript.yml b/.github/workflows/pr-codeql-analysis-javascript.yml index 6c5264c926a..d24b7db9671 100644 --- a/.github/workflows/pr-codeql-analysis-javascript.yml +++ b/.github/workflows/pr-codeql-analysis-javascript.yml @@ -28,9 +28,9 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: languages: "javascript" - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/pr-codeql-analysis-python.yml b/.github/workflows/pr-codeql-analysis-python.yml index aea55365afc..4e8b1b14747 100644 --- a/.github/workflows/pr-codeql-analysis-python.yml +++ b/.github/workflows/pr-codeql-analysis-python.yml @@ -26,9 +26,9 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: languages: "python" - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 From 85b0b47efdd811b46af2715aec7a54943d985ea6 Mon Sep 17 00:00:00 2001 From: Alexander Akhmetov Date: Tue, 11 Mar 2025 19:53:28 +0100 Subject: [PATCH 022/141] Alerting: Allow disabling provenance in the Prometheus conversion API (#101573) When creating Grafana-managed alerts from Prometheus rule definitions with mimirtool or cortextool, the rules are marked as "provisioned" and are not editable in the Grafana UI. This PR allows changing this by providing an extra header: --extra-header="X-Disable-Provenance=true". When provenance is disabled, we do not keep the original rule definition in YAML, so it is impossible to read it back using the Prometheus conversion API (mimirtool/cortextool). This is intentional because if we did keep it and the rule was later changed in the UI, its Prometheus YAML definition would no longer reflect the latest version of the alert rule, as it would be unchanged. --- .../ngalert/api/api_convert_prometheus.go | 38 +++++- .../api/api_convert_prometheus_test.go | 115 +++++++++++++++++ pkg/services/ngalert/prom/convert.go | 32 +++-- pkg/services/ngalert/prom/convert_test.go | 68 ++++++++++ .../ngalert/provisioning/alert_rules_test.go | 36 ++++++ .../provisioning/validation/provenance.go | 20 ++- .../validation/provenance_test.go | 60 +++++++++ .../alerting/api_convert_prometheus_test.go | 117 ++++++++++++++++++ 8 files changed, 466 insertions(+), 20 deletions(-) diff --git a/pkg/services/ngalert/api/api_convert_prometheus.go b/pkg/services/ngalert/api/api_convert_prometheus.go index 3c7878bc500..57d312768cb 100644 --- a/pkg/services/ngalert/api/api_convert_prometheus.go +++ b/pkg/services/ngalert/api/api_convert_prometheus.go @@ -188,11 +188,12 @@ func (srv *ConvertPrometheusSrv) RouteConvertPrometheusDeleteNamespace(c *contex } logger.Info("Deleting all Prometheus-imported rule groups", "folder_uid", namespace.UID, "folder_title", namespaceTitle) + provenance := getProvenance(c) filterOpts := &provisioning.FilterOptions{ NamespaceUIDs: []string{namespace.UID}, ImportedPrometheusRule: util.Pointer(true), } - err = srv.alertRuleService.DeleteRuleGroups(c.Req.Context(), c.SignedInUser, models.ProvenanceConvertedPrometheus, filterOpts) + err = srv.alertRuleService.DeleteRuleGroups(c.Req.Context(), c.SignedInUser, provenance, filterOpts) if errors.Is(err, models.ErrAlertRuleGroupNotFound) { return response.Empty(http.StatusNotFound) } @@ -218,7 +219,8 @@ func (srv *ConvertPrometheusSrv) RouteConvertPrometheusDeleteRuleGroup(c *contex } logger.Info("Deleting Prometheus-imported rule group", "folder_uid", folder.UID, "folder_title", namespaceTitle, "group", group) - err = srv.alertRuleService.DeleteRuleGroup(c.Req.Context(), c.SignedInUser, folder.UID, group, models.ProvenanceConvertedPrometheus) + provenance := getProvenance(c) + err = srv.alertRuleService.DeleteRuleGroup(c.Req.Context(), c.SignedInUser, folder.UID, group, provenance) if errors.Is(err, models.ErrAlertRuleGroupNotFound) { return response.Empty(http.StatusNotFound) } @@ -352,13 +354,21 @@ func (srv *ConvertPrometheusSrv) RouteConvertPrometheusPostRuleGroup(c *contextm return errorToResponse(err) } - group, err := srv.convertToGrafanaRuleGroup(c, ds, ns.UID, promGroup, logger) + provenance := getProvenance(c) + + // If the provenance is not ConvertedPrometheus, we don't keep the original rule definition. + // This is because the rules can be modified through the UI, which may break compatibility + // with the Prometheus format. We only preserve the original rule definition + // to ensure we can return them in this API in Prometheus format. + keepOriginalRuleDefinition := provenance == models.ProvenanceConvertedPrometheus + + group, err := srv.convertToGrafanaRuleGroup(c, ds, ns.UID, promGroup, keepOriginalRuleDefinition, logger) if err != nil { logger.Error("Failed to convert Prometheus rules to Grafana rules", "error", err) return errorToResponse(err) } - err = srv.alertRuleService.ReplaceRuleGroup(c.Req.Context(), c.SignedInUser, *group, models.ProvenanceConvertedPrometheus) + err = srv.alertRuleService.ReplaceRuleGroup(c.Req.Context(), c.SignedInUser, *group, provenance) if err != nil { logger.Error("Failed to replace rule group", "error", err) return errorToResponse(err) @@ -387,7 +397,14 @@ func (srv *ConvertPrometheusSrv) getOrCreateNamespace(c *contextmodel.ReqContext return ns, nil } -func (srv *ConvertPrometheusSrv) convertToGrafanaRuleGroup(c *contextmodel.ReqContext, ds *datasources.DataSource, namespaceUID string, promGroup apimodels.PrometheusRuleGroup, logger log.Logger) (*models.AlertRuleGroup, error) { +func (srv *ConvertPrometheusSrv) convertToGrafanaRuleGroup( + c *contextmodel.ReqContext, + ds *datasources.DataSource, + namespaceUID string, + promGroup apimodels.PrometheusRuleGroup, + keepOriginalRuleDefinition bool, + logger log.Logger, +) (*models.AlertRuleGroup, error) { logger.Info("Converting Prometheus rules to Grafana rules", "rules", len(promGroup.Rules), "folder_uid", namespaceUID, "datasource_uid", ds.UID, "datasource_type", ds.Type) rules := make([]prom.PrometheusRule, len(promGroup.Rules)) @@ -429,6 +446,7 @@ func (srv *ConvertPrometheusSrv) convertToGrafanaRuleGroup(c *contextmodel.ReqCo AlertRules: prom.RulesConfig{ IsPaused: pauseAlertRules, }, + KeepOriginalRuleDefinition: util.Pointer(keepOriginalRuleDefinition), }, ) if err != nil { @@ -537,3 +555,13 @@ func promGroupHasRecordingRules(promGroup apimodels.PrometheusRuleGroup) bool { } return false } + +// getProvenance determines the provenance value to use for rules created via the Prometheus conversion API. +// If the X-Disable-Provenance header is present in the request, returns ProvenanceNone, +// otherwise returns ProvenanceConvertedPrometheus. +func getProvenance(ctx *contextmodel.ReqContext) models.Provenance { + if _, disabled := ctx.Req.Header[disableProvenanceHeaderName]; disabled { + return models.ProvenanceNone + } + return models.ProvenanceConvertedPrometheus +} diff --git a/pkg/services/ngalert/api/api_convert_prometheus_test.go b/pkg/services/ngalert/api/api_convert_prometheus_test.go index c6b0a33166a..fda8be8cecd 100644 --- a/pkg/services/ngalert/api/api_convert_prometheus_test.go +++ b/pkg/services/ngalert/api/api_convert_prometheus_test.go @@ -144,6 +144,11 @@ func TestRouteConvertPrometheusPostRuleGroup(t *testing.T) { promDefinition, err := r.PrometheusRuleDefinition() require.NoError(t, err) require.Equal(t, expectedDef, promDefinition) + + // Verify provenance was set to ProvenanceConvertedPrometheus + prov, err := provenanceStore.GetProvenance(context.Background(), r, 1) + require.NoError(t, err) + require.Equal(t, models.ProvenanceConvertedPrometheus, prov) } }) @@ -341,6 +346,41 @@ func TestRouteConvertPrometheusPostRuleGroup(t *testing.T) { }) } }) + + t.Run("with disable provenance header should use ProvenanceNone", func(t *testing.T) { + provenanceStore := fakes.NewFakeProvisioningStore() + srv, _, ruleStore, folderService := createConvertPrometheusSrv(t, withProvenanceStore(provenanceStore)) + + // Create a folder in the root + fldr := randFolder() + fldr.ParentUID = "" + folderService.ExpectedFolder = fldr + folderService.ExpectedFolders = []*folder.Folder{fldr} + ruleStore.Folders[1] = append(ruleStore.Folders[1], fldr) + + // Create request with the X-Disable-Provenance header + rc := createRequestCtx() + rc.Req.Header.Set("X-Disable-Provenance", "true") + + response := srv.RouteConvertPrometheusPostRuleGroup(rc, fldr.Title, simpleGroup) + require.Equal(t, http.StatusAccepted, response.Status()) + + // Get the created rules + rules, err := ruleStore.ListAlertRules(context.Background(), &models.ListAlertRulesQuery{ + OrgID: 1, + }) + require.NoError(t, err) + require.Len(t, rules, 2) + + // Verify provenance was set to ProvenanceNone + for _, r := range rules { + prov, err := provenanceStore.GetProvenance(context.Background(), r, 1) + require.NoError(t, err) + require.Equal(t, models.ProvenanceNone, prov, "Provenance should be ProvenanceNone when X-Disable-Provenance header is set") + // Prometheus rule definition should not be saved when provenance is disabled + require.Nil(t, r.Metadata.PrometheusStyleRule) + } + }) } func TestRouteConvertPrometheusGetRuleGroup(t *testing.T) { @@ -743,6 +783,29 @@ func TestRouteConvertPrometheusDeleteNamespace(t *testing.T) { require.NoError(t, err) require.NotNil(t, remaining) }) + + t.Run("with disable provenance header should still be able to delete rules", func(t *testing.T) { + provenanceStore := fakes.NewFakeProvisioningStore() + srv, ruleStore, fldr, rule := initNamespace("prometheus definition", withProvenanceStore(provenanceStore)) + + // Mark the rule as provisioned with API provenance + err := provenanceStore.SetProvenance(context.Background(), rule, 1, models.ProvenanceConvertedPrometheus) + require.NoError(t, err) + + rc := createRequestCtx() + rc.Req.Header.Set("X-Disable-Provenance", "true") + + response := srv.RouteConvertPrometheusDeleteNamespace(rc, fldr.Title) + require.Equal(t, http.StatusAccepted, response.Status()) + + // Verify the rule was deleted + remaining, err := ruleStore.GetAlertRuleByUID(context.Background(), &models.GetAlertRuleByUIDQuery{ + UID: rule.UID, + OrgID: rule.OrgID, + }) + require.Error(t, err) + require.Nil(t, remaining) + }) }) } @@ -854,6 +917,29 @@ func TestRouteConvertPrometheusDeleteRuleGroup(t *testing.T) { require.NoError(t, err) require.NotNil(t, remaining) }) + + t.Run("with disable provenance header should still be able to delete rules", func(t *testing.T) { + provenanceStore := fakes.NewFakeProvisioningStore() + srv, ruleStore, fldr, rule := initGroup("", groupName, withProvenanceStore(provenanceStore)) + + // Mark the rule as provisioned with API provenance + err := provenanceStore.SetProvenance(context.Background(), rule, 1, models.ProvenanceConvertedPrometheus) + require.NoError(t, err) + + rc := createRequestCtx() + rc.Req.Header.Set("X-Disable-Provenance", "true") + + response := srv.RouteConvertPrometheusDeleteRuleGroup(rc, fldr.Title, groupName) + require.Equal(t, http.StatusAccepted, response.Status()) + + // Verify the rule was deleted + remaining, err := ruleStore.GetAlertRuleByUID(context.Background(), &models.GetAlertRuleByUIDQuery{ + UID: rule.UID, + OrgID: rule.OrgID, + }) + require.Error(t, err) + require.Nil(t, remaining) + }) }) } @@ -995,3 +1081,32 @@ func TestGetWorkingFolderUID(t *testing.T) { require.Equal(t, specifiedFolderUID, folderUID) }) } + +func TestGetProvenance(t *testing.T) { + t.Run("should return ProvenanceConvertedPrometheus when header is not present", func(t *testing.T) { + rc := createRequestCtx() + // Ensure the header is not present + rc.Req.Header.Del(disableProvenanceHeaderName) + + provenance := getProvenance(rc) + require.Equal(t, models.ProvenanceConvertedPrometheus, provenance) + }) + + t.Run("should return ProvenanceNone when header is present", func(t *testing.T) { + rc := createRequestCtx() + // Set the disable provenance header + rc.Req.Header.Set(disableProvenanceHeaderName, "true") + + provenance := getProvenance(rc) + require.Equal(t, models.ProvenanceNone, provenance) + }) + + t.Run("should return ProvenanceNone when header is present with any value", func(t *testing.T) { + rc := createRequestCtx() + // Set the disable provenance header with an empty value + rc.Req.Header.Set(disableProvenanceHeaderName, "") + + provenance := getProvenance(rc) + require.Equal(t, models.ProvenanceNone, provenance) + }) +} diff --git a/pkg/services/ngalert/prom/convert.go b/pkg/services/ngalert/prom/convert.go index ccbbf89e23b..f64b17bfc5e 100644 --- a/pkg/services/ngalert/prom/convert.go +++ b/pkg/services/ngalert/prom/convert.go @@ -37,8 +37,12 @@ type Config struct { EvaluationOffset *time.Duration ExecErrState models.ExecutionErrorState NoDataState models.NoDataState - RecordingRules RulesConfig - AlertRules RulesConfig + // KeepOriginalRuleDefinition indicates whether the original Prometheus rule definition + // if saved to the alert rule metadata. If not, then it will not be possible to convert + // the alert rule back to Prometheus format. + KeepOriginalRuleDefinition *bool + RecordingRules RulesConfig + AlertRules RulesConfig } // RulesConfig contains configuration that applies to either recording or alerting rules. @@ -51,10 +55,11 @@ var ( defaultEvaluationOffset = 0 * time.Minute defaultConfig = Config{ - FromTimeRange: &defaultTimeRange, - EvaluationOffset: &defaultEvaluationOffset, - ExecErrState: models.ErrorErrState, - NoDataState: models.OK, + FromTimeRange: &defaultTimeRange, + EvaluationOffset: &defaultEvaluationOffset, + ExecErrState: models.ErrorErrState, + NoDataState: models.OK, + KeepOriginalRuleDefinition: util.Pointer(true), } ) @@ -87,7 +92,9 @@ func NewConverter(cfg Config) (*Converter, error) { if cfg.NoDataState == "" { cfg.NoDataState = defaultConfig.NoDataState } - + if cfg.KeepOriginalRuleDefinition == nil { + cfg.KeepOriginalRuleDefinition = defaultConfig.KeepOriginalRuleDefinition + } if cfg.DatasourceType != datasources.DS_PROMETHEUS && cfg.DatasourceType != datasources.DS_LOKI { return nil, fmt.Errorf("invalid datasource type: %s", cfg.DatasourceType) } @@ -233,11 +240,12 @@ func (p *Converter) convertRule(orgID int64, namespaceUID string, promGroup Prom RuleGroup: promGroup.Name, IsPaused: isPaused, Record: record, - Metadata: models.AlertRuleMetadata{ - PrometheusStyleRule: &models.PrometheusStyleRule{ - OriginalRuleDefinition: string(originalRuleDefinition), - }, - }, + } + + if p.cfg.KeepOriginalRuleDefinition != nil && *p.cfg.KeepOriginalRuleDefinition { + result.Metadata.PrometheusStyleRule = &models.PrometheusStyleRule{ + OriginalRuleDefinition: string(originalRuleDefinition), + } } return result, nil diff --git a/pkg/services/ngalert/prom/convert_test.go b/pkg/services/ngalert/prom/convert_test.go index 2d7e2b26535..2281a9da1c0 100644 --- a/pkg/services/ngalert/prom/convert_test.go +++ b/pkg/services/ngalert/prom/convert_test.go @@ -618,3 +618,71 @@ func TestPrometheusRulesToGrafana_UID(t *testing.T) { }) }) } + +func TestPrometheusRulesToGrafana_KeepOriginalRuleDefinition(t *testing.T) { + orgID := int64(1) + namespace := "namespace" + + promGroup := PrometheusRuleGroup{ + Name: "test-group", + Rules: []PrometheusRule{ + { + Alert: "test-alert", + Expr: "up == 0", + }, + }, + } + + testCases := []struct { + name string + keepOriginalRuleDefinition *bool + expectDefinition bool + }{ + { + name: "keep original rule definition is true", + keepOriginalRuleDefinition: util.Pointer(true), + expectDefinition: true, + }, + { + name: "keep original rule definition is false", + keepOriginalRuleDefinition: util.Pointer(false), + expectDefinition: false, + }, + { + name: "keep original rule definition is nil (should use default)", + keepOriginalRuleDefinition: nil, + expectDefinition: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + cfg := Config{ + DatasourceUID: "datasource-uid", + DatasourceType: datasources.DS_PROMETHEUS, + DefaultInterval: 1 * time.Minute, + KeepOriginalRuleDefinition: tc.keepOriginalRuleDefinition, + } + + converter, err := NewConverter(cfg) + require.NoError(t, err) + + // Convert the Prometheus rule to Grafana + grafanaGroup, err := converter.PrometheusRulesToGrafana(orgID, namespace, promGroup) + require.NoError(t, err) + require.Len(t, grafanaGroup.Rules, 1) + + if tc.expectDefinition { + originalRuleDefinition, err := yaml.Marshal(promGroup.Rules[0]) + require.NoError(t, err) + require.Equal( + t, + string(originalRuleDefinition), + grafanaGroup.Rules[0].Metadata.PrometheusStyleRule.OriginalRuleDefinition, + ) + } else { + require.Nil(t, grafanaGroup.Rules[0].Metadata.PrometheusStyleRule) + } + }) + } +} diff --git a/pkg/services/ngalert/provisioning/alert_rules_test.go b/pkg/services/ngalert/provisioning/alert_rules_test.go index c98c293fd34..68991883c03 100644 --- a/pkg/services/ngalert/provisioning/alert_rules_test.go +++ b/pkg/services/ngalert/provisioning/alert_rules_test.go @@ -674,6 +674,42 @@ func TestAlertRuleService(t *testing.T) { to: models.ProvenanceNone, errNil: false, }, + { + name: "should be able to update from provenance none to 'converted prometheus'", + from: models.ProvenanceNone, + to: models.ProvenanceConvertedPrometheus, + errNil: true, + }, + { + name: "should be able to update from provenance 'converted prometheus' to none", + from: models.ProvenanceConvertedPrometheus, + to: models.ProvenanceNone, + errNil: true, + }, + { + name: "should not be able to update from provenance 'converted prometheus' to api", + from: models.ProvenanceConvertedPrometheus, + to: models.ProvenanceAPI, + errNil: false, + }, + { + name: "should not be able to update from provenance 'converted prometheus' to file", + from: models.ProvenanceConvertedPrometheus, + to: models.ProvenanceFile, + errNil: false, + }, + { + name: "should not be able to update from provenance api to 'converted prometheus'", + from: models.ProvenanceAPI, + to: models.ProvenanceConvertedPrometheus, + errNil: false, + }, + { + name: "should not be able to update from provenance file to 'converted prometheus'", + from: models.ProvenanceFile, + to: models.ProvenanceConvertedPrometheus, + errNil: false, + }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { diff --git a/pkg/services/ngalert/provisioning/validation/provenance.go b/pkg/services/ngalert/provisioning/validation/provenance.go index 387cdd56b3e..b57dcee37db 100644 --- a/pkg/services/ngalert/provisioning/validation/provenance.go +++ b/pkg/services/ngalert/provisioning/validation/provenance.go @@ -7,9 +7,23 @@ import ( // CanUpdateProvenanceInRuleGroup checks if a provenance can be updated for a rule group and its alerts. // ReplaceRuleGroup function intends to replace an entire rule group: inserting, updating, and removing rules. func CanUpdateProvenanceInRuleGroup(storedProvenance, provenance models.Provenance) bool { - return storedProvenance == provenance || - storedProvenance == models.ProvenanceNone || - (storedProvenance == models.ProvenanceAPI && provenance == models.ProvenanceNone) + // Same provenance is always allowed + if storedProvenance == provenance { + return true + } + + // Can always update stored ProvenanceNone + if storedProvenance == models.ProvenanceNone { + return true + } + + // Can reset to ProvenanceNone from specific provenances + if provenance == models.ProvenanceNone { + return storedProvenance == models.ProvenanceAPI || + storedProvenance == models.ProvenanceConvertedPrometheus + } + + return false } type ProvenanceStatusTransitionValidator = func(from, to models.Provenance) error diff --git a/pkg/services/ngalert/provisioning/validation/provenance_test.go b/pkg/services/ngalert/provisioning/validation/provenance_test.go index b98ce241b03..04b9c61eccb 100644 --- a/pkg/services/ngalert/provisioning/validation/provenance_test.go +++ b/pkg/services/ngalert/provisioning/validation/provenance_test.go @@ -15,6 +15,7 @@ func TestValidateProvenanceRelaxed(t *testing.T) { models.ProvenanceNone, models.ProvenanceAPI, models.ProvenanceFile, + models.ProvenanceConvertedPrometheus, models.Provenance(fmt.Sprintf("random-%s", util.GenerateShortUID())), } t.Run("all transitions from 'none' are allowed", func(t *testing.T) { @@ -49,3 +50,62 @@ func TestValidateProvenanceRelaxed(t *testing.T) { } }) } + +func TestCanUpdateProvenanceInRuleGroup(t *testing.T) { + all := []models.Provenance{ + models.ProvenanceNone, + models.ProvenanceAPI, + models.ProvenanceFile, + models.ProvenanceConvertedPrometheus, + models.Provenance(fmt.Sprintf("random-%s", util.GenerateShortUID())), + } + + t.Run("same provenance transitions are allowed", func(t *testing.T) { + for _, provenance := range all { + assert.True(t, CanUpdateProvenanceInRuleGroup(provenance, provenance)) + } + }) + + t.Run("all transitions from 'none' are allowed", func(t *testing.T) { + for _, provenance := range all { + assert.True(t, CanUpdateProvenanceInRuleGroup(models.ProvenanceNone, provenance)) + } + }) + + t.Run("only specific provenances can transition to 'none'", func(t *testing.T) { + allowed := []models.Provenance{ + models.ProvenanceAPI, + models.ProvenanceConvertedPrometheus, + } + + for _, from := range allowed { + assert.True(t, CanUpdateProvenanceInRuleGroup(from, models.ProvenanceNone), + "transition %s -> 'none' should be allowed", from) + } + + notAllowed := []models.Provenance{ + models.ProvenanceFile, + models.Provenance(fmt.Sprintf("random-%s", util.GenerateShortUID())), + } + + for _, from := range notAllowed { + assert.False(t, CanUpdateProvenanceInRuleGroup(from, models.ProvenanceNone), + "transition %s -> 'none' should not be allowed", from) + } + }) + + t.Run("transitions between different provenances are not allowed", func(t *testing.T) { + for _, from := range all { + if from == models.ProvenanceNone { + continue // always allowed + } + for _, to := range all { + if from == to || to == models.ProvenanceNone { + continue // always allowed + } + assert.False(t, CanUpdateProvenanceInRuleGroup(from, to), + "transition %s -> '%s' should not be allowed", from, to) + } + } + }) +} diff --git a/pkg/tests/api/alerting/api_convert_prometheus_test.go b/pkg/tests/api/alerting/api_convert_prometheus_test.go index ec881bc09c5..e07382e6bca 100644 --- a/pkg/tests/api/alerting/api_convert_prometheus_test.go +++ b/pkg/tests/api/alerting/api_convert_prometheus_test.go @@ -12,6 +12,7 @@ import ( "github.com/grafana/grafana/pkg/expr" "github.com/grafana/grafana/pkg/services/datasources" apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" + "github.com/grafana/grafana/pkg/services/ngalert/models" "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/tests/testinfra" @@ -603,6 +604,122 @@ func TestIntegrationConvertPrometheusEndpoints_FolderUIDHeader(t *testing.T) { }) } +func TestIntegrationConvertPrometheusEndpoints_Provenance(t *testing.T) { + runTest := func(t *testing.T, enableLokiPaths bool) { + testinfra.SQLiteIntegrationTest(t) + + // Setup Grafana and its Database + dir, gpath := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{ + DisableLegacyAlerting: true, + EnableUnifiedAlerting: true, + DisableAnonymous: true, + AppModeProduction: true, + EnableFeatureToggles: []string{"alertingConversionAPI", "grafanaManagedRecordingRulesDatasources", "grafanaManagedRecordingRules"}, + EnableRecordingRules: true, + }) + + grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, gpath) + + // Create admin user + createUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{ + DefaultOrgRole: string(org.RoleAdmin), + Password: "password", + Login: "admin", + }) + adminClient := newAlertingApiClient(grafanaListedAddr, "admin", "password") + adminClient.prometheusConversionUseLokiPaths = enableLokiPaths + + ds := adminClient.CreateDatasource(t, datasources.DS_PROMETHEUS) + + t.Run("default provenance is ProvenanceConvertedPrometheus", func(t *testing.T) { + namespace := "test-namespace-provenance-" + util.GenerateShortUID() + + // We have to create a folder to get its UID to use in the ruler API later to fetch the rule group. + namespaceUID := util.GenerateShortUID() + adminClient.CreateFolder(t, namespaceUID, namespace) + + adminClient.ConvertPrometheusPostRuleGroup(t, namespace, ds.Body.Datasource.UID, promGroup1, nil) + + // Get the rule group using the ruler API and check its provenance + ruleGroup, status := adminClient.GetRulesGroup(t, namespaceUID, promGroup1.Name) + require.Equal(t, http.StatusAccepted, status) + for _, rule := range ruleGroup.Rules { + require.Equal(t, apimodels.Provenance(models.ProvenanceConvertedPrometheus), rule.GrafanaManagedAlert.Provenance) + } + }) + + t.Run("with disable provenance header should use ProvenanceNone", func(t *testing.T) { + namespace := "test-namespace-provenance-" + util.GenerateShortUID() + + // We have to create a folder to get its UID to use in the ruler API later to fetch the rule group. + namespaceUID := util.GenerateShortUID() + adminClient.CreateFolder(t, namespaceUID, namespace) + + // Create rule group with the X-Disable-Provenance header + headers := map[string]string{ + "X-Disable-Provenance": "true", + } + adminClient.ConvertPrometheusPostRuleGroup(t, namespace, ds.Body.Datasource.UID, promGroup1, headers) + + // Get the rule group using the ruler API and check its provenance + ruleGroup, status := adminClient.GetRulesGroup(t, namespaceUID, promGroup1.Name) + require.Equal(t, http.StatusAccepted, status) + for _, rule := range ruleGroup.Rules { + require.Equal(t, apimodels.Provenance(models.ProvenanceNone), rule.GrafanaManagedAlert.Provenance) + } + }) + + t.Run("can delete rule groups with X-Disable-Provenance header", func(t *testing.T) { + namespace := "test-namespace-delete-provenance-" + util.GenerateShortUID() + namespaceUID := util.GenerateShortUID() + adminClient.CreateFolder(t, namespaceUID, namespace) + + // Create a rule group + adminClient.ConvertPrometheusPostRuleGroup(t, namespace, ds.Body.Datasource.UID, promGroup1, nil) + + // Now try to delete with X-Disable-Provenance header + // This should succeed + headers := map[string]string{ + "X-Disable-Provenance": "true", + } + adminClient.ConvertPrometheusDeleteRuleGroup(t, namespace, promGroup1.Name, headers) + + // Verify the rule group is gone + _, status, _ := adminClient.GetRulesGroupWithStatus(t, namespaceUID, promGroup1.Name) + require.Equal(t, http.StatusNotFound, status) + }) + + t.Run("can delete namespaces with X-Disable-Provenance header", func(t *testing.T) { + namespace := "test-namespace-delete-ns-provenance-" + util.GenerateShortUID() + namespaceUID := util.GenerateShortUID() + adminClient.CreateFolder(t, namespaceUID, namespace) + + // Create a rule group with provenance=ProvenanceConvertedPrometheus + adminClient.ConvertPrometheusPostRuleGroup(t, namespace, ds.Body.Datasource.UID, promGroup1, nil) + + // Now delete with X-Disable-Provenance header + // This should succeed + headers := map[string]string{ + "X-Disable-Provenance": "true", + } + adminClient.ConvertPrometheusDeleteNamespace(t, namespace, headers) + + // Verify the namespace has no rule groups + namespaces := adminClient.ConvertPrometheusGetAllRules(t, nil) + _, exists := namespaces[namespace] + require.False(t, exists) + }) + } + + t.Run("with the mimirtool paths", func(t *testing.T) { + runTest(t, false) + }) + + t.Run("with the cortextool Loki paths", func(t *testing.T) { + runTest(t, true) + }) +} + func TestIntegrationConvertPrometheusEndpoints_Delete(t *testing.T) { runTest := func(t *testing.T, enableLokiPaths bool) { testinfra.SQLiteIntegrationTest(t) From 700f1225df6e8ae1f0f3b31c60a05959eced9c4b Mon Sep 17 00:00:00 2001 From: Isabella Siu Date: Tue, 11 Mar 2025 16:09:22 -0400 Subject: [PATCH 023/141] AWS Datasources: Update grafana assume role docs to remove unnecessary flags (#101086) Co-authored-by: Larissa Wandzura <126723338+lwandz13@users.noreply.github.com> --- .../datasources/aws-cloudwatch/aws-authentication/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/datasources/aws-cloudwatch/aws-authentication/index.md b/docs/sources/datasources/aws-cloudwatch/aws-authentication/index.md index 9ae64f44856..6cebd2089fd 100644 --- a/docs/sources/datasources/aws-cloudwatch/aws-authentication/index.md +++ b/docs/sources/datasources/aws-cloudwatch/aws-authentication/index.md @@ -164,7 +164,7 @@ Grafana Assume Role is currently in [private preview](https://grafana.com/docs/r It's currently only available for Amazon CloudWatch. -To get early access this feature, reach out to Customer Support and ask for the `awsDatasourcesTempCredentials` feature toggle to be enabled and the `cloudwatchRemoteDatasource` and `athenaRemoteDatasource` feature toggles to be disabled on your account. +To gain early access to this feature, contact Customer Support and ask for the `awsDatasourcesTempCredentials` feature toggle to be enabled on your account. {{% /admonition %}} The Grafana Assume Role authentication provider lets you authenticate with AWS without having to create and maintain long term AWS users or rotate their access and secret keys. Instead, you can create an IAM role that has permissions to access CloudWatch and a trust relationship with Grafana's AWS account. Grafana's AWS account then makes an STS request to AWS to create temporary credentials to access your AWS data. It makes this STS request by passing along an `externalID` that's unique per Cloud account, to ensure that Grafana Cloud users can only access their own AWS data. For more information, refer to the [AWS documentation on external ID](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-user_externalid.html). From 9870718c3a9804031a8daf4a6b208d0f0ac19aea Mon Sep 17 00:00:00 2001 From: Tom Ratcliffe Date: Tue, 11 Mar 2025 20:31:47 +0000 Subject: [PATCH 024/141] Alerting: Enable `jsx-no-useless-fragment` rule (#101884) * Add no-useless-fragment rule for alerting code * Auto-fix most no-useless-fragment cases * Manually fix remaining no-useless-fragment cases * Fix `invalid` passing to Field component * Allow AlertingPageWrapper to have optional children --- eslint.config.js | 1 + .../features/alerting/unified/RuleViewer.tsx | 10 +- .../components/AlertingPageWrapper.tsx | 7 +- .../contact-points/ContactPoint.tsx | 16 +- .../EditDefaultPolicyForm.tsx | 54 +-- .../notification-policies/Policy.tsx | 352 +++++++++--------- .../receivers/AlertInstanceModalSelector.tsx | 4 +- .../components/receivers/TemplateForm.tsx | 22 +- .../receivers/form/GenerateAlertDataModal.tsx | 48 ++- .../receivers/form/ReceiverForm.tsx | 58 ++- .../receivers/form/fields/DeletedSubform.tsx | 2 +- .../rule-editor/NotificationsStep.tsx | 12 +- .../alert-rule-form/ModifyExportRuleForm.tsx | 46 ++- .../NotificationRouteDetailsModal.tsx | 12 +- .../CloudDataSourceSelector.tsx | 62 ++- .../components/rules/EditRuleGroupModal.tsx | 219 ++++++----- .../rules/Filter/RulesFilter.v2.tsx | 94 +++-- .../unified/components/rules/NoRulesCTA.tsx | 20 +- .../components/rules/RulesFilter.test.tsx | 2 +- .../central-state-history/EventDetails.tsx | 24 +- .../rules/state-history/LokiStateHistory.tsx | 18 +- .../alerting/unified/home/Insights.tsx | 34 +- .../components/RuleGroupActionsMenu.tsx | 28 +- 23 files changed, 549 insertions(+), 596 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index 8c4910b7310..3b29e36521b 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -262,6 +262,7 @@ module.exports = [ 'prefer-const': 'error', 'react/no-unused-prop-types': 'error', 'react/self-closing-comp': 'error', + 'react/jsx-no-useless-fragment': ['error', { allowExpressions: true }], 'unicorn/no-unused-properties': 'error', }, }, diff --git a/public/app/features/alerting/unified/RuleViewer.tsx b/public/app/features/alerting/unified/RuleViewer.tsx index 9beaea7325a..4d4889eab84 100644 --- a/public/app/features/alerting/unified/RuleViewer.tsx +++ b/public/app/features/alerting/unified/RuleViewer.tsx @@ -15,7 +15,7 @@ import { stringifyErrorLike } from './utils/misc'; import { getRuleIdFromPathname, parse as parseRuleId } from './utils/rule-id'; import { withPageErrorBoundary } from './withPageErrorBoundary'; -const RuleViewer = (): JSX.Element => { +const RuleViewer = () => { const params = useParams(); const id = getRuleIdFromPathname(params); @@ -48,11 +48,7 @@ const RuleViewer = (): JSX.Element => { } if (loading) { - return ( - - <> - - ); + return ; } if (rule) { @@ -73,7 +69,7 @@ const RuleViewer = (): JSX.Element => { } // we should never get to this state - return <>; + return null; }; export const defaultPageNav: NavModelItem = { diff --git a/public/app/features/alerting/unified/components/AlertingPageWrapper.tsx b/public/app/features/alerting/unified/components/AlertingPageWrapper.tsx index 68b011d7d22..e6dd4a486f8 100644 --- a/public/app/features/alerting/unified/components/AlertingPageWrapper.tsx +++ b/public/app/features/alerting/unified/components/AlertingPageWrapper.tsx @@ -1,4 +1,4 @@ -import { PropsWithChildren } from 'react'; +import { PropsWithChildren, ReactNode } from 'react'; import { useLocation } from 'react-use'; import { Page } from 'app/core/components/Page/Page'; @@ -12,9 +12,10 @@ import { NoAlertManagerWarning } from './NoAlertManagerWarning'; /** * This is the main alerting page wrapper, used by the alertmanager page wrapper and the alert rules list view */ -interface AlertingPageWrapperProps extends PageProps { +type AlertingPageWrapperProps = Omit & { isLoading?: boolean; -} + children?: ReactNode; +}; export const AlertingPageWrapper = ({ children, isLoading, ...rest }: AlertingPageWrapperProps) => ( diff --git a/public/app/features/alerting/unified/components/contact-points/ContactPoint.tsx b/public/app/features/alerting/unified/components/contact-points/ContactPoint.tsx index c7a637e489f..dfe2d07589c 100644 --- a/public/app/features/alerting/unified/components/contact-points/ContactPoint.tsx +++ b/public/app/features/alerting/unified/components/contact-points/ContactPoint.tsx @@ -223,15 +223,13 @@ const ContactPointReceiverMetadataRow = ({ diagnostics, sendingResolved }: Conta {/* this is shown when the last delivery failed – we don't show any additional metadata */} {failedToSend ? ( - <> - - - - Last delivery attempt failed - - - - + + + + Last delivery attempt failed + + + ) : ( <> {/* this is shown when we have a last delivery attempt */} diff --git a/public/app/features/alerting/unified/components/notification-policies/EditDefaultPolicyForm.tsx b/public/app/features/alerting/unified/components/notification-policies/EditDefaultPolicyForm.tsx index 475b6f358d3..ea90e5397fe 100644 --- a/public/app/features/alerting/unified/components/notification-policies/EditDefaultPolicyForm.tsx +++ b/public/app/features/alerting/unified/components/notification-policies/EditDefaultPolicyForm.tsx @@ -51,32 +51,34 @@ export const AmRootRouteForm = ({ actionButtons, alertManagerSourceName, onSubmi }); return (
- - <> -
- ( - handleContactPointSelect(changeValue, onChange), - }} - selectedContactPointName={value} - /> - )} - control={control} - name="receiver" - rules={{ required: { value: true, message: 'Required.' } }} - /> - or - - Create a contact point - -
- + +
+ ( + handleContactPointSelect(changeValue, onChange), + }} + selectedContactPointName={value} + /> + )} + control={control} + name="receiver" + rules={{ required: { value: true, message: 'Required.' } }} + /> + or + + Create a contact point + +
{ const showMore = moreCount > 0; return ( - <> - -
- {/* continueMatching and showMatchesAllLabelsWarning are mutually exclusive so the icons can't overlap */} - {continueMatching && } - {showMatchesAllLabelsWarning && } + +
+ {/* continueMatching and showMatchesAllLabelsWarning are mutually exclusive so the icons can't overlap */} + {continueMatching && } + {showMatchesAllLabelsWarning && } -
- - {/* Matchers and actions */} -
- - {hasChildPolicies ? ( - - ) : null} - {isImmutablePolicy ? ( - isAutogeneratedPolicyRoot ? ( - - ) : ( - - ) - ) : hasMatchers ? ( - - ) : ( - - No matchers - - )} - - {/* TODO maybe we should move errors to the gutter instead? */} - {errors.length > 0 && } - {provisioned && } - - {!isAutoGenerated && !readOnly && ( - - - {isDefaultPolicy ? ( - - ) : ( - - onAddPolicy(currentRoute, 'above')} - /> - onAddPolicy(currentRoute, 'below')} - /> - - onAddPolicy(currentRoute, 'child')} - /> - - } - > - - - )} - - - )} - {dropdownMenuActions.length > 0 && ( - {dropdownMenuActions}}> - - - )} - - -
- - {/* Metadata row */} - -
-
-
-
- {showPolicyChildren && ( - <> - {pageOfChildren.map((child) => { - const childInheritedProperties = getInheritedProperties(currentRoute, child, inheritedProperties); - // This child is autogenerated if it's the autogenerated root or if it's a child of an autogenerated policy. - const isThisChildAutoGenerated = isAutoGeneratedRootAndSimplifiedEnabled(child) || isAutoGenerated; - /* pass the "readOnly" prop from the parent, because for any child policy , if its parent it's not editable, - then the child policy should not be editable either */ - const isThisChildReadOnly = readOnly || provisioned || isAutoGenerated; - - return ( - + + {/* Matchers and actions */} +
+ + {hasChildPolicies ? ( + - ); - })} - {showMore && ( - - )} - - )} + ) : null} + {isImmutablePolicy ? ( + isAutogeneratedPolicyRoot ? ( + + ) : ( + + ) + ) : hasMatchers ? ( + + ) : ( + + No matchers + + )} + + {/* TODO maybe we should move errors to the gutter instead? */} + {errors.length > 0 && } + {provisioned && } + + {!isAutoGenerated && !readOnly && ( + + + {isDefaultPolicy ? ( + + ) : ( + + onAddPolicy(currentRoute, 'above')} + /> + onAddPolicy(currentRoute, 'below')} + /> + + onAddPolicy(currentRoute, 'child')} + /> + + } + > + + + )} + + + )} + {dropdownMenuActions.length > 0 && ( + {dropdownMenuActions}}> + + + )} + + +
+ + {/* Metadata row */} + +
- {showExportDrawer && } -
- +
+
+ {showPolicyChildren && ( + <> + {pageOfChildren.map((child) => { + const childInheritedProperties = getInheritedProperties(currentRoute, child, inheritedProperties); + // This child is autogenerated if it's the autogenerated root or if it's a child of an autogenerated policy. + const isThisChildAutoGenerated = isAutoGeneratedRootAndSimplifiedEnabled(child) || isAutoGenerated; + /* pass the "readOnly" prop from the parent, because for any child policy , if its parent it's not editable, + then the child policy should not be editable either */ + const isThisChildReadOnly = readOnly || provisioned || isAutoGenerated; + + return ( + + ); + })} + {showMore && ( + + )} + + )} +
+ {showExportDrawer && } +
); }; @@ -513,14 +511,12 @@ function MetadataRow({ )} {timingOptions && } {hasInheritedProperties && ( - <> - - - Inherited - - - - + + + Inherited + + + )} diff --git a/public/app/features/alerting/unified/components/receivers/AlertInstanceModalSelector.tsx b/public/app/features/alerting/unified/components/receivers/AlertInstanceModalSelector.tsx index cd3e7c5d284..96bca41b8db 100644 --- a/public/app/features/alerting/unified/components/receivers/AlertInstanceModalSelector.tsx +++ b/public/app/features/alerting/unified/components/receivers/AlertInstanceModalSelector.tsx @@ -113,9 +113,7 @@ export function AlertInstanceModalSelector({ >
{ruleName}
- <> - {filteredRules[ruleName][0].labels.grafana_folder ?? ''} - + {filteredRules[ruleName][0].labels.grafana_folder ?? ''}
); diff --git a/public/app/features/alerting/unified/components/receivers/TemplateForm.tsx b/public/app/features/alerting/unified/components/receivers/TemplateForm.tsx index 29186ded954..fdc83f84204 100644 --- a/public/app/features/alerting/unified/components/receivers/TemplateForm.tsx +++ b/public/app/features/alerting/unified/components/receivers/TemplateForm.tsx @@ -317,18 +317,16 @@ export const TemplateForm = ({ originalTemplate, prefill, alertmanager }: Props) {/* preview column – full height and half-width */} {isGrafanaAlertManager && ( - <> -
-
- -
- +
+
+ +
)}
diff --git a/public/app/features/alerting/unified/components/receivers/form/GenerateAlertDataModal.tsx b/public/app/features/alerting/unified/components/receivers/form/GenerateAlertDataModal.tsx index 417ccb4c3ba..4726ab7d27a 100644 --- a/public/app/features/alerting/unified/components/receivers/form/GenerateAlertDataModal.tsx +++ b/public/app/features/alerting/unified/components/receivers/form/GenerateAlertDataModal.tsx @@ -97,31 +97,29 @@ export const GenerateAlertDataModal = ({ isOpen, onDismiss, onAccept }: Props) = setStatus('firing'); }} > - <> - - -
- -
-
- -
-
- setStatus(value)} /> - -
-
-
- + + +
+ +
+
+ +
+
+ setStatus(value)} /> + +
+
+
{alerts.length > 0 && ( diff --git a/public/app/features/alerting/unified/components/receivers/form/ReceiverForm.tsx b/public/app/features/alerting/unified/components/receivers/form/ReceiverForm.tsx index b3bee2c5074..74c2058a999 100644 --- a/public/app/features/alerting/unified/components/receivers/form/ReceiverForm.tsx +++ b/public/app/features/alerting/unified/components/receivers/form/ReceiverForm.tsx @@ -197,38 +197,36 @@ export function ReceiverForm({ /> ); })} - <> + {isEditable && ( + + )} +
{isEditable && ( - + <> + {isSubmitting && ( + + )} + {!isSubmitting && } + )} -
- {isEditable && ( - <> - {isSubmitting && ( - - )} - {!isSubmitting && } - - )} - - Cancel - -
- + + Cancel + +
); diff --git a/public/app/features/alerting/unified/components/receivers/form/fields/DeletedSubform.tsx b/public/app/features/alerting/unified/components/receivers/form/fields/DeletedSubform.tsx index 256b20ecfa3..bb5537cbbee 100644 --- a/public/app/features/alerting/unified/components/receivers/form/fields/DeletedSubform.tsx +++ b/public/app/features/alerting/unified/components/receivers/form/fields/DeletedSubform.tsx @@ -17,5 +17,5 @@ export function DeletedSubForm({ pathPrefix }: Props): JSX.Element { register(`${pathPrefix}.__deleted`); }, [register, pathPrefix]); - return <>; + return <>{null}; } diff --git a/public/app/features/alerting/unified/components/rule-editor/NotificationsStep.tsx b/public/app/features/alerting/unified/components/rule-editor/NotificationsStep.tsx index 5bdcb964f13..7d1d429b312 100644 --- a/public/app/features/alerting/unified/components/rule-editor/NotificationsStep.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/NotificationsStep.tsx @@ -240,16 +240,12 @@ function NeedHelpInfoForNotificationPolicy() { contentText={ - <> - Firing alert instances are routed to notification policies based on matching labels. The default - notification policy matches all alert instances. - + Firing alert instances are routed to notification policies based on matching labels. The default + notification policy matches all alert instances. - <> - Custom labels change the way your notifications are routed. First, add labels to your alert rule and then - connect them to your notification policy by adding label matchers. - + Custom labels change the way your notifications are routed. First, add labels to your alert rule and then + connect them to your notification policy by adding label matchers. - - -
e.preventDefault()}> -
- - {/* Step 1 */} - - {/* Step 2 */} - - {/* Step 3-4-5 */} - + + + e.preventDefault()}> +
+ + {/* Step 1 */} + + {/* Step 2 */} + + {/* Step 3-4-5 */} + - {/* Step 4 & 5 */} - - {/* Notifications step*/} - - {/* Annotations only for cloud and Grafana */} - - -
- - {exportData && } -
- + {/* Step 4 & 5 */} + + {/* Notifications step*/} + + {/* Annotations only for cloud and Grafana */} + +
+
+ + {exportData && } +
); } diff --git a/public/app/features/alerting/unified/components/rule-editor/notificaton-preview/NotificationRouteDetailsModal.tsx b/public/app/features/alerting/unified/components/rule-editor/notificaton-preview/NotificationRouteDetailsModal.tsx index 8650ba3e3fb..35741d4b2ee 100644 --- a/public/app/features/alerting/unified/components/rule-editor/notificaton-preview/NotificationRouteDetailsModal.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/notificaton-preview/NotificationRouteDetailsModal.tsx @@ -81,13 +81,11 @@ export function NotificationRouteDetailsModal({ {isDefault &&
Default policy
}
{!isDefault && ( - <> - - + )}
diff --git a/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/CloudDataSourceSelector.tsx b/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/CloudDataSourceSelector.tsx index 5bcbaa7e620..c450216bb64 100644 --- a/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/CloudDataSourceSelector.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/CloudDataSourceSelector.tsx @@ -23,38 +23,36 @@ export const CloudDataSourceSelector = ({ disabled, onChangeCloudDatasource }: C const ruleFormType = watch('type'); return ( - <> -
- {(ruleFormType === RuleFormType.cloudAlerting || ruleFormType === RuleFormType.cloudRecording) && ( - - ( - { - // reset expression as they don't need to persist after changing datasources - setValue('expression', ''); - onChange(ds?.name ?? null); - onChangeCloudDatasource(ds?.uid ?? null); - }} - /> - )} - name="dataSourceName" - control={control} - rules={{ - required: { value: true, message: 'Please select a data source' }, - }} - /> - - )} -
- +
+ {(ruleFormType === RuleFormType.cloudAlerting || ruleFormType === RuleFormType.cloudRecording) && ( + + ( + { + // reset expression as they don't need to persist after changing datasources + setValue('expression', ''); + onChange(ds?.name ?? null); + onChangeCloudDatasource(ds?.uid ?? null); + }} + /> + )} + name="dataSourceName" + control={control} + rules={{ + required: { value: true, message: 'Please select a data source' }, + }} + /> + + )} +
); }; diff --git a/public/app/features/alerting/unified/components/rules/EditRuleGroupModal.tsx b/public/app/features/alerting/unified/components/rules/EditRuleGroupModal.tsx index ae15d299c37..0d748604cf4 100644 --- a/public/app/features/alerting/unified/components/rules/EditRuleGroupModal.tsx +++ b/public/app/features/alerting/unified/components/rules/EditRuleGroupModal.tsx @@ -316,124 +316,113 @@ export function EditRuleGroupModalForm(props: ModalFormProps): React.ReactElemen return (
- <> - {!props.hideFolder && ( - - - {nameSpaceLabel} - - } - invalid={Boolean(errors.namespaceName) ? true : undefined} - error={errors.namespaceName?.message} - > - - - {isGrafanaManagedGroup && props.folderUrl && ( - - )} - - )} - - Evaluation group - - } - invalid={!!errors.groupName} - error={errors.groupName?.message} - > - - - - Evaluation interval - - } - invalid={Boolean(errors.groupInterval) ? true : undefined} - error={errors.groupInterval?.message} - > - + {!props.hideFolder && ( + + + {nameSpaceLabel} + + } + invalid={Boolean(errors.namespaceName) ? true : undefined} + error={errors.namespaceName?.message} + > - setValue('groupInterval', value, { shouldValidate: true, shouldDirty: true })} - /> - - - - {/* if we're dealing with a Grafana-managed group, check if the evaluation interval is valid / permitted */} - {isGrafanaManagedGroup && checkEvaluationIntervalGlobalLimit(watch('groupInterval')).exceedsLimit && ( - - )} - - {!hasSomeNoRecordingRules &&
This group does not contain alert rules.
} - {hasSomeNoRecordingRules && ( - <> -
List of rules that belong to this group
-
- #Eval column represents the number of evaluations needed before alert starts firing. -
- - - )} - {error && {stringifyErrorLike(error)}} -
- - - - -
- + icon="folder-open" + target="_blank" + /> + )} + + )} + + Evaluation group + + } + invalid={!!errors.groupName} + error={errors.groupName?.message} + > + + + + Evaluation interval + + } + invalid={Boolean(errors.groupInterval) ? true : undefined} + error={errors.groupInterval?.message} + > + + + setValue('groupInterval', value, { shouldValidate: true, shouldDirty: true })} + /> + + + + {/* if we're dealing with a Grafana-managed group, check if the evaluation interval is valid / permitted */} + {isGrafanaManagedGroup && checkEvaluationIntervalGlobalLimit(watch('groupInterval')).exceedsLimit && ( + + )} + + {!hasSomeNoRecordingRules &&
This group does not contain alert rules.
} + {hasSomeNoRecordingRules && ( + <> +
List of rules that belong to this group
+
+ #Eval column represents the number of evaluations needed before alert starts firing. +
+ + + )} + {error && {stringifyErrorLike(error)}} +
+ + + + +
); diff --git a/public/app/features/alerting/unified/components/rules/Filter/RulesFilter.v2.tsx b/public/app/features/alerting/unified/components/rules/Filter/RulesFilter.v2.tsx index 1c682cc689e..aa5c9edafec 100644 --- a/public/app/features/alerting/unified/components/rules/Filter/RulesFilter.v2.tsx +++ b/public/app/features/alerting/unified/components/rules/Filter/RulesFilter.v2.tsx @@ -158,54 +158,52 @@ const SavedSearches = () => { const applySearch = useCallback((name: string) => {}, []); return ( - <> - - - - columns={[ - { - id: 'name', - header: 'Saved search name', - cell: ({ row }) => ( - - {row.original.name} - {row.original.default ? : null} - - ), - }, - { - id: 'actions', - cell: ({ row }) => ( - - - - - ), - }, - ]} - data={[ - { - name: 'My saved search', - default: true, - }, - { - name: 'Another saved search', - }, - { - name: 'This one has a really long name and some emojis too 🥒', - }, - ]} - getRowId={(row) => row.name} - /> - - - + + + + columns={[ + { + id: 'name', + header: 'Saved search name', + cell: ({ row }) => ( + + {row.original.name} + {row.original.default ? : null} + + ), + }, + { + id: 'actions', + cell: ({ row }) => ( + + + + + ), + }, + ]} + data={[ + { + name: 'My saved search', + default: true, + }, + { + name: 'Another saved search', + }, + { + name: 'This one has a really long name and some emojis too 🥒', + }, + ]} + getRowId={(row) => row.name} + /> + + ); }; diff --git a/public/app/features/alerting/unified/components/rules/NoRulesCTA.tsx b/public/app/features/alerting/unified/components/rules/NoRulesCTA.tsx index f33db5d9962..37d977b004d 100644 --- a/public/app/features/alerting/unified/components/rules/NoRulesCTA.tsx +++ b/public/app/features/alerting/unified/components/rules/NoRulesCTA.tsx @@ -82,17 +82,15 @@ export const NoRulesSplash = () => { ) : null } > - <> - - You can also define rules through file provisioning or Terraform.{' '} - - Learn more - - - + + You can also define rules through file provisioning or Terraform.{' '} + + Learn more + +
); diff --git a/public/app/features/alerting/unified/components/rules/RulesFilter.test.tsx b/public/app/features/alerting/unified/components/rules/RulesFilter.test.tsx index 1484cfaa4ec..720e0ed1275 100644 --- a/public/app/features/alerting/unified/components/rules/RulesFilter.test.tsx +++ b/public/app/features/alerting/unified/components/rules/RulesFilter.test.tsx @@ -16,7 +16,7 @@ jest.mock('./MultipleDataSourcePicker', () => { const original = jest.requireActual('./MultipleDataSourcePicker'); return { ...original, - MultipleDataSourcePicker: () => <>, + MultipleDataSourcePicker: () => null, }; }); diff --git a/public/app/features/alerting/unified/components/rules/central-state-history/EventDetails.tsx b/public/app/features/alerting/unified/components/rules/central-state-history/EventDetails.tsx index a037703c420..cfc14895d4c 100644 --- a/public/app/features/alerting/unified/components/rules/central-state-history/EventDetails.tsx +++ b/public/app/features/alerting/unified/components/rules/central-state-history/EventDetails.tsx @@ -220,19 +220,17 @@ const Annotations = ({ rule }: AnnotationsProps) => { return null; } return ( - <> -
- {Object.entries(annotations).map(([name, value]) => { - const capitalizedName = capitalize(name); - return ( - - {capitalizedName} - - - ); - })} -
- +
+ {Object.entries(annotations).map(([name, value]) => { + const capitalizedName = capitalize(name); + return ( + + {capitalizedName} + + + ); + })} +
); }; interface ValueInTransitionProps { diff --git a/public/app/features/alerting/unified/components/rules/state-history/LokiStateHistory.tsx b/public/app/features/alerting/unified/components/rules/state-history/LokiStateHistory.tsx index 76ba417dd20..a449b7ceee3 100644 --- a/public/app/features/alerting/unified/components/rules/state-history/LokiStateHistory.tsx +++ b/public/app/features/alerting/unified/components/rules/state-history/LokiStateHistory.tsx @@ -114,16 +114,14 @@ const LokiStateHistory = ({ ruleUID }: Props) => { )} {isEmpty(frameSubset) ? ( - <> -
- {emptyStateMessage} - {totalRecordsCount > 0 && ( - - )} -
- +
+ {emptyStateMessage} + {totalRecordsCount > 0 && ( + + )} +
) : ( <>
diff --git a/public/app/features/alerting/unified/home/Insights.tsx b/public/app/features/alerting/unified/home/Insights.tsx index 9d85797751b..14bd54b0682 100644 --- a/public/app/features/alerting/unified/home/Insights.tsx +++ b/public/app/features/alerting/unified/home/Insights.tsx @@ -176,24 +176,22 @@ export function getInsightsScenes() { component: SectionSubheader, props: { children: ( - <> - - Monitor the status of your system{' '} - - Alerting insights provides pre-built dashboards to monitor your alerting data. -
-
- You can identify patterns in why things go wrong and discover trends in alerting performance - within your organization. -
- } - > - - - - + + Monitor the status of your system{' '} + + Alerting insights provides pre-built dashboards to monitor your alerting data. +
+
+ You can identify patterns in why things go wrong and discover trends in alerting performance within + your organization. +
+ } + > + + + ), }, }), diff --git a/public/app/features/alerting/unified/rule-list/components/RuleGroupActionsMenu.tsx b/public/app/features/alerting/unified/rule-list/components/RuleGroupActionsMenu.tsx index 7c2735ea5cd..5b61f83d543 100644 --- a/public/app/features/alerting/unified/rule-list/components/RuleGroupActionsMenu.tsx +++ b/public/app/features/alerting/unified/rule-list/components/RuleGroupActionsMenu.tsx @@ -3,20 +3,18 @@ import { t } from 'app/core/internationalization'; export function RuleGroupActionsMenu() { return ( - <> - - - - - - - - } - > - - - + + + + + + + + } + > + + ); } From 943b73a68200dfccb227bcc9906fc6d1916bfc87 Mon Sep 17 00:00:00 2001 From: Yuri Tseretyan Date: Tue, 11 Mar 2025 16:58:26 -0400 Subject: [PATCH 025/141] Alerting: Add scheduled clean-up of deleted rules (#101963) * add scheduled clean up of deleted rules --------- Signed-off-by: Yuri Tseretyan --- conf/defaults.ini | 8 ++ conf/sample.ini | 8 ++ pkg/server/wire.go | 3 + pkg/services/cleanup/cleanup.go | 22 +++- pkg/services/ngalert/store/alert_rule.go | 20 ++- pkg/services/ngalert/store/alert_rule_test.go | 124 +++++++++++++++++- pkg/setting/setting_unified_alerting.go | 8 ++ 7 files changed, 190 insertions(+), 3 deletions(-) diff --git a/conf/defaults.ini b/conf/defaults.ini index ee703f76d2a..8fc8296fead 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -1406,6 +1406,14 @@ resolved_alert_retention = 15m # 0 value means no limit rule_version_record_limit = 0 +# The retention period for deleted alerting rules. +# Determines how long deleted rules are retained before being permanently removed. +# The retention duration must be specified using a time format with unit suffixes +# such as ms, s, m, h, d (e.g., 30d for 30 days). +# Default: 30d +# 0 value means that rules are deleted permanently immediately. +deleted_rule_retention = 30d + [unified_alerting.screenshots] # Enable screenshots in notifications. You must have either installed the Grafana image rendering # plugin, or set up Grafana to use a remote rendering service. diff --git a/conf/sample.ini b/conf/sample.ini index 207fa535410..152fbf6fb96 100644 --- a/conf/sample.ini +++ b/conf/sample.ini @@ -1389,6 +1389,14 @@ # 0 value means no limit ;rule_version_record_limit= 0 +# The retention period for deleted alerting rules. +# Determines how long deleted rules are retained before being permanently removed. +# The retention duration must be specified using a time format with unit suffixes +# such as ms, s, m, h, d (e.g., 30d for 30 days). +# Default: 30d +# 0 value means that rules are deleted permanently immediately. +;deleted_rule_retention = 30d + [unified_alerting.screenshots] # Enable screenshots in notifications. You must have either installed the Grafana image rendering # plugin, or set up Grafana to use a remote rendering service. diff --git a/pkg/server/wire.go b/pkg/server/wire.go index 82b7e891669..c24a40e80d4 100644 --- a/pkg/server/wire.go +++ b/pkg/server/wire.go @@ -10,6 +10,7 @@ import ( "github.com/google/wire" sdkhttpclient "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient" + "github.com/grafana/grafana/pkg/api" "github.com/grafana/grafana/pkg/api/avatar" "github.com/grafana/grafana/pkg/api/routing" @@ -421,6 +422,7 @@ var wireSet = wire.NewSet( prefimpl.ProvideService, oauthtoken.ProvideService, wire.Bind(new(oauthtoken.OAuthTokenService), new(*oauthtoken.Service)), + wire.Bind(new(cleanup.AlertRuleService), new(*ngstore.DBstore)), ) var wireCLISet = wire.NewSet( @@ -453,6 +455,7 @@ var wireTestSet = wire.NewSet( oauthtoken.ProvideService, oauthtokentest.ProvideService, wire.Bind(new(oauthtoken.OAuthTokenService), new(*oauthtokentest.Service)), + wire.Bind(new(cleanup.AlertRuleService), new(*ngstore.DBstore)), ) func Initialize(cfg *setting.Cfg, opts Options, apiOpts api.ServerOptions) (*Server, error) { diff --git a/pkg/services/cleanup/cleanup.go b/pkg/services/cleanup/cleanup.go index 240bcf532fd..479d34a6a75 100644 --- a/pkg/services/cleanup/cleanup.go +++ b/pkg/services/cleanup/cleanup.go @@ -27,6 +27,10 @@ import ( "github.com/grafana/grafana/pkg/setting" ) +type AlertRuleService interface { + CleanUpDeletedAlertRules(ctx context.Context) (int64, error) +} + type CleanUpService struct { log log.Logger tracer tracing.Tracer @@ -41,12 +45,13 @@ type CleanUpService struct { tempUserService tempuser.Service annotationCleaner annotations.Cleaner dashboardService dashboards.DashboardService + alertRuleService AlertRuleService } func ProvideService(cfg *setting.Cfg, serverLockService *serverlock.ServerLockService, shortURLService shorturls.Service, sqlstore db.DB, queryHistoryService queryhistory.Service, dashboardVersionService dashver.Service, dashSnapSvc dashboardsnapshots.Service, deleteExpiredImageService *image.DeleteExpiredService, - tempUserService tempuser.Service, tracer tracing.Tracer, annotationCleaner annotations.Cleaner, dashboardService dashboards.DashboardService) *CleanUpService { + tempUserService tempuser.Service, tracer tracing.Tracer, annotationCleaner annotations.Cleaner, dashboardService dashboards.DashboardService, service AlertRuleService) *CleanUpService { s := &CleanUpService{ Cfg: cfg, ServerLockService: serverLockService, @@ -61,6 +66,7 @@ func ProvideService(cfg *setting.Cfg, serverLockService *serverlock.ServerLockSe tracer: tracer, annotationCleaner: annotationCleaner, dashboardService: dashboardService, + alertRuleService: service, } return s } @@ -112,6 +118,10 @@ func (srv *CleanUpService) clean(ctx context.Context) { cleanupJobs = append(cleanupJobs, cleanUpJob{"delete stale short URLs", srv.deleteStaleShortURLs}) } + if srv.Cfg.UnifiedAlerting.DeletedRuleRetention > 0 { + cleanupJobs = append(cleanupJobs, cleanUpJob{"cleanup trash alert rules", srv.cleanUpTrashAlertRules}) + } + logger := srv.log.FromContext(ctx) logger.Debug("Starting cleanup jobs", "jobs", fmt.Sprintf("%v", cleanupJobs)) @@ -313,3 +323,13 @@ func (srv *CleanUpService) cleanUpTrashDashboards(ctx context.Context) { logger.Debug("Cleaned up deleted dashboards", "dashboards affected", affected) } } + +func (srv *CleanUpService) cleanUpTrashAlertRules(ctx context.Context) { + logger := srv.log.FromContext(ctx) + affected, err := srv.alertRuleService.CleanUpDeletedAlertRules(ctx) + if err != nil { + logger.Error("Problem cleaning up deleted alert rules", "error", err) + } else { + logger.Debug("Cleaned up deleted alert rules", "rows affected", affected) + } +} diff --git a/pkg/services/ngalert/store/alert_rule.go b/pkg/services/ngalert/store/alert_rule.go index d4fb0202a78..ccba09c5a3b 100644 --- a/pkg/services/ngalert/store/alert_rule.go +++ b/pkg/services/ngalert/store/alert_rule.go @@ -73,7 +73,7 @@ func (st DBstore) DeleteAlertRulesByUID(ctx context.Context, orgID int64, user * logger.Debug("Deleted alert rule state", "count", rows) var versions []alertRuleVersion - if st.FeatureToggles.IsEnabledGlobally(featuremgmt.FlagAlertRuleRestore) { + if st.FeatureToggles.IsEnabledGlobally(featuremgmt.FlagAlertRuleRestore) && st.Cfg.DeletedRuleRetention > 0 { // save deleted version only if retention is greater than 0 versions, err = st.getLatestVersionOfRulesByUID(ctx, orgID, ruleUID) if err != nil { logger.Error("Failed to get latest version of deleted alert rules. The recovery will not be possible", "error", err) @@ -1243,6 +1243,24 @@ func (st DBstore) GetNamespacesByRuleUID(ctx context.Context, orgID int64, uids return result, err } +func (st DBstore) CleanUpDeletedAlertRules(ctx context.Context) (int64, error) { + affectedRows := int64(-1) + err := st.SQLStore.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error { + expire := TimeNow().Add(-st.Cfg.DeletedRuleRetention) + st.Logger.Debug("Permanently remove expired deleted rules", "deletedBefore", expire) + result, err := sess.Exec("DELETE FROM alert_rule_version WHERE rule_uid='' AND created <= ?", expire) + if err != nil { + return err + } + affectedRows, err = result.RowsAffected() + if err != nil { + st.Logger.Warn("Failed to get rows affected by the delete operation", "error", err) + } + return nil + }) + return affectedRows, err +} + func getINSubQueryArgs[T any](inputSlice []T) ([]any, []string) { args := make([]any, 0, len(inputSlice)) in := make([]string, 0, len(inputSlice)) diff --git a/pkg/services/ngalert/store/alert_rule_test.go b/pkg/services/ngalert/store/alert_rule_test.go index c3e94e7e806..46fc19eb1fb 100644 --- a/pkg/services/ngalert/store/alert_rule_test.go +++ b/pkg/services/ngalert/store/alert_rule_test.go @@ -784,13 +784,15 @@ func TestIntegration_DeleteAlertRulesByUID(t *testing.T) { require.Empty(t, savedInstances) }) - t.Run("should remove all version and insert one with empty rule_uid", func(t *testing.T) { + t.Run("should remove all version and insert one with empty rule_uid when DeletedRuleRetention is set", func(t *testing.T) { orgID := int64(rand.Intn(1000)) gen = gen.With(gen.WithOrgID(orgID)) // Create a new store to pass the custom bus to check the signal b := &fakeBus{} logger := log.New("test-dbstore") + cfg.UnifiedAlerting.DeletedRuleRetention = 1000 * time.Hour + store := createTestStore(sqlStore, folderService, logger, cfg.UnifiedAlerting, b) store.FeatureToggles = featuremgmt.WithFeatures(featuremgmt.FlagAlertRuleRestore) @@ -848,6 +850,59 @@ func TestIntegration_DeleteAlertRulesByUID(t *testing.T) { return nil }) }) + + t.Run("should remove all versions and not keep history if DeletedRuleRetention = 0", func(t *testing.T) { + orgID := int64(rand.Intn(1000)) + gen = gen.With(gen.WithOrgID(orgID)) + // Create a new store to pass the custom bus to check the signal + b := &fakeBus{} + logger := log.New("test-dbstore") + + cfg.UnifiedAlerting.DeletedRuleRetention = 0 + + store := createTestStore(sqlStore, folderService, logger, cfg.UnifiedAlerting, b) + store.FeatureToggles = featuremgmt.WithFeatures(featuremgmt.FlagAlertRuleRestore) + + result, err := store.InsertAlertRules(context.Background(), &models.AlertingUserUID, gen.GenerateMany(3)) + uids := make([]string, 0, len(result)) + for _, rule := range result { + uids = append(uids, rule.UID) + } + require.NoError(t, err) + rules, err := store.ListAlertRules(context.Background(), &models.ListAlertRulesQuery{OrgID: orgID, RuleUIDs: uids}) + require.NoError(t, err) + + updates := make([]models.UpdateRule, 0, len(rules)) + for _, rule := range rules { + rule2 := models.CopyRule(rule, gen.WithTitle(util.GenerateShortUID())) + updates = append(updates, models.UpdateRule{ + Existing: rule, + New: *rule2, + }) + } + err = store.UpdateAlertRules(context.Background(), &models.AlertingUserUID, updates) + require.NoError(t, err) + + versions, err := store.GetAlertRuleVersions(context.Background(), orgID, rules[0].GUID) + require.NoError(t, err) + require.Len(t, versions, 2) + + err = store.DeleteAlertRulesByUID(context.Background(), orgID, util.Pointer(models.UserUID("test")), uids...) + require.NoError(t, err) + + guids := make([]string, 0, len(rules)) + for _, rule := range rules { + guids = append(guids, rule.GUID) + } + + _ = sqlStore.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error { + var versions []alertRuleVersion + err = sess.Table(alertRuleVersion{}).Where(`rule_uid = ''`).In("rule_guid", guids).Find(&versions) + require.NoError(t, err) + require.Emptyf(t, versions, "some rules were not permanently deleted") // should be one version per GUID + return nil + }) + }) } func TestIntegrationInsertAlertRules(t *testing.T) { @@ -1962,6 +2017,7 @@ func TestIntegration_ListDeletedRules(t *testing.T) { cfg.UnifiedAlerting = setting.UnifiedAlertingSettings{ BaseInterval: 1 * time.Second, RuleVersionRecordLimit: -1, + DeletedRuleRetention: 10 * time.Hour, } sqlStore := db.InitTestDB(t) folderService := setupFolderService(t, sqlStore, cfg, featuremgmt.WithFeatures()) @@ -2011,6 +2067,72 @@ func TestIntegration_ListDeletedRules(t *testing.T) { }) } +func TestIntegration_CleanUpDeletedAlertRules(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test") + } + + oldClk := TimeNow + t.Cleanup(func() { + TimeNow = oldClk + }) + + t0 := time.Now().UTC().Truncate(time.Second) + TimeNow = func() time.Time { + return t0 + } + + sqlStore := db.InitTestDB(t, sqlstore.InitTestDBOpt{ + Cfg: nil, + }) + cfg := setting.NewCfg() + cfg.UnifiedAlerting.BaseInterval = 1 * time.Second + cfg.UnifiedAlerting.RuleVersionRecordLimit = -1 + cfg.UnifiedAlerting.DeletedRuleRetention = 10 * time.Second + + folderService := setupFolderService(t, sqlStore, cfg, featuremgmt.WithFeatures()) + logger := log.New("test-dbstore") + store := createTestStore(sqlStore, folderService, logger, cfg.UnifiedAlerting, &fakeBus{}) + store.FeatureToggles = featuremgmt.WithFeatures(featuremgmt.FlagAlertRuleRestore) + + gen := models.RuleGen + orgID := int64(rand.Intn(1000)) + + gen = gen.With(gen.WithOrgID(orgID)) + + result, err := store.InsertAlertRules(context.Background(), &models.AlertingUserUID, gen.GenerateMany(3)) + uids := make([]string, 0, len(result)) + for _, rule := range result { + uids = append(uids, rule.UID) + } + require.NoError(t, err) + + // simulate rule deletion at different time. + // t0, t0+10s, t0+20s + for idx, uid := range uids { + TimeNow = func() time.Time { + return t0.Add(time.Duration(idx) * 10 * time.Second) + } + err = store.DeleteAlertRulesByUID(context.Background(), orgID, util.Pointer(models.UserUID("test")), uid) + require.NoError(t, err) + } + + before, err := store.ListDeletedRules(context.Background(), orgID) + require.NoError(t, err) + require.Len(t, before, 3) + + // retention is 10s, now=t+20s, therefore, only one row should be deleted + _, err = store.CleanUpDeletedAlertRules(context.Background()) + require.NoError(t, err) + + after, err := store.ListDeletedRules(context.Background(), orgID) + require.NoError(t, err) + assert.Len(t, after, 1) + for _, rule := range after { + assert.GreaterOrEqual(t, rule.Updated, TimeNow().Add(-cfg.UnifiedAlerting.DeletedRuleRetention)) + } +} + func createTestStore( sqlStore db.DB, folderService folder.Service, diff --git a/pkg/setting/setting_unified_alerting.go b/pkg/setting/setting_unified_alerting.go index 33f002cb571..b14d22b97bc 100644 --- a/pkg/setting/setting_unified_alerting.go +++ b/pkg/setting/setting_unified_alerting.go @@ -129,6 +129,9 @@ type UnifiedAlertingSettings struct { // should be stored in the database for each alert_rule in an organization including the current one. // 0 value means no limit RuleVersionRecordLimit int + + // DeletedRuleRetention defines the maximum duration to retain deleted alerting rules before permanent removal. + DeletedRuleRetention time.Duration } type RecordingRuleSettings struct { @@ -477,6 +480,11 @@ func (cfg *Cfg) ReadUnifiedAlertingSettings(iniFile *ini.File) error { return fmt.Errorf("setting 'rule_version_record_limit' is invalid, only 0 or a positive integer are allowed") } + uaCfg.DeletedRuleRetention = ua.Key("deleted_rule_retention").MustDuration(30 * 24 * time.Hour) + if uaCfg.DeletedRuleRetention < 0 { + return fmt.Errorf("setting 'deleted_rule_retention' is invalid, only 0 or a positive duration are allowed") + } + cfg.UnifiedAlerting = uaCfg return nil } From 7dd6f526306608941ec65a5bed248c0ca6f75c2a Mon Sep 17 00:00:00 2001 From: Alexander Akhmetov Date: Tue, 11 Mar 2025 22:12:06 +0100 Subject: [PATCH 026/141] Alerting: Add MissingSeriesEvalsToResolve option to the AlertRule (#101184) --- go.work.sum | 4 +- pkg/services/ngalert/models/alert_rule.go | 61 ++- .../ngalert/models/alert_rule_test.go | 79 +++- pkg/services/ngalert/models/testing.go | 57 +-- .../ngalert/schedule/registry_test.go | 3 + pkg/services/ngalert/state/manager.go | 16 +- .../ngalert/state/manager_private_test.go | 382 +++++++++++++++++- pkg/services/ngalert/state/manager_test.go | 2 +- pkg/services/ngalert/store/compat.go | 148 +++---- pkg/services/ngalert/store/models.go | 67 +-- .../sqlstore/migrations/migrations.go | 2 + ...rt_rule_missing_series_evals_to_resolve.go | 17 + 12 files changed, 665 insertions(+), 173 deletions(-) create mode 100644 pkg/services/sqlstore/migrations/ualert/alert_rule_missing_series_evals_to_resolve.go diff --git a/go.work.sum b/go.work.sum index c9cfa4ae15a..f29575e7e8d 100644 --- a/go.work.sum +++ b/go.work.sum @@ -692,7 +692,6 @@ github.com/couchbase/ghistogram v0.1.0/go.mod h1:s1Jhy76zqfEecpNWJfWUiKZookAFaiG 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.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/creasty/defaults v1.8.0 h1:z27FJxCAa0JKt3utc0sCImAEb+spPucmKoOdLHvHYKk= @@ -923,6 +922,7 @@ github.com/grafana/tail v0.0.0-20230510142333-77b18831edf0/go.mod h1:7t5XR+2IA8P 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.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.0/go.mod h1:qOchhhIlmRcqk/O9uCo/puJlyo07YINaIqdZfZG3Jkc= github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1/go.mod h1:5SN9VR2LTsRFsrEC6FHgRbTWrTHu6tqPeKxEQv15giM= github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= @@ -1197,7 +1197,6 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5I github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= 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 v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= 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= @@ -1460,7 +1459,6 @@ golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= -golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM= golang.org/x/oauth2 v0.19.0/go.mod h1:vYi7skDa1x015PmRRYZ7+s1cWyPgrPiSYRe4rnsexc8= golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= diff --git a/pkg/services/ngalert/models/alert_rule.go b/pkg/services/ngalert/models/alert_rule.go index 69219b8b09f..022599a7158 100644 --- a/pkg/services/ngalert/models/alert_rule.go +++ b/pkg/services/ngalert/models/alert_rule.go @@ -294,6 +294,11 @@ type AlertRule struct { IsPaused bool NotificationSettings []NotificationSettings Metadata AlertRuleMetadata + // MissingSeriesEvalsToResolve specifies the number of consecutive evaluation intervals + // required before resolving an alert state (a dimension) when data is missing. + // If nil, alerts resolve after 2 missing evaluation intervals + // (i.e., resolution occurs during the second evaluation where data is absent). + MissingSeriesEvalsToResolve *int } type AlertRuleMetadata struct { @@ -578,6 +583,18 @@ func (alertRule *AlertRule) GetGroupKey() AlertRuleGroupKey { return AlertRuleGroupKey{OrgID: alertRule.OrgID, NamespaceUID: alertRule.NamespaceUID, RuleGroup: alertRule.RuleGroup} } +// GetMissingSeriesEvalsToResolve returns the number of consecutive evaluation intervals +// to wait before resolving an alert rule instance when its data is missing. +// If not configured, it returns the default value (2), which means the alert +// resolves after missing for two evaluation intervals. +func (alertRule *AlertRule) GetMissingSeriesEvalsToResolve() int { + if alertRule.MissingSeriesEvalsToResolve == nil { + return 2 // default value + } + + return *alertRule.MissingSeriesEvalsToResolve +} + // PreSave sets default values and loads the updated model for each alert query. func (alertRule *AlertRule) PreSave(timeNow func() time.Time, userUID *UserUID) error { for i, q := range alertRule.Data { @@ -659,6 +676,10 @@ func validateAlertRuleFields(rule *AlertRule) error { return err } + if rule.MissingSeriesEvalsToResolve != nil && *rule.MissingSeriesEvalsToResolve <= 0 { + return fmt.Errorf("%w: field `missing_series_evals_to_resolve` must be greater than 0", ErrAlertRuleFailedValidation) + } + return nil } @@ -708,25 +729,26 @@ func (alertRule *AlertRule) Copy() *AlertRule { return nil } result := AlertRule{ - ID: alertRule.ID, - GUID: alertRule.GUID, - OrgID: alertRule.OrgID, - Title: alertRule.Title, - Condition: alertRule.Condition, - Updated: alertRule.Updated, - UpdatedBy: alertRule.UpdatedBy, - IntervalSeconds: alertRule.IntervalSeconds, - Version: alertRule.Version, - UID: alertRule.UID, - NamespaceUID: alertRule.NamespaceUID, - RuleGroup: alertRule.RuleGroup, - RuleGroupIndex: alertRule.RuleGroupIndex, - NoDataState: alertRule.NoDataState, - ExecErrState: alertRule.ExecErrState, - For: alertRule.For, - Record: alertRule.Record, - IsPaused: alertRule.IsPaused, - Metadata: alertRule.Metadata, + ID: alertRule.ID, + GUID: alertRule.GUID, + OrgID: alertRule.OrgID, + Title: alertRule.Title, + Condition: alertRule.Condition, + Updated: alertRule.Updated, + UpdatedBy: alertRule.UpdatedBy, + IntervalSeconds: alertRule.IntervalSeconds, + Version: alertRule.Version, + UID: alertRule.UID, + NamespaceUID: alertRule.NamespaceUID, + RuleGroup: alertRule.RuleGroup, + RuleGroupIndex: alertRule.RuleGroupIndex, + NoDataState: alertRule.NoDataState, + ExecErrState: alertRule.ExecErrState, + For: alertRule.For, + Record: alertRule.Record, + IsPaused: alertRule.IsPaused, + Metadata: alertRule.Metadata, + MissingSeriesEvalsToResolve: alertRule.MissingSeriesEvalsToResolve, } if alertRule.DashboardUID != nil { @@ -789,6 +811,7 @@ func ClearRecordingRuleIgnoredFields(rule *AlertRule) { rule.Condition = "" rule.For = 0 rule.NotificationSettings = nil + rule.MissingSeriesEvalsToResolve = nil } // GetAlertRuleByUIDQuery is the query for retrieving/deleting an alert rule by UID and organisation ID. diff --git a/pkg/services/ngalert/models/alert_rule_test.go b/pkg/services/ngalert/models/alert_rule_test.go index b8e47df7bbf..978b6d49127 100644 --- a/pkg/services/ngalert/models/alert_rule_test.go +++ b/pkg/services/ngalert/models/alert_rule_test.go @@ -18,6 +18,7 @@ import ( "golang.org/x/exp/maps" "gopkg.in/yaml.v3" + "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/util/cmputil" ) @@ -386,6 +387,7 @@ func TestPatchPartialAlertRule(t *testing.T) { }) } +// nolint:gocyclo func TestDiff(t *testing.T) { t.Run("should return nil if there is no diff", func(t *testing.T) { rule1 := RuleGen.GenerateRef() @@ -406,7 +408,9 @@ func TestDiff(t *testing.T) { t.Run("should find diff in simple fields", func(t *testing.T) { rule1 := RuleGen.GenerateRef() - rule2 := RuleGen.GenerateRef() + rule2 := RuleGen.With( + RuleGen.WithMissingSeriesEvalsToResolve(*rule1.MissingSeriesEvalsToResolve + 1), + ).GenerateRef() diffs := rule1.Diff(rule2, "Data", "Annotations", "Labels", "NotificationSettings", "Metadata") // these fields will be tested separately @@ -540,6 +544,13 @@ func TestDiff(t *testing.T) { assert.Equal(t, rule2.Record, diff[0].Right.String()) difCnt++ } + if rule1.MissingSeriesEvalsToResolve != rule2.MissingSeriesEvalsToResolve { + diff := diffs.GetDiffsForField("MissingSeriesEvalsToResolve") + assert.Len(t, diff, 1) + assert.Equal(t, *rule1.MissingSeriesEvalsToResolve, int(diff[0].Left.Int())) + assert.Equal(t, *rule2.MissingSeriesEvalsToResolve, int(diff[0].Right.Int())) + difCnt++ + } require.Lenf(t, diffs, difCnt, "Got some unexpected diffs. Either add to ignore or add assert to it") @@ -963,6 +974,21 @@ func TestAlertRuleGetKeyWithGroup(t *testing.T) { }) } +func TestAlertRuleGetMissingSeriesEvalsToResolve(t *testing.T) { + t.Run("should return the default 2 if MissingSeriesEvalsToResolve is nil", func(t *testing.T) { + rule := RuleGen.GenerateRef() + rule.MissingSeriesEvalsToResolve = nil + require.Equal(t, 2, rule.GetMissingSeriesEvalsToResolve()) + }) + + t.Run("should return the correct value", func(t *testing.T) { + rule := RuleGen.With( + RuleMuts.WithMissingSeriesEvalsToResolve(3), + ).GenerateRef() + require.Equal(t, 3, rule.GetMissingSeriesEvalsToResolve()) + }) +} + func TestAlertRuleCopy(t *testing.T) { t.Run("should return a copy of the rule", func(t *testing.T) { for i := 0; i < 100; i++ { @@ -1084,3 +1110,54 @@ func TestAlertRule_PrometheusRuleDefinition(t *testing.T) { }) } } + +func TestMissingSeriesEvalsToResolveValidation(t *testing.T) { + testCases := []struct { + name string + missingSeriesEvalsToResolve *int + expectedErrorContains string + }{ + { + name: "should allow nil value", + missingSeriesEvalsToResolve: nil, + }, + { + name: "should reject negative value", + missingSeriesEvalsToResolve: util.Pointer(-1), + expectedErrorContains: "field `missing_series_evals_to_resolve` must be greater than 0", + }, + { + name: "should reject 0", + missingSeriesEvalsToResolve: util.Pointer(0), + expectedErrorContains: "field `missing_series_evals_to_resolve` must be greater than 0", + }, + { + name: "should accept positive value", + missingSeriesEvalsToResolve: util.Pointer(2), + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + baseIntervalSeconds := int64(10) + cfg := setting.UnifiedAlertingSettings{ + BaseInterval: time.Duration(baseIntervalSeconds) * time.Second, + } + + rule := RuleGen.With( + RuleMuts.WithIntervalSeconds(baseIntervalSeconds * 2), + ).Generate() + rule.MissingSeriesEvalsToResolve = tc.missingSeriesEvalsToResolve + + err := rule.ValidateAlertRule(cfg) + + if tc.expectedErrorContains != "" { + require.Error(t, err) + require.ErrorIs(t, err, ErrAlertRuleFailedValidation) + require.Contains(t, err.Error(), tc.expectedErrorContains) + } else { + require.NoError(t, err) + } + }) + } +} diff --git a/pkg/services/ngalert/models/testing.go b/pkg/services/ngalert/models/testing.go index eb9d838f8df..242aafc0025 100644 --- a/pkg/services/ngalert/models/testing.go +++ b/pkg/services/ngalert/models/testing.go @@ -103,29 +103,30 @@ func (g *AlertRuleGenerator) Generate() AlertRule { } rule := AlertRule{ - ID: 0, - GUID: uuid.NewString(), - OrgID: rand.Int63n(1500) + 1, // Prevent OrgID=0 as this does not pass alert rule validation. - Title: fmt.Sprintf("title-%s", util.GenerateShortUID()), - Condition: "A", - Data: []AlertQuery{g.GenerateQuery()}, - Updated: time.Now().Add(-time.Duration(rand.Intn(100) + 1)), - UpdatedBy: updatedBy, - IntervalSeconds: rand.Int63n(60) + 1, - Version: rand.Int63n(1500), // Don't generate a rule ID too big for postgres - UID: util.GenerateShortUID(), - NamespaceUID: util.GenerateShortUID(), - DashboardUID: dashUID, - PanelID: panelID, - RuleGroup: fmt.Sprintf("group-%s,", util.GenerateShortUID()), - RuleGroupIndex: rand.Intn(1500), - NoDataState: randNoDataState(), - ExecErrState: randErrState(), - For: forInterval, - Annotations: annotations, - Labels: labels, - NotificationSettings: ns, - Metadata: GenerateMetadata(), + ID: 0, + GUID: uuid.NewString(), + OrgID: rand.Int63n(1500) + 1, // Prevent OrgID=0 as this does not pass alert rule validation. + Title: fmt.Sprintf("title-%s", util.GenerateShortUID()), + Condition: "A", + Data: []AlertQuery{g.GenerateQuery()}, + Updated: time.Now().Add(-time.Duration(rand.Intn(100) + 1)), + UpdatedBy: updatedBy, + IntervalSeconds: rand.Int63n(60) + 1, + Version: rand.Int63n(1500), // Don't generate a rule ID too big for postgres + UID: util.GenerateShortUID(), + NamespaceUID: util.GenerateShortUID(), + DashboardUID: dashUID, + PanelID: panelID, + RuleGroup: fmt.Sprintf("group-%s,", util.GenerateShortUID()), + RuleGroupIndex: rand.Intn(1500), + NoDataState: randNoDataState(), + ExecErrState: randErrState(), + For: forInterval, + Annotations: annotations, + Labels: labels, + NotificationSettings: ns, + Metadata: GenerateMetadata(), + MissingSeriesEvalsToResolve: util.Pointer(2), } for _, mutator := range g.mutators { @@ -499,6 +500,15 @@ func (a *AlertRuleMutators) WithSameGroup() AlertRuleMutator { } } +func (a *AlertRuleMutators) WithMissingSeriesEvalsToResolve(timesOfInterval int) AlertRuleMutator { + return func(rule *AlertRule) { + if timesOfInterval <= 0 { + panic("timesOfInterval must be greater than 0") + } + rule.MissingSeriesEvalsToResolve = util.Pointer(timesOfInterval) + } +} + func (a *AlertRuleMutators) WithNotificationSettingsGen(ns func() NotificationSettings) AlertRuleMutator { return func(rule *AlertRule) { rule.NotificationSettings = []NotificationSettings{ns()} @@ -1343,6 +1353,7 @@ func ConvertToRecordingRule(rule *AlertRule) { rule.ExecErrState = "" rule.For = 0 rule.NotificationSettings = nil + rule.MissingSeriesEvalsToResolve = nil } func nameToUid(name string) string { // Avoid legacy_storage.NameToUid import cycle. diff --git a/pkg/services/ngalert/schedule/registry_test.go b/pkg/services/ngalert/schedule/registry_test.go index 2a52f67b264..01acc80fa41 100644 --- a/pkg/services/ngalert/schedule/registry_test.go +++ b/pkg/services/ngalert/schedule/registry_test.go @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/require" "github.com/grafana/grafana/pkg/services/ngalert/models" + "github.com/grafana/grafana/pkg/util" ) func TestSchedulableAlertRulesRegistry(t *testing.T) { @@ -211,6 +212,7 @@ func TestRuleWithFolderFingerprint(t *testing.T) { SimplifiedNotificationsSection: false, }, }, + MissingSeriesEvalsToResolve: util.Pointer(2), } r2 := &models.AlertRule{ ID: 2, @@ -255,6 +257,7 @@ func TestRuleWithFolderFingerprint(t *testing.T) { SimplifiedQueryAndExpressionsSection: true, }, }, + MissingSeriesEvalsToResolve: util.Pointer(1), } excludedFields := map[string]struct{}{ diff --git a/pkg/services/ngalert/state/manager.go b/pkg/services/ngalert/state/manager.go index f66002bbb3b..e23d3bce143 100644 --- a/pkg/services/ngalert/state/manager.go +++ b/pkg/services/ngalert/state/manager.go @@ -519,7 +519,7 @@ func (st *Manager) deleteStaleStatesFromCache(logger log.Logger, evaluatedAt tim // If we are removing two or more stale series it makes sense to share the resolved image as the alert rule is the same. // TODO: We will need to change this when we support images without screenshots as each series will have a different image staleStates := st.cache.deleteRuleStates(alertRule.GetKey(), func(s *State) bool { - return stateIsStale(evaluatedAt, s.LastEvaluationTime, alertRule.IntervalSeconds) + return stateIsStale(evaluatedAt, s.LastEvaluationTime, alertRule.IntervalSeconds, alertRule.GetMissingSeriesEvalsToResolve()) }) resolvedStates := make([]StateTransition, 0, len(staleStates)) @@ -551,8 +551,18 @@ func (st *Manager) deleteStaleStatesFromCache(logger log.Logger, evaluatedAt tim return resolvedStates } -func stateIsStale(evaluatedAt time.Time, lastEval time.Time, intervalSeconds int64) bool { - return !lastEval.Add(2 * time.Duration(intervalSeconds) * time.Second).After(evaluatedAt) +// stateIsStale determines whether the evaluation state is considered stale. +// A state is considered stale if the data has been missing for at least missingSeriesEvalsToResolve evaluation intervals. +func stateIsStale(evaluatedAt time.Time, lastEval time.Time, intervalSeconds int64, missingSeriesEvalsToResolve int) bool { + // If the last evaluation time equals the current evaluation time, the state is not stale. + if evaluatedAt.Equal(lastEval) { + return false + } + + resolveIfMissingDuration := time.Duration(int64(missingSeriesEvalsToResolve)*intervalSeconds) * time.Second + + // timeSinceLastEval >= resolveIfMissingDuration + return evaluatedAt.Sub(lastEval) >= resolveIfMissingDuration } func StatesToRuleStatus(states []*State) ngModels.RuleStatus { diff --git a/pkg/services/ngalert/state/manager_private_test.go b/pkg/services/ngalert/state/manager_private_test.go index 16de5cd57ea..42fcddf41a3 100644 --- a/pkg/services/ngalert/state/manager_private_test.go +++ b/pkg/services/ngalert/state/manager_private_test.go @@ -31,40 +31,81 @@ func TestStateIsStale(t *testing.T) { now := time.Now() intervalSeconds := rand.Int63n(10) + 5 + threeIntervals := time.Duration(intervalSeconds) * time.Second * 3 + fourIntervals := time.Duration(intervalSeconds) * time.Second * 4 + fiveIntervals := time.Duration(intervalSeconds) * time.Second * 5 + testCases := []struct { - name string - lastEvaluation time.Time - expectedResult bool + name string + lastEvaluation time.Time + expectedResult bool + missingSeriesEvalsToResolve int }{ { - name: "false if last evaluation is now", - lastEvaluation: now, - expectedResult: false, + name: "false if last evaluation is now", + lastEvaluation: now, + missingSeriesEvalsToResolve: 2, + expectedResult: false, }, { - name: "false if last evaluation is 1 interval before now", - lastEvaluation: now.Add(-time.Duration(intervalSeconds)), - expectedResult: false, + name: "false if last evaluation is 1 interval before now", + lastEvaluation: now.Add(-time.Duration(intervalSeconds)), + missingSeriesEvalsToResolve: 2, + expectedResult: false, }, { - name: "false if last evaluation is little less than 2 interval before now", - lastEvaluation: now.Add(-time.Duration(intervalSeconds) * time.Second * 2).Add(100 * time.Millisecond), - expectedResult: false, + name: "false if last evaluation is little less than 2 interval before now", + lastEvaluation: now.Add(-time.Duration(intervalSeconds) * time.Second * 2).Add(100 * time.Millisecond), + missingSeriesEvalsToResolve: 2, + expectedResult: false, }, { - name: "true if last evaluation is 2 intervals from now", - lastEvaluation: now.Add(-time.Duration(intervalSeconds) * time.Second * 2), - expectedResult: true, + name: "true if last evaluation is 2 intervals from now", + lastEvaluation: now.Add(-time.Duration(intervalSeconds) * time.Second * 2), + missingSeriesEvalsToResolve: 2, + expectedResult: true, }, { - name: "true if last evaluation is 3 intervals from now", - lastEvaluation: now.Add(-time.Duration(intervalSeconds) * time.Second * 3), - expectedResult: true, + name: "true if last evaluation is 3 intervals from now", + lastEvaluation: now.Add(-time.Duration(intervalSeconds) * time.Second * 3), + missingSeriesEvalsToResolve: 2, + expectedResult: true, + }, + { + name: "false if last evaluation is within custom resolve after missing for", + lastEvaluation: now.Add(-threeIntervals), + missingSeriesEvalsToResolve: 4, + expectedResult: false, + }, + { + name: "true if last evaluation equals custom resolve after missing for", + lastEvaluation: now.Add(-fourIntervals), + missingSeriesEvalsToResolve: 4, + expectedResult: true, + }, + { + name: "true if last evaluation exceeds custom resolve after missing for", + lastEvaluation: now.Add(-fiveIntervals), + missingSeriesEvalsToResolve: 4, + expectedResult: true, + }, + { + name: "when missingSeriesEvalsToResolve is 1 and the state is just created", + lastEvaluation: now, + missingSeriesEvalsToResolve: 1, + expectedResult: false, + }, + { + name: "when missingSeriesEvalsToResolve is 1 and the state is created in the past", + lastEvaluation: now.Add(-time.Duration(intervalSeconds) * time.Second * 1), + missingSeriesEvalsToResolve: 1, + expectedResult: true, }, } + for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - require.Equal(t, tc.expectedResult, stateIsStale(now, tc.lastEvaluation, intervalSeconds)) + require.Equal(t, tc.expectedResult, stateIsStale(now, tc.lastEvaluation, intervalSeconds, tc.missingSeriesEvalsToResolve)) }) } } @@ -115,6 +156,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { t1 := tN(1) t2 := tN(2) t3 := tN(3) + t4 := tN(4) baseRule := &ngmodels.AlertRule{ OrgID: 1, @@ -738,6 +780,308 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { }, }, }, + { + desc: "t1[1:alerting] t2[NoData] t3[NoData] at t2,t3", + alertRule: baseRule, + results: map[time.Time]eval.Results{ + t1: { + newResult(eval.WithState(eval.Alerting), eval.WithLabels(labels1)), + }, + t2: { + newResult(eval.WithState(eval.NoData), eval.WithLabels(noDataLabels)), + }, + t3: { + newResult(eval.WithState(eval.NoData), eval.WithLabels(noDataLabels)), + }, + }, + expectedTransitions: map[time.Time][]StateTransition{ + t1: { + { + PreviousState: eval.Normal, + State: &State{ + Labels: labels["system + rule + labels1"], + State: eval.Alerting, + LatestResult: newEvaluation(t1, eval.Alerting), + StartsAt: t1, + EndsAt: t1.Add(ResendDelay * 4), + LastEvaluationTime: t1, + LastSentAt: &t1, + }, + }, + }, + t2: { + { + PreviousState: eval.Normal, + State: &State{ + Labels: labels["system + rule + no-data"], + State: eval.NoData, + LatestResult: newEvaluation(t2, eval.NoData), + StartsAt: t2, + EndsAt: t2.Add(ResendDelay * 4), + LastEvaluationTime: t2, + LastSentAt: &t2, + }, + }, + }, + t3: { + { + PreviousState: eval.NoData, + State: &State{ + Labels: labels["system + rule + no-data"], + State: eval.NoData, + LatestResult: newEvaluation(t3, eval.NoData), + StartsAt: t2, + EndsAt: t3.Add(ResendDelay * 4), + LastSentAt: &t2, + LastEvaluationTime: t3, + }, + }, + // This is the transition of the alerting state from t1 to Normal + // after 2 evaluations as it became stale. + { + PreviousState: eval.Alerting, + State: &State{ + Labels: labels["system + rule + labels1"], + State: eval.Normal, + StateReason: ngmodels.StateReasonMissingSeries, + LatestResult: newEvaluation(t1, eval.Alerting), + StartsAt: t1, + EndsAt: t3, + LastEvaluationTime: t3, + ResolvedAt: &t3, + LastSentAt: &t3, + }, + }, + }, + }, + }, + { + desc: "t1[1:alerting] t2[NoData] t3[NoData] t4[NoData] with missing_series_evals_to_resolve=3 at t3,t4", + alertRule: baseRuleWith(ngmodels.RuleMuts.WithMissingSeriesEvalsToResolve(3)), + results: map[time.Time]eval.Results{ + t1: { + newResult(eval.WithState(eval.Alerting), eval.WithLabels(labels1)), + }, + t2: { + newResult(eval.WithState(eval.NoData), eval.WithLabels(noDataLabels)), + }, + t3: { + newResult(eval.WithState(eval.NoData), eval.WithLabels(noDataLabels)), + }, + t4: { + newResult(eval.WithState(eval.NoData), eval.WithLabels(noDataLabels)), + }, + }, + expectedTransitions: map[time.Time][]StateTransition{ + t3: { + { + PreviousState: eval.NoData, + State: &State{ + Labels: labels["system + rule + no-data"], + State: eval.NoData, + LatestResult: newEvaluation(t3, eval.NoData), + StartsAt: t2, + EndsAt: t3.Add(ResendDelay * 4), + LastEvaluationTime: t3, + LastSentAt: &t2, + }, + }, + }, + t4: { + { + PreviousState: eval.NoData, + State: &State{ + Labels: labels["system + rule + no-data"], + State: eval.NoData, + LatestResult: newEvaluation(t4, eval.NoData), + StartsAt: t2, + EndsAt: t4.Add(ResendDelay * 4), + LastSentAt: &t2, + LastEvaluationTime: t4, + }, + }, + // This is the transition of the alerting state from t1 to Normal + // after 3 evaluations as it became stale. + { + PreviousState: eval.Alerting, + State: &State{ + Labels: labels["system + rule + labels1"], + State: eval.Normal, + StateReason: ngmodels.StateReasonMissingSeries, + LatestResult: newEvaluation(t1, eval.Alerting), + StartsAt: t1, + EndsAt: t4, + LastEvaluationTime: t4, + ResolvedAt: &t4, + LastSentAt: &t4, + }, + }, + }, + }, + }, + { + desc: "t1[1:alerting] t2[NoData] t3[NoData] with missing_series_evals_to_resolve=1 at t2,t3", + alertRule: baseRuleWith(ngmodels.RuleMuts.WithMissingSeriesEvalsToResolve(1)), + results: map[time.Time]eval.Results{ + t1: { + newResult(eval.WithState(eval.Alerting), eval.WithLabels(labels1)), + }, + t2: { + newResult(eval.WithState(eval.NoData), eval.WithLabels(noDataLabels)), + }, + t3: { + newResult(eval.WithState(eval.NoData), eval.WithLabels(noDataLabels)), + }, + }, + expectedTransitions: map[time.Time][]StateTransition{ + t1: { + { + PreviousState: eval.Normal, + State: &State{ + Labels: labels["system + rule + labels1"], + State: eval.Alerting, + LatestResult: newEvaluation(t1, eval.Alerting), + StartsAt: t1, + EndsAt: t1.Add(ResendDelay * 4), + LastEvaluationTime: t1, + LastSentAt: &t1, + }, + }, + }, + t2: { + { + PreviousState: eval.Normal, + State: &State{ + Labels: labels["system + rule + no-data"], + State: eval.NoData, + LatestResult: newEvaluation(t2, eval.NoData), + StartsAt: t2, + EndsAt: t2.Add(ResendDelay * 4), + LastEvaluationTime: t2, + LastSentAt: &t2, + }, + }, + // This is the transition of the alerting state from t1 to Normal + // after 2 evaluations as it became stale. + { + PreviousState: eval.Alerting, + State: &State{ + Labels: labels["system + rule + labels1"], + State: eval.Normal, + StateReason: ngmodels.StateReasonMissingSeries, + LatestResult: newEvaluation(t1, eval.Alerting), + StartsAt: t1, + EndsAt: t2, + LastEvaluationTime: t2, + ResolvedAt: &t2, + LastSentAt: &t2, + }, + }, + }, + t3: { + { + PreviousState: eval.NoData, + State: &State{ + Labels: labels["system + rule + no-data"], + State: eval.NoData, + LatestResult: newEvaluation(t3, eval.NoData), + StartsAt: t2, + EndsAt: t3.Add(ResendDelay * 4), + LastSentAt: &t2, + LastEvaluationTime: t3, + }, + }, + }, + }, + }, + { + desc: "t1[1:alerting,2:alerting] t2[1:alerting] t3[1:alerting] with missing_series_evals_to_resolve=1 at t2,t3", + alertRule: baseRuleWith(ngmodels.RuleMuts.WithMissingSeriesEvalsToResolve(1)), + results: map[time.Time]eval.Results{ + t1: { + newResult(eval.WithState(eval.Alerting), eval.WithLabels(labels1)), + newResult(eval.WithState(eval.Alerting), eval.WithLabels(labels2)), + }, + t2: { + newResult(eval.WithState(eval.Alerting), eval.WithLabels(labels2)), + }, + t3: { + newResult(eval.WithState(eval.Alerting), eval.WithLabels(labels2)), + }, + }, + expectedTransitions: map[time.Time][]StateTransition{ + t1: { + { + PreviousState: eval.Normal, + State: &State{ + Labels: labels["system + rule + labels1"], + State: eval.Alerting, + LatestResult: newEvaluation(t1, eval.Alerting), + StartsAt: t1, + EndsAt: t1.Add(ResendDelay * 4), + LastEvaluationTime: t1, + LastSentAt: &t1, + }, + }, + { + PreviousState: eval.Normal, + State: &State{ + Labels: labels["system + rule + labels2"], + State: eval.Alerting, + LatestResult: newEvaluation(t1, eval.Alerting), + StartsAt: t1, + EndsAt: t1.Add(ResendDelay * 4), + LastEvaluationTime: t1, + LastSentAt: &t1, + }, + }, + }, + t2: { + { + PreviousState: eval.Alerting, + State: &State{ + Labels: labels["system + rule + labels2"], + State: eval.Alerting, + LatestResult: newEvaluation(t2, eval.Alerting), + StartsAt: t1, + EndsAt: t2.Add(ResendDelay * 4), + LastEvaluationTime: t2, + LastSentAt: &t1, + }, + }, + // This is the transition of the alerting state from t1 to Normal + // after 2 evaluations as it became stale. + { + PreviousState: eval.Alerting, + State: &State{ + Labels: labels["system + rule + labels1"], + State: eval.Normal, + StateReason: ngmodels.StateReasonMissingSeries, + LatestResult: newEvaluation(t1, eval.Alerting), + StartsAt: t1, + EndsAt: t2, + LastEvaluationTime: t2, + ResolvedAt: &t2, + LastSentAt: &t2, + }, + }, + }, + t3: { + { + PreviousState: eval.Alerting, + State: &State{ + Labels: labels["system + rule + labels2"], + State: eval.Alerting, + LatestResult: newEvaluation(t3, eval.Alerting), + StartsAt: t1, + EndsAt: t3.Add(ResendDelay * 4), + LastEvaluationTime: t3, + LastSentAt: &t1, + }, + }, + }, + }, + }, { desc: "t1[{}:normal] t2[{}:alerting] at t2", alertRule: baseRule, diff --git a/pkg/services/ngalert/state/manager_test.go b/pkg/services/ngalert/state/manager_test.go index b6b863f40df..1af185871b8 100644 --- a/pkg/services/ngalert/state/manager_test.go +++ b/pkg/services/ngalert/state/manager_test.go @@ -1906,7 +1906,7 @@ func TestStaleResults(t *testing.T) { st := state.NewManager(cfg, state.NewNoopPersister()) gen := models.RuleGen - rule := gen.With(gen.WithFor(0)).GenerateRef() + rule := gen.With(gen.WithFor(0), gen.WithMissingSeriesEvalsToResolve(2)).GenerateRef() initResults := eval.Results{ eval.ResultGen(eval.WithState(eval.Alerting), eval.WithEvaluatedAt(clk.Now()))(), diff --git a/pkg/services/ngalert/store/compat.go b/pkg/services/ngalert/store/compat.go index 8021233ee93..8d489a2b240 100644 --- a/pkg/services/ngalert/store/compat.go +++ b/pkg/services/ngalert/store/compat.go @@ -18,23 +18,24 @@ func alertRuleToModelsAlertRule(ar alertRule, l log.Logger) (models.AlertRule, e } result := models.AlertRule{ - ID: ar.ID, - OrgID: ar.OrgID, - GUID: ar.GUID, - Title: ar.Title, - Condition: ar.Condition, - Data: data, - Updated: ar.Updated, - IntervalSeconds: ar.IntervalSeconds, - Version: ar.Version, - UID: ar.UID, - NamespaceUID: ar.NamespaceUID, - DashboardUID: ar.DashboardUID, - PanelID: ar.PanelID, - RuleGroup: ar.RuleGroup, - RuleGroupIndex: ar.RuleGroupIndex, - For: ar.For, - IsPaused: ar.IsPaused, + ID: ar.ID, + OrgID: ar.OrgID, + GUID: ar.GUID, + Title: ar.Title, + Condition: ar.Condition, + Data: data, + Updated: ar.Updated, + IntervalSeconds: ar.IntervalSeconds, + Version: ar.Version, + UID: ar.UID, + NamespaceUID: ar.NamespaceUID, + DashboardUID: ar.DashboardUID, + PanelID: ar.PanelID, + RuleGroup: ar.RuleGroup, + RuleGroupIndex: ar.RuleGroupIndex, + For: ar.For, + IsPaused: ar.IsPaused, + MissingSeriesEvalsToResolve: ar.MissingSeriesEvalsToResolve, } if ar.UpdatedBy != nil { @@ -107,24 +108,25 @@ func parseNotificationSettings(s string) ([]models.NotificationSettings, error) func alertRuleFromModelsAlertRule(ar models.AlertRule) (alertRule, error) { result := alertRule{ - ID: ar.ID, - GUID: ar.GUID, - OrgID: ar.OrgID, - Title: ar.Title, - Condition: ar.Condition, - Updated: ar.Updated, - IntervalSeconds: ar.IntervalSeconds, - Version: ar.Version, - UID: ar.UID, - NamespaceUID: ar.NamespaceUID, - DashboardUID: ar.DashboardUID, - PanelID: ar.PanelID, - RuleGroup: ar.RuleGroup, - RuleGroupIndex: ar.RuleGroupIndex, - NoDataState: ar.NoDataState.String(), - ExecErrState: ar.ExecErrState.String(), - For: ar.For, - IsPaused: ar.IsPaused, + ID: ar.ID, + GUID: ar.GUID, + OrgID: ar.OrgID, + Title: ar.Title, + Condition: ar.Condition, + Updated: ar.Updated, + IntervalSeconds: ar.IntervalSeconds, + Version: ar.Version, + UID: ar.UID, + NamespaceUID: ar.NamespaceUID, + DashboardUID: ar.DashboardUID, + PanelID: ar.PanelID, + RuleGroup: ar.RuleGroup, + RuleGroupIndex: ar.RuleGroupIndex, + NoDataState: ar.NoDataState.String(), + ExecErrState: ar.ExecErrState.String(), + For: ar.For, + IsPaused: ar.IsPaused, + MissingSeriesEvalsToResolve: ar.MissingSeriesEvalsToResolve, } if ar.UpdatedBy != nil { @@ -181,30 +183,31 @@ func alertRuleFromModelsAlertRule(ar models.AlertRule) (alertRule, error) { func alertRuleToAlertRuleVersion(rule alertRule) alertRuleVersion { return alertRuleVersion{ - RuleOrgID: rule.OrgID, - RuleGUID: rule.GUID, - RuleUID: rule.UID, - RuleNamespaceUID: rule.NamespaceUID, - RuleGroup: rule.RuleGroup, - RuleGroupIndex: rule.RuleGroupIndex, - ParentVersion: 0, - RestoredFrom: 0, - Version: rule.Version, - Created: rule.Updated, // assuming the Updated time as the creation time - CreatedBy: rule.UpdatedBy, - Title: rule.Title, - Condition: rule.Condition, - Data: rule.Data, - IntervalSeconds: rule.IntervalSeconds, - Record: rule.Record, - NoDataState: rule.NoDataState, - ExecErrState: rule.ExecErrState, - For: rule.For, - Annotations: rule.Annotations, - Labels: rule.Labels, - IsPaused: rule.IsPaused, - NotificationSettings: rule.NotificationSettings, - Metadata: rule.Metadata, + RuleOrgID: rule.OrgID, + RuleGUID: rule.GUID, + RuleUID: rule.UID, + RuleNamespaceUID: rule.NamespaceUID, + RuleGroup: rule.RuleGroup, + RuleGroupIndex: rule.RuleGroupIndex, + ParentVersion: 0, + RestoredFrom: 0, + Version: rule.Version, + Created: rule.Updated, // assuming the Updated time as the creation time + CreatedBy: rule.UpdatedBy, + Title: rule.Title, + Condition: rule.Condition, + Data: rule.Data, + IntervalSeconds: rule.IntervalSeconds, + Record: rule.Record, + NoDataState: rule.NoDataState, + ExecErrState: rule.ExecErrState, + For: rule.For, + Annotations: rule.Annotations, + Labels: rule.Labels, + IsPaused: rule.IsPaused, + NotificationSettings: rule.NotificationSettings, + Metadata: rule.Metadata, + MissingSeriesEvalsToResolve: rule.MissingSeriesEvalsToResolve, } } @@ -224,18 +227,19 @@ func alertRuleVersionToAlertRule(version alertRuleVersion) alertRule { NamespaceUID: version.RuleNamespaceUID, // Versions do not store Dashboard\Panel as separate column. // However, these fields are part of annotations and information in these fields is redundant - DashboardUID: nil, - PanelID: nil, - RuleGroup: version.RuleGroup, - RuleGroupIndex: version.RuleGroupIndex, - Record: version.Record, - NoDataState: version.NoDataState, - ExecErrState: version.ExecErrState, - For: version.For, - Annotations: version.Annotations, - Labels: version.Labels, - IsPaused: version.IsPaused, - NotificationSettings: version.NotificationSettings, - Metadata: version.Metadata, + DashboardUID: nil, + PanelID: nil, + RuleGroup: version.RuleGroup, + RuleGroupIndex: version.RuleGroupIndex, + Record: version.Record, + NoDataState: version.NoDataState, + ExecErrState: version.ExecErrState, + For: version.For, + Annotations: version.Annotations, + Labels: version.Labels, + IsPaused: version.IsPaused, + NotificationSettings: version.NotificationSettings, + Metadata: version.Metadata, + MissingSeriesEvalsToResolve: version.MissingSeriesEvalsToResolve, } } diff --git a/pkg/services/ngalert/store/models.go b/pkg/services/ngalert/store/models.go index 8c5d44a9d8e..63cb4e21cd4 100644 --- a/pkg/services/ngalert/store/models.go +++ b/pkg/services/ngalert/store/models.go @@ -4,31 +4,32 @@ import "time" // alertRule represents a record in alert_rule table type alertRule struct { - ID int64 `xorm:"pk autoincr 'id'"` - GUID string `xorm:"guid"` - OrgID int64 `xorm:"org_id"` - Title string - Condition string - Data string - Updated time.Time - UpdatedBy *string `xorm:"updated_by"` - IntervalSeconds int64 - Version int64 `xorm:"version"` // this tag makes xorm add optimistic lock (see https://xorm.io/docs/chapter-06/1.lock/) - UID string `xorm:"uid"` - NamespaceUID string `xorm:"namespace_uid"` - DashboardUID *string `xorm:"dashboard_uid"` - PanelID *int64 `xorm:"panel_id"` - RuleGroup string - RuleGroupIndex int `xorm:"rule_group_idx"` - Record string - NoDataState string - ExecErrState string - For time.Duration - Annotations string - Labels string - IsPaused bool - NotificationSettings string `xorm:"notification_settings"` - Metadata string `xorm:"metadata"` + ID int64 `xorm:"pk autoincr 'id'"` + GUID string `xorm:"guid"` + OrgID int64 `xorm:"org_id"` + Title string + Condition string + Data string + Updated time.Time + UpdatedBy *string `xorm:"updated_by"` + IntervalSeconds int64 + Version int64 `xorm:"version"` // this tag makes xorm add optimistic lock (see https://xorm.io/docs/chapter-06/1.lock/) + UID string `xorm:"uid"` + NamespaceUID string `xorm:"namespace_uid"` + DashboardUID *string `xorm:"dashboard_uid"` + PanelID *int64 `xorm:"panel_id"` + RuleGroup string + RuleGroupIndex int `xorm:"rule_group_idx"` + Record string + NoDataState string + ExecErrState string + For time.Duration + Annotations string + Labels string + IsPaused bool + NotificationSettings string `xorm:"notification_settings"` + Metadata string `xorm:"metadata"` + MissingSeriesEvalsToResolve *int `xorm:"missing_series_evals_to_resolve"` } func (a alertRule) TableName() string { @@ -59,12 +60,13 @@ type alertRuleVersion struct { ExecErrState string // ideally this field should have been apimodels.ApiDuration // but this is currently not possible because of circular dependencies - For time.Duration - Annotations string - Labels string - IsPaused bool - NotificationSettings string `xorm:"notification_settings"` - Metadata string `xorm:"metadata"` + For time.Duration + Annotations string + Labels string + IsPaused bool + NotificationSettings string `xorm:"notification_settings"` + Metadata string `xorm:"metadata"` + MissingSeriesEvalsToResolve *int `xorm:"missing_series_evals_to_resolve"` } // EqualSpec compares two alertRuleVersion objects for equality based on their specifications and returns true if they match. @@ -88,7 +90,8 @@ func (a alertRuleVersion) EqualSpec(b alertRuleVersion) bool { a.Labels == b.Labels && a.IsPaused == b.IsPaused && a.NotificationSettings == b.NotificationSettings && - a.Metadata == b.Metadata + a.Metadata == b.Metadata && + a.MissingSeriesEvalsToResolve == b.MissingSeriesEvalsToResolve } func (a alertRuleVersion) TableName() string { diff --git a/pkg/services/sqlstore/migrations/migrations.go b/pkg/services/sqlstore/migrations/migrations.go index 47cc25c2888..edf822bb0b6 100644 --- a/pkg/services/sqlstore/migrations/migrations.go +++ b/pkg/services/sqlstore/migrations/migrations.go @@ -147,4 +147,6 @@ func (oss *OSSMigrations) AddMigration(mg *Migrator) { ualert.AddAlertRuleStateTable(mg) ualert.AddAlertRuleGuidMigration(mg) + + ualert.AddAlertRuleMissingSeriesEvalsToResolve(mg) } diff --git a/pkg/services/sqlstore/migrations/ualert/alert_rule_missing_series_evals_to_resolve.go b/pkg/services/sqlstore/migrations/ualert/alert_rule_missing_series_evals_to_resolve.go new file mode 100644 index 00000000000..a114a12aaa3 --- /dev/null +++ b/pkg/services/sqlstore/migrations/ualert/alert_rule_missing_series_evals_to_resolve.go @@ -0,0 +1,17 @@ +package ualert + +import "github.com/grafana/grafana/pkg/services/sqlstore/migrator" + +// AddAlertRuleMissingSeriesEvalsToResolve adds missing_series_evals_to_resolve column to alert_rule and alert_rule_version tables. +func AddAlertRuleMissingSeriesEvalsToResolve(mg *migrator.Migrator) { + column := &migrator.Column{Name: "missing_series_evals_to_resolve", Type: migrator.DB_SmallInt, Nullable: true} + + mg.AddMigration( + "add missing_series_evals_to_resolve column to alert_rule", + migrator.NewAddColumnMigration(migrator.Table{Name: "alert_rule"}, column), + ) + mg.AddMigration( + "add missing_series_evals_to_resolve column to alert_rule_version", + migrator.NewAddColumnMigration(migrator.Table{Name: "alert_rule_version"}, column), + ) +} From f296b66b3771f059da50a6de3e76256e68d9b330 Mon Sep 17 00:00:00 2001 From: Jev Forsberg <46619047+baldm0mma@users.noreply.github.com> Date: Tue, 11 Mar 2025 15:27:54 -0600 Subject: [PATCH 027/141] Chore: Migrate storybook verification to GHAs (#101968) * baldm0mma/ add storybook-verification workflow file * baldm0mma/ build out storybook jobs to drone spec * baldm0mma/ add node fallback and remove runner id * baldm0mma/ replace with cypress action * baldm0mma/ update codeowners * baldm0mma/ add workflow dispatch for testing * baldm0mma/ update trigger for testing * baldm0mma/ update path * baldm0mma/ update paths * baldm0mma/ update node file --- .github/CODEOWNERS | 1 + .github/workflows/storybook-verification.yml | 40 ++++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 .github/workflows/storybook-verification.yml diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 7ff10207c35..d6ebd9a6a09 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -798,6 +798,7 @@ embed.go @grafana/grafana-as-code /.github/workflows/remove-milestone.yml @grafana/grafana-developer-enablement-squad /.github/workflows/scripts/json-file-to-job-output.js @grafana/plugins-platform-frontend /.github/workflows/stale.yml @grafana/grafana-developer-enablement-squad +/.github/workflows/storybook-verification.yml @grafana/grafana-frontend-platform /.github/workflows/update-changelog.yml @grafana/grafana-developer-enablement-squad /.github/workflows/update-make-docs.yml @grafana/docs-tooling /.github/workflows/scripts/kinds/verify-kinds.go @grafana/platform-monitoring diff --git a/.github/workflows/storybook-verification.yml b/.github/workflows/storybook-verification.yml new file mode 100644 index 00000000000..72eb6a4ad4c --- /dev/null +++ b/.github/workflows/storybook-verification.yml @@ -0,0 +1,40 @@ +name: Verify Storybook + +on: + pull_request: + paths: + - 'packages/grafana-ui/**' + - '.github/workflows/storybook-verification.yml' + - '!docs/**' + - '!*.md' + +jobs: + verify-storybook: + name: Verify Storybook + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version-file: 'package.json' + cache: 'yarn' + + - name: Install dependencies + run: yarn install --immutable + + - name: Run Storybook and E2E tests + uses: cypress-io/github-action@v6 + with: + browser: chrome + start: yarn storybook --quiet + wait-on: 'http://localhost:9001' + wait-on-timeout: 60 + command: yarn e2e:storybook + install: false + env: + HOST: localhost + PORT: 9001 From 1ceab26cb4d23a22dadb9dab7f150648354b91d3 Mon Sep 17 00:00:00 2001 From: jev forsberg Date: Tue, 11 Mar 2025 16:41:35 -0600 Subject: [PATCH 028/141] baldm0mma/ add pr-lint-build-docs.yml --- .github/workflows/pr-lint-build-docs.yml | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 .github/workflows/pr-lint-build-docs.yml diff --git a/.github/workflows/pr-lint-build-docs.yml b/.github/workflows/pr-lint-build-docs.yml new file mode 100644 index 00000000000..c0e3d6d638b --- /dev/null +++ b/.github/workflows/pr-lint-build-docs.yml @@ -0,0 +1,9 @@ +name: Lint and Build Documentation + +on: + pull_request: + paths: + - '*.md' + - 'docs/**' + - 'packages/**/*.md' + - 'latest.json' From 687c06419295d40de29d636f544a28cf5f0eb23b Mon Sep 17 00:00:00 2001 From: jev forsberg Date: Tue, 11 Mar 2025 16:43:09 -0600 Subject: [PATCH 029/141] baldm0mma/ update node version --- .github/workflows/pr-lint-build-docs.yml | 46 ++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/.github/workflows/pr-lint-build-docs.yml b/.github/workflows/pr-lint-build-docs.yml index c0e3d6d638b..7e91af0ba15 100644 --- a/.github/workflows/pr-lint-build-docs.yml +++ b/.github/workflows/pr-lint-build-docs.yml @@ -7,3 +7,49 @@ on: - 'docs/**' - 'packages/**/*.md' - 'latest.json' + +jobs: + docs: + name: Build & Verify Docs + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version-file: 'package.json' + cache: 'yarn' + + - name: Install dependencies + run: yarn install --immutable || yarn install --immutable + + - name: Lint docs + run: yarn run prettier:checkDocs + env: + NODE_OPTIONS: --max_old_space_size=8192 + + - name: Build docs website + uses: docker://grafana/docs-base:latest + with: + entrypoint: /bin/sh + args: | + -c "mkdir -p /github/workspace/hugo/content/docs/grafana/latest && \ + echo -e '---\\nredirectURL: /docs/grafana/latest/\\ntype: redirect\\nversioned: true\\n---\\n' > /github/workspace/hugo/content/docs/grafana/_index.md && \ + cp -r /github/workspace/docs/sources/* /github/workspace/hugo/content/docs/grafana/latest/ && \ + cd /github/workspace/hugo && make prod" + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: '1.24.1' + + - name: Verify generated CUE code + run: | + make gen-cue + if [ -n "$(git diff)" ]; then + echo "Generated CUE code is not in sync with its inputs. Please run 'make gen-cue' and commit the changes." + git diff + exit 1 + fi From 12e1ae0751d15c95c56656c54cd338f77c637fd9 Mon Sep 17 00:00:00 2001 From: jev forsberg Date: Tue, 11 Mar 2025 16:43:27 -0600 Subject: [PATCH 030/141] baldm0mma/ remove double yarn dip --- .github/workflows/pr-lint-build-docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr-lint-build-docs.yml b/.github/workflows/pr-lint-build-docs.yml index 7e91af0ba15..61edc5f90b0 100644 --- a/.github/workflows/pr-lint-build-docs.yml +++ b/.github/workflows/pr-lint-build-docs.yml @@ -23,7 +23,7 @@ jobs: cache: 'yarn' - name: Install dependencies - run: yarn install --immutable || yarn install --immutable + run: yarn install --immutable - name: Lint docs run: yarn run prettier:checkDocs From ff74cb954fd307650f9366bd669c6a81afd774c6 Mon Sep 17 00:00:00 2001 From: jev forsberg Date: Tue, 11 Mar 2025 16:46:08 -0600 Subject: [PATCH 031/141] baldm0mma/ remove cue gen and verification step --- .github/workflows/pr-lint-build-docs.yml | 28 +++++------------------- 1 file changed, 6 insertions(+), 22 deletions(-) diff --git a/.github/workflows/pr-lint-build-docs.yml b/.github/workflows/pr-lint-build-docs.yml index 61edc5f90b0..12f47ce6f67 100644 --- a/.github/workflows/pr-lint-build-docs.yml +++ b/.github/workflows/pr-lint-build-docs.yml @@ -1,4 +1,4 @@ -name: Lint and Build Documentation +name: Documentation on: pull_request: @@ -31,25 +31,9 @@ jobs: NODE_OPTIONS: --max_old_space_size=8192 - name: Build docs website - uses: docker://grafana/docs-base:latest - with: - entrypoint: /bin/sh - args: | - -c "mkdir -p /github/workspace/hugo/content/docs/grafana/latest && \ - echo -e '---\\nredirectURL: /docs/grafana/latest/\\ntype: redirect\\nversioned: true\\n---\\n' > /github/workspace/hugo/content/docs/grafana/_index.md && \ - cp -r /github/workspace/docs/sources/* /github/workspace/hugo/content/docs/grafana/latest/ && \ - cd /github/workspace/hugo && make prod" - - - name: Setup Go - uses: actions/setup-go@v5 - with: - go-version: '1.24.1' - - - name: Verify generated CUE code run: | - make gen-cue - if [ -n "$(git diff)" ]; then - echo "Generated CUE code is not in sync with its inputs. Please run 'make gen-cue' and commit the changes." - git diff - exit 1 - fi + mkdir -p hugo/content/docs/grafana/latest + echo -e '---\nredirectURL: /docs/grafana/latest/\ntype: redirect\nversioned: true\n---\n' > hugo/content/docs/grafana/_index.md + cp -r docs/sources/* hugo/content/docs/grafana/latest/ + + docker run --rm -v $(pwd):/src grafana/docs-base:latest /bin/sh -c "cd /src/hugo && make prod" From 4d6d37d20f087531ee7b0ba4b7142c2c0d320082 Mon Sep 17 00:00:00 2001 From: jev forsberg Date: Tue, 11 Mar 2025 16:52:14 -0600 Subject: [PATCH 032/141] baldm0mma/ remove make installation --- .github/workflows/pr-lint-build-docs.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/pr-lint-build-docs.yml b/.github/workflows/pr-lint-build-docs.yml index 12f47ce6f67..4a3154e2a76 100644 --- a/.github/workflows/pr-lint-build-docs.yml +++ b/.github/workflows/pr-lint-build-docs.yml @@ -37,3 +37,11 @@ jobs: cp -r docs/sources/* hugo/content/docs/grafana/latest/ docker run --rm -v $(pwd):/src grafana/docs-base:latest /bin/sh -c "cd /src/hugo && make prod" + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version-file: 'go.mod' + + - name: Verify generated CUE code + run: CODEGEN_VERIFY=1 make gen-cue From 10621c40d3729ac4cc7c32b2768f2cf26390f940 Mon Sep 17 00:00:00 2001 From: jev forsberg Date: Tue, 11 Mar 2025 16:59:39 -0600 Subject: [PATCH 033/141] baldm0mma/ annotate mem lim --- .github/workflows/pr-lint-build-docs.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/pr-lint-build-docs.yml b/.github/workflows/pr-lint-build-docs.yml index 4a3154e2a76..056cf593afc 100644 --- a/.github/workflows/pr-lint-build-docs.yml +++ b/.github/workflows/pr-lint-build-docs.yml @@ -28,6 +28,7 @@ jobs: - name: Lint docs run: yarn run prettier:checkDocs env: + # Increase the memory limit for Node.js processes to 8GB to handle the larger docs files NODE_OPTIONS: --max_old_space_size=8192 - name: Build docs website From 2f893faf039d02a99f722aed09bfe462b28416e8 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 11 Mar 2025 23:02:02 +0000 Subject: [PATCH 034/141] Update dependency @babel/runtime to v7.26.10 [SECURITY] (#101975) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 46 +++++++--------------------------------------- 2 files changed, 8 insertions(+), 40 deletions(-) diff --git a/package.json b/package.json index eed73cf59f3..d77bd8cef07 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,7 @@ "devDependencies": { "@babel/core": "7.26.9", "@babel/preset-env": "7.26.9", - "@babel/runtime": "7.26.9", + "@babel/runtime": "7.26.10", "@betterer/betterer": "5.4.0", "@betterer/cli": "5.4.0", "@cypress/webpack-preprocessor": "6.0.2", diff --git a/yarn.lock b/yarn.lock index a65a3d0f5dc..4c463770276 100644 --- a/yarn.lock +++ b/yarn.lock @@ -81,7 +81,7 @@ __metadata: languageName: node linkType: hard -"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.10.3, @babel/code-frame@npm:^7.10.4, @babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.16.7, @babel/code-frame@npm:^7.24.2, @babel/code-frame@npm:^7.25.9, @babel/code-frame@npm:^7.26.2": +"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.10.3, @babel/code-frame@npm:^7.10.4, @babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.16.7, @babel/code-frame@npm:^7.24.2, @babel/code-frame@npm:^7.26.2": version: 7.26.2 resolution: "@babel/code-frame@npm:7.26.2" dependencies: @@ -362,17 +362,6 @@ __metadata: languageName: node linkType: hard -"@babel/parser@npm:^7.25.9": - version: 7.26.7 - resolution: "@babel/parser@npm:7.26.7" - dependencies: - "@babel/types": "npm:^7.26.7" - bin: - parser: ./bin/babel-parser.js - checksum: 10/3ccc384366ca9a9b49c54f5b24c9d8cff9a505f2fbdd1cfc04941c8e1897084cc32f100e77900c12bc14a176cf88daa3c155faad680d9a23491b997fd2a59ffc - languageName: node - linkType: hard - "@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:^7.25.9": version: 7.25.9 resolution: "@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:7.25.9" @@ -1427,27 +1416,16 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:7.26.9, @babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.10.1, @babel/runtime@npm:^7.11.1, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.0, @babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.12.13, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.13.10, @babel/runtime@npm:^7.15.4, @babel/runtime@npm:^7.16.3, @babel/runtime@npm:^7.17.8, @babel/runtime@npm:^7.18.0, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.20.0, @babel/runtime@npm:^7.20.7, @babel/runtime@npm:^7.23.2, @babel/runtime@npm:^7.23.9, @babel/runtime@npm:^7.24.1, @babel/runtime@npm:^7.24.5, @babel/runtime@npm:^7.24.7, @babel/runtime@npm:^7.25.0, @babel/runtime@npm:^7.25.6, @babel/runtime@npm:^7.25.7, @babel/runtime@npm:^7.3.1, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7": - version: 7.26.9 - resolution: "@babel/runtime@npm:7.26.9" +"@babel/runtime@npm:7.26.10, @babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.10.1, @babel/runtime@npm:^7.11.1, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.0, @babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.12.13, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.13.10, @babel/runtime@npm:^7.15.4, @babel/runtime@npm:^7.16.3, @babel/runtime@npm:^7.17.8, @babel/runtime@npm:^7.18.0, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.20.0, @babel/runtime@npm:^7.20.7, @babel/runtime@npm:^7.23.2, @babel/runtime@npm:^7.23.9, @babel/runtime@npm:^7.24.1, @babel/runtime@npm:^7.24.5, @babel/runtime@npm:^7.24.7, @babel/runtime@npm:^7.25.0, @babel/runtime@npm:^7.25.6, @babel/runtime@npm:^7.25.7, @babel/runtime@npm:^7.3.1, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7": + version: 7.26.10 + resolution: "@babel/runtime@npm:7.26.10" dependencies: regenerator-runtime: "npm:^0.14.0" - checksum: 10/08edd07d774eafbf157fdc8450ed6ddd22416fdd8e2a53e4a00349daba1b502c03ab7f7ad3ad3a7c46b9a24d99b5697591d0f852ee2f84642082ef7dda90b83d + checksum: 10/9d7ff8e96abe3791047c1138789c742411e3ef19c4d7ca18ce916f83cec92c06ec5dc64401759f6dd1e377cf8a01bbd2c62e033eb7550f435cf6579768d0d4a5 languageName: node linkType: hard -"@babel/template@npm:^7.22.5, @babel/template@npm:^7.24.7, @babel/template@npm:^7.25.9, @babel/template@npm:^7.3.3": - version: 7.25.9 - resolution: "@babel/template@npm:7.25.9" - dependencies: - "@babel/code-frame": "npm:^7.25.9" - "@babel/parser": "npm:^7.25.9" - "@babel/types": "npm:^7.25.9" - checksum: 10/e861180881507210150c1335ad94aff80fd9e9be6202e1efa752059c93224e2d5310186ddcdd4c0f0b0fc658ce48cb47823f15142b5c00c8456dde54f5de80b2 - languageName: node - linkType: hard - -"@babel/template@npm:^7.26.9": +"@babel/template@npm:^7.22.5, @babel/template@npm:^7.24.7, @babel/template@npm:^7.25.9, @babel/template@npm:^7.26.9, @babel/template@npm:^7.3.3": version: 7.26.9 resolution: "@babel/template@npm:7.26.9" dependencies: @@ -1483,16 +1461,6 @@ __metadata: languageName: node linkType: hard -"@babel/types@npm:^7.26.7": - version: 7.26.7 - resolution: "@babel/types@npm:7.26.7" - dependencies: - "@babel/helper-string-parser": "npm:^7.25.9" - "@babel/helper-validator-identifier": "npm:^7.25.9" - checksum: 10/2264efd02cc261ca5d1c5bc94497c8995238f28afd2b7483b24ea64dd694cf46b00d51815bf0c87f0d0061ea221569c77893aeecb0d4b4bb254e9c2f938d7669 - languageName: node - linkType: hard - "@bcoe/v8-coverage@npm:^0.2.3": version: 0.2.3 resolution: "@bcoe/v8-coverage@npm:0.2.3" @@ -18071,7 +18039,7 @@ __metadata: dependencies: "@babel/core": "npm:7.26.9" "@babel/preset-env": "npm:7.26.9" - "@babel/runtime": "npm:7.26.9" + "@babel/runtime": "npm:7.26.10" "@betterer/betterer": "npm:5.4.0" "@betterer/cli": "npm:5.4.0" "@bsull/augurs": "npm:^0.9.0" From 868aabeac21ac8d61c69c701faed7b66e6ff6b69 Mon Sep 17 00:00:00 2001 From: jev forsberg Date: Tue, 11 Mar 2025 17:02:28 -0600 Subject: [PATCH 035/141] Revert "baldm0mma/ annotate mem lim" This reverts commit 10621c40d3729ac4cc7c32b2768f2cf26390f940. --- .github/workflows/pr-lint-build-docs.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/pr-lint-build-docs.yml b/.github/workflows/pr-lint-build-docs.yml index 056cf593afc..4a3154e2a76 100644 --- a/.github/workflows/pr-lint-build-docs.yml +++ b/.github/workflows/pr-lint-build-docs.yml @@ -28,7 +28,6 @@ jobs: - name: Lint docs run: yarn run prettier:checkDocs env: - # Increase the memory limit for Node.js processes to 8GB to handle the larger docs files NODE_OPTIONS: --max_old_space_size=8192 - name: Build docs website From 172a4ca43b65dd7e84106a3f0746e93adb4ad0c2 Mon Sep 17 00:00:00 2001 From: jev forsberg Date: Tue, 11 Mar 2025 17:03:06 -0600 Subject: [PATCH 036/141] Revert "baldm0mma/ remove make installation" This reverts commit 4d6d37d20f087531ee7b0ba4b7142c2c0d320082. --- .github/workflows/pr-lint-build-docs.yml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/.github/workflows/pr-lint-build-docs.yml b/.github/workflows/pr-lint-build-docs.yml index 4a3154e2a76..12f47ce6f67 100644 --- a/.github/workflows/pr-lint-build-docs.yml +++ b/.github/workflows/pr-lint-build-docs.yml @@ -37,11 +37,3 @@ jobs: cp -r docs/sources/* hugo/content/docs/grafana/latest/ docker run --rm -v $(pwd):/src grafana/docs-base:latest /bin/sh -c "cd /src/hugo && make prod" - - - name: Setup Go - uses: actions/setup-go@v5 - with: - go-version-file: 'go.mod' - - - name: Verify generated CUE code - run: CODEGEN_VERIFY=1 make gen-cue From 92cf578dc3263cb6d6f2df6793917c4dac6d57c5 Mon Sep 17 00:00:00 2001 From: jev forsberg Date: Tue, 11 Mar 2025 17:03:19 -0600 Subject: [PATCH 037/141] Revert "baldm0mma/ remove cue gen and verification step" This reverts commit ff74cb954fd307650f9366bd669c6a81afd774c6. --- .github/workflows/pr-lint-build-docs.yml | 28 +++++++++++++++++++----- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/.github/workflows/pr-lint-build-docs.yml b/.github/workflows/pr-lint-build-docs.yml index 12f47ce6f67..61edc5f90b0 100644 --- a/.github/workflows/pr-lint-build-docs.yml +++ b/.github/workflows/pr-lint-build-docs.yml @@ -1,4 +1,4 @@ -name: Documentation +name: Lint and Build Documentation on: pull_request: @@ -31,9 +31,25 @@ jobs: NODE_OPTIONS: --max_old_space_size=8192 - name: Build docs website + uses: docker://grafana/docs-base:latest + with: + entrypoint: /bin/sh + args: | + -c "mkdir -p /github/workspace/hugo/content/docs/grafana/latest && \ + echo -e '---\\nredirectURL: /docs/grafana/latest/\\ntype: redirect\\nversioned: true\\n---\\n' > /github/workspace/hugo/content/docs/grafana/_index.md && \ + cp -r /github/workspace/docs/sources/* /github/workspace/hugo/content/docs/grafana/latest/ && \ + cd /github/workspace/hugo && make prod" + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: '1.24.1' + + - name: Verify generated CUE code run: | - mkdir -p hugo/content/docs/grafana/latest - echo -e '---\nredirectURL: /docs/grafana/latest/\ntype: redirect\nversioned: true\n---\n' > hugo/content/docs/grafana/_index.md - cp -r docs/sources/* hugo/content/docs/grafana/latest/ - - docker run --rm -v $(pwd):/src grafana/docs-base:latest /bin/sh -c "cd /src/hugo && make prod" + make gen-cue + if [ -n "$(git diff)" ]; then + echo "Generated CUE code is not in sync with its inputs. Please run 'make gen-cue' and commit the changes." + git diff + exit 1 + fi From a2464f7e392ecbafdaa33ea937df971a59e7f5c9 Mon Sep 17 00:00:00 2001 From: jev forsberg Date: Tue, 11 Mar 2025 17:03:30 -0600 Subject: [PATCH 038/141] Revert "baldm0mma/ remove double yarn dip" This reverts commit 12e1ae0751d15c95c56656c54cd338f77c637fd9. --- .github/workflows/pr-lint-build-docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr-lint-build-docs.yml b/.github/workflows/pr-lint-build-docs.yml index 61edc5f90b0..7e91af0ba15 100644 --- a/.github/workflows/pr-lint-build-docs.yml +++ b/.github/workflows/pr-lint-build-docs.yml @@ -23,7 +23,7 @@ jobs: cache: 'yarn' - name: Install dependencies - run: yarn install --immutable + run: yarn install --immutable || yarn install --immutable - name: Lint docs run: yarn run prettier:checkDocs From c5c26cb62f09c95b8ced43de68a23990101557aa Mon Sep 17 00:00:00 2001 From: jev forsberg Date: Tue, 11 Mar 2025 17:03:47 -0600 Subject: [PATCH 039/141] Revert "baldm0mma/ update node version" This reverts commit 687c06419295d40de29d636f544a28cf5f0eb23b. --- .github/workflows/pr-lint-build-docs.yml | 46 ------------------------ 1 file changed, 46 deletions(-) diff --git a/.github/workflows/pr-lint-build-docs.yml b/.github/workflows/pr-lint-build-docs.yml index 7e91af0ba15..c0e3d6d638b 100644 --- a/.github/workflows/pr-lint-build-docs.yml +++ b/.github/workflows/pr-lint-build-docs.yml @@ -7,49 +7,3 @@ on: - 'docs/**' - 'packages/**/*.md' - 'latest.json' - -jobs: - docs: - name: Build & Verify Docs - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version-file: 'package.json' - cache: 'yarn' - - - name: Install dependencies - run: yarn install --immutable || yarn install --immutable - - - name: Lint docs - run: yarn run prettier:checkDocs - env: - NODE_OPTIONS: --max_old_space_size=8192 - - - name: Build docs website - uses: docker://grafana/docs-base:latest - with: - entrypoint: /bin/sh - args: | - -c "mkdir -p /github/workspace/hugo/content/docs/grafana/latest && \ - echo -e '---\\nredirectURL: /docs/grafana/latest/\\ntype: redirect\\nversioned: true\\n---\\n' > /github/workspace/hugo/content/docs/grafana/_index.md && \ - cp -r /github/workspace/docs/sources/* /github/workspace/hugo/content/docs/grafana/latest/ && \ - cd /github/workspace/hugo && make prod" - - - name: Setup Go - uses: actions/setup-go@v5 - with: - go-version: '1.24.1' - - - name: Verify generated CUE code - run: | - make gen-cue - if [ -n "$(git diff)" ]; then - echo "Generated CUE code is not in sync with its inputs. Please run 'make gen-cue' and commit the changes." - git diff - exit 1 - fi From 9ed864a94479e08dcf96ff2e2fa18d37a3a7435a Mon Sep 17 00:00:00 2001 From: jev forsberg Date: Tue, 11 Mar 2025 17:03:59 -0600 Subject: [PATCH 040/141] Revert "baldm0mma/ add pr-lint-build-docs.yml" This reverts commit 1ceab26cb4d23a22dadb9dab7f150648354b91d3. --- .github/workflows/pr-lint-build-docs.yml | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 .github/workflows/pr-lint-build-docs.yml diff --git a/.github/workflows/pr-lint-build-docs.yml b/.github/workflows/pr-lint-build-docs.yml deleted file mode 100644 index c0e3d6d638b..00000000000 --- a/.github/workflows/pr-lint-build-docs.yml +++ /dev/null @@ -1,9 +0,0 @@ -name: Lint and Build Documentation - -on: - pull_request: - paths: - - '*.md' - - 'docs/**' - - 'packages/**/*.md' - - 'latest.json' From 2bec167be5559ce84f2fd1775606f98cabfa212a Mon Sep 17 00:00:00 2001 From: "grafana-pr-automation[bot]" <140550294+grafana-pr-automation[bot]@users.noreply.github.com> Date: Wed, 12 Mar 2025 02:30:46 +0200 Subject: [PATCH 041/141] I18n: Download translations from Crowdin (#101984) New Crowdin translations by GitHub Action Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- public/locales/cs-CZ/grafana.json | 5 ++++- public/locales/de-DE/grafana.json | 5 ++++- public/locales/es-ES/grafana.json | 5 ++++- public/locales/fr-FR/grafana.json | 5 ++++- public/locales/hu-HU/grafana.json | 5 ++++- public/locales/id-ID/grafana.json | 5 ++++- public/locales/it-IT/grafana.json | 5 ++++- public/locales/ja-JP/grafana.json | 5 ++++- public/locales/ko-KR/grafana.json | 5 ++++- public/locales/nl-NL/grafana.json | 5 ++++- public/locales/pl-PL/grafana.json | 5 ++++- public/locales/pt-BR/grafana.json | 5 ++++- public/locales/pt-PT/grafana.json | 5 ++++- public/locales/ru-RU/grafana.json | 5 ++++- public/locales/sv-SE/grafana.json | 5 ++++- public/locales/tr-TR/grafana.json | 5 ++++- public/locales/zh-Hans/grafana.json | 5 ++++- public/locales/zh-Hant/grafana.json | 5 ++++- 18 files changed, 72 insertions(+), 18 deletions(-) diff --git a/public/locales/cs-CZ/grafana.json b/public/locales/cs-CZ/grafana.json index af5d7e65575..eda5329aff4 100644 --- a/public/locales/cs-CZ/grafana.json +++ b/public/locales/cs-CZ/grafana.json @@ -383,6 +383,8 @@ "list-view": { "empty": { "new-alert-rule": "", + "new-ds-managed-recording-rule": "", + "new-grafana-recording-rule": "", "new-recording-rule": "", "provisioning": "" }, @@ -3143,7 +3145,8 @@ "old-password-required": "", "passwords-must-match": "", "strong-password-validation-register": "" - } + }, + "change-theme": "" }, "public-dashboard": { "acknowledgment-checkboxes": { diff --git a/public/locales/de-DE/grafana.json b/public/locales/de-DE/grafana.json index f58adc0d513..dac2fef8922 100644 --- a/public/locales/de-DE/grafana.json +++ b/public/locales/de-DE/grafana.json @@ -379,6 +379,8 @@ "list-view": { "empty": { "new-alert-rule": "", + "new-ds-managed-recording-rule": "", + "new-grafana-recording-rule": "", "new-recording-rule": "", "provisioning": "" }, @@ -3123,7 +3125,8 @@ "old-password-required": "Altes Passwort ist erforderlich", "passwords-must-match": "Passwörter müssen übereinstimmen", "strong-password-validation-register": "Passwort entspricht nicht den strengen Kennwortrichtlinien" - } + }, + "change-theme": "" }, "public-dashboard": { "acknowledgment-checkboxes": { diff --git a/public/locales/es-ES/grafana.json b/public/locales/es-ES/grafana.json index ca06ff0f505..dee14dc57cc 100644 --- a/public/locales/es-ES/grafana.json +++ b/public/locales/es-ES/grafana.json @@ -379,6 +379,8 @@ "list-view": { "empty": { "new-alert-rule": "", + "new-ds-managed-recording-rule": "", + "new-grafana-recording-rule": "", "new-recording-rule": "", "provisioning": "" }, @@ -3123,7 +3125,8 @@ "old-password-required": "Se requiere la contraseña antigua", "passwords-must-match": "Las contraseñas deben coincidir", "strong-password-validation-register": "La contraseña no cumple con la política de contraseñas seguras" - } + }, + "change-theme": "" }, "public-dashboard": { "acknowledgment-checkboxes": { diff --git a/public/locales/fr-FR/grafana.json b/public/locales/fr-FR/grafana.json index cec4d85c830..f7e3ce2684b 100644 --- a/public/locales/fr-FR/grafana.json +++ b/public/locales/fr-FR/grafana.json @@ -379,6 +379,8 @@ "list-view": { "empty": { "new-alert-rule": "", + "new-ds-managed-recording-rule": "", + "new-grafana-recording-rule": "", "new-recording-rule": "", "provisioning": "" }, @@ -3123,7 +3125,8 @@ "old-password-required": "Vous devez saisir l'ancien mot de passe", "passwords-must-match": "Les mots de passe doivent être identiques", "strong-password-validation-register": "Selon notre politique, votre mot de passe n'est pas suffisamment sécurisé" - } + }, + "change-theme": "" }, "public-dashboard": { "acknowledgment-checkboxes": { diff --git a/public/locales/hu-HU/grafana.json b/public/locales/hu-HU/grafana.json index f3a7ee625b6..19430667f64 100644 --- a/public/locales/hu-HU/grafana.json +++ b/public/locales/hu-HU/grafana.json @@ -379,6 +379,8 @@ "list-view": { "empty": { "new-alert-rule": "", + "new-ds-managed-recording-rule": "", + "new-grafana-recording-rule": "", "new-recording-rule": "", "provisioning": "" }, @@ -3123,7 +3125,8 @@ "old-password-required": "", "passwords-must-match": "", "strong-password-validation-register": "" - } + }, + "change-theme": "" }, "public-dashboard": { "acknowledgment-checkboxes": { diff --git a/public/locales/id-ID/grafana.json b/public/locales/id-ID/grafana.json index 7d47740aff8..7821be91c18 100644 --- a/public/locales/id-ID/grafana.json +++ b/public/locales/id-ID/grafana.json @@ -377,6 +377,8 @@ "list-view": { "empty": { "new-alert-rule": "", + "new-ds-managed-recording-rule": "", + "new-grafana-recording-rule": "", "new-recording-rule": "", "provisioning": "" }, @@ -3113,7 +3115,8 @@ "old-password-required": "", "passwords-must-match": "", "strong-password-validation-register": "" - } + }, + "change-theme": "" }, "public-dashboard": { "acknowledgment-checkboxes": { diff --git a/public/locales/it-IT/grafana.json b/public/locales/it-IT/grafana.json index f3a7ee625b6..19430667f64 100644 --- a/public/locales/it-IT/grafana.json +++ b/public/locales/it-IT/grafana.json @@ -379,6 +379,8 @@ "list-view": { "empty": { "new-alert-rule": "", + "new-ds-managed-recording-rule": "", + "new-grafana-recording-rule": "", "new-recording-rule": "", "provisioning": "" }, @@ -3123,7 +3125,8 @@ "old-password-required": "", "passwords-must-match": "", "strong-password-validation-register": "" - } + }, + "change-theme": "" }, "public-dashboard": { "acknowledgment-checkboxes": { diff --git a/public/locales/ja-JP/grafana.json b/public/locales/ja-JP/grafana.json index 7d47740aff8..7821be91c18 100644 --- a/public/locales/ja-JP/grafana.json +++ b/public/locales/ja-JP/grafana.json @@ -377,6 +377,8 @@ "list-view": { "empty": { "new-alert-rule": "", + "new-ds-managed-recording-rule": "", + "new-grafana-recording-rule": "", "new-recording-rule": "", "provisioning": "" }, @@ -3113,7 +3115,8 @@ "old-password-required": "", "passwords-must-match": "", "strong-password-validation-register": "" - } + }, + "change-theme": "" }, "public-dashboard": { "acknowledgment-checkboxes": { diff --git a/public/locales/ko-KR/grafana.json b/public/locales/ko-KR/grafana.json index 7d47740aff8..7821be91c18 100644 --- a/public/locales/ko-KR/grafana.json +++ b/public/locales/ko-KR/grafana.json @@ -377,6 +377,8 @@ "list-view": { "empty": { "new-alert-rule": "", + "new-ds-managed-recording-rule": "", + "new-grafana-recording-rule": "", "new-recording-rule": "", "provisioning": "" }, @@ -3113,7 +3115,8 @@ "old-password-required": "", "passwords-must-match": "", "strong-password-validation-register": "" - } + }, + "change-theme": "" }, "public-dashboard": { "acknowledgment-checkboxes": { diff --git a/public/locales/nl-NL/grafana.json b/public/locales/nl-NL/grafana.json index f3a7ee625b6..19430667f64 100644 --- a/public/locales/nl-NL/grafana.json +++ b/public/locales/nl-NL/grafana.json @@ -379,6 +379,8 @@ "list-view": { "empty": { "new-alert-rule": "", + "new-ds-managed-recording-rule": "", + "new-grafana-recording-rule": "", "new-recording-rule": "", "provisioning": "" }, @@ -3123,7 +3125,8 @@ "old-password-required": "", "passwords-must-match": "", "strong-password-validation-register": "" - } + }, + "change-theme": "" }, "public-dashboard": { "acknowledgment-checkboxes": { diff --git a/public/locales/pl-PL/grafana.json b/public/locales/pl-PL/grafana.json index af5d7e65575..eda5329aff4 100644 --- a/public/locales/pl-PL/grafana.json +++ b/public/locales/pl-PL/grafana.json @@ -383,6 +383,8 @@ "list-view": { "empty": { "new-alert-rule": "", + "new-ds-managed-recording-rule": "", + "new-grafana-recording-rule": "", "new-recording-rule": "", "provisioning": "" }, @@ -3143,7 +3145,8 @@ "old-password-required": "", "passwords-must-match": "", "strong-password-validation-register": "" - } + }, + "change-theme": "" }, "public-dashboard": { "acknowledgment-checkboxes": { diff --git a/public/locales/pt-BR/grafana.json b/public/locales/pt-BR/grafana.json index 3a29acad057..5a281c2145e 100644 --- a/public/locales/pt-BR/grafana.json +++ b/public/locales/pt-BR/grafana.json @@ -379,6 +379,8 @@ "list-view": { "empty": { "new-alert-rule": "", + "new-ds-managed-recording-rule": "", + "new-grafana-recording-rule": "", "new-recording-rule": "", "provisioning": "" }, @@ -3123,7 +3125,8 @@ "old-password-required": "A senha antiga é obrigatória", "passwords-must-match": "As senhas devem corresponder", "strong-password-validation-register": "A senha não está de acordo com a política de senha forte" - } + }, + "change-theme": "" }, "public-dashboard": { "acknowledgment-checkboxes": { diff --git a/public/locales/pt-PT/grafana.json b/public/locales/pt-PT/grafana.json index f3a7ee625b6..19430667f64 100644 --- a/public/locales/pt-PT/grafana.json +++ b/public/locales/pt-PT/grafana.json @@ -379,6 +379,8 @@ "list-view": { "empty": { "new-alert-rule": "", + "new-ds-managed-recording-rule": "", + "new-grafana-recording-rule": "", "new-recording-rule": "", "provisioning": "" }, @@ -3123,7 +3125,8 @@ "old-password-required": "", "passwords-must-match": "", "strong-password-validation-register": "" - } + }, + "change-theme": "" }, "public-dashboard": { "acknowledgment-checkboxes": { diff --git a/public/locales/ru-RU/grafana.json b/public/locales/ru-RU/grafana.json index af5d7e65575..eda5329aff4 100644 --- a/public/locales/ru-RU/grafana.json +++ b/public/locales/ru-RU/grafana.json @@ -383,6 +383,8 @@ "list-view": { "empty": { "new-alert-rule": "", + "new-ds-managed-recording-rule": "", + "new-grafana-recording-rule": "", "new-recording-rule": "", "provisioning": "" }, @@ -3143,7 +3145,8 @@ "old-password-required": "", "passwords-must-match": "", "strong-password-validation-register": "" - } + }, + "change-theme": "" }, "public-dashboard": { "acknowledgment-checkboxes": { diff --git a/public/locales/sv-SE/grafana.json b/public/locales/sv-SE/grafana.json index f3a7ee625b6..19430667f64 100644 --- a/public/locales/sv-SE/grafana.json +++ b/public/locales/sv-SE/grafana.json @@ -379,6 +379,8 @@ "list-view": { "empty": { "new-alert-rule": "", + "new-ds-managed-recording-rule": "", + "new-grafana-recording-rule": "", "new-recording-rule": "", "provisioning": "" }, @@ -3123,7 +3125,8 @@ "old-password-required": "", "passwords-must-match": "", "strong-password-validation-register": "" - } + }, + "change-theme": "" }, "public-dashboard": { "acknowledgment-checkboxes": { diff --git a/public/locales/tr-TR/grafana.json b/public/locales/tr-TR/grafana.json index f3a7ee625b6..19430667f64 100644 --- a/public/locales/tr-TR/grafana.json +++ b/public/locales/tr-TR/grafana.json @@ -379,6 +379,8 @@ "list-view": { "empty": { "new-alert-rule": "", + "new-ds-managed-recording-rule": "", + "new-grafana-recording-rule": "", "new-recording-rule": "", "provisioning": "" }, @@ -3123,7 +3125,8 @@ "old-password-required": "", "passwords-must-match": "", "strong-password-validation-register": "" - } + }, + "change-theme": "" }, "public-dashboard": { "acknowledgment-checkboxes": { diff --git a/public/locales/zh-Hans/grafana.json b/public/locales/zh-Hans/grafana.json index 4d5daac76cc..bf7cbd6980c 100644 --- a/public/locales/zh-Hans/grafana.json +++ b/public/locales/zh-Hans/grafana.json @@ -377,6 +377,8 @@ "list-view": { "empty": { "new-alert-rule": "", + "new-ds-managed-recording-rule": "", + "new-grafana-recording-rule": "", "new-recording-rule": "", "provisioning": "" }, @@ -3113,7 +3115,8 @@ "old-password-required": "旧密码是必需项", "passwords-must-match": "密码必须一致", "strong-password-validation-register": "密码不符合强密码政策" - } + }, + "change-theme": "" }, "public-dashboard": { "acknowledgment-checkboxes": { diff --git a/public/locales/zh-Hant/grafana.json b/public/locales/zh-Hant/grafana.json index 7d47740aff8..7821be91c18 100644 --- a/public/locales/zh-Hant/grafana.json +++ b/public/locales/zh-Hant/grafana.json @@ -377,6 +377,8 @@ "list-view": { "empty": { "new-alert-rule": "", + "new-ds-managed-recording-rule": "", + "new-grafana-recording-rule": "", "new-recording-rule": "", "provisioning": "" }, @@ -3113,7 +3115,8 @@ "old-password-required": "", "passwords-must-match": "", "strong-password-validation-register": "" - } + }, + "change-theme": "" }, "public-dashboard": { "acknowledgment-checkboxes": { From f02803b02765f082b3a319d788e38bc22b70cc5d Mon Sep 17 00:00:00 2001 From: Alex Khomenko Date: Wed, 12 Mar 2025 07:00:49 +0100 Subject: [PATCH 042/141] Openapi: Remove duplicate group (#101933) Remove duplicate group --- pkg/tests/apis/openapi_test.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/pkg/tests/apis/openapi_test.go b/pkg/tests/apis/openapi_test.go index 83852b6bc4d..28c17981efa 100644 --- a/pkg/tests/apis/openapi_test.go +++ b/pkg/tests/apis/openapi_test.go @@ -74,9 +74,6 @@ func TestIntegrationOpenAPIs(t *testing.T) { }, { Group: "investigations.grafana.app", Version: "v0alpha1", - }, { - Group: "folder.grafana.app", - Version: "v0alpha1", }} for _, gv := range groups { VerifyOpenAPISnapshots(t, dir, gv, h) From cd7b66e2e82cf4ee93ef776997efa058629d3aee Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Wed, 12 Mar 2025 10:01:55 +0300 Subject: [PATCH 043/141] Provisioning: Add RTK client in main (#101991) add frontend --- pkg/apis/folder/v0alpha1/register.go | 3 +- pkg/apis/provisioning/v0alpha1/types.go | 27 +- .../v0alpha1/zz_generated.deepcopy.go | 32 +- .../v0alpha1/zz_generated.openapi.go | 76 +- ...enerated.openapi_violation_exceptions.list | 3 +- .../provisioning/v0alpha1/resourcecount.go | 15 +- .../provisioning.grafana.app-v0alpha1.json | 1830 ++++++++++++++++- pkg/tests/apis/openapi_test.go | 3 - .../provisioning/api/endpoints.gen.ts | 470 ++++- 9 files changed, 2421 insertions(+), 38 deletions(-) diff --git a/pkg/apis/folder/v0alpha1/register.go b/pkg/apis/folder/v0alpha1/register.go index 4f317ac272b..27f1e0ed670 100644 --- a/pkg/apis/folder/v0alpha1/register.go +++ b/pkg/apis/folder/v0alpha1/register.go @@ -3,10 +3,11 @@ package v0alpha1 import ( "fmt" - "github.com/grafana/grafana/pkg/apimachinery/utils" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" + + "github.com/grafana/grafana/pkg/apimachinery/utils" ) const ( diff --git a/pkg/apis/provisioning/v0alpha1/types.go b/pkg/apis/provisioning/v0alpha1/types.go index 8a6f2478636..170b655d999 100644 --- a/pkg/apis/provisioning/v0alpha1/types.go +++ b/pkg/apis/provisioning/v0alpha1/types.go @@ -4,6 +4,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" + "github.com/grafana/grafana/pkg/apimachinery/utils" ) // When this code is changed, make sure to update the code generation. @@ -347,15 +348,31 @@ type ResourceStats struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata,omitempty"` + // Stats across all unified storage + // When legacy storage is still used, this will offer a shim // +listType=atomic - Items []ResourceCount `json:"items,omitempty"` + Instance []ResourceCount `json:"instance,omitempty"` + + // Stats for each manager + // +listType=atomic + Managed []ManagerStats `json:"managed,omitempty"` +} + +type ManagerStats struct { + // Manager kind + Kind utils.ManagerKind `json:"kind,omitempty"` + + // Manager identity + Identity string `json:"id,omitempty"` + + // stats + Stats []ResourceCount `json:"stats"` } type ResourceCount struct { - Repository string `json:"repository,omitempty"` - Group string `json:"group"` - Resource string `json:"resource"` - Count int64 `json:"count"` + Group string `json:"group"` + Resource string `json:"resource"` + Count int64 `json:"count"` } // HistoryList is a list of versions of a resource diff --git a/pkg/apis/provisioning/v0alpha1/zz_generated.deepcopy.go b/pkg/apis/provisioning/v0alpha1/zz_generated.deepcopy.go index 6cdc255840a..6ab9dfc8a11 100644 --- a/pkg/apis/provisioning/v0alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/provisioning/v0alpha1/zz_generated.deepcopy.go @@ -352,6 +352,27 @@ func (in *LocalRepositoryConfig) DeepCopy() *LocalRepositoryConfig { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ManagerStats) DeepCopyInto(out *ManagerStats) { + *out = *in + if in.Stats != nil { + in, out := &in.Stats, &out.Stats + *out = make([]ResourceCount, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ManagerStats. +func (in *ManagerStats) DeepCopy() *ManagerStats { + if in == nil { + return nil + } + out := new(ManagerStats) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *MigrateJobOptions) DeepCopyInto(out *MigrateJobOptions) { *out = *in @@ -656,11 +677,18 @@ func (in *ResourceStats) DeepCopyInto(out *ResourceStats) { *out = *in out.TypeMeta = in.TypeMeta in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items + if in.Instance != nil { + in, out := &in.Instance, &out.Instance *out = make([]ResourceCount, len(*in)) copy(*out, *in) } + if in.Managed != nil { + in, out := &in.Managed, &out.Managed + *out = make([]ManagerStats, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } return } diff --git a/pkg/apis/provisioning/v0alpha1/zz_generated.openapi.go b/pkg/apis/provisioning/v0alpha1/zz_generated.openapi.go index 91e3ae5de2d..ec6e3c0cdbc 100644 --- a/pkg/apis/provisioning/v0alpha1/zz_generated.openapi.go +++ b/pkg/apis/provisioning/v0alpha1/zz_generated.openapi.go @@ -28,6 +28,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.JobSpec": schema_pkg_apis_provisioning_v0alpha1_JobSpec(ref), "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.JobStatus": schema_pkg_apis_provisioning_v0alpha1_JobStatus(ref), "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.LocalRepositoryConfig": schema_pkg_apis_provisioning_v0alpha1_LocalRepositoryConfig(ref), + "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.ManagerStats": schema_pkg_apis_provisioning_v0alpha1_ManagerStats(ref), "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.MigrateJobOptions": schema_pkg_apis_provisioning_v0alpha1_MigrateJobOptions(ref), "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.PullRequestJobOptions": schema_pkg_apis_provisioning_v0alpha1_PullRequestJobOptions(ref), "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.Repository": schema_pkg_apis_provisioning_v0alpha1_Repository(ref), @@ -761,6 +762,49 @@ func schema_pkg_apis_provisioning_v0alpha1_LocalRepositoryConfig(ref common.Refe } } +func schema_pkg_apis_provisioning_v0alpha1_ManagerStats(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: "Manager kind", + Type: []string{"string"}, + Format: "", + }, + }, + "id": { + SchemaProps: spec.SchemaProps{ + Description: "Manager identity", + Type: []string{"string"}, + Format: "", + }, + }, + "stats": { + SchemaProps: spec.SchemaProps{ + Description: "stats", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.ResourceCount"), + }, + }, + }, + }, + }, + }, + Required: []string{"stats"}, + }, + }, + Dependencies: []string{ + "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.ResourceCount"}, + } +} + func schema_pkg_apis_provisioning_v0alpha1_MigrateJobOptions(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -1187,12 +1231,6 @@ func schema_pkg_apis_provisioning_v0alpha1_ResourceCount(ref common.ReferenceCal SchemaProps: spec.SchemaProps{ Type: []string{"object"}, Properties: map[string]spec.Schema{ - "repository": { - SchemaProps: spec.SchemaProps{ - Type: []string{"string"}, - Format: "", - }, - }, "group": { SchemaProps: spec.SchemaProps{ Default: "", @@ -1468,14 +1506,15 @@ func schema_pkg_apis_provisioning_v0alpha1_ResourceStats(ref common.ReferenceCal Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), }, }, - "items": { + "instance": { VendorExtensible: spec.VendorExtensible{ Extensions: spec.Extensions{ "x-kubernetes-list-type": "atomic", }, }, SchemaProps: spec.SchemaProps{ - Type: []string{"array"}, + Description: "Stats across all unified storage When legacy storage is still used, this will offer a shim", + Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -1486,11 +1525,30 @@ func schema_pkg_apis_provisioning_v0alpha1_ResourceStats(ref common.ReferenceCal }, }, }, + "managed": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Stats for each manager", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.ManagerStats"), + }, + }, + }, + }, + }, }, }, }, Dependencies: []string{ - "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.ResourceCount", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.ManagerStats", "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.ResourceCount", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, } } diff --git a/pkg/apis/provisioning/v0alpha1/zz_generated.openapi_violation_exceptions.list b/pkg/apis/provisioning/v0alpha1/zz_generated.openapi_violation_exceptions.list index 95d45ccebb1..093db63eeab 100644 --- a/pkg/apis/provisioning/v0alpha1/zz_generated.openapi_violation_exceptions.list +++ b/pkg/apis/provisioning/v0alpha1/zz_generated.openapi_violation_exceptions.list @@ -3,14 +3,15 @@ API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/provis API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,JobResourceSummary,Errors API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,JobStatus,Errors API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,JobStatus,Summary +API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,ManagerStats,Stats API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,RepositoryList,Items API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,RepositorySpec,Workflows API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,RepositoryViewList,Items API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,ResourceList,Items -API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,ResourceStats,Items API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,TestResults,Errors API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,WebhookStatus,SubscribedEvents API rule violation: names_match,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,JobSpec,PullRequest +API rule violation: names_match,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,ManagerStats,Identity API rule violation: names_match,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,RepositorySpec,GitHub API rule violation: names_match,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,ResourceWrapper,URLs API rule violation: names_match,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,SyncStatus,JobID diff --git a/pkg/generated/applyconfiguration/provisioning/v0alpha1/resourcecount.go b/pkg/generated/applyconfiguration/provisioning/v0alpha1/resourcecount.go index 6c0be497a97..8330fbdce42 100644 --- a/pkg/generated/applyconfiguration/provisioning/v0alpha1/resourcecount.go +++ b/pkg/generated/applyconfiguration/provisioning/v0alpha1/resourcecount.go @@ -7,10 +7,9 @@ package v0alpha1 // ResourceCountApplyConfiguration represents a declarative configuration of the ResourceCount type for use // with apply. type ResourceCountApplyConfiguration struct { - Repository *string `json:"repository,omitempty"` - Group *string `json:"group,omitempty"` - Resource *string `json:"resource,omitempty"` - Count *int64 `json:"count,omitempty"` + Group *string `json:"group,omitempty"` + Resource *string `json:"resource,omitempty"` + Count *int64 `json:"count,omitempty"` } // ResourceCountApplyConfiguration constructs a declarative configuration of the ResourceCount type for use with @@ -19,14 +18,6 @@ func ResourceCount() *ResourceCountApplyConfiguration { return &ResourceCountApplyConfiguration{} } -// WithRepository sets the Repository field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Repository field is set to the value of the last call. -func (b *ResourceCountApplyConfiguration) WithRepository(value string) *ResourceCountApplyConfiguration { - b.Repository = &value - return b -} - // WithGroup sets the Group field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the Group field is set to the value of the last call. diff --git a/pkg/tests/apis/openapi_snapshots/provisioning.grafana.app-v0alpha1.json b/pkg/tests/apis/openapi_snapshots/provisioning.grafana.app-v0alpha1.json index 912222edee1..7e305e99568 100644 --- a/pkg/tests/apis/openapi_snapshots/provisioning.grafana.app-v0alpha1.json +++ b/pkg/tests/apis/openapi_snapshots/provisioning.grafana.app-v0alpha1.json @@ -1092,6 +1092,855 @@ } ] }, + "/apis/provisioning.grafana.app/v0alpha1/namespaces/{namespace}/repositories/{name}/export": { + "post": { + "tags": [ + "Repository" + ], + "description": "Export from grafana into the remote repository", + "operationId": "createRepositoryExport", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "identifier" + ], + "properties": { + "branch": { + "description": "Target branch for export (only git)", + "type": "string" + }, + "folder": { + "description": "The source folder (or empty) to export", + "type": "string" + }, + "identifier": { + "description": "Include the identifier in the exported metadata", + "type": "boolean", + "default": false + }, + "prefix": { + "description": "Prefix in target file system", + "type": "string" + } + } + }, + "example": { + "folder": "grafan-folder-ref", + "branch": "target-branch", + "prefix": "prefix/in/repo/tree", + "identifier": false + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.Job" + } + } + } + } + }, + "x-kubernetes-action": "connect", + "x-kubernetes-group-version-kind": { + "group": "provisioning.grafana.app", + "version": "v0alpha1", + "kind": "Job" + } + }, + "parameters": [ + { + "name": "name", + "in": "path", + "description": "name of the Job", + "required": true, + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "namespace", + "in": "path", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "schema": { + "type": "string", + "uniqueItems": true + } + } + ] + }, + "/apis/provisioning.grafana.app/v0alpha1/namespaces/{namespace}/repositories/{name}/files/": { + "get": { + "tags": [ + "Repository" + ], + "summary": "File listing", + "description": "Get the files and content hash", + "operationId": "getRepositoryFiles", + "parameters": [ + { + "name": "ref", + "in": "query", + "description": "branch or commit hash", + "schema": { + "type": "string" + }, + "examples": { + "": { + "summary": "The default" + }, + "branch": { + "summary": "Select branch", + "value": "my-branch" + }, + "commit": { + "summary": "Commit hash (or prefix)", + "value": "7f7cc2153" + } + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "description": "Information we can get just from the file listing", + "type": "object", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "items": { + "type": "array", + "items": { + "default": {} + }, + "x-kubernetes-list-type": "atomic" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "default": {} + } + } + } + } + } + } + }, + "x-kubernetes-action": "connect", + "x-kubernetes-group-version-kind": { + "group": "provisioning.grafana.app", + "version": "v0alpha1", + "kind": "ResourceWrapper" + } + }, + "parameters": [ + { + "name": "name", + "in": "path", + "description": "name of the ResourceWrapper", + "required": true, + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "namespace", + "in": "path", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "schema": { + "type": "string", + "uniqueItems": true + } + } + ] + }, + "/apis/provisioning.grafana.app/v0alpha1/namespaces/{namespace}/repositories/{name}/files/{path}": { + "get": { + "tags": [ + "Repository" + ], + "description": "Read value from upstream repository", + "operationId": "getRepositoryFilesWithPath", + "parameters": [ + { + "name": "ref", + "in": "query", + "description": "branch or commit hash", + "schema": { + "type": "string" + }, + "examples": { + "": { + "summary": "The default" + }, + "branch": { + "summary": "Select branch", + "value": "my-branch" + }, + "commit": { + "summary": "Commit hash (or prefix)", + "value": "7f7cc2153" + } + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.ResourceWrapper" + } + } + } + } + }, + "x-kubernetes-action": "connect", + "x-kubernetes-group-version-kind": { + "group": "provisioning.grafana.app", + "version": "v0alpha1", + "kind": "ResourceWrapper" + } + }, + "put": { + "tags": [ + "Repository" + ], + "description": "connect PUT requests to files of Repository", + "operationId": "replaceRepositoryFilesWithPath", + "parameters": [ + { + "name": "ref", + "in": "query", + "description": "branch or commit hash", + "schema": { + "type": "string" + }, + "examples": { + "": { + "summary": "The default" + }, + "branch": { + "summary": "Select branch", + "value": "my-branch" + }, + "commit": { + "summary": "Commit hash (or prefix)", + "value": "7f7cc2153" + } + } + }, + { + "name": "message", + "in": "query", + "description": "optional message sent with any changes", + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true + }, + "examples": { + "dashboard": { + "value": { + "spec": { + "hello": "dashboard" + } + } + }, + "playlist": { + "value": { + "spec": { + "hello": "playlist" + } + } + } + } + }, + "application/x-yaml": { + "schema": { + "type": "object", + "additionalProperties": true + }, + "examples": { + "dashboard": { + "value": "apiVersion: dashboards.grafana.app/v0alpha1\nkind: Dashboard\nspec:\n title: Sample dashboard\n" + }, + "playlist": { + "value": "apiVersion: playlist.grafana.app/v0alpha1\nkind: Playlist\nspec:\n title: Playlist from provisioning\n interval: 5m\n items:\n - type: dashboard_by_tag\n value: panel-tests\n" + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.ResourceWrapper" + } + } + } + } + }, + "x-kubernetes-action": "connect", + "x-kubernetes-group-version-kind": { + "group": "provisioning.grafana.app", + "version": "v0alpha1", + "kind": "ResourceWrapper" + } + }, + "post": { + "tags": [ + "Repository" + ], + "description": "connect POST requests to files of Repository", + "operationId": "createRepositoryFilesWithPath", + "parameters": [ + { + "name": "ref", + "in": "query", + "description": "branch or commit hash", + "schema": { + "type": "string" + }, + "examples": { + "": { + "summary": "The default" + }, + "branch": { + "summary": "Select branch", + "value": "my-branch" + }, + "commit": { + "summary": "Commit hash (or prefix)", + "value": "7f7cc2153" + } + } + }, + { + "name": "message", + "in": "query", + "description": "optional message sent with any changes", + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true + }, + "examples": { + "dashboard": { + "value": { + "spec": { + "hello": "dashboard" + } + } + }, + "playlist": { + "value": { + "spec": { + "hello": "playlist" + } + } + } + } + }, + "application/x-yaml": { + "schema": { + "type": "object", + "additionalProperties": true + }, + "examples": { + "dashboard": { + "value": "apiVersion: dashboards.grafana.app/v0alpha1\nkind: Dashboard\nspec:\n title: Sample dashboard\n" + }, + "playlist": { + "value": "apiVersion: playlist.grafana.app/v0alpha1\nkind: Playlist\nspec:\n title: Playlist from provisioning\n interval: 5m\n items:\n - type: dashboard_by_tag\n value: panel-tests\n" + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.ResourceWrapper" + } + } + } + } + }, + "x-kubernetes-action": "connect", + "x-kubernetes-group-version-kind": { + "group": "provisioning.grafana.app", + "version": "v0alpha1", + "kind": "ResourceWrapper" + } + }, + "delete": { + "tags": [ + "Repository" + ], + "description": "connect DELETE requests to files of Repository", + "operationId": "deleteRepositoryFilesWithPath", + "parameters": [ + { + "name": "ref", + "in": "query", + "description": "branch or commit hash", + "schema": { + "type": "string" + }, + "examples": { + "": { + "summary": "The default" + }, + "branch": { + "summary": "Select branch", + "value": "my-branch" + }, + "commit": { + "summary": "Commit hash (or prefix)", + "value": "7f7cc2153" + } + } + }, + { + "name": "message", + "in": "query", + "description": "optional message sent with any changes", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.ResourceWrapper" + } + } + } + } + }, + "x-kubernetes-action": "connect", + "x-kubernetes-group-version-kind": { + "group": "provisioning.grafana.app", + "version": "v0alpha1", + "kind": "ResourceWrapper" + } + }, + "parameters": [ + { + "name": "name", + "in": "path", + "description": "name of the ResourceWrapper", + "required": true, + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "namespace", + "in": "path", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "path", + "in": "path", + "description": "path to the resource", + "required": true, + "schema": { + "type": "string", + "uniqueItems": true + } + } + ] + }, + "/apis/provisioning.grafana.app/v0alpha1/namespaces/{namespace}/repositories/{name}/history": { + "get": { + "tags": [ + "Repository" + ], + "description": "Get the history of the repository", + "operationId": "getRepositoryHistory", + "parameters": [ + { + "name": "ref", + "in": "query", + "description": "branch or commit hash", + "schema": { + "type": "string" + }, + "examples": { + "": { + "summary": "The default" + }, + "branch": { + "summary": "Select branch", + "value": "my-branch" + }, + "commit": { + "summary": "Commit hash (or prefix)", + "value": "7f7cc2153" + } + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "string" + } + } + } + } + }, + "x-kubernetes-action": "connect", + "x-kubernetes-group-version-kind": { + "group": "provisioning.grafana.app", + "version": "v0alpha1", + "kind": "HistoryList" + } + }, + "parameters": [ + { + "name": "name", + "in": "path", + "description": "name of the HistoryList", + "required": true, + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "namespace", + "in": "path", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "schema": { + "type": "string", + "uniqueItems": true + } + } + ] + }, + "/apis/provisioning.grafana.app/v0alpha1/namespaces/{namespace}/repositories/{name}/history/{path}": { + "get": { + "tags": [ + "Repository" + ], + "description": "Get the history of a path", + "operationId": "getRepositoryHistoryWithPath", + "parameters": [ + { + "name": "ref", + "in": "query", + "description": "branch or commit hash", + "schema": { + "type": "string" + }, + "examples": { + "": { + "summary": "The default" + }, + "branch": { + "summary": "Select branch", + "value": "my-branch" + }, + "commit": { + "summary": "Commit hash (or prefix)", + "value": "7f7cc2153" + } + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "string" + } + } + } + } + }, + "x-kubernetes-action": "connect", + "x-kubernetes-group-version-kind": { + "group": "provisioning.grafana.app", + "version": "v0alpha1", + "kind": "HistoryList" + } + }, + "parameters": [ + { + "name": "name", + "in": "path", + "description": "name of the HistoryList", + "required": true, + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "namespace", + "in": "path", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "path", + "in": "path", + "description": "path to the resource", + "required": true, + "schema": { + "type": "string", + "uniqueItems": true + } + } + ] + }, + "/apis/provisioning.grafana.app/v0alpha1/namespaces/{namespace}/repositories/{name}/migrate": { + "post": { + "tags": [ + "Repository" + ], + "description": "Export from grafana into the remote repository", + "operationId": "createRepositoryMigrate", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "identifier" + ], + "properties": { + "history": { + "description": "Preserve history (if possible)", + "type": "boolean" + }, + "identifier": { + "description": "Include the identifier in the exported metadata", + "type": "boolean", + "default": false + }, + "prefix": { + "description": "Target file prefix", + "type": "string" + } + } + }, + "example": { + "prefix": "prefix/in/repo/tree", + "history": true, + "identifier": false + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.Job" + } + } + } + } + }, + "x-kubernetes-action": "connect", + "x-kubernetes-group-version-kind": { + "group": "provisioning.grafana.app", + "version": "v0alpha1", + "kind": "Job" + } + }, + "parameters": [ + { + "name": "name", + "in": "path", + "description": "name of the Job", + "required": true, + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "namespace", + "in": "path", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "schema": { + "type": "string", + "uniqueItems": true + } + } + ] + }, + "/apis/provisioning.grafana.app/v0alpha1/namespaces/{namespace}/repositories/{name}/render/{path}": { + "get": { + "tags": [ + "Repository" + ], + "description": "get a rendered preview image", + "operationId": "getRepositoryRenderWithPath", + "responses": { + "200": { + "description": "OK", + "content": { + "image/png": {} + } + } + }, + "x-kubernetes-action": "connect", + "x-kubernetes-group-version-kind": { + "group": "provisioning.grafana.app", + "version": "v0alpha1", + "kind": "Repository" + } + }, + "parameters": [ + { + "name": "name", + "in": "path", + "description": "name of the Repository", + "required": true, + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "namespace", + "in": "path", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "path", + "in": "path", + "description": "path to the resource", + "required": true, + "schema": { + "type": "string", + "uniqueItems": true + } + } + ] + }, + "/apis/provisioning.grafana.app/v0alpha1/namespaces/{namespace}/repositories/{name}/resources": { + "get": { + "tags": [ + "Repository" + ], + "description": "connect GET requests to resources of Repository", + "operationId": "getRepositoryResources", + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.ResourceList" + } + } + } + } + }, + "x-kubernetes-action": "connect", + "x-kubernetes-group-version-kind": { + "group": "provisioning.grafana.app", + "version": "v0alpha1", + "kind": "ResourceList" + } + }, + "parameters": [ + { + "name": "name", + "in": "path", + "description": "name of the ResourceList", + "required": true, + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "namespace", + "in": "path", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "schema": { + "type": "string", + "uniqueItems": true + } + } + ] + }, "/apis/provisioning.grafana.app/v0alpha1/namespaces/{namespace}/repositories/{name}/status": { "get": { "tags": [ @@ -1381,10 +2230,324 @@ } } ] + }, + "/apis/provisioning.grafana.app/v0alpha1/namespaces/{namespace}/repositories/{name}/sync": { + "post": { + "tags": [ + "Repository" + ], + "description": "Sync from repository into Grafana", + "operationId": "createRepositorySync", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "incremental" + ], + "properties": { + "incremental": { + "description": "Incremental synchronization for versioned repositories", + "type": "boolean", + "default": false + } + } + }, + "example": { + "incremental": false + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.Job" + } + } + } + } + }, + "x-kubernetes-action": "connect", + "x-kubernetes-group-version-kind": { + "group": "provisioning.grafana.app", + "version": "v0alpha1", + "kind": "Job" + } + }, + "parameters": [ + { + "name": "name", + "in": "path", + "description": "name of the Job", + "required": true, + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "namespace", + "in": "path", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "schema": { + "type": "string", + "uniqueItems": true + } + } + ] + }, + "/apis/provisioning.grafana.app/v0alpha1/namespaces/{namespace}/repositories/{name}/test": { + "post": { + "tags": [ + "Repository" + ], + "description": "Check if the configuration is valid", + "operationId": "createRepositoryTest", + "requestBody": { + "content": { + "application/json": { + "schema": { + "description": "When this code is changed, make sure to update the code generation. As of writing, this can be done via the hack dir in the root of the repo: ./hack/update-codegen.sh provisioning If you've opened the generated files in this dir at some point in VSCode, you may also have to re-open them to clear errors.", + "type": "object", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "default": {} + }, + "spec": { + "default": {} + }, + "status": { + "default": {} + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.TestResults" + } + } + } + } + }, + "x-kubernetes-action": "connect", + "x-kubernetes-group-version-kind": { + "group": "provisioning.grafana.app", + "version": "v0alpha1", + "kind": "TestResults" + } + }, + "parameters": [ + { + "name": "name", + "in": "path", + "description": "name of the TestResults", + "required": true, + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "namespace", + "in": "path", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "schema": { + "type": "string", + "uniqueItems": true + } + } + ] + }, + "/apis/provisioning.grafana.app/v0alpha1/namespaces/{namespace}/repositories/{name}/webhook": { + "get": { + "tags": [ + "Repository" + ], + "description": "connect GET requests to webhook of Repository", + "operationId": "getRepositoryWebhook", + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.WebhookResponse" + } + } + } + } + }, + "x-kubernetes-action": "connect", + "x-kubernetes-group-version-kind": { + "group": "provisioning.grafana.app", + "version": "v0alpha1", + "kind": "WebhookResponse" + } + }, + "post": { + "tags": [ + "Repository" + ], + "description": "Currently only supports github webhooks", + "operationId": "createRepositoryWebhook", + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.WebhookResponse" + } + } + } + } + }, + "x-kubernetes-action": "connect", + "x-kubernetes-group-version-kind": { + "group": "provisioning.grafana.app", + "version": "v0alpha1", + "kind": "WebhookResponse" + } + }, + "parameters": [ + { + "name": "name", + "in": "path", + "description": "name of the WebhookResponse", + "required": true, + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "namespace", + "in": "path", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "schema": { + "type": "string", + "uniqueItems": true + } + } + ] + }, + "/apis/provisioning.grafana.app/v0alpha1/namespaces/{namespace}/settings": { + "get": { + "tags": [ + "Provisioning", + "Repository" + ], + "description": "Get the frontend settings for this namespace", + "operationId": "getFrontendSettings", + "parameters": [ + { + "name": "namespace", + "in": "path", + "description": "workspace", + "required": true, + "schema": { + "type": "string" + }, + "example": "default" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.RepositoryViewList" + } + } + } + } + } + } + }, + "/apis/provisioning.grafana.app/v0alpha1/namespaces/{namespace}/stats": { + "get": { + "tags": [ + "Provisioning", + "Repository" + ], + "description": "Get resource stats for this namespace", + "operationId": "getResourceStats", + "parameters": [ + { + "name": "namespace", + "in": "path", + "description": "workspace", + "required": true, + "schema": { + "type": "string" + }, + "example": "default" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.ResourceStats" + } + } + } + } + } + } } }, "components": { "schemas": { + "com.github.grafana.grafana.pkg.apimachinery.apis.common.v0alpha1.Unstructured": { + "type": "object", + "additionalProperties": true, + "x-kubernetes-preserve-unknown-fields": true + }, + "com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.Author": { + "type": "object", + "required": [ + "name", + "username" + ], + "properties": { + "avatarURL": { + "type": "string" + }, + "name": { + "type": "string", + "default": "" + }, + "username": { + "type": "string", + "default": "" + } + } + }, "com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.ExportJobOptions": { "type": "object", "required": [ @@ -1410,6 +2573,56 @@ } } }, + "com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.FileItem": { + "type": "object", + "required": [ + "path" + ], + "properties": { + "author": { + "type": "string" + }, + "hash": { + "type": "string" + }, + "modified": { + "type": "integer", + "format": "int64" + }, + "path": { + "type": "string", + "default": "" + }, + "size": { + "type": "integer", + "format": "int64" + } + } + }, + "com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.FileList": { + "description": "Information we can get just from the file listing", + "type": "object", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "items": { + "type": "array", + "items": { + "default": {} + }, + "x-kubernetes-list-type": "atomic" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "default": {} + } + } + }, "com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.GitHubRepositoryConfig": { "type": "object", "required": [ @@ -1468,6 +2681,61 @@ } } }, + "com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.HistoryItem": { + "type": "object", + "required": [ + "ref", + "message", + "authors", + "createdAt" + ], + "properties": { + "authors": { + "type": "array", + "items": { + "default": {} + }, + "x-kubernetes-list-type": "atomic" + }, + "createdAt": { + "type": "integer", + "format": "int64", + "default": 0 + }, + "message": { + "type": "string", + "default": "" + }, + "ref": { + "type": "string", + "default": "" + } + } + }, + "com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.HistoryList": { + "description": "HistoryList is a list of versions of a resource", + "type": "object", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "items": { + "type": "array", + "items": { + "default": {} + }, + "x-kubernetes-list-type": "atomic" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "default": {} + } + } + }, "com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.Job": { "description": "The repository name and type are stored as labels", "type": "object", @@ -1722,6 +2990,33 @@ } } }, + "com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.ManagerStats": { + "type": "object", + "required": [ + "stats" + ], + "properties": { + "id": { + "description": "Manager identity", + "type": "string" + }, + "kind": { + "description": "Manager kind", + "type": "string" + }, + "stats": { + "description": "stats", + "type": "array", + "items": { + "allOf": [ + { + "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.ResourceCount" + } + ] + } + } + } + }, "com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.MigrateJobOptions": { "type": "object", "required": [ @@ -1982,6 +3277,83 @@ } } }, + "com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.RepositoryView": { + "type": "object", + "required": [ + "name", + "title", + "readOnly", + "type", + "target" + ], + "properties": { + "name": { + "description": "The k8s name for this repository", + "type": "string", + "default": "" + }, + "readOnly": { + "description": "Edit options within the repository", + "type": "boolean", + "default": false + }, + "target": { + "description": "When syncing, where values are saved\n\nPossible enum values:\n - `\"folder\"` Resources will be saved into a folder managed by this repository It will contain a copy of everything from the remote The folder k8s name will be the same as the repository k8s name\n - `\"instance\"` Resources are saved in the global context Only one repository may specify the `instance` target When this exists, the UI will promote writing to the instance repo rather than the grafana database (where possible)", + "type": "string", + "default": "", + "enum": [ + "folder", + "instance" + ] + }, + "title": { + "description": "Repository display", + "type": "string", + "default": "" + }, + "type": { + "description": "The repository type\n\nPossible enum values:\n - `\"github\"`\n - `\"local\"`", + "type": "string", + "default": "", + "enum": [ + "github", + "local" + ] + } + } + }, + "com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.RepositoryViewList": { + "description": "Summary shows a view of the configuration that is sanitized and is OK for logged in users to see", + "type": "object", + "required": [ + "items" + ], + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "items": { + "type": "array", + "items": { + "allOf": [ + { + "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.RepositoryView" + } + ] + }, + "x-kubernetes-map-type": "atomic" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "legacyStorage": { + "description": "The backend is using legacy storage FIXME: Not sure where this should be exposed... but we need it somewhere The UI should force the onboarding workflow when this is true", + "type": "boolean" + } + } + }, "com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.ResourceCount": { "type": "object", "required": [ @@ -1999,15 +3371,368 @@ "type": "string", "default": "" }, - "repository": { - "type": "string" - }, "resource": { "type": "string", "default": "" } } }, + "com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.ResourceList": { + "description": "Information we can get just from the file listing", + "type": "object", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "items": { + "type": "array", + "items": { + "default": {}, + "allOf": [ + { + "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.ResourceListItem" + } + ] + }, + "x-kubernetes-list-type": "atomic" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "default": {}, + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta" + } + ] + } + }, + "x-kubernetes-group-version-kind": [ + { + "group": "provisioning.grafana.app", + "kind": "ResourceList", + "version": "__internal" + }, + { + "group": "provisioning.grafana.app", + "kind": "ResourceList", + "version": "v0alpha1" + } + ] + }, + "com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.ResourceListItem": { + "type": "object", + "required": [ + "path", + "group", + "resource", + "name", + "hash" + ], + "properties": { + "folder": { + "type": "string" + }, + "group": { + "type": "string", + "default": "" + }, + "hash": { + "description": "the k8s identifier", + "type": "string", + "default": "" + }, + "name": { + "type": "string", + "default": "" + }, + "path": { + "type": "string", + "default": "" + }, + "resource": { + "type": "string", + "default": "" + }, + "time": { + "type": "integer", + "format": "int64" + }, + "title": { + "type": "string" + } + } + }, + "com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.ResourceObjects": { + "type": "object", + "required": [ + "type" + ], + "properties": { + "action": { + "description": "The action required/used for dryRun\n\nPossible enum values:\n - `\"create\"`\n - `\"delete\"`\n - `\"update\"`", + "type": "string", + "enum": [ + "create", + "delete", + "update" + ] + }, + "dryRun": { + "description": "The value returned from a dryRun request", + "allOf": [ + { + "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apimachinery.apis.common.v0alpha1.Unstructured" + } + ] + }, + "existing": { + "description": "The same value, currently saved in the grafana database", + "allOf": [ + { + "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apimachinery.apis.common.v0alpha1.Unstructured" + } + ] + }, + "file": { + "description": "The resource from the repository with all modifications applied eg, the name, folder etc will all be applied to this object", + "allOf": [ + { + "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apimachinery.apis.common.v0alpha1.Unstructured" + } + ] + }, + "type": { + "description": "The identified type for this object", + "default": {}, + "allOf": [ + { + "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.ResourceType" + } + ] + }, + "upsert": { + "description": "For write events, this will return the value that was added or updated", + "allOf": [ + { + "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apimachinery.apis.common.v0alpha1.Unstructured" + } + ] + } + } + }, + "com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.ResourceRepositoryInfo": { + "type": "object", + "required": [ + "type", + "title", + "namespace", + "name" + ], + "properties": { + "name": { + "description": "The name (identifier)", + "type": "string", + "default": "" + }, + "namespace": { + "description": "The namespace this belongs to", + "type": "string", + "default": "" + }, + "title": { + "description": "The display name for this repository", + "type": "string", + "default": "" + }, + "type": { + "description": "The repository type\n\nPossible enum values:\n - `\"github\"`\n - `\"local\"`", + "type": "string", + "default": "", + "enum": [ + "github", + "local" + ] + } + } + }, + "com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.ResourceStats": { + "description": "Information we can get just from the file listing", + "type": "object", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "instance": { + "description": "Stats across all unified storage When legacy storage is still used, this will offer a shim", + "type": "array", + "items": { + "allOf": [ + { + "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.ResourceCount" + } + ] + }, + "x-kubernetes-list-type": "atomic" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "managed": { + "description": "Stats for each manager", + "type": "array", + "items": { + "allOf": [ + { + "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.ManagerStats" + } + ] + }, + "x-kubernetes-list-type": "atomic" + }, + "metadata": { + "default": {} + } + } + }, + "com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.ResourceType": { + "type": "object", + "properties": { + "classic": { + "description": "For non-k8s native formats, what did this start as\n\nPossible enum values:\n - `\"access-control\"` Access control https://github.com/grafana/grafana/blob/v11.3.1/conf/provisioning/access-control/sample.yaml\n - `\"alerting\"` Alert configuration https://github.com/grafana/grafana/blob/v11.3.1/conf/provisioning/alerting/sample.yaml\n - `\"dashboard\"` Dashboard JSON\n - `\"datasources\"` Datasource definitions eg: https://github.com/grafana/grafana/blob/v11.3.1/conf/provisioning/datasources/sample.yaml", + "type": "string", + "enum": [ + "access-control", + "alerting", + "dashboard", + "datasources" + ] + }, + "group": { + "type": "string" + }, + "kind": { + "type": "string" + }, + "resource": { + "type": "string" + }, + "version": { + "type": "string" + } + } + }, + "com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.ResourceURLs": { + "type": "object", + "properties": { + "compareURL": { + "description": "Compare this version to the target branch", + "type": "string" + }, + "newPullRequestURL": { + "description": "A URL that will create a new pull requeset for this branch", + "type": "string" + }, + "repositoryURL": { + "description": "A URL pointing to the repository this lives in", + "type": "string" + }, + "sourceURL": { + "description": "A URL pointing to the this file in the repository", + "type": "string" + } + } + }, + "com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.ResourceWrapper": { + "description": "This is a container type for any resource type", + "type": "object", + "required": [ + "repository", + "resource" + ], + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "errors": { + "description": "If errors exist, show them here", + "type": "array", + "items": { + "type": "string", + "default": "" + }, + "x-kubernetes-list-type": "atomic" + }, + "hash": { + "description": "The repo hash value", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "path": { + "description": "Path to the remote file", + "type": "string" + }, + "ref": { + "description": "The request ref (or branch if exists)", + "type": "string" + }, + "repository": { + "description": "Basic repository info", + "default": {}, + "allOf": [ + { + "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.ResourceRepositoryInfo" + } + ] + }, + "resource": { + "description": "Different flavors of the same object", + "default": {}, + "allOf": [ + { + "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.ResourceObjects" + } + ] + }, + "timestamp": { + "description": "The modified time in the remote file system", + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Time" + } + ] + }, + "urls": { + "description": "Typed links for this file (only supported by external systems, github etc)", + "allOf": [ + { + "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.ResourceURLs" + } + ] + } + }, + "x-kubernetes-group-version-kind": [ + { + "group": "provisioning.grafana.app", + "kind": "ResourceWrapper", + "version": "__internal" + }, + { + "group": "provisioning.grafana.app", + "kind": "ResourceWrapper", + "version": "v0alpha1" + } + ] + }, "com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.SyncJobOptions": { "type": "object", "required": [ @@ -2105,6 +3830,105 @@ } } }, + "com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.TestResults": { + "description": "HistoryList is a list of versions of a resource", + "type": "object", + "required": [ + "code", + "success" + ], + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "code": { + "description": "HTTP status code", + "type": "integer", + "format": "int32", + "default": 0 + }, + "details": { + "description": "Optional details", + "allOf": [ + { + "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apimachinery.apis.common.v0alpha1.Unstructured" + } + ] + }, + "errors": { + "description": "Error descriptions", + "type": "array", + "items": { + "type": "string", + "default": "" + } + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "success": { + "description": "Is the connection healthy", + "type": "boolean", + "default": false + } + }, + "x-kubernetes-group-version-kind": [ + { + "group": "provisioning.grafana.app", + "kind": "TestResults", + "version": "__internal" + }, + { + "group": "provisioning.grafana.app", + "kind": "TestResults", + "version": "v0alpha1" + } + ] + }, + "com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.WebhookResponse": { + "type": "object", + "properties": { + "added": { + "description": "Optional message", + "type": "string" + }, + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "code": { + "description": "HTTP Status code 200 implies that the payload was understood but nothing is required 202 implies that an async job has been scheduled to handle the request", + "type": "integer", + "format": "int32" + }, + "job": { + "description": "Jobs to be processed When the response is 202 (Accepted) the queued jobs will be returned", + "allOf": [ + { + "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.JobSpec" + } + ] + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + } + }, + "x-kubernetes-group-version-kind": [ + { + "group": "provisioning.grafana.app", + "kind": "WebhookResponse", + "version": "__internal" + }, + { + "group": "provisioning.grafana.app", + "kind": "WebhookResponse", + "version": "v0alpha1" + } + ] + }, "com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.WebhookStatus": { "type": "object", "properties": { diff --git a/pkg/tests/apis/openapi_test.go b/pkg/tests/apis/openapi_test.go index 28c17981efa..80e7d191bbd 100644 --- a/pkg/tests/apis/openapi_test.go +++ b/pkg/tests/apis/openapi_test.go @@ -68,9 +68,6 @@ func TestIntegrationOpenAPIs(t *testing.T) { }, { Group: "iam.grafana.app", Version: "v0alpha1", - }, { - Group: "provisioning.grafana.app", - Version: "v0alpha1", }, { Group: "investigations.grafana.app", Version: "v0alpha1", diff --git a/public/app/features/provisioning/api/endpoints.gen.ts b/public/app/features/provisioning/api/endpoints.gen.ts index b26ecf21837..fd8429fee34 100644 --- a/public/app/features/provisioning/api/endpoints.gen.ts +++ b/public/app/features/provisioning/api/endpoints.gen.ts @@ -1,5 +1,5 @@ import { baseAPI as api } from './baseAPI'; -export const addTagTypes = ['Job', 'Repository'] as const; +export const addTagTypes = ['Job', 'Repository', 'Provisioning'] as const; const injectedRtkApi = api .enhanceEndpoints({ addTagTypes, @@ -128,6 +128,102 @@ const injectedRtkApi = api }), invalidatesTags: ['Repository'], }), + createRepositoryExport: build.mutation({ + query: (queryArg) => ({ url: `/repositories/${queryArg.name}/export`, method: 'POST', body: queryArg.body }), + invalidatesTags: ['Repository'], + }), + getRepositoryFiles: build.query({ + query: (queryArg) => ({ + url: `/repositories/${queryArg.name}/files/`, + params: { + ref: queryArg.ref, + }, + }), + providesTags: ['Repository'], + }), + getRepositoryFilesWithPath: build.query({ + query: (queryArg) => ({ + url: `/repositories/${queryArg.name}/files/${queryArg.path}`, + params: { + ref: queryArg.ref, + }, + }), + providesTags: ['Repository'], + }), + replaceRepositoryFilesWithPath: build.mutation< + ReplaceRepositoryFilesWithPathResponse, + ReplaceRepositoryFilesWithPathArg + >({ + query: (queryArg) => ({ + url: `/repositories/${queryArg.name}/files/${queryArg.path}`, + method: 'PUT', + body: queryArg.body, + params: { + ref: queryArg.ref, + message: queryArg.message, + }, + }), + invalidatesTags: ['Repository'], + }), + createRepositoryFilesWithPath: build.mutation< + CreateRepositoryFilesWithPathResponse, + CreateRepositoryFilesWithPathArg + >({ + query: (queryArg) => ({ + url: `/repositories/${queryArg.name}/files/${queryArg.path}`, + method: 'POST', + body: queryArg.body, + params: { + ref: queryArg.ref, + message: queryArg.message, + }, + }), + invalidatesTags: ['Repository'], + }), + deleteRepositoryFilesWithPath: build.mutation< + DeleteRepositoryFilesWithPathResponse, + DeleteRepositoryFilesWithPathArg + >({ + query: (queryArg) => ({ + url: `/repositories/${queryArg.name}/files/${queryArg.path}`, + method: 'DELETE', + params: { + ref: queryArg.ref, + message: queryArg.message, + }, + }), + invalidatesTags: ['Repository'], + }), + getRepositoryHistory: build.query({ + query: (queryArg) => ({ + url: `/repositories/${queryArg.name}/history`, + params: { + ref: queryArg.ref, + }, + }), + providesTags: ['Repository'], + }), + getRepositoryHistoryWithPath: build.query({ + query: (queryArg) => ({ + url: `/repositories/${queryArg.name}/history/${queryArg.path}`, + params: { + ref: queryArg.ref, + }, + }), + providesTags: ['Repository'], + }), + createRepositoryMigrate: build.mutation({ + query: (queryArg) => ({ url: `/repositories/${queryArg.name}/migrate`, method: 'POST', body: queryArg.body }), + invalidatesTags: ['Repository'], + }), + getRepositoryRenderWithPath: build.query({ + query: (queryArg) => ({ url: `/repositories/${queryArg.name}/render/${queryArg.path}` }), + providesTags: ['Repository'], + }), + getRepositoryResources: build.query({ + query: (queryArg) => ({ url: `/repositories/${queryArg.name}/resources` }), + providesTags: ['Repository'], + }), getRepositoryStatus: build.query({ query: (queryArg) => ({ url: `/repositories/${queryArg.name}/status`, @@ -151,6 +247,30 @@ const injectedRtkApi = api }), invalidatesTags: ['Repository'], }), + createRepositorySync: build.mutation({ + query: (queryArg) => ({ url: `/repositories/${queryArg.name}/sync`, method: 'POST', body: queryArg.body }), + invalidatesTags: ['Repository'], + }), + createRepositoryTest: build.mutation({ + query: (queryArg) => ({ url: `/repositories/${queryArg.name}/test`, method: 'POST', body: queryArg.body }), + invalidatesTags: ['Repository'], + }), + getRepositoryWebhook: build.query({ + query: (queryArg) => ({ url: `/repositories/${queryArg.name}/webhook` }), + providesTags: ['Repository'], + }), + createRepositoryWebhook: build.mutation({ + query: (queryArg) => ({ url: `/repositories/${queryArg.name}/webhook`, method: 'POST' }), + invalidatesTags: ['Repository'], + }), + getFrontendSettings: build.query({ + query: () => ({ url: `/settings` }), + providesTags: ['Provisioning', 'Repository'], + }), + getResourceStats: build.query({ + query: () => ({ url: `/stats` }), + providesTags: ['Provisioning', 'Repository'], + }), }), overrideExisting: false, }); @@ -356,6 +476,124 @@ export type DeleteRepositoryArg = { /** Whether and how garbage collection will be performed. Either this field or OrphanDependents may be set, but not both. The default policy is decided by the existing finalizer set in the metadata.finalizers and the resource-specific default policy. Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - allow the garbage collector to delete the dependents in the background; 'Foreground' - a cascading policy that deletes all dependents in the foreground. */ propagationPolicy?: string; }; +export type CreateRepositoryExportResponse = /** status 200 OK */ Job; +export type CreateRepositoryExportArg = { + /** name of the Job */ + name: string; + body: { + /** Target branch for export (only git) */ + branch?: string; + /** The source folder (or empty) to export */ + folder?: string; + /** Include the identifier in the exported metadata */ + identifier: boolean; + /** Prefix in target file system */ + prefix?: string; + }; +}; +export type GetRepositoryFilesResponse = /** status 200 OK */ { + /** APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources */ + apiVersion?: string; + items?: any[]; + /** Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */ + kind?: string; + metadata?: any; +}; +export type GetRepositoryFilesArg = { + /** name of the ResourceWrapper */ + name: string; + /** branch or commit hash */ + ref?: string; +}; +export type GetRepositoryFilesWithPathResponse = /** status 200 OK */ ResourceWrapper; +export type GetRepositoryFilesWithPathArg = { + /** name of the ResourceWrapper */ + name: string; + /** path to the resource */ + path: string; + /** branch or commit hash */ + ref?: string; +}; +export type ReplaceRepositoryFilesWithPathResponse = /** status 200 OK */ ResourceWrapper; +export type ReplaceRepositoryFilesWithPathArg = { + /** name of the ResourceWrapper */ + name: string; + /** path to the resource */ + path: string; + /** branch or commit hash */ + ref?: string; + /** optional message sent with any changes */ + message?: string; + body: { + [key: string]: any; + }; +}; +export type CreateRepositoryFilesWithPathResponse = /** status 200 OK */ ResourceWrapper; +export type CreateRepositoryFilesWithPathArg = { + /** name of the ResourceWrapper */ + name: string; + /** path to the resource */ + path: string; + /** branch or commit hash */ + ref?: string; + /** optional message sent with any changes */ + message?: string; + body: { + [key: string]: any; + }; +}; +export type DeleteRepositoryFilesWithPathResponse = /** status 200 OK */ ResourceWrapper; +export type DeleteRepositoryFilesWithPathArg = { + /** name of the ResourceWrapper */ + name: string; + /** path to the resource */ + path: string; + /** branch or commit hash */ + ref?: string; + /** optional message sent with any changes */ + message?: string; +}; +export type GetRepositoryHistoryResponse = /** status 200 OK */ string; +export type GetRepositoryHistoryArg = { + /** name of the HistoryList */ + name: string; + /** branch or commit hash */ + ref?: string; +}; +export type GetRepositoryHistoryWithPathResponse = /** status 200 OK */ string; +export type GetRepositoryHistoryWithPathArg = { + /** name of the HistoryList */ + name: string; + /** path to the resource */ + path: string; + /** branch or commit hash */ + ref?: string; +}; +export type CreateRepositoryMigrateResponse = /** status 200 OK */ Job; +export type CreateRepositoryMigrateArg = { + /** name of the Job */ + name: string; + body: { + /** Preserve history (if possible) */ + history?: boolean; + /** Include the identifier in the exported metadata */ + identifier: boolean; + /** Target file prefix */ + prefix?: string; + }; +}; +export type GetRepositoryRenderWithPathResponse = unknown; +export type GetRepositoryRenderWithPathArg = { + /** name of the Repository */ + name: string; + /** path to the resource */ + path: string; +}; +export type GetRepositoryResourcesResponse = /** status 200 OK */ ResourceList; +export type GetRepositoryResourcesArg = { + /** name of the ResourceList */ + name: string; +}; export type GetRepositoryStatusResponse = /** status 200 OK */ Repository; export type GetRepositoryStatusArg = { /** name of the Repository */ @@ -377,6 +615,43 @@ export type ReplaceRepositoryStatusArg = { fieldValidation?: string; repository: Repository; }; +export type CreateRepositorySyncResponse = /** status 200 OK */ Job; +export type CreateRepositorySyncArg = { + /** name of the Job */ + name: string; + body: { + /** Incremental synchronization for versioned repositories */ + incremental: boolean; + }; +}; +export type CreateRepositoryTestResponse = /** status 200 OK */ TestResults; +export type CreateRepositoryTestArg = { + /** name of the TestResults */ + name: string; + body: { + /** APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources */ + apiVersion?: string; + /** Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */ + kind?: string; + metadata?: any; + spec?: any; + status?: any; + }; +}; +export type GetRepositoryWebhookResponse = /** status 200 OK */ WebhookResponse; +export type GetRepositoryWebhookArg = { + /** name of the WebhookResponse */ + name: string; +}; +export type CreateRepositoryWebhookResponse = /** status 200 OK */ WebhookResponse; +export type CreateRepositoryWebhookArg = { + /** name of the WebhookResponse */ + name: string; +}; +export type GetFrontendSettingsResponse = /** status 200 undefined */ RepositoryViewList; +export type GetFrontendSettingsArg = void; +export type GetResourceStatsResponse = /** status 200 undefined */ ResourceStats; +export type GetResourceStatsArg = void; export type Time = string; export type FieldsV1 = object; export type ManagedFieldsEntry = { @@ -624,7 +899,6 @@ export type HealthStatus = { export type ResourceCount = { count: number; group: string; - repository?: string; resource: string; }; export type SyncStatus = { @@ -731,6 +1005,181 @@ export type Status = { /** Status of the operation. One of: "Success" or "Failure". More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status */ status?: string; }; +export type ResourceRepositoryInfo = { + /** The name (identifier) */ + name: string; + /** The namespace this belongs to */ + namespace: string; + /** The display name for this repository */ + title: string; + /** The repository type + + Possible enum values: + - `"github"` + - `"local"` */ + type: 'github' | 'local'; +}; +export type Unstructured = { + [key: string]: any; +}; +export type ResourceType = { + /** For non-k8s native formats, what did this start as + + Possible enum values: + - `"access-control"` Access control https://github.com/grafana/grafana/blob/v11.3.1/conf/provisioning/access-control/sample.yaml + - `"alerting"` Alert configuration https://github.com/grafana/grafana/blob/v11.3.1/conf/provisioning/alerting/sample.yaml + - `"dashboard"` Dashboard JSON + - `"datasources"` Datasource definitions eg: https://github.com/grafana/grafana/blob/v11.3.1/conf/provisioning/datasources/sample.yaml */ + classic?: 'access-control' | 'alerting' | 'dashboard' | 'datasources'; + group?: string; + kind?: string; + resource?: string; + version?: string; +}; +export type ResourceObjects = { + /** The action required/used for dryRun + + Possible enum values: + - `"create"` + - `"delete"` + - `"update"` */ + action?: 'create' | 'delete' | 'update'; + /** The value returned from a dryRun request */ + dryRun?: Unstructured; + /** The same value, currently saved in the grafana database */ + existing?: Unstructured; + /** The resource from the repository with all modifications applied eg, the name, folder etc will all be applied to this object */ + file?: Unstructured; + /** The identified type for this object */ + type: ResourceType; + /** For write events, this will return the value that was added or updated */ + upsert?: Unstructured; +}; +export type ResourceUrLs = { + /** Compare this version to the target branch */ + compareURL?: string; + /** A URL that will create a new pull requeset for this branch */ + newPullRequestURL?: string; + /** A URL pointing to the repository this lives in */ + repositoryURL?: string; + /** A URL pointing to the this file in the repository */ + sourceURL?: string; +}; +export type ResourceWrapper = { + /** APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources */ + apiVersion?: string; + /** If errors exist, show them here */ + errors?: string[]; + /** The repo hash value */ + hash?: string; + /** Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */ + kind?: string; + /** Path to the remote file */ + path?: string; + /** The request ref (or branch if exists) */ + ref?: string; + /** Basic repository info */ + repository: ResourceRepositoryInfo; + /** Different flavors of the same object */ + resource: ResourceObjects; + /** The modified time in the remote file system */ + timestamp?: Time; + /** Typed links for this file (only supported by external systems, github etc) */ + urls?: ResourceUrLs; +}; +export type ResourceListItem = { + folder?: string; + group: string; + /** the k8s identifier */ + hash: string; + name: string; + path: string; + resource: string; + time?: number; + title?: string; +}; +export type ResourceList = { + /** APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources */ + apiVersion?: string; + items?: ResourceListItem[]; + /** Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */ + kind?: string; + metadata?: ListMeta; +}; +export type TestResults = { + /** APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources */ + apiVersion?: string; + /** HTTP status code */ + code: number; + /** Optional details */ + details?: Unstructured; + /** Error descriptions */ + errors?: string[]; + /** Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */ + kind?: string; + /** Is the connection healthy */ + success: boolean; +}; +export type WebhookResponse = { + /** Optional message */ + added?: string; + /** APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources */ + apiVersion?: string; + /** HTTP Status code 200 implies that the payload was understood but nothing is required 202 implies that an async job has been scheduled to handle the request */ + code?: number; + /** Jobs to be processed When the response is 202 (Accepted) the queued jobs will be returned */ + job?: JobSpec; + /** Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */ + kind?: string; +}; +export type RepositoryView = { + /** The k8s name for this repository */ + name: string; + /** Edit options within the repository */ + readOnly: boolean; + /** When syncing, where values are saved + + Possible enum values: + - `"folder"` Resources will be saved into a folder managed by this repository It will contain a copy of everything from the remote The folder k8s name will be the same as the repository k8s name + - `"instance"` Resources are saved in the global context Only one repository may specify the `instance` target When this exists, the UI will promote writing to the instance repo rather than the grafana database (where possible) */ + target: 'folder' | 'instance'; + /** Repository display */ + title: string; + /** The repository type + + Possible enum values: + - `"github"` + - `"local"` */ + type: 'github' | 'local'; +}; +export type RepositoryViewList = { + /** APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources */ + apiVersion?: string; + items: RepositoryView[]; + /** Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */ + kind?: string; + /** The backend is using legacy storage FIXME: Not sure where this should be exposed... but we need it somewhere The UI should force the onboarding workflow when this is true */ + legacyStorage?: boolean; +}; +export type ManagerStats = { + /** Manager identity */ + id?: string; + /** Manager kind */ + kind?: string; + /** stats */ + stats: ResourceCount[]; +}; +export type ResourceStats = { + /** APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources */ + apiVersion?: string; + /** Stats across all unified storage When legacy storage is still used, this will offer a shim */ + instance?: ResourceCount[]; + /** Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */ + kind?: string; + /** Stats for each manager */ + managed?: ManagerStats[]; + metadata?: any; +}; export const { useListJobQuery, useGetJobQuery, @@ -740,6 +1189,23 @@ export const { useGetRepositoryQuery, useReplaceRepositoryMutation, useDeleteRepositoryMutation, + useCreateRepositoryExportMutation, + useGetRepositoryFilesQuery, + useGetRepositoryFilesWithPathQuery, + useReplaceRepositoryFilesWithPathMutation, + useCreateRepositoryFilesWithPathMutation, + useDeleteRepositoryFilesWithPathMutation, + useGetRepositoryHistoryQuery, + useGetRepositoryHistoryWithPathQuery, + useCreateRepositoryMigrateMutation, + useGetRepositoryRenderWithPathQuery, + useGetRepositoryResourcesQuery, useGetRepositoryStatusQuery, useReplaceRepositoryStatusMutation, + useCreateRepositorySyncMutation, + useCreateRepositoryTestMutation, + useGetRepositoryWebhookQuery, + useCreateRepositoryWebhookMutation, + useGetFrontendSettingsQuery, + useGetResourceStatsQuery, } = injectedRtkApi; From 848d49e70f2aa2755a7e57a3f110320e591af76a Mon Sep 17 00:00:00 2001 From: Matheus Macabu Date: Wed, 12 Mar 2025 08:26:41 +0100 Subject: [PATCH 044/141] Chore: Add username option for redis remote cache (#101787) * Chore: Add username option for redis remote cache (cherry picked from commit 25e28dc85e646e8cb7ab9ab582de8fb247b58b07) * Chore: Update docs and config with sample Redis conn with user+pass --------- Co-authored-by: Thomas Fournier --- conf/defaults.ini | 2 +- conf/sample.ini | 2 +- docs/sources/setup-grafana/configure-grafana/_index.md | 4 +++- pkg/infra/remotecache/redis_storage.go | 2 ++ pkg/infra/remotecache/redis_storage_test.go | 3 ++- 5 files changed, 9 insertions(+), 4 deletions(-) diff --git a/conf/defaults.ini b/conf/defaults.ini index 8fc8296fead..ba04540e320 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -198,7 +198,7 @@ type = database # cache connectionstring options # database: will use Grafana primary database. -# redis: config like redis server e.g. `addr=127.0.0.1:6379,pool_size=100,db=0,ssl=false`. Only addr is required. ssl may be 'true', 'false', or 'insecure'. +# redis: config like redis server e.g. `addr=127.0.0.1:6379,pool_size=100,db=0,username=grafana,password=grafanaRocks,ssl=false`. Only addr is required. ssl may be 'true', 'false', or 'insecure'. # memcache: 127.0.0.1:11211 connstr = diff --git a/conf/sample.ini b/conf/sample.ini index 152fbf6fb96..15f94510ba6 100644 --- a/conf/sample.ini +++ b/conf/sample.ini @@ -197,7 +197,7 @@ # cache connectionstring options # database: will use Grafana primary database. -# redis: config like redis server e.g. `addr=127.0.0.1:6379,pool_size=100,db=0,ssl=false`. Only addr is required. ssl may be 'true', 'false', or 'insecure'. +# redis: config like redis server e.g. `addr=127.0.0.1:6379,pool_size=100,db=0,username=grafana,password=grafanaRocks,ssl=false`. Only addr is required. ssl may be 'true', 'false', or 'insecure'. # memcache: 127.0.0.1:11211 ;connstr = diff --git a/docs/sources/setup-grafana/configure-grafana/_index.md b/docs/sources/setup-grafana/configure-grafana/_index.md index 9f16b072e63..c77d5cab8d7 100644 --- a/docs/sources/setup-grafana/configure-grafana/_index.md +++ b/docs/sources/setup-grafana/configure-grafana/_index.md @@ -491,11 +491,13 @@ Leave empty when using `database` and Grafana uses the primary database. ##### `redis` -Example connection string: `addr=127.0.0.1:6379,pool_size=100,db=0,ssl=false` +Example connection string: `addr=127.0.0.1:6379,pool_size=100,db=0,username=grafana,password=grafanaRocks,ssl=false` - `addr` is the host `:` port of the Redis server. - `pool_size` (optional) is the number of underlying connections that can be made to Redis. - `db` (optional) is the number identifier of the Redis database you want to use. +- `username` (optional) is the connection identifier to authenticate the current connection. +- `password` (optional) is the connection secret to authenticate the current connection. - `ssl` (optional) is if SSL should be used to connect to Redis server. The value may be `true`, `false`, or `insecure`. Setting the value to `insecure` skips verification of the certificate chain and hostname when making the connection. ##### `memcache` diff --git a/pkg/infra/remotecache/redis_storage.go b/pkg/infra/remotecache/redis_storage.go index 84c9a081f81..de10aa88eb3 100644 --- a/pkg/infra/remotecache/redis_storage.go +++ b/pkg/infra/remotecache/redis_storage.go @@ -39,6 +39,8 @@ func parseRedisConnStr(connStr string) (*redis.Options, error) { switch connKey { case "addr": options.Addr = connVal + case "username": + options.Username = connVal case "password": options.Password = connVal case "db": diff --git a/pkg/infra/remotecache/redis_storage_test.go b/pkg/infra/remotecache/redis_storage_test.go index 32138431f21..6f796bd5ab6 100644 --- a/pkg/infra/remotecache/redis_storage_test.go +++ b/pkg/infra/remotecache/redis_storage_test.go @@ -16,11 +16,12 @@ func Test_parseRedisConnStr(t *testing.T) { ShouldErr bool }{ "all redis options should parse": { - "addr=127.0.0.1:6379,pool_size=100,db=1,password=grafanaRocks,ssl=false", + "addr=127.0.0.1:6379,pool_size=100,db=1,username=grafana,password=grafanaRocks,ssl=false", &redis.Options{ Addr: "127.0.0.1:6379", PoolSize: 100, DB: 1, + Username: "grafana", Password: "grafanaRocks", Network: "tcp", TLSConfig: nil, From 13cd9c3c60e7105e0e5a6da1799a8ee5aa3451a9 Mon Sep 17 00:00:00 2001 From: "Ren Goto (@ren510dev)" Date: Wed, 12 Mar 2025 16:33:15 +0900 Subject: [PATCH 045/141] Docs: Fix incorrect label groupings (#101491) fix incorrect label groupings in alerting documents Co-authored-by: Matheus Macabu --- .../fundamentals/notifications/group-alert-notifications.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/alerting/fundamentals/notifications/group-alert-notifications.md b/docs/sources/alerting/fundamentals/notifications/group-alert-notifications.md index 8e2eab652e8..4434a6f9a88 100644 --- a/docs/sources/alerting/fundamentals/notifications/group-alert-notifications.md +++ b/docs/sources/alerting/fundamentals/notifications/group-alert-notifications.md @@ -55,7 +55,7 @@ Alert instances are grouped together if they have the same exact label values fo For example, given the `Group by` option set to the `team` label: - `alertname:foo, team=frontend`, and `alertname:bar, team=frontend` are in one group. -- `alertname:foo, team=backend`, and `alertname:qux, team=backend` are in another group. +- `alertname:foo, team=frontend`, and `alertname:qux, team=backend` are in another group. ### Group by alert rule or labels From e28c993465dc32405ac51484bf1db0b37178b865 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 12 Mar 2025 08:35:44 +0100 Subject: [PATCH 046/141] DashboardScene: De-select object after they are removed (#101940) --- .../dashboard-scene/edit-pane/DashboardEditPane.tsx | 8 +++++++- public/app/features/dashboard-scene/edit-pane/shared.ts | 4 ++++ .../scene/layout-default/DefaultGridLayoutManager.tsx | 4 +++- .../ResponsiveGridLayoutManager.tsx | 3 ++- .../scene/layout-rows/RowsLayoutManager.tsx | 3 ++- .../scene/layout-tabs/TabsLayoutManager.tsx | 3 +++ 6 files changed, 21 insertions(+), 4 deletions(-) diff --git a/public/app/features/dashboard-scene/edit-pane/DashboardEditPane.tsx b/public/app/features/dashboard-scene/edit-pane/DashboardEditPane.tsx index cef94e5be88..072d80ba367 100644 --- a/public/app/features/dashboard-scene/edit-pane/DashboardEditPane.tsx +++ b/public/app/features/dashboard-scene/edit-pane/DashboardEditPane.tsx @@ -21,7 +21,7 @@ import { DashboardAddPane } from './DashboardAddPane'; import { DashboardOutline } from './DashboardOutline'; import { ElementEditPane } from './ElementEditPane'; import { ElementSelection } from './ElementSelection'; -import { NewObjectAddedToCanvasEvent } from './shared'; +import { NewObjectAddedToCanvasEvent, ObjectRemovedFromCanvasEvent } from './shared'; import { useEditableElement } from './useEditableElement'; export interface DashboardEditPaneState extends SceneObjectState { @@ -53,6 +53,12 @@ export class DashboardEditPane extends SceneObjectBase { this.newObjectAddedToCanvas(payload); }) ); + + this._subs.add( + dashboard.subscribeToEvent(ObjectRemovedFromCanvasEvent, ({ payload }) => { + this.clearSelection(); + }) + ); } public enableSelection() { diff --git a/public/app/features/dashboard-scene/edit-pane/shared.ts b/public/app/features/dashboard-scene/edit-pane/shared.ts index 7f284ef945c..f599a50ef8c 100644 --- a/public/app/features/dashboard-scene/edit-pane/shared.ts +++ b/public/app/features/dashboard-scene/edit-pane/shared.ts @@ -58,3 +58,7 @@ export function hasEditableElement(sceneObj: SceneObject | undefined): boolean { export class NewObjectAddedToCanvasEvent extends BusEventWithPayload { static type = 'new-object-added-to-canvas'; } + +export class ObjectRemovedFromCanvasEvent extends BusEventWithPayload { + static type = 'object-removed-from-canvas'; +} 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 992a6f9102d..c30558f3221 100644 --- a/public/app/features/dashboard-scene/scene/layout-default/DefaultGridLayoutManager.tsx +++ b/public/app/features/dashboard-scene/scene/layout-default/DefaultGridLayoutManager.tsx @@ -15,7 +15,7 @@ import { GRID_COLUMN_COUNT } from 'app/core/constants'; import { t } from 'app/core/internationalization'; import DashboardEmpty from 'app/features/dashboard/dashgrid/DashboardEmpty'; -import { NewObjectAddedToCanvasEvent } from '../../edit-pane/shared'; +import { NewObjectAddedToCanvasEvent, ObjectRemovedFromCanvasEvent } from '../../edit-pane/shared'; import { isClonedKey, joinCloneKeys } from '../../utils/clone'; import { dashboardSceneGraph } from '../../utils/dashboardSceneGraph'; import { @@ -108,6 +108,8 @@ export class DefaultGridLayoutManager this.state.grid.setState({ children: layout.state.children.filter((child) => child !== gridItem), }); + + this.publishEvent(new ObjectRemovedFromCanvasEvent(panel), true); } public duplicatePanel(vizPanel: VizPanel) { 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 f610ceac675..eefe03a13a5 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 @@ -2,7 +2,7 @@ import { SceneComponentProps, SceneCSSGridLayout, SceneObjectBase, SceneObjectSt import { t } from 'app/core/internationalization'; import { OptionsPaneItemDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor'; -import { NewObjectAddedToCanvasEvent } from '../../edit-pane/shared'; +import { NewObjectAddedToCanvasEvent, ObjectRemovedFromCanvasEvent } from '../../edit-pane/shared'; import { joinCloneKeys } from '../../utils/clone'; import { dashboardSceneGraph } from '../../utils/dashboardSceneGraph'; import { getGridItemKeyForPanelId, getPanelIdForVizPanel, getVizPanelKeyForPanelId } from '../../utils/utils'; @@ -68,6 +68,7 @@ export class ResponsiveGridLayoutManager public removePanel(panel: VizPanel) { const element = panel.parent; this.state.layout.setState({ children: this.state.layout.state.children.filter((child) => child !== element) }); + this.publishEvent(new ObjectRemovedFromCanvasEvent(panel), true); } public duplicatePanel(panel: VizPanel) { 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 ceccc165557..4c1acfe940a 100644 --- a/public/app/features/dashboard-scene/scene/layout-rows/RowsLayoutManager.tsx +++ b/public/app/features/dashboard-scene/scene/layout-rows/RowsLayoutManager.tsx @@ -1,7 +1,7 @@ import { SceneGridItemLike, SceneGridRow, SceneObjectBase, SceneObjectState, VizPanel } from '@grafana/scenes'; import { t } from 'app/core/internationalization'; -import { NewObjectAddedToCanvasEvent } from '../../edit-pane/shared'; +import { NewObjectAddedToCanvasEvent, ObjectRemovedFromCanvasEvent } from '../../edit-pane/shared'; import { isClonedKey } from '../../utils/clone'; import { dashboardSceneGraph } from '../../utils/dashboardSceneGraph'; import { DashboardGridItem } from '../layout-default/DashboardGridItem'; @@ -128,6 +128,7 @@ export class RowsLayoutManager extends SceneObjectBase i public removeRow(row: RowItem) { const rows = this.state.rows.filter((r) => r !== row); this.setState({ rows: rows.length === 0 ? [new RowItem()] : rows }); + this.publishEvent(new ObjectRemovedFromCanvasEvent(row), true); } public moveRowUp(row: RowItem) { diff --git a/public/app/features/dashboard-scene/scene/layout-tabs/TabsLayoutManager.tsx b/public/app/features/dashboard-scene/scene/layout-tabs/TabsLayoutManager.tsx index 319210697cd..3c7f1b24bd4 100644 --- a/public/app/features/dashboard-scene/scene/layout-tabs/TabsLayoutManager.tsx +++ b/public/app/features/dashboard-scene/scene/layout-tabs/TabsLayoutManager.tsx @@ -7,6 +7,7 @@ import { } from '@grafana/scenes'; import { t } from 'app/core/internationalization'; +import { ObjectRemovedFromCanvasEvent } from '../../edit-pane/shared'; import { DashboardLayoutManager } from '../types/DashboardLayoutManager'; import { LayoutRegistryItem } from '../types/LayoutRegistryItem'; @@ -115,6 +116,7 @@ export class TabsLayoutManager extends SceneObjectBase i if (currentTab === tabToRemove) { const nextTabIndex = this.state.currentTabIndex > 0 ? this.state.currentTabIndex - 1 : 0; this.setState({ tabs: this.state.tabs.filter((t) => t !== tabToRemove), currentTabIndex: nextTabIndex }); + this.publishEvent(new ObjectRemovedFromCanvasEvent(tabToRemove), true); return; } @@ -122,6 +124,7 @@ export class TabsLayoutManager extends SceneObjectBase i const tabs = filteredTab.length === 0 ? [new TabItem()] : filteredTab; this.setState({ tabs, currentTabIndex: 0 }); + this.publishEvent(new ObjectRemovedFromCanvasEvent(tabToRemove), true); } public addTabBefore(tab: TabItem) { From e6f682bc14ec73dcd67b9c4983aed1ad797bc9d5 Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Wed, 12 Mar 2025 10:46:12 +0300 Subject: [PATCH 047/141] K8s/Dashboards: Fix title extraction (#101990) --- pkg/apimachinery/utils/meta.go | 16 ++++++++++++++++ .../migration/conversion/conversion_test.go | 14 +++++++++++--- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/pkg/apimachinery/utils/meta.go b/pkg/apimachinery/utils/meta.go index 49bbcd3eb02..53cb66fca4a 100644 --- a/pkg/apimachinery/utils/meta.go +++ b/pkg/apimachinery/utils/meta.go @@ -607,6 +607,22 @@ func (m *grafanaMetaAccessor) FindTitle(defaultTitle string) string { if name.IsValid() && name.Kind() == reflect.String { return name.String() } + + // Unstructured uses Object subtype + object := spec.FieldByName("Object") + if object.IsValid() && object.Kind() == reflect.Map { + key := reflect.ValueOf("title") + value := object.MapIndex(key) + if value.IsValid() { + if value.CanInterface() { + v := value.Interface() + t, ok := v.(string) + if ok { + return t + } + } + } + } } obj, ok := m.obj.(*unstructured.Unstructured) diff --git a/pkg/apis/dashboard/migration/conversion/conversion_test.go b/pkg/apis/dashboard/migration/conversion/conversion_test.go index a5c2b02decd..cddf508e9a3 100644 --- a/pkg/apis/dashboard/migration/conversion/conversion_test.go +++ b/pkg/apis/dashboard/migration/conversion/conversion_test.go @@ -2,12 +2,15 @@ package conversion import ( "fmt" + "strings" "testing" "github.com/stretchr/testify/require" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" + "github.com/grafana/grafana/pkg/apimachinery/utils" dashboardV0 "github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1" dashboardV1 "github.com/grafana/grafana/pkg/apis/dashboard/v1alpha1" dashboardV2 "github.com/grafana/grafana/pkg/apis/dashboard/v2alpha1" @@ -15,9 +18,9 @@ import ( func TestConversionMatrixExist(t *testing.T) { versions := []v1.Object{ - &dashboardV0.Dashboard{}, - &dashboardV1.Dashboard{}, - &dashboardV2.Dashboard{}, + &dashboardV0.Dashboard{Spec: v0alpha1.Unstructured{Object: map[string]any{"title": "dashboardV0"}}}, + &dashboardV1.Dashboard{Spec: v0alpha1.Unstructured{Object: map[string]any{"title": "dashboardV1"}}}, + &dashboardV2.Dashboard{Spec: dashboardV2.DashboardSpec{Title: "dashboardV2"}}, } scheme := runtime.NewScheme() @@ -34,6 +37,11 @@ func TestConversionMatrixExist(t *testing.T) { err = scheme.Convert(in, out, nil) require.NoError(t, err) } + + // Make sure we get the right title for each value + meta, err := utils.MetaAccessor(in) + require.NoError(t, err) + require.True(t, strings.HasPrefix(meta.FindTitle(""), "dashboard")) }) } } From 89882749124f6d6e1ffb521bd89378e525756e13 Mon Sep 17 00:00:00 2001 From: Ben Sully Date: Wed, 12 Mar 2025 08:53:20 +0000 Subject: [PATCH 048/141] Dashboards: update `@grafana/llm` to v0.13.2 and update usage (#101814) This version of the package deprecates the `openai` object in favour of the vendor-agnostic `llm` object, so this PR also updates the usage of the package to use the new object and take advantage of the vendor-agnostic APIs. --- package.json | 2 +- .../components/GenAI/GenAIButton.test.tsx | 20 +- .../components/GenAI/GenAIButton.tsx | 13 +- .../GenAI/GenAIDashboardChangesButton.tsx | 4 +- .../components/GenAI/GenAIHistory.tsx | 8 +- .../dashboard/components/GenAI/hooks.ts | 20 +- .../dashboard/components/GenAI/utils.test.ts | 15 +- .../dashboard/components/GenAI/utils.ts | 15 +- yarn.lock | 865 ++---------------- 9 files changed, 128 insertions(+), 834 deletions(-) diff --git a/package.json b/package.json index d77bd8cef07..70e2bc7716c 100644 --- a/package.json +++ b/package.json @@ -270,7 +270,7 @@ "@grafana/flamegraph": "workspace:*", "@grafana/google-sdk": "0.1.2", "@grafana/lezer-logql": "0.2.7", - "@grafana/llm": "0.12.0", + "@grafana/llm": "0.13.2", "@grafana/monaco-logql": "^0.0.8", "@grafana/o11y-ds-frontend": "workspace:*", "@grafana/plugin-ui": "0.10.1", diff --git a/public/app/features/dashboard/components/GenAI/GenAIButton.test.tsx b/public/app/features/dashboard/components/GenAI/GenAIButton.test.tsx index ab003448e23..0f4e44263a9 100644 --- a/public/app/features/dashboard/components/GenAI/GenAIButton.test.tsx +++ b/public/app/features/dashboard/components/GenAI/GenAIButton.test.tsx @@ -6,11 +6,11 @@ import { render } from 'test/test-utils'; import { selectors } from '@grafana/e2e-selectors'; import { GenAIButton, GenAIButtonProps } from './GenAIButton'; -import { StreamStatus, useOpenAIStream } from './hooks'; +import { StreamStatus, useLLMStream } from './hooks'; import { EventTrackingSrc } from './tracking'; import { Role } from './utils'; -const mockedUseOpenAiStreamState = { +const mockedUseLLMStreamState = { messages: [], setMessages: jest.fn(), reply: 'I am a robot', @@ -20,7 +20,7 @@ const mockedUseOpenAiStreamState = { }; jest.mock('./hooks', () => ({ - useOpenAIStream: jest.fn(() => mockedUseOpenAiStreamState), + useLLMStream: jest.fn(() => mockedUseLLMStreamState), StreamStatus: { IDLE: 'idle', GENERATING: 'generating', @@ -37,7 +37,7 @@ describe('GenAIButton', () => { describe('when LLM plugin is not configured', () => { beforeAll(() => { - jest.mocked(useOpenAIStream).mockReturnValue({ + jest.mocked(useLLMStream).mockReturnValue({ messages: [], error: undefined, streamStatus: StreamStatus.IDLE, @@ -65,7 +65,7 @@ describe('GenAIButton', () => { setMessagesMock.mockClear(); setShouldStopMock.mockClear(); - jest.mocked(useOpenAIStream).mockReturnValue({ + jest.mocked(useLLMStream).mockReturnValue({ messages: [], error: undefined, streamStatus: StreamStatus.IDLE, @@ -151,7 +151,7 @@ describe('GenAIButton', () => { const setShouldStopMock = jest.fn(); beforeEach(() => { - jest.mocked(useOpenAIStream).mockReturnValue({ + jest.mocked(useLLMStream).mockReturnValue({ messages: [], error: undefined, streamStatus: StreamStatus.GENERATING, @@ -222,7 +222,7 @@ describe('GenAIButton', () => { }; jest - .mocked(useOpenAIStream) + .mocked(useLLMStream) .mockImplementationOnce((options) => { options?.onResponse?.(reply); return returnValue; @@ -257,7 +257,7 @@ describe('GenAIButton', () => { setMessagesMock.mockClear(); setShouldStopMock.mockClear(); - jest.mocked(useOpenAIStream).mockReturnValue({ + jest.mocked(useLLMStream).mockReturnValue({ messages: [], error: new Error('Something went wrong'), streamStatus: StreamStatus.IDLE, @@ -308,7 +308,7 @@ describe('GenAIButton', () => { await userEvent.hover(tooltip); expect(tooltip).toBeVisible(); expect(tooltip).toHaveTextContent( - 'Failed to generate content using OpenAI. Please try again or if the problem persists, contact your organization admin.' + 'Failed to generate content using LLM. Please try again or if the problem persists, contact your organization admin.' ); }); @@ -331,7 +331,7 @@ describe('GenAIButton', () => { await userEvent.hover(tooltip); expect(tooltip).toBeVisible(); expect(tooltip).toHaveTextContent( - 'Failed to generate content using OpenAI. Please try again or if the problem persists, contact your organization admin.' + 'Failed to generate content using LLM. Please try again or if the problem persists, contact your organization admin.' ); }); diff --git a/public/app/features/dashboard/components/GenAI/GenAIButton.tsx b/public/app/features/dashboard/components/GenAI/GenAIButton.tsx index 6b6f55823ae..ec51fc91275 100644 --- a/public/app/features/dashboard/components/GenAI/GenAIButton.tsx +++ b/public/app/features/dashboard/components/GenAI/GenAIButton.tsx @@ -3,12 +3,13 @@ import { useCallback, useState } from 'react'; import * as React from 'react'; import { GrafanaTheme2 } from '@grafana/data'; +import { llm } from '@grafana/llm'; import { Button, Spinner, useStyles2, Tooltip, Toggletip, Text } from '@grafana/ui'; import { GenAIHistory } from './GenAIHistory'; -import { StreamStatus, useOpenAIStream } from './hooks'; +import { StreamStatus, useLLMStream } from './hooks'; import { AutoGenerateItem, EventTrackingSrc, reportAutoGenerateInteraction } from './tracking'; -import { OAI_MODEL, DEFAULT_OAI_MODEL, Message, sanitizeReply } from './utils'; +import { DEFAULT_LLM_MODEL, Message, sanitizeReply } from './utils'; export interface GenAIButtonProps { // Button label text @@ -23,7 +24,7 @@ export interface GenAIButtonProps { // Temperature for the LLM plugin. Default is 1. // Closer to 0 means more conservative, closer to 1 means more creative. temperature?: number; - model?: OAI_MODEL; + model?: llm.Model; // Event tracking source. Send as `src` to Rudderstack event eventTrackingSrc: EventTrackingSrc; // Whether the button should be disabled @@ -42,7 +43,7 @@ export const GenAIButton = ({ text = 'Auto-generate', toggleTipTitle = '', onClick: onClickProp, - model = DEFAULT_OAI_MODEL, + model = DEFAULT_LLM_MODEL, messages, onGenerate, temperature = 1, @@ -66,7 +67,7 @@ export const GenAIButton = ({ [onGenerate, unshiftHistoryEntry] ); - const { setMessages, stopGeneration, value, error, streamStatus } = useOpenAIStream({ + const { setMessages, stopGeneration, value, error, streamStatus } = useLLMStream({ model, temperature, onResponse, @@ -85,7 +86,7 @@ export const GenAIButton = ({ const showTooltip = error || tooltip ? undefined : false; const tooltipContent = error - ? 'Failed to generate content using OpenAI. Please try again or if the problem persists, contact your organization admin.' + ? 'Failed to generate content using LLM. Please try again or if the problem persists, contact your organization admin.' : tooltip || ''; const onClick = (e: React.MouseEvent) => { diff --git a/public/app/features/dashboard/components/GenAI/GenAIDashboardChangesButton.tsx b/public/app/features/dashboard/components/GenAI/GenAIDashboardChangesButton.tsx index 73be188f57e..0c3b1bdd774 100644 --- a/public/app/features/dashboard/components/GenAI/GenAIDashboardChangesButton.tsx +++ b/public/app/features/dashboard/components/GenAI/GenAIDashboardChangesButton.tsx @@ -1,5 +1,7 @@ import { useCallback } from 'react'; +import { llm } from '@grafana/llm'; + import { DashboardModel } from '../../state/DashboardModel'; import { GenAIButton } from './GenAIButton'; @@ -42,7 +44,7 @@ export const GenAIDashboardChangesButton = ({ dashboard, onGenerate, disabled }: messages={messages} onGenerate={onGenerate} temperature={0} - model={'gpt-3.5-turbo-16k'} + model={llm.Model.BASE} eventTrackingSrc={EventTrackingSrc.dashboardChanges} toggleTipTitle={'Improve your dashboard changes summary'} disabled={disabled} diff --git a/public/app/features/dashboard/components/GenAI/GenAIHistory.tsx b/public/app/features/dashboard/components/GenAI/GenAIHistory.tsx index 68cf5d68712..05a825bdf1d 100644 --- a/public/app/features/dashboard/components/GenAI/GenAIHistory.tsx +++ b/public/app/features/dashboard/components/GenAI/GenAIHistory.tsx @@ -8,9 +8,9 @@ import { Trans } from 'app/core/internationalization'; import { STOP_GENERATION_TEXT } from './GenAIButton'; import { GenerationHistoryCarousel } from './GenerationHistoryCarousel'; import { QuickFeedback } from './QuickFeedback'; -import { StreamStatus, useOpenAIStream } from './hooks'; +import { StreamStatus, useLLMStream } from './hooks'; import { AutoGenerateItem, EventTrackingSrc, reportAutoGenerateInteraction } from './tracking'; -import { getFeedbackMessage, Message, DEFAULT_OAI_MODEL, QuickFeedbackType, sanitizeReply } from './utils'; +import { getFeedbackMessage, Message, DEFAULT_LLM_MODEL, QuickFeedbackType, sanitizeReply } from './utils'; export interface GenAIHistoryProps { history: string[]; @@ -41,8 +41,8 @@ export const GenAIHistory = ({ [updateHistory] ); - const { setMessages, stopGeneration, reply, streamStatus, error } = useOpenAIStream({ - model: DEFAULT_OAI_MODEL, + const { setMessages, stopGeneration, reply, streamStatus, error } = useLLMStream({ + model: DEFAULT_LLM_MODEL, temperature, onResponse, }); diff --git a/public/app/features/dashboard/components/GenAI/hooks.ts b/public/app/features/dashboard/components/GenAI/hooks.ts index c33dafebb3f..e3640bd5a70 100644 --- a/public/app/features/dashboard/components/GenAI/hooks.ts +++ b/public/app/features/dashboard/components/GenAI/hooks.ts @@ -2,15 +2,15 @@ import { Dispatch, SetStateAction, useCallback, useEffect, useState } from 'reac import { useAsync } from 'react-use'; import { Subscription } from 'rxjs'; -import { openai } from '@grafana/llm'; +import { llm } from '@grafana/llm'; import { createMonitoringLogger } from '@grafana/runtime'; import { useAppNotification } from 'app/core/copy/appNotification'; -import { isLLMPluginEnabled, DEFAULT_OAI_MODEL } from './utils'; +import { isLLMPluginEnabled, DEFAULT_LLM_MODEL } from './utils'; // Declared instead of imported from utils to make this hook modular // Ideally we will want to move the hook itself to a different scope later. -type Message = openai.Message; +type Message = llm.Message; const genAILogger = createMonitoringLogger('features.dashboards.genai'); @@ -29,11 +29,11 @@ interface Options { } const defaultOptions = { - model: DEFAULT_OAI_MODEL, + model: DEFAULT_LLM_MODEL, temperature: 1, }; -interface UseOpenAIStreamResponse { +interface UseLLMStreamResponse { setMessages: Dispatch>; stopGeneration: () => void; messages: Message[]; @@ -47,7 +47,7 @@ interface UseOpenAIStreamResponse { } // TODO: Add tests -export function useOpenAIStream({ model, temperature, onResponse }: Options = defaultOptions): UseOpenAIStreamResponse { +export function useLLMStream({ model, temperature, onResponse }: Options = defaultOptions): UseLLMStreamResponse { // The messages array to send to the LLM, updated when the button is clicked. const [messages, setMessages] = useState([]); @@ -65,7 +65,7 @@ export function useOpenAIStream({ model, temperature, onResponse }: Options = de setMessages([]); setError(e); notifyError( - 'Failed to generate content using OpenAI', + 'Failed to generate content using LLM', 'Please try again or if the problem persists, contact your organization admin.' ); console.error(e); @@ -93,7 +93,7 @@ export function useOpenAIStream({ model, temperature, onResponse }: Options = de setStreamStatus(StreamStatus.GENERATING); setError(undefined); // Stream the completions. Each element is the next stream chunk. - const stream = openai + const stream = llm .streamChatCompletions({ model, temperature, @@ -102,7 +102,7 @@ export function useOpenAIStream({ model, temperature, onResponse }: Options = de .pipe( // Accumulate the stream content into a stream of strings, where each // element contains the accumulated message so far. - openai.accumulateContent() + llm.accumulateContent() // The stream is just a regular Observable, so we can use standard rxjs // functionality to update state, e.g. recording when the stream // has completed. @@ -148,7 +148,7 @@ export function useOpenAIStream({ model, temperature, onResponse }: Options = de let timeout: NodeJS.Timeout | undefined; if (streamStatus === StreamStatus.GENERATING && reply === '') { timeout = setTimeout(() => { - onError(new Error(`OpenAI stream timed out after ${TIMEOUT}ms`)); + onError(new Error(`LLM stream timed out after ${TIMEOUT}ms`)); }, TIMEOUT); } diff --git a/public/app/features/dashboard/components/GenAI/utils.test.ts b/public/app/features/dashboard/components/GenAI/utils.test.ts index 2731d5f4927..80060af4c7d 100644 --- a/public/app/features/dashboard/components/GenAI/utils.test.ts +++ b/public/app/features/dashboard/components/GenAI/utils.test.ts @@ -1,4 +1,4 @@ -import { openai } from '@grafana/llm'; +import { llm } from '@grafana/llm'; import { DASHBOARD_SCHEMA_VERSION } from '../../state/DashboardMigrator'; import { createDashboardModelFixture, createPanelSaveModel } from '../../state/__fixtures__/dashboardFixtures'; @@ -6,13 +6,14 @@ import { NEW_PANEL_TITLE } from '../../utils/dashboard'; import { getDashboardChanges, getPanelStrings, isLLMPluginEnabled, sanitizeReply } from './utils'; -// Mock the openai module +// Mock the llm module jest.mock('@grafana/llm', () => ({ ...jest.requireActual('@grafana/llm'), - openai: { + llm: { streamChatCompletions: jest.fn(), accumulateContent: jest.fn(), health: jest.fn(), + Model: { LARGE: 'large' }, }, })); @@ -99,8 +100,8 @@ describe('getDashboardChanges', () => { describe('isLLMPluginEnabled', () => { it('should return false if LLM plugin is not enabled', async () => { - // Mock openai.health to return false - jest.mocked(openai.health).mockResolvedValue({ ok: false, configured: false }); + // Mock llm.health to return false + jest.mocked(llm.health).mockResolvedValue({ ok: false, configured: false }); const enabled = await isLLMPluginEnabled(); @@ -108,8 +109,8 @@ describe('isLLMPluginEnabled', () => { }); it('should return true if LLM plugin is enabled', async () => { - // Mock openai.health to return true - jest.mocked(openai.health).mockResolvedValue({ ok: true, configured: false }); + // Mock llm.health to return true + jest.mocked(llm.health).mockResolvedValue({ ok: true, configured: false }); const enabled = await isLLMPluginEnabled(); diff --git a/public/app/features/dashboard/components/GenAI/utils.ts b/public/app/features/dashboard/components/GenAI/utils.ts index 8d8938827be..ffc3740b57c 100644 --- a/public/app/features/dashboard/components/GenAI/utils.ts +++ b/public/app/features/dashboard/components/GenAI/utils.ts @@ -1,6 +1,6 @@ import { pick } from 'lodash'; -import { openai } from '@grafana/llm'; +import { llm } from '@grafana/llm'; import { config } from '@grafana/runtime'; import { Panel } from '@grafana/schema'; @@ -18,7 +18,7 @@ export enum Role { 'user' = 'user', } -export type Message = openai.Message; +export type Message = llm.Message; export enum QuickFeedbackType { Shorter = 'Even shorter', @@ -27,11 +27,12 @@ export enum QuickFeedbackType { } /** - * The OpenAI model to be used. + * The LLM model to be used. + * + * The LLM app abstracts the actual model name since it depends on the provider. + * We want to default to whatever the 'large' model is. */ -export const DEFAULT_OAI_MODEL = 'gpt-4'; - -export type OAI_MODEL = 'gpt-4' | 'gpt-4-32k' | 'gpt-3.5-turbo' | 'gpt-3.5-turbo-16k'; +export const DEFAULT_LLM_MODEL: llm.Model = llm.Model.LARGE; /** * Sanitize the reply from OpenAI by removing the leading and trailing quotes. @@ -80,7 +81,7 @@ export async function isLLMPluginEnabled(): Promise { // Check if the LLM plugin is enabled. // If not, we won't be able to make requests, so return early. llmHealthCheck = new Promise((resolve) => { - openai.health().then((response) => { + llm.health().then((response) => { if (!response.ok) { // Health check fail clear cached promise so we can try again later llmHealthCheck = undefined; diff --git a/yarn.lock b/yarn.lock index 4c463770276..0e67aedf8af 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1416,7 +1416,7 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:7.26.10, @babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.10.1, @babel/runtime@npm:^7.11.1, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.0, @babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.12.13, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.13.10, @babel/runtime@npm:^7.15.4, @babel/runtime@npm:^7.16.3, @babel/runtime@npm:^7.17.8, @babel/runtime@npm:^7.18.0, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.20.0, @babel/runtime@npm:^7.20.7, @babel/runtime@npm:^7.23.2, @babel/runtime@npm:^7.23.9, @babel/runtime@npm:^7.24.1, @babel/runtime@npm:^7.24.5, @babel/runtime@npm:^7.24.7, @babel/runtime@npm:^7.25.0, @babel/runtime@npm:^7.25.6, @babel/runtime@npm:^7.25.7, @babel/runtime@npm:^7.3.1, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7": +"@babel/runtime@npm:7.26.10": version: 7.26.10 resolution: "@babel/runtime@npm:7.26.10" dependencies: @@ -1425,6 +1425,15 @@ __metadata: languageName: node linkType: hard +"@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.10.1, @babel/runtime@npm:^7.11.1, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.0, @babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.12.13, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.13.10, @babel/runtime@npm:^7.15.4, @babel/runtime@npm:^7.16.3, @babel/runtime@npm:^7.17.8, @babel/runtime@npm:^7.18.0, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.20.0, @babel/runtime@npm:^7.20.7, @babel/runtime@npm:^7.23.2, @babel/runtime@npm:^7.23.9, @babel/runtime@npm:^7.24.5, @babel/runtime@npm:^7.24.7, @babel/runtime@npm:^7.25.0, @babel/runtime@npm:^7.25.6, @babel/runtime@npm:^7.25.7, @babel/runtime@npm:^7.3.1, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7": + version: 7.26.9 + resolution: "@babel/runtime@npm:7.26.9" + dependencies: + regenerator-runtime: "npm:^0.14.0" + checksum: 10/08edd07d774eafbf157fdc8450ed6ddd22416fdd8e2a53e4a00349daba1b502c03ab7f7ad3ad3a7c46b9a24d99b5697591d0f852ee2f84642082ef7dda90b83d + languageName: node + linkType: hard + "@babel/template@npm:^7.22.5, @babel/template@npm:^7.24.7, @babel/template@npm:^7.25.9, @babel/template@npm:^7.26.9, @babel/template@npm:^7.3.3": version: 7.26.9 resolution: "@babel/template@npm:7.26.9" @@ -1839,7 +1848,7 @@ __metadata: languageName: node linkType: hard -"@emotion/babel-plugin@npm:^11.11.0, @emotion/babel-plugin@npm:^11.12.0, @emotion/babel-plugin@npm:^11.13.5": +"@emotion/babel-plugin@npm:^11.11.0, @emotion/babel-plugin@npm:^11.13.5": version: 11.13.5 resolution: "@emotion/babel-plugin@npm:11.13.5" dependencies: @@ -1858,7 +1867,7 @@ __metadata: languageName: node linkType: hard -"@emotion/cache@npm:^11.11.0, @emotion/cache@npm:^11.13.0, @emotion/cache@npm:^11.13.5, @emotion/cache@npm:^11.14.0, @emotion/cache@npm:^11.4.0": +"@emotion/cache@npm:^11.11.0, @emotion/cache@npm:^11.13.5, @emotion/cache@npm:^11.14.0, @emotion/cache@npm:^11.4.0": version: 11.14.0 resolution: "@emotion/cache@npm:11.14.0" dependencies: @@ -1884,19 +1893,6 @@ __metadata: languageName: node linkType: hard -"@emotion/css@npm:11.13.4": - version: 11.13.4 - resolution: "@emotion/css@npm:11.13.4" - dependencies: - "@emotion/babel-plugin": "npm:^11.12.0" - "@emotion/cache": "npm:^11.13.0" - "@emotion/serialize": "npm:^1.3.0" - "@emotion/sheet": "npm:^1.4.0" - "@emotion/utils": "npm:^1.4.0" - checksum: 10/57565a8bd9b712b0ade1c8b972bf2f84d2026e4372b3b035fb9d93a85a8f36ca7f2fbe67ecf32cc3fd03956587ece56ab89dd5bd43a76d3aed542a76841c76e5 - languageName: node - linkType: hard - "@emotion/css@npm:11.13.5, @emotion/css@npm:^11.11.2": version: 11.13.5 resolution: "@emotion/css@npm:11.13.5" @@ -1951,27 +1947,6 @@ __metadata: languageName: node linkType: hard -"@emotion/react@npm:11.13.3": - version: 11.13.3 - resolution: "@emotion/react@npm:11.13.3" - dependencies: - "@babel/runtime": "npm:^7.18.3" - "@emotion/babel-plugin": "npm:^11.12.0" - "@emotion/cache": "npm:^11.13.0" - "@emotion/serialize": "npm:^1.3.1" - "@emotion/use-insertion-effect-with-fallbacks": "npm:^1.1.0" - "@emotion/utils": "npm:^1.4.0" - "@emotion/weak-memoize": "npm:^0.4.0" - hoist-non-react-statics: "npm:^3.3.1" - peerDependencies: - react: ">=16.8.0" - peerDependenciesMeta: - "@types/react": - optional: true - checksum: 10/ee70d3afc2e8dd771e6fe176d27dd87a5e21a54e54d871438fd1caa5aa2312d848c6866292fdc65a6ea1c945147c8422bda2d22ed739178af9902dc86d6b298a - languageName: node - linkType: hard - "@emotion/react@npm:11.14.0, @emotion/react@npm:^11.8.1": version: 11.14.0 resolution: "@emotion/react@npm:11.14.0" @@ -1993,20 +1968,7 @@ __metadata: languageName: node linkType: hard -"@emotion/serialize@npm:1.3.2": - version: 1.3.2 - resolution: "@emotion/serialize@npm:1.3.2" - dependencies: - "@emotion/hash": "npm:^0.9.2" - "@emotion/memoize": "npm:^0.9.0" - "@emotion/unitless": "npm:^0.10.0" - "@emotion/utils": "npm:^1.4.1" - csstype: "npm:^3.0.2" - checksum: 10/ead557c1ff19d917ef8169c02738ef36f0851fbfdf0bf69a543045bddea3b7281dc8252ee466cc5fb44ed27d1e61280ff943bb60a2c04158751fb07b3457cc93 - languageName: node - linkType: hard - -"@emotion/serialize@npm:1.3.3, @emotion/serialize@npm:^1.1.2, @emotion/serialize@npm:^1.3.0, @emotion/serialize@npm:^1.3.1, @emotion/serialize@npm:^1.3.3": +"@emotion/serialize@npm:1.3.3, @emotion/serialize@npm:^1.1.2, @emotion/serialize@npm:^1.3.3": version: 1.3.3 resolution: "@emotion/serialize@npm:1.3.3" dependencies: @@ -2033,7 +1995,7 @@ __metadata: languageName: node linkType: hard -"@emotion/use-insertion-effect-with-fallbacks@npm:^1.1.0, @emotion/use-insertion-effect-with-fallbacks@npm:^1.2.0": +"@emotion/use-insertion-effect-with-fallbacks@npm:^1.2.0": version: 1.2.0 resolution: "@emotion/use-insertion-effect-with-fallbacks@npm:1.2.0" peerDependencies: @@ -2042,7 +2004,7 @@ __metadata: languageName: node linkType: hard -"@emotion/utils@npm:^1.2.1, @emotion/utils@npm:^1.4.0, @emotion/utils@npm:^1.4.1, @emotion/utils@npm:^1.4.2": +"@emotion/utils@npm:^1.2.1, @emotion/utils@npm:^1.4.2": version: 1.4.2 resolution: "@emotion/utils@npm:1.4.2" checksum: 10/e5f3b8bca066b3361a7ad9064baeb9d01ed1bf51d98416a67359b62cb3affec6bb0249802c4ed11f4f8030f93cc4b67506909420bdb110adec6983d712897208 @@ -2375,20 +2337,6 @@ __metadata: languageName: node linkType: hard -"@floating-ui/react@npm:0.26.24": - version: 0.26.24 - resolution: "@floating-ui/react@npm:0.26.24" - dependencies: - "@floating-ui/react-dom": "npm:^2.1.2" - "@floating-ui/utils": "npm:^0.2.8" - tabbable: "npm:^6.0.0" - peerDependencies: - react: ">=16.8.0" - react-dom: ">=16.8.0" - checksum: 10/903ffbee2c6726d117086e2a83f43d6ad339970758ce7979fd16cc7cf8dc0f5b869bd72c2c8ee1bcd6c63b190bb0960effd4d403e63685fb5aeed6b185041b08 - languageName: node - linkType: hard - "@floating-ui/react@npm:0.27.5": version: 0.27.5 resolution: "@floating-ui/react@npm:0.27.5" @@ -2998,41 +2946,6 @@ __metadata: languageName: node linkType: hard -"@grafana/data@npm:11.4.0, @grafana/data@npm:^10.4.0 ||^11": - version: 11.4.0 - resolution: "@grafana/data@npm:11.4.0" - dependencies: - "@braintree/sanitize-url": "npm:7.0.1" - "@grafana/schema": "npm:11.4.0" - "@types/d3-interpolate": "npm:^3.0.0" - "@types/string-hash": "npm:1.1.3" - d3-interpolate: "npm:3.0.1" - date-fns: "npm:3.6.0" - dompurify: "npm:^3.0.0" - eventemitter3: "npm:5.0.1" - fast_array_intersect: "npm:1.1.0" - history: "npm:4.10.1" - lodash: "npm:4.17.21" - marked: "npm:12.0.2" - marked-mangle: "npm:1.1.9" - moment: "npm:2.30.1" - moment-timezone: "npm:0.5.46" - ol: "npm:7.4.0" - papaparse: "npm:5.4.1" - react-use: "npm:17.5.1" - rxjs: "npm:7.8.1" - string-hash: "npm:^1.1.3" - tinycolor2: "npm:1.6.0" - tslib: "npm:2.7.0" - uplot: "npm:1.6.31" - xss: "npm:^1.0.14" - peerDependencies: - react: ^18.0.0 - react-dom: ^18.0.0 - checksum: 10/14bbf83a7c1fe3f8bbc3ddf44e2fd6393f46aa0cdf802e61ac4e2b2719e05008ceef8ddb1e4d0d164ae86ef01afef8310cf79b927bf6f1cfce45ed0c48a21af3 - languageName: node - linkType: hard - "@grafana/data@npm:11.6.0-pre, @grafana/data@workspace:*, @grafana/data@workspace:packages/grafana-data": version: 0.0.0-use.local resolution: "@grafana/data@workspace:packages/grafana-data" @@ -3085,17 +2998,6 @@ __metadata: languageName: unknown linkType: soft -"@grafana/e2e-selectors@npm:11.4.0": - version: 11.4.0 - resolution: "@grafana/e2e-selectors@npm:11.4.0" - dependencies: - "@grafana/tsconfig": "npm:^2.0.0" - tslib: "npm:2.7.0" - typescript: "npm:5.5.4" - checksum: 10/dd6861415430ab8e9a5e66d8f008dd4ceadd3a7f2ba50be6d6fa7c24455e43773a12ef094838e72ede617f14d202693e01b08db44f02657039ded2858fc9fa24 - languageName: node - linkType: hard - "@grafana/e2e-selectors@npm:11.6.0-pre, @grafana/e2e-selectors@workspace:*, @grafana/e2e-selectors@workspace:packages/grafana-e2e-selectors": version: 0.0.0-use.local resolution: "@grafana/e2e-selectors@workspace:packages/grafana-e2e-selectors" @@ -3160,7 +3062,7 @@ __metadata: languageName: node linkType: hard -"@grafana/faro-web-sdk@npm:^1.13.2, @grafana/faro-web-sdk@npm:^1.3.6": +"@grafana/faro-web-sdk@npm:^1.13.2": version: 1.13.2 resolution: "@grafana/faro-web-sdk@npm:1.13.2" dependencies: @@ -3278,18 +3180,19 @@ __metadata: languageName: node linkType: hard -"@grafana/llm@npm:0.12.0": - version: 0.12.0 - resolution: "@grafana/llm@npm:0.12.0" +"@grafana/llm@npm:0.13.2": + version: 0.13.2 + resolution: "@grafana/llm@npm:0.13.2" dependencies: - "@grafana/data": "npm:^10.4.0 ||^11" - "@grafana/runtime": "npm:^10.4.0 || ^11" - react: "npm:^18" - react-use: "npm:^17.5.0" - rxjs: "npm:^7.8.1" + react-use: "npm:^17.6.0" semver: "npm:^7.6.3" - uuid: "npm:^10.0.0" - checksum: 10/5214e244f9ead7fdb17775d83a0463e151e63aa4c716a1e5b92e47b3675aff66dd8891a87fed3ace8361cfe24966c45baadfa821d0b6f46d910c6faecad8021d + uuid: "npm:^11.0.5" + peerDependencies: + "@grafana/data": ^10.4.0 ||^11 + "@grafana/runtime": ^10.4.0 || ^11 + react: ^18 + rxjs: ^7.8.1 + checksum: 10/f30a637902cd8a2de6c9bb82ed9e31d98a7817c833023c0930c658a4087a299d2e40036b0262fd4acca1335e029994664a221037cc9279d99bebe3e3f43322ac languageName: node linkType: hard @@ -3542,26 +3445,6 @@ __metadata: languageName: unknown linkType: soft -"@grafana/runtime@npm:^10.4.0 || ^11": - version: 11.4.0 - resolution: "@grafana/runtime@npm:11.4.0" - dependencies: - "@grafana/data": "npm:11.4.0" - "@grafana/e2e-selectors": "npm:11.4.0" - "@grafana/faro-web-sdk": "npm:^1.3.6" - "@grafana/schema": "npm:11.4.0" - "@grafana/ui": "npm:11.4.0" - history: "npm:4.10.1" - lodash: "npm:4.17.21" - rxjs: "npm:7.8.1" - tslib: "npm:2.7.0" - peerDependencies: - react: ^18.0.0 - react-dom: ^18.0.0 - checksum: 10/4a462803fb4e5f0fff05a8b50bd6a518a4d30bb3a6bb8e2d3d2acbd411f62dffa5606d322b91ed8bc76a37258c429c07f55b5d8539d41b7c8d111d706fb9caff - languageName: node - linkType: hard - "@grafana/saga-icons@workspace:*, @grafana/saga-icons@workspace:packages/grafana-icons": version: 0.0.0-use.local resolution: "@grafana/saga-icons@workspace:packages/grafana-icons" @@ -3641,15 +3524,6 @@ __metadata: languageName: node linkType: hard -"@grafana/schema@npm:11.4.0": - version: 11.4.0 - resolution: "@grafana/schema@npm:11.4.0" - dependencies: - tslib: "npm:2.7.0" - checksum: 10/ed115437cf4a3c95194c4eeb1c2723be06887fc5c5b5e4f5eea1beb49f5bab15fab3d20de95e918019bd333f4e6505a2dace57cef60cbc9327bd4e643139b1b8 - languageName: node - linkType: hard - "@grafana/schema@npm:11.6.0-pre, @grafana/schema@workspace:*, @grafana/schema@workspace:packages/grafana-schema": version: 0.0.0-use.local resolution: "@grafana/schema@workspace:packages/grafana-schema" @@ -3719,83 +3593,6 @@ __metadata: languageName: node linkType: hard -"@grafana/ui@npm:11.4.0": - version: 11.4.0 - resolution: "@grafana/ui@npm:11.4.0" - dependencies: - "@emotion/css": "npm:11.13.4" - "@emotion/react": "npm:11.13.3" - "@emotion/serialize": "npm:1.3.2" - "@floating-ui/react": "npm:0.26.24" - "@grafana/data": "npm:11.4.0" - "@grafana/e2e-selectors": "npm:11.4.0" - "@grafana/faro-web-sdk": "npm:^1.3.6" - "@grafana/schema": "npm:11.4.0" - "@hello-pangea/dnd": "npm:16.6.0" - "@leeoniya/ufuzzy": "npm:1.0.14" - "@monaco-editor/react": "npm:4.6.0" - "@popperjs/core": "npm:2.11.8" - "@react-aria/dialog": "npm:3.5.18" - "@react-aria/focus": "npm:3.18.3" - "@react-aria/overlays": "npm:3.23.3" - "@react-aria/utils": "npm:3.25.3" - "@tanstack/react-virtual": "npm:^3.5.1" - "@types/jquery": "npm:3.5.31" - "@types/lodash": "npm:4.17.10" - "@types/react-table": "npm:7.7.20" - ansicolor: "npm:1.1.100" - calculate-size: "npm:1.1.1" - classnames: "npm:2.5.1" - d3: "npm:7.9.0" - date-fns: "npm:3.6.0" - downshift: "npm:^9.0.6" - hoist-non-react-statics: "npm:3.3.2" - i18next: "npm:^23.0.0" - i18next-browser-languagedetector: "npm:^7.0.2" - immutable: "npm:4.3.7" - is-hotkey: "npm:0.2.0" - jquery: "npm:3.7.1" - lodash: "npm:4.17.21" - micro-memoize: "npm:^4.1.2" - moment: "npm:2.30.1" - monaco-editor: "npm:0.34.1" - ol: "npm:7.4.0" - prismjs: "npm:1.29.0" - rc-cascader: "npm:3.28.1" - rc-drawer: "npm:7.2.0" - rc-slider: "npm:11.1.7" - rc-time-picker: "npm:^3.7.3" - rc-tooltip: "npm:6.2.1" - react-calendar: "npm:5.0.0" - react-colorful: "npm:5.6.1" - react-custom-scrollbars-2: "npm:4.5.0" - react-dropzone: "npm:14.2.9" - react-highlight-words: "npm:0.20.0" - react-hook-form: "npm:^7.49.2" - react-i18next: "npm:^14.0.0" - react-inlinesvg: "npm:3.0.2" - react-loading-skeleton: "npm:3.5.0" - react-router-dom-v5-compat: "npm:^6.26.1" - react-select: "npm:5.8.1" - react-table: "npm:7.8.0" - react-transition-group: "npm:4.4.5" - react-use: "npm:17.5.1" - react-window: "npm:1.8.10" - rxjs: "npm:7.8.1" - slate: "npm:0.47.9" - slate-plain-serializer: "npm:0.7.13" - slate-react: "npm:0.22.10" - tinycolor2: "npm:1.6.0" - tslib: "npm:2.7.0" - uplot: "npm:1.6.31" - uuid: "npm:9.0.1" - peerDependencies: - react: ^18.0.0 - react-dom: ^18.0.0 - checksum: 10/9019f6b549ae70808476902bdbbda1d3d80077dd84663d64f9bbecd86708c3e96f56dd1f7c8eff605710bbdf644b52520dbf2848ba4f0684f202ab896827d1e2 - languageName: node - linkType: hard - "@grafana/ui@npm:11.6.0-pre, @grafana/ui@workspace:*, @grafana/ui@workspace:packages/grafana-ui": version: 0.0.0-use.local resolution: "@grafana/ui@workspace:packages/grafana-ui" @@ -3950,24 +3747,6 @@ __metadata: languageName: node linkType: hard -"@hello-pangea/dnd@npm:16.6.0": - version: 16.6.0 - resolution: "@hello-pangea/dnd@npm:16.6.0" - dependencies: - "@babel/runtime": "npm:^7.24.1" - css-box-model: "npm:^1.2.1" - memoize-one: "npm:^6.0.0" - raf-schd: "npm:^4.0.3" - react-redux: "npm:^8.1.3" - redux: "npm:^4.2.1" - use-memo-one: "npm:^1.1.3" - peerDependencies: - react: ^16.8.5 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.5 || ^17.0.0 || ^18.0.0 - checksum: 10/f377461d400c8223174745e4d7ecf4fb0146f9e807413f98120ebbcf075282e631273988d336daaf1fb8e6b6c6a1a8e4f99beefecd7a6b68ccc3bb064d38f13f - languageName: node - linkType: hard - "@hello-pangea/dnd@npm:17.0.0, @hello-pangea/dnd@npm:^17.0.0": version: 17.0.0 resolution: "@hello-pangea/dnd@npm:17.0.0" @@ -4848,13 +4627,6 @@ __metadata: languageName: node linkType: hard -"@leeoniya/ufuzzy@npm:1.0.14": - version: 1.0.14 - resolution: "@leeoniya/ufuzzy@npm:1.0.14" - checksum: 10/852b580a8eaaf92e2d448f5b720e3c53e4bea22187bf5e8459256677c47183321b47b8384982e15751f42da7e77a216fd86c80e6185677d8270adeab4a4fb771 - languageName: node - linkType: hard - "@leeoniya/ufuzzy@npm:1.0.18, @leeoniya/ufuzzy@npm:^1.0.16": version: 1.0.18 resolution: "@leeoniya/ufuzzy@npm:1.0.18" @@ -6529,23 +6301,6 @@ __metadata: languageName: node linkType: hard -"@react-aria/dialog@npm:3.5.18": - version: 3.5.18 - resolution: "@react-aria/dialog@npm:3.5.18" - dependencies: - "@react-aria/focus": "npm:^3.18.3" - "@react-aria/overlays": "npm:^3.23.3" - "@react-aria/utils": "npm:^3.25.3" - "@react-types/dialog": "npm:^3.5.13" - "@react-types/shared": "npm:^3.25.0" - "@swc/helpers": "npm:^0.5.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 - checksum: 10/dbd40d14baeea7dae56956985234e29ada74a93899177c737c3312ec788b22a6d65179b7132cdbd6609e973d410f749071c68ceb6620d3cf6f60a03ddf648983 - languageName: node - linkType: hard - "@react-aria/dialog@npm:3.5.21": version: 3.5.21 resolution: "@react-aria/dialog@npm:3.5.21" @@ -6563,22 +6318,7 @@ __metadata: languageName: node linkType: hard -"@react-aria/focus@npm:3.18.3": - version: 3.18.3 - resolution: "@react-aria/focus@npm:3.18.3" - dependencies: - "@react-aria/interactions": "npm:^3.22.3" - "@react-aria/utils": "npm:^3.25.3" - "@react-types/shared": "npm:^3.25.0" - "@swc/helpers": "npm:^0.5.0" - clsx: "npm:^2.0.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 - checksum: 10/b11632e638de2f40ec12a4a8c818059b9bf7e90b288a93b46985350c887ae7ecdf037391537f86fbacb2a186dec7e7c41a8f2ff767fd232a8cac3189f03735b2 - languageName: node - linkType: hard - -"@react-aria/focus@npm:3.19.1, @react-aria/focus@npm:^3.18.3, @react-aria/focus@npm:^3.19.1": +"@react-aria/focus@npm:3.19.1, @react-aria/focus@npm:^3.19.1": version: 3.19.1 resolution: "@react-aria/focus@npm:3.19.1" dependencies: @@ -6594,7 +6334,7 @@ __metadata: languageName: node linkType: hard -"@react-aria/i18n@npm:^3.12.3, @react-aria/i18n@npm:^3.12.5": +"@react-aria/i18n@npm:^3.12.5": version: 3.12.5 resolution: "@react-aria/i18n@npm:3.12.5" dependencies: @@ -6613,7 +6353,7 @@ __metadata: languageName: node linkType: hard -"@react-aria/interactions@npm:^3.22.3, @react-aria/interactions@npm:^3.23.0": +"@react-aria/interactions@npm:^3.23.0": version: 3.23.0 resolution: "@react-aria/interactions@npm:3.23.0" dependencies: @@ -6628,29 +6368,7 @@ __metadata: languageName: node linkType: hard -"@react-aria/overlays@npm:3.23.3": - version: 3.23.3 - resolution: "@react-aria/overlays@npm:3.23.3" - dependencies: - "@react-aria/focus": "npm:^3.18.3" - "@react-aria/i18n": "npm:^3.12.3" - "@react-aria/interactions": "npm:^3.22.3" - "@react-aria/ssr": "npm:^3.9.6" - "@react-aria/utils": "npm:^3.25.3" - "@react-aria/visually-hidden": "npm:^3.8.16" - "@react-stately/overlays": "npm:^3.6.11" - "@react-types/button": "npm:^3.10.0" - "@react-types/overlays": "npm:^3.8.10" - "@react-types/shared": "npm:^3.25.0" - "@swc/helpers": "npm:^0.5.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 - checksum: 10/c70af63d4ae828963b9fa780330cabf49e5a70f8981ae65d173e32934fa190fc8df1283de65d6a8b71b6340050718df19c2e7353b406114962d85ee5deb811ee - languageName: node - linkType: hard - -"@react-aria/overlays@npm:3.25.0, @react-aria/overlays@npm:^3.23.3, @react-aria/overlays@npm:^3.25.0": +"@react-aria/overlays@npm:3.25.0, @react-aria/overlays@npm:^3.25.0": version: 3.25.0 resolution: "@react-aria/overlays@npm:3.25.0" dependencies: @@ -6672,7 +6390,7 @@ __metadata: languageName: node linkType: hard -"@react-aria/ssr@npm:^3.9.6, @react-aria/ssr@npm:^3.9.7": +"@react-aria/ssr@npm:^3.9.7": version: 3.9.7 resolution: "@react-aria/ssr@npm:3.9.7" dependencies: @@ -6683,22 +6401,7 @@ __metadata: languageName: node linkType: hard -"@react-aria/utils@npm:3.25.3": - version: 3.25.3 - resolution: "@react-aria/utils@npm:3.25.3" - dependencies: - "@react-aria/ssr": "npm:^3.9.6" - "@react-stately/utils": "npm:^3.10.4" - "@react-types/shared": "npm:^3.25.0" - "@swc/helpers": "npm:^0.5.0" - clsx: "npm:^2.0.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 - checksum: 10/86aed35da5cb0d48d949e40bf8226d5a6d6c92a8cdc60e3e12d524d1f3cc91ab6b54c5e1642823773cbb889fb61af7da22e89488b704b56fc5f4d8d59da7519b - languageName: node - linkType: hard - -"@react-aria/utils@npm:3.27.0, @react-aria/utils@npm:^3.25.3, @react-aria/utils@npm:^3.27.0": +"@react-aria/utils@npm:3.27.0, @react-aria/utils@npm:^3.27.0": version: 3.27.0 resolution: "@react-aria/utils@npm:3.27.0" dependencies: @@ -6714,7 +6417,7 @@ __metadata: languageName: node linkType: hard -"@react-aria/visually-hidden@npm:^3.8.16, @react-aria/visually-hidden@npm:^3.8.19": +"@react-aria/visually-hidden@npm:^3.8.19": version: 3.8.19 resolution: "@react-aria/visually-hidden@npm:3.8.19" dependencies: @@ -6763,7 +6466,7 @@ __metadata: languageName: node linkType: hard -"@react-stately/overlays@npm:^3.6.11, @react-stately/overlays@npm:^3.6.13": +"@react-stately/overlays@npm:^3.6.13": version: 3.6.13 resolution: "@react-stately/overlays@npm:3.6.13" dependencies: @@ -6776,7 +6479,7 @@ __metadata: languageName: node linkType: hard -"@react-stately/utils@npm:^3.10.4, @react-stately/utils@npm:^3.10.5": +"@react-stately/utils@npm:^3.10.5": version: 3.10.5 resolution: "@react-stately/utils@npm:3.10.5" dependencies: @@ -6787,7 +6490,7 @@ __metadata: languageName: node linkType: hard -"@react-types/button@npm:3.10.2, @react-types/button@npm:^3.10.0, @react-types/button@npm:^3.10.2": +"@react-types/button@npm:3.10.2, @react-types/button@npm:^3.10.2": version: 3.10.2 resolution: "@react-types/button@npm:3.10.2" dependencies: @@ -6798,7 +6501,7 @@ __metadata: languageName: node linkType: hard -"@react-types/dialog@npm:^3.5.13, @react-types/dialog@npm:^3.5.15": +"@react-types/dialog@npm:^3.5.15": version: 3.5.15 resolution: "@react-types/dialog@npm:3.5.15" dependencies: @@ -6822,7 +6525,7 @@ __metadata: languageName: node linkType: hard -"@react-types/overlays@npm:3.8.12, @react-types/overlays@npm:^3.8.10, @react-types/overlays@npm:^3.8.12": +"@react-types/overlays@npm:3.8.12, @react-types/overlays@npm:^3.8.12": version: 3.8.12 resolution: "@react-types/overlays@npm:3.8.12" dependencies: @@ -6833,7 +6536,7 @@ __metadata: languageName: node linkType: hard -"@react-types/shared@npm:3.27.0, @react-types/shared@npm:^3.25.0, @react-types/shared@npm:^3.27.0": +"@react-types/shared@npm:3.27.0, @react-types/shared@npm:^3.27.0": version: 3.27.0 resolution: "@react-types/shared@npm:3.27.0" peerDependencies: @@ -9879,15 +9582,6 @@ __metadata: languageName: node linkType: hard -"@types/jquery@npm:3.5.31": - version: 3.5.31 - resolution: "@types/jquery@npm:3.5.31" - dependencies: - "@types/sizzle": "npm:*" - checksum: 10/c14b3db4d2c34eb44b30ae119f1983d9d94231a02d44357b08f3ef406852c777edd928eb35875e879515a96eb8eb2188ed5572a0de35f322019bf6de858ce610 - languageName: node - linkType: hard - "@types/jquery@npm:3.5.32": version: 3.5.32 resolution: "@types/jquery@npm:3.5.32" @@ -9968,13 +9662,6 @@ __metadata: languageName: node linkType: hard -"@types/lodash@npm:4.17.10": - version: 4.17.10 - resolution: "@types/lodash@npm:4.17.10" - checksum: 10/10fe24a93adc6048cb23e4135c1ed1d52cc39033682e6513f4f51b74a9af6d7a24fbea92203c22dc4e01e35f1ab3aa0fd0a2b487e8a4a2bbdf1fc05970094066 - languageName: node - linkType: hard - "@types/lodash@npm:4.17.7": version: 4.17.7 resolution: "@types/lodash@npm:4.17.7" @@ -11607,13 +11294,6 @@ __metadata: languageName: node linkType: hard -"ansicolor@npm:1.1.100": - version: 1.1.100 - resolution: "ansicolor@npm:1.1.100" - checksum: 10/9420e96f44b578153dfd11e2d829d633ca2699452419aff48314219d66b7b69a9b6d994d2f2350b1d29a9826e33500b87fe85606629a8889cd52ce806908f4a9 - languageName: node - linkType: hard - "ansicolor@npm:2.0.3": version: 2.0.3 resolution: "ansicolor@npm:2.0.3" @@ -11979,7 +11659,7 @@ __metadata: languageName: node linkType: hard -"attr-accept@npm:^2.2.2, attr-accept@npm:^2.2.4": +"attr-accept@npm:^2.2.4": version: 2.2.5 resolution: "attr-accept@npm:2.2.5" checksum: 10/474b1c53e62c5b881c745d1f098196f190c8b493245e95d4b0fea9298d3acb56f551868fc12806885277e55e9d8ad3c5963e92d93456f4e4081dfc5190977bfd @@ -12216,16 +11896,6 @@ __metadata: languageName: node linkType: hard -"babel-runtime@npm:6.x, babel-runtime@npm:^6.26.0": - version: 6.26.0 - resolution: "babel-runtime@npm:6.26.0" - dependencies: - core-js: "npm:^2.4.0" - regenerator-runtime: "npm:^0.11.0" - checksum: 10/2cdf0f083b9598a43cdb11cbf1e7060584079a9a2230f06aec997ba81e887ef17fdcb5ad813a484ee099e06d2de0cea832bdd3011c06325acb284284c754ee8f - languageName: node - linkType: hard - "balanced-match@npm:^1.0.0": version: 1.0.2 resolution: "balanced-match@npm:1.0.2" @@ -13511,22 +13181,6 @@ __metadata: languageName: node linkType: hard -"component-classes@npm:^1.2.5": - version: 1.2.6 - resolution: "component-classes@npm:1.2.6" - dependencies: - component-indexof: "npm:0.0.3" - checksum: 10/aa70f282b85a19d7a190dabb2c72c9b8a2a5565fc42d72ca0661e8ce47e55e53da29f468467771d7e0bf4ba8c9723e5a21954fc38b942d9d873ee56be856adfc - languageName: node - linkType: hard - -"component-indexof@npm:0.0.3": - version: 0.0.3 - resolution: "component-indexof@npm:0.0.3" - checksum: 10/34a720e96fc0be1043a4517845b1b7483989736c559089eca285f7ac9ef049eacc8ab12fc31f924dfa2d4ecea0714611a695d087ce11caf30502b5a80646e9e9 - languageName: node - linkType: hard - "compressible@npm:~2.0.16": version: 2.0.18 resolution: "compressible@npm:2.0.18" @@ -13818,7 +13472,7 @@ __metadata: languageName: node linkType: hard -"core-js@npm:^2.4.0, core-js@npm:^2.6.5": +"core-js@npm:^2.6.5": version: 2.6.12 resolution: "core-js@npm:2.6.12" checksum: 10/7c624eb00a59c74c769d5d80f751f3bf1fc6201205b6562f27286ad5e00bbca1483f2f7eb0c2854b86f526ef5c7dc958b45f2ff536f8a31b8e9cb1a13a96efca @@ -13997,16 +13651,6 @@ __metadata: languageName: node linkType: hard -"css-animation@npm:^1.3.2": - version: 1.6.1 - resolution: "css-animation@npm:1.6.1" - dependencies: - babel-runtime: "npm:6.x" - component-classes: "npm:^1.2.5" - checksum: 10/5aea8fd333300c6b15f523ac741dd2ed367489485c7a9a43647f7779fef9e5f338c405e3ad9ed8db338503b4aefea86108112a01bf78651b623f8bd2e146e0e0 - languageName: node - linkType: hard - "css-box-model@npm:^1.2.1": version: 1.2.1 resolution: "css-box-model@npm:1.2.1" @@ -14857,13 +14501,6 @@ __metadata: languageName: node linkType: hard -"date-fns@npm:3.6.0": - version: 3.6.0 - resolution: "date-fns@npm:3.6.0" - checksum: 10/cac35c58926a3b5d577082ff2b253612ec1c79eb6754fddef46b6a8e826501ea2cb346ecbd211205f1ba382ddd1f9d8c3f00bf433ad63cc3063454d294e3a6b8 - languageName: node - linkType: hard - "date-fns@npm:4.1.0": version: 4.1.0 resolution: "date-fns@npm:4.1.0" @@ -15379,7 +15016,7 @@ __metadata: languageName: node linkType: hard -"dompurify@npm:3.2.4, dompurify@npm:^3.0.0": +"dompurify@npm:3.2.4": version: 3.2.4 resolution: "dompurify@npm:3.2.4" dependencies: @@ -16659,13 +16296,6 @@ __metadata: languageName: node linkType: hard -"exenv@npm:^1.2.2": - version: 1.2.2 - resolution: "exenv@npm:1.2.2" - checksum: 10/6840185e421394bcb143debb866d31d19c3e4a4bca87d2f319d68d61afff353b3c678f2eb389e3b98ab9aecbec19f6bebbdc4193984378af0a3366c498a7efc8 - languageName: node - linkType: hard - "exif-parser@npm:^0.1.12": version: 0.1.12 resolution: "exif-parser@npm:0.1.12" @@ -16996,15 +16626,6 @@ __metadata: languageName: node linkType: hard -"file-selector@npm:^0.6.0": - version: 0.6.0 - resolution: "file-selector@npm:0.6.0" - dependencies: - tslib: "npm:^2.4.0" - checksum: 10/6add4098ae07fd1e9050b1e8d3fd9f128680c1d6648c0676af54ace4586e6e5bfcb8fdfa45b69e9131ffd8175bf630d54a445a5facf9be244f85b99ce309183e - languageName: node - linkType: hard - "file-selector@npm:^2.1.0": version: 2.1.0 resolution: "file-selector@npm:2.1.0" @@ -18063,7 +17684,7 @@ __metadata: "@grafana/flamegraph": "workspace:*" "@grafana/google-sdk": "npm:0.1.2" "@grafana/lezer-logql": "npm:0.2.7" - "@grafana/llm": "npm:0.12.0" + "@grafana/llm": "npm:0.13.2" "@grafana/monaco-logql": "npm:^0.0.8" "@grafana/o11y-ds-frontend": "workspace:*" "@grafana/plugin-e2e": "npm:1.17.1" @@ -19073,15 +18694,6 @@ __metadata: languageName: node linkType: hard -"i18next-browser-languagedetector@npm:^7.0.2": - version: 7.2.2 - resolution: "i18next-browser-languagedetector@npm:7.2.2" - dependencies: - "@babel/runtime": "npm:^7.23.2" - checksum: 10/6f6dd5db3e83c2ed3b24d7fb754d0c41fd2056a0f06fb3d2a4604541c6ee0031d3f2cbbcfad726bbfc9741bcbbd6cde3acf94d339d600b6d9f49f0bbe51179d9 - languageName: node - linkType: hard - "i18next-browser-languagedetector@npm:^8.0.0": version: 8.0.2 resolution: "i18next-browser-languagedetector@npm:8.0.2" @@ -19136,7 +18748,7 @@ __metadata: languageName: node linkType: hard -"i18next@npm:^23.0.0, i18next@npm:^23.11.5": +"i18next@npm:^23.11.5": version: 23.16.8 resolution: "i18next@npm:23.16.8" dependencies: @@ -19239,13 +18851,6 @@ __metadata: languageName: node linkType: hard -"immutable@npm:4.3.7, immutable@npm:^4.3.6": - version: 4.3.7 - resolution: "immutable@npm:4.3.7" - checksum: 10/37d963c5050f03ae5f3714ba7a43d469aa482051087f4c65d673d1501c309ea231d87480c792e19fa85e2eaf965f76af5d0aa92726505f3cfe4af91619dfb80b - languageName: node - linkType: hard - "immutable@npm:5.0.3, immutable@npm:^5.0.2": version: 5.0.3 resolution: "immutable@npm:5.0.3" @@ -19260,6 +18865,13 @@ __metadata: languageName: node linkType: hard +"immutable@npm:^4.3.6": + version: 4.3.7 + resolution: "immutable@npm:4.3.7" + checksum: 10/37d963c5050f03ae5f3714ba7a43d469aa482051087f4c65d673d1501c309ea231d87480c792e19fa85e2eaf965f76af5d0aa92726505f3cfe4af91619dfb80b + languageName: node + linkType: hard + "import-fresh@npm:^3.2.1, import-fresh@npm:^3.3.0": version: 3.3.0 resolution: "import-fresh@npm:3.3.0" @@ -22137,24 +21749,6 @@ __metadata: languageName: node linkType: hard -"marked-mangle@npm:1.1.9": - version: 1.1.9 - resolution: "marked-mangle@npm:1.1.9" - peerDependencies: - marked: ">=4 <15" - checksum: 10/745e44bea9b52bc9c52e41f5d2b146eb21072a92ddf85b4b2f210b091da93fcbf2f5447b02f849fd19e79224bdca385462a42b96882493778d1d0ff0a3da9a8c - languageName: node - linkType: hard - -"marked@npm:12.0.2": - version: 12.0.2 - resolution: "marked@npm:12.0.2" - bin: - marked: bin/marked.js - checksum: 10/24d4fc58d37c1779197fa7f93c504d8c71d4df54eb69cbbc14a55ba2a8e2ad83d723801fc25452c21ce74b38a483c5863c53449f130253a597be9e9c1d3e7e2b - languageName: node - linkType: hard - "marked@npm:15.0.6": version: 15.0.6 resolution: "marked@npm:15.0.6" @@ -22752,15 +22346,6 @@ __metadata: languageName: node linkType: hard -"moment-timezone@npm:0.5.46": - version: 0.5.46 - resolution: "moment-timezone@npm:0.5.46" - dependencies: - moment: "npm:^2.29.4" - checksum: 10/7613ba388fa6004af62675fb9945cb0d37758b559d07470a5e188419ffe1ac03eb2ed16fe80aa34d1e7dd39fc5bd67dc02cd59e8dcdab95504cface2c78e4b3d - languageName: node - linkType: hard - "moment-timezone@npm:0.5.47": version: 0.5.47 resolution: "moment-timezone@npm:0.5.47" @@ -22770,7 +22355,7 @@ __metadata: languageName: node linkType: hard -"moment@npm:2.30.1, moment@npm:2.x, moment@npm:^2.20.1, moment@npm:^2.29.4, moment@npm:^2.30.1": +"moment@npm:2.30.1, moment@npm:^2.20.1, moment@npm:^2.29.4, moment@npm:^2.30.1": version: 2.30.1 resolution: "moment@npm:2.30.1" checksum: 10/ae42d876d4ec831ef66110bdc302c0657c664991e45cf2afffc4b0f6cd6d251dde11375c982a5c0564ccc0fa593fc564576ddceb8c8845e87c15f58aa6baca69 @@ -24117,13 +23702,6 @@ __metadata: languageName: node linkType: hard -"papaparse@npm:5.4.1": - version: 5.4.1 - resolution: "papaparse@npm:5.4.1" - checksum: 10/5e6dc978187182ad2efa1d264ffe73d2042cd23b8fb1dcb0b0f5c8c7c772c11e3eb4e166fb0893880ed24529a96abe9065d704cc5b4cb96abf037413cfe43788 - languageName: node - linkType: hard - "papaparse@npm:5.5.2": version: 5.5.2 resolution: "papaparse@npm:5.5.2" @@ -25194,13 +24772,6 @@ __metadata: languageName: node linkType: hard -"prismjs@npm:1.29.0": - version: 1.29.0 - resolution: "prismjs@npm:1.29.0" - checksum: 10/2080db382c2dde0cfc7693769e89b501ef1bfc8ff4f8d25c07fd4c37ca31bc443f6133d5b7c145a73309dc396e829ddb7cc18560026d862a887ae08864ef6b07 - languageName: node - linkType: hard - "prismjs@npm:1.30.0, prismjs@npm:^1.27.0, prismjs@npm:^1.29.0": version: 1.30.0 resolution: "prismjs@npm:1.30.0" @@ -25535,7 +25106,7 @@ __metadata: languageName: node linkType: hard -"raf@npm:^3.1.0, raf@npm:^3.4.0, raf@npm:^3.4.1": +"raf@npm:^3.1.0, raf@npm:^3.4.1": version: 3.4.1 resolution: "raf@npm:3.4.1" dependencies: @@ -25617,18 +25188,6 @@ __metadata: languageName: node linkType: hard -"rc-align@npm:^2.4.0": - version: 2.4.5 - resolution: "rc-align@npm:2.4.5" - dependencies: - babel-runtime: "npm:^6.26.0" - dom-align: "npm:^1.7.0" - prop-types: "npm:^15.5.8" - rc-util: "npm:^4.0.4" - checksum: 10/6a82f7b47dda397b90c7b6d41e5500b9e3e427891d15a54f95708abf31a8aba19d882bd9e5ab42f4979b455aa2236b96cf6403152507bcd1fd7663be08a0ceb5 - languageName: node - linkType: hard - "rc-align@npm:^4.0.0": version: 4.0.15 resolution: "rc-align@npm:4.0.15" @@ -25645,21 +25204,6 @@ __metadata: languageName: node linkType: hard -"rc-animate@npm:2.x": - version: 2.11.1 - resolution: "rc-animate@npm:2.11.1" - dependencies: - babel-runtime: "npm:6.x" - classnames: "npm:^2.2.6" - css-animation: "npm:^1.3.2" - prop-types: "npm:15.x" - raf: "npm:^3.4.0" - rc-util: "npm:^4.15.3" - react-lifecycles-compat: "npm:^3.0.4" - checksum: 10/afb54ad896c9d50af212ae7a56a216b47b38238a4e8e187437fe965c2cf100f1ca82668ed90d38dcd3b0e3ced53f54383e417af09c6dc09f6db0f78cba2aa9e4 - languageName: node - linkType: hard - "rc-cascader@npm:1.0.1": version: 1.0.1 resolution: "rc-cascader@npm:1.0.1" @@ -25675,23 +25219,6 @@ __metadata: languageName: node linkType: hard -"rc-cascader@npm:3.28.1": - version: 3.28.1 - resolution: "rc-cascader@npm:3.28.1" - dependencies: - "@babel/runtime": "npm:^7.12.5" - array-tree-filter: "npm:^2.1.0" - classnames: "npm:^2.3.1" - rc-select: "npm:~14.15.0" - rc-tree: "npm:~5.9.0" - rc-util: "npm:^5.37.0" - peerDependencies: - react: ">=16.9.0" - react-dom: ">=16.9.0" - checksum: 10/bb2feb79c0db19f459b265e9a0afb87611f4ba06c4776a5ea8ddcb0bfc81403292ada3a1c29cd7511872f8d2bfda7c29eab6dbc32052a60baf49576a50866d77 - languageName: node - linkType: hard - "rc-cascader@npm:3.33.0": version: 3.33.0 resolution: "rc-cascader@npm:3.33.0" @@ -25813,24 +25340,6 @@ __metadata: languageName: node linkType: hard -"rc-select@npm:~14.15.0": - version: 14.15.2 - resolution: "rc-select@npm:14.15.2" - dependencies: - "@babel/runtime": "npm:^7.10.1" - "@rc-component/trigger": "npm:^2.1.1" - classnames: "npm:2.x" - rc-motion: "npm:^2.0.1" - rc-overflow: "npm:^1.3.1" - rc-util: "npm:^5.16.1" - rc-virtual-list: "npm:^3.5.2" - peerDependencies: - react: "*" - react-dom: "*" - checksum: 10/707d9de38aaf83063ede754a925b56d6f02740197a3bed93f886c132ce797321d9e70a2fe32cff0546c54d9a11414d6d2c8fc1f914ac665fa11ad2b30a08bc85 - languageName: node - linkType: hard - "rc-select@npm:~14.16.2": version: 14.16.3 resolution: "rc-select@npm:14.16.3" @@ -25849,20 +25358,6 @@ __metadata: languageName: node linkType: hard -"rc-slider@npm:11.1.7": - version: 11.1.7 - resolution: "rc-slider@npm:11.1.7" - dependencies: - "@babel/runtime": "npm:^7.10.1" - classnames: "npm:^2.2.5" - rc-util: "npm:^5.36.0" - peerDependencies: - react: ">=16.9.0" - react-dom: ">=16.9.0" - checksum: 10/3b484d7ba4e4b6fc695666c27b767622c64b5819d0386cc0afb6d186c08f8ed4f93dd72f55377af9a957c77ee77db4d7fa73f85ccdaab0f39d9670daf42a17c5 - languageName: node - linkType: hard - "rc-slider@npm:11.1.8": version: 11.1.8 resolution: "rc-slider@npm:11.1.8" @@ -25877,34 +25372,6 @@ __metadata: languageName: node linkType: hard -"rc-time-picker@npm:^3.7.3": - version: 3.7.3 - resolution: "rc-time-picker@npm:3.7.3" - dependencies: - classnames: "npm:2.x" - moment: "npm:2.x" - prop-types: "npm:^15.5.8" - raf: "npm:^3.4.1" - rc-trigger: "npm:^2.2.0" - react-lifecycles-compat: "npm:^3.0.4" - checksum: 10/236ba0dd1b1cee4dd398d2542c251a7e0da21b31d3390a23c45cc7f2ea266cfddd1f0b7f681fe205f2d92edaa72771cb087505790b5124eb0ca31668feb74da5 - languageName: node - linkType: hard - -"rc-tooltip@npm:6.2.1": - version: 6.2.1 - resolution: "rc-tooltip@npm:6.2.1" - dependencies: - "@babel/runtime": "npm:^7.11.2" - "@rc-component/trigger": "npm:^2.0.0" - classnames: "npm:^2.3.1" - peerDependencies: - react: ">=16.9.0" - react-dom: ">=16.9.0" - checksum: 10/a82064d6d521ba4c03d074505402f6c38f9f50439037235969546b694e38977bab3420b46080bde295aca2436e6725d64b0a330a5ccdd51932deabbb1bf7585b - languageName: node - linkType: hard - "rc-tooltip@npm:6.4.0": version: 6.4.0 resolution: "rc-tooltip@npm:6.4.0" @@ -25936,37 +25403,6 @@ __metadata: languageName: node linkType: hard -"rc-tree@npm:~5.9.0": - version: 5.9.0 - resolution: "rc-tree@npm:5.9.0" - dependencies: - "@babel/runtime": "npm:^7.10.1" - classnames: "npm:2.x" - rc-motion: "npm:^2.0.1" - rc-util: "npm:^5.16.1" - rc-virtual-list: "npm:^3.5.1" - peerDependencies: - react: "*" - react-dom: "*" - checksum: 10/d7525c4a524c6de8e177ebc90fe9b924046951a02bacee85efd4529fb05a66add936802b43d5ca8d84469f9c63b8d542437365b911480f62a78e03e2c7fbaca0 - languageName: node - linkType: hard - -"rc-trigger@npm:^2.2.0": - version: 2.6.5 - resolution: "rc-trigger@npm:2.6.5" - dependencies: - babel-runtime: "npm:6.x" - classnames: "npm:^2.2.6" - prop-types: "npm:15.x" - rc-align: "npm:^2.4.0" - rc-animate: "npm:2.x" - rc-util: "npm:^4.4.0" - react-lifecycles-compat: "npm:^3.0.4" - checksum: 10/a3ed5f0c453a37ab00fce302e9f40daa64870eb001576a3409a94550802af1e01cbd7b050b3adf7225af03e82e2070d095477957fcd07209ee32602a2f3fba31 - languageName: node - linkType: hard - "rc-trigger@npm:^4.0.0": version: 4.4.3 resolution: "rc-trigger@npm:4.4.3" @@ -25981,7 +25417,7 @@ __metadata: languageName: node linkType: hard -"rc-util@npm:^4.0.4, rc-util@npm:^4.15.3, rc-util@npm:^4.4.0": +"rc-util@npm:^4.0.4": version: 4.21.1 resolution: "rc-util@npm:4.21.1" dependencies: @@ -26072,25 +25508,6 @@ __metadata: languageName: node linkType: hard -"react-calendar@npm:5.0.0": - version: 5.0.0 - resolution: "react-calendar@npm:5.0.0" - dependencies: - "@wojtekmaj/date-utils": "npm:^1.1.3" - clsx: "npm:^2.0.0" - get-user-locale: "npm:^2.2.1" - warning: "npm:^4.0.0" - peerDependencies: - "@types/react": ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - "@types/react": - optional: true - checksum: 10/1172828652e796a946beec4f7f4125bfbe775a39c4cdab2179cef04b0688e892062f628ec9263bdfea6d9be41c3de1414586036d03d6694e6008cd4763649581 - languageName: node - linkType: hard - "react-calendar@npm:^4.8.0": version: 4.8.0 resolution: "react-calendar@npm:4.8.0" @@ -26267,19 +25684,6 @@ __metadata: languageName: node linkType: hard -"react-dropzone@npm:14.2.9": - version: 14.2.9 - resolution: "react-dropzone@npm:14.2.9" - dependencies: - attr-accept: "npm:^2.2.2" - file-selector: "npm:^0.6.0" - prop-types: "npm:^15.8.1" - peerDependencies: - react: ">= 16.8 || 18.0.0" - checksum: 10/a8ff584a9dbf952dbd630f4ddf59b0b7a010eff49c3b97b363e30ab357f9cc7b8a0c7694069badeb4cf32361a00f3bbd1063964bd6438e9a68c6fe49ff879a38 - languageName: node - linkType: hard - "react-dropzone@npm:14.3.5, react-dropzone@npm:^14.2.3": version: 14.3.5 resolution: "react-dropzone@npm:14.3.5" @@ -26309,15 +25713,6 @@ __metadata: languageName: node linkType: hard -"react-from-dom@npm:^0.6.2": - version: 0.6.2 - resolution: "react-from-dom@npm:0.6.2" - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - checksum: 10/f3954737c2677e82f72ecedcdcf5f187d2a1b86a6c5b915f7300796ac153437581ee0111c9f524e7c18124d25d0d31391d54cdee318ea390722ca57e66813ed7 - languageName: node - linkType: hard - "react-from-dom@npm:^0.7.5": version: 0.7.5 resolution: "react-from-dom@npm:0.7.5" @@ -26361,19 +25756,6 @@ __metadata: languageName: node linkType: hard -"react-highlight-words@npm:0.20.0": - version: 0.20.0 - resolution: "react-highlight-words@npm:0.20.0" - dependencies: - highlight-words-core: "npm:^1.2.0" - memoize-one: "npm:^4.0.0" - prop-types: "npm:^15.5.8" - peerDependencies: - react: ^0.14.0 || ^15.0.0 || ^16.0.0-0 || ^17.0.0-0 || ^18.0.0-0 - checksum: 10/5adf2cfb1f325ae51ea4dd2cb7522eb433b25534355868d1a3f4556b2b9f7a774c2a1aaa143abebb63a1b3a5590e70ba3d765942a47ff754a1a513cdc5b2f58b - languageName: node - linkType: hard - "react-highlight-words@npm:0.21.0": version: 0.21.0 resolution: "react-highlight-words@npm:0.21.0" @@ -26404,24 +25786,6 @@ __metadata: languageName: node linkType: hard -"react-i18next@npm:^14.0.0": - version: 14.1.3 - resolution: "react-i18next@npm:14.1.3" - dependencies: - "@babel/runtime": "npm:^7.23.9" - html-parse-stringify: "npm:^3.0.1" - peerDependencies: - i18next: ">= 23.2.3" - react: ">= 16.8.0" - peerDependenciesMeta: - react-dom: - optional: true - react-native: - optional: true - checksum: 10/d0fa0f2717103c60758f9ddc1710e529f52e341465ca3f106ffa9168d88ad2db1bdbae58c77cca389933ae14bc39835abb37d1982049551ca15f6d310e2b3f57 - languageName: node - linkType: hard - "react-i18next@npm:^15.0.0": version: 15.4.0 resolution: "react-i18next@npm:15.4.0" @@ -26462,18 +25826,6 @@ __metadata: languageName: node linkType: hard -"react-inlinesvg@npm:3.0.2": - version: 3.0.2 - resolution: "react-inlinesvg@npm:3.0.2" - dependencies: - exenv: "npm:^1.2.2" - react-from-dom: "npm:^0.6.2" - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - checksum: 10/740fa33c7a09012bb96509f9003dc26e4e412eed2fc861ca40bfee9a3dddcf7c4d86fd20f824d4c017e44526aa9d747d6c9543ef3f2215bc0ace72754e025316 - languageName: node - linkType: hard - "react-inlinesvg@npm:4.2.0": version: 4.2.0 resolution: "react-inlinesvg@npm:4.2.0" @@ -26798,26 +26150,6 @@ __metadata: languageName: node linkType: hard -"react-select@npm:5.8.1": - version: 5.8.1 - resolution: "react-select@npm:5.8.1" - dependencies: - "@babel/runtime": "npm:^7.12.0" - "@emotion/cache": "npm:^11.4.0" - "@emotion/react": "npm:^11.8.1" - "@floating-ui/dom": "npm:^1.0.1" - "@types/react-transition-group": "npm:^4.4.0" - memoize-one: "npm:^6.0.0" - prop-types: "npm:^15.6.0" - react-transition-group: "npm:^4.3.0" - use-isomorphic-layout-effect: "npm:^1.1.2" - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - checksum: 10/53168b156435c5bef7c271ae7ebe67bff912e568dd1638f37859ea0d76cbd273d422714b6cb9669aa811d3fb44bda0f666b5e397a90a76ac2888a9b0ab47495a - languageName: node - linkType: hard - "react-selecto@npm:^1.25.0": version: 1.26.3 resolution: "react-selecto@npm:1.26.3" @@ -26976,32 +26308,7 @@ __metadata: languageName: node linkType: hard -"react-use@npm:17.5.1": - version: 17.5.1 - resolution: "react-use@npm:17.5.1" - dependencies: - "@types/js-cookie": "npm:^2.2.6" - "@xobotyi/scrollbar-width": "npm:^1.9.5" - copy-to-clipboard: "npm:^3.3.1" - fast-deep-equal: "npm:^3.1.3" - fast-shallow-equal: "npm:^1.0.0" - js-cookie: "npm:^2.2.1" - nano-css: "npm:^5.6.2" - react-universal-interface: "npm:^0.6.2" - resize-observer-polyfill: "npm:^1.5.1" - screenfull: "npm:^5.1.0" - set-harmonic-interval: "npm:^1.0.1" - throttle-debounce: "npm:^3.0.1" - ts-easing: "npm:^0.2.0" - tslib: "npm:^2.1.0" - peerDependencies: - react: "*" - react-dom: "*" - checksum: 10/2da403a9949dbd964b9b8e20dcd354db66b7f7d5ca1f42572fbcdb06bd49ee828c295be4912cb87abc163d1b54820bb8c5fa85314a16c4579d9e30bf9cbd5759 - languageName: node - linkType: hard - -"react-use@npm:17.6.0, react-use@npm:^17.3.1, react-use@npm:^17.4.0, react-use@npm:^17.5.0": +"react-use@npm:17.6.0, react-use@npm:^17.3.1, react-use@npm:^17.4.0, react-use@npm:^17.6.0": version: 17.6.0 resolution: "react-use@npm:17.6.0" dependencies: @@ -27067,19 +26374,6 @@ __metadata: languageName: node linkType: hard -"react-window@npm:1.8.10": - version: 1.8.10 - resolution: "react-window@npm:1.8.10" - dependencies: - "@babel/runtime": "npm:^7.0.0" - memoize-one: "npm:>=3.1.1 <6" - peerDependencies: - react: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 - react-dom: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 - checksum: 10/6f4a713a2012d605370ef4c7026a45ddd6801e428faa4cad558b12b05ba54c00de72de9a360db109db9666f972a3d955b63af9e5a4cd5fbc52411a382273107b - languageName: node - linkType: hard - "react-window@npm:1.8.11": version: 1.8.11 resolution: "react-window@npm:1.8.11" @@ -27103,7 +26397,7 @@ __metadata: languageName: node linkType: hard -"react@npm:18.3.1, react@npm:^18": +"react@npm:18.3.1": version: 18.3.1 resolution: "react@npm:18.3.1" dependencies: @@ -27384,13 +26678,6 @@ __metadata: languageName: node linkType: hard -"regenerator-runtime@npm:^0.11.0": - version: 0.11.1 - resolution: "regenerator-runtime@npm:0.11.1" - checksum: 10/64e62d78594c227e7d5269811bca9e4aa6451332adaae8c79a30cab0fa98733b1ad90bdb9d038095c340c6fad3b414a49a8d9e0b6b424ab7ff8f94f35704f8a2 - languageName: node - linkType: hard - "regenerator-runtime@npm:^0.13.4": version: 0.13.11 resolution: "regenerator-runtime@npm:0.13.11" @@ -28032,7 +27319,7 @@ __metadata: languageName: node linkType: hard -"rxjs@npm:7.8.1, rxjs@npm:^7.5.1, rxjs@npm:^7.5.5, rxjs@npm:^7.8.1": +"rxjs@npm:7.8.1, rxjs@npm:^7.5.1, rxjs@npm:^7.5.5": version: 7.8.1 resolution: "rxjs@npm:7.8.1" dependencies: @@ -30632,13 +29919,6 @@ __metadata: languageName: node linkType: hard -"tslib@npm:2.7.0": - version: 2.7.0 - resolution: "tslib@npm:2.7.0" - checksum: 10/9a5b47ddac65874fa011c20ff76db69f97cf90c78cff5934799ab8894a5342db2d17b4e7613a087046bc1d133d21547ddff87ac558abeec31ffa929c88b7fce6 - languageName: node - linkType: hard - "tslib@npm:^1.10.0, tslib@npm:^1.8.1": version: 1.14.1 resolution: "tslib@npm:1.14.1" @@ -31180,7 +30460,7 @@ __metadata: languageName: node linkType: hard -"use-isomorphic-layout-effect@npm:^1.1.2, use-isomorphic-layout-effect@npm:^1.2.0": +"use-isomorphic-layout-effect@npm:^1.2.0": version: 1.2.0 resolution: "use-isomorphic-layout-effect@npm:1.2.0" peerDependencies: @@ -31262,15 +30542,6 @@ __metadata: languageName: node linkType: hard -"uuid@npm:9.0.1, uuid@npm:^9.0.0": - version: 9.0.1 - resolution: "uuid@npm:9.0.1" - bin: - uuid: dist/bin/uuid - checksum: 10/9d0b6adb72b736e36f2b1b53da0d559125ba3e39d913b6072f6f033e0c87835b414f0836b45bcfaf2bdf698f92297fea1c3cc19b0b258bc182c9c43cc0fab9f2 - languageName: node - linkType: hard - "uuid@npm:^10.0.0": version: 10.0.0 resolution: "uuid@npm:10.0.0" @@ -31280,6 +30551,15 @@ __metadata: languageName: node linkType: hard +"uuid@npm:^11.0.5": + version: 11.1.0 + resolution: "uuid@npm:11.1.0" + bin: + uuid: dist/esm/bin/uuid + checksum: 10/d2da43b49b154d154574891ced66d0c83fc70caaad87e043400cf644423b067542d6f3eb641b7c819224a7cd3b4c2f21906acbedd6ec9c6a05887aa9115a9cf5 + languageName: node + linkType: hard + "uuid@npm:^8.3.2": version: 8.3.2 resolution: "uuid@npm:8.3.2" @@ -31289,6 +30569,15 @@ __metadata: languageName: node linkType: hard +"uuid@npm:^9.0.0": + version: 9.0.1 + resolution: "uuid@npm:9.0.1" + bin: + uuid: dist/bin/uuid + checksum: 10/9d0b6adb72b736e36f2b1b53da0d559125ba3e39d913b6072f6f033e0c87835b414f0836b45bcfaf2bdf698f92297fea1c3cc19b0b258bc182c9c43cc0fab9f2 + languageName: node + linkType: hard + "v8-compile-cache-lib@npm:^3.0.1": version: 3.0.1 resolution: "v8-compile-cache-lib@npm:3.0.1" From e128c3612776f012aa83d67962344fafd0c5039d Mon Sep 17 00:00:00 2001 From: Selene Date: Wed, 12 Mar 2025 10:12:56 +0100 Subject: [PATCH 049/141] Codegen: Cog and go fixes (#101408) * Update to latest cog version and update workspaces * Update generated go files * Try to avoid concurrency issues * Update workspaces * Try to remove the sync... * Remove grafana dependency from xorm go.mod file --- apps/alerting/notifications/go.sum | 8 +- apps/investigations/go.mod | 1 + apps/investigations/go.sum | 4 +- apps/playlist/go.mod | 1 + apps/playlist/go.sum | 4 +- go.mod | 4 +- go.sum | 8 +- go.work.sum | 2 + pkg/aggregator/go.mod | 4 +- pkg/aggregator/go.sum | 8 +- pkg/apiserver/go.mod | 2 +- pkg/apiserver/go.sum | 4 +- pkg/build/go.mod | 2 +- pkg/build/go.sum | 4 +- pkg/build/wire/go.mod | 4 +- pkg/build/wire/go.sum | 8 +- pkg/codegen/go.mod | 6 +- pkg/codegen/go.sum | 12 +- pkg/codegen/jenny_go_spec.go | 4 +- pkg/kinds/dashboard/dashboard_spec_gen.go | 1402 +++++++++-------- .../librarypanel/librarypanel_spec_gen.go | 58 +- pkg/kinds/preferences/preferences_spec_gen.go | 54 +- pkg/plugins/codegen/go.mod | 6 +- pkg/plugins/codegen/go.sum | 12 +- pkg/promlib/go.mod | 4 +- pkg/promlib/go.sum | 8 +- pkg/storage/unified/apistore/go.mod | 4 +- pkg/storage/unified/apistore/go.sum | 8 +- pkg/storage/unified/resource/go.mod | 4 +- pkg/storage/unified/resource/go.sum | 8 +- .../kinds/dataquery/types_dataquery_gen.go | 214 +-- .../kinds/dataquery/types_dataquery_gen.go | 22 +- .../kinds/dataquery/types_dataquery_gen.go | 256 +-- .../kinds/dataquery/types_dataquery_gen.go | 1187 +++++++------- .../kinds/dataquery/types_dataquery_gen.go | 102 +- 35 files changed, 1726 insertions(+), 1713 deletions(-) diff --git a/apps/alerting/notifications/go.sum b/apps/alerting/notifications/go.sum index 1c891acf02e..c2e7b8b7d13 100644 --- a/apps/alerting/notifications/go.sum +++ b/apps/alerting/notifications/go.sum @@ -214,8 +214,8 @@ golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= -golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM= +golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -252,8 +252,8 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE= -golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588= +golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY= +golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/apps/investigations/go.mod b/apps/investigations/go.mod index a8362a39961..aada9c2c3b4 100644 --- a/apps/investigations/go.mod +++ b/apps/investigations/go.mod @@ -72,6 +72,7 @@ require ( golang.org/x/term v0.29.0 // indirect golang.org/x/text v0.22.0 // indirect golang.org/x/time v0.9.0 // indirect + golang.org/x/tools v0.30.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250204164813-702378808489 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250207221924-e9438ea467c6 // indirect diff --git a/apps/investigations/go.sum b/apps/investigations/go.sum index 46332e3fe34..129a8163693 100644 --- a/apps/investigations/go.sum +++ b/apps/investigations/go.sum @@ -181,8 +181,8 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE= -golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588= +golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY= +golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/apps/playlist/go.mod b/apps/playlist/go.mod index cb656a1b84a..b8162370ed4 100644 --- a/apps/playlist/go.mod +++ b/apps/playlist/go.mod @@ -73,6 +73,7 @@ require ( golang.org/x/term v0.29.0 // indirect golang.org/x/text v0.22.0 // indirect golang.org/x/time v0.9.0 // indirect + golang.org/x/tools v0.30.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250204164813-702378808489 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250207221924-e9438ea467c6 // indirect diff --git a/apps/playlist/go.sum b/apps/playlist/go.sum index 46332e3fe34..129a8163693 100644 --- a/apps/playlist/go.sum +++ b/apps/playlist/go.sum @@ -181,8 +181,8 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE= -golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588= +golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY= +golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/go.mod b/go.mod index 09c1ee4237e..e1efdbd7ed0 100644 --- a/go.mod +++ b/go.mod @@ -171,13 +171,13 @@ require ( gocloud.dev v0.40.0 // @grafana/grafana-app-platform-squad golang.org/x/crypto v0.35.0 // @grafana/grafana-backend-group golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // @grafana/alerting-backend - golang.org/x/mod v0.22.0 // indirect; @grafana/grafana-backend-group + golang.org/x/mod v0.23.0 // indirect; @grafana/grafana-backend-group golang.org/x/net v0.36.0 // @grafana/oss-big-tent @grafana/partner-datasources golang.org/x/oauth2 v0.27.0 // @grafana/identity-access-team golang.org/x/sync v0.11.0 // @grafana/alerting-backend golang.org/x/text v0.22.0 // @grafana/grafana-backend-group golang.org/x/time v0.9.0 // @grafana/grafana-backend-group - golang.org/x/tools v0.29.0 // indirect; @grafana/grafana-as-code + golang.org/x/tools v0.30.0 // indirect; @grafana/grafana-as-code gonum.org/v1/gonum v0.15.1 // @grafana/oss-big-tent google.golang.org/api v0.220.0 // @grafana/grafana-backend-group google.golang.org/grpc v1.70.0 // @grafana/plugins-platform-backend diff --git a/go.sum b/go.sum index 1b581e0291f..743a5da82aa 100644 --- a/go.sum +++ b/go.sum @@ -2655,8 +2655,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= -golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM= +golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -3052,8 +3052,8 @@ golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= -golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE= -golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588= +golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY= +golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/go.work.sum b/go.work.sum index f29575e7e8d..5a25a1d8f71 100644 --- a/go.work.sum +++ b/go.work.sum @@ -1447,6 +1447,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.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211123203042-d83791d6bcd9/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= @@ -1459,6 +1460,7 @@ golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= +golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM= golang.org/x/oauth2 v0.19.0/go.mod h1:vYi7skDa1x015PmRRYZ7+s1cWyPgrPiSYRe4rnsexc8= golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= diff --git a/pkg/aggregator/go.mod b/pkg/aggregator/go.mod index b2a85fd8e50..27ed1f96d1b 100644 --- a/pkg/aggregator/go.mod +++ b/pkg/aggregator/go.mod @@ -136,7 +136,7 @@ require ( go.uber.org/zap v1.27.0 // indirect golang.org/x/crypto v0.35.0 // indirect golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect - golang.org/x/mod v0.22.0 // indirect + golang.org/x/mod v0.23.0 // indirect golang.org/x/net v0.36.0 // indirect golang.org/x/oauth2 v0.27.0 // indirect golang.org/x/sync v0.11.0 // indirect @@ -144,7 +144,7 @@ require ( golang.org/x/term v0.29.0 // indirect golang.org/x/text v0.22.0 // indirect golang.org/x/time v0.9.0 // indirect - golang.org/x/tools v0.29.0 // indirect + golang.org/x/tools v0.30.0 // indirect golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect google.golang.org/genproto v0.0.0-20250122153221-138b5a5a4fd4 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250204164813-702378808489 // indirect diff --git a/pkg/aggregator/go.sum b/pkg/aggregator/go.sum index c54a584b8b2..7e1a462ca5b 100644 --- a/pkg/aggregator/go.sum +++ b/pkg/aggregator/go.sum @@ -408,8 +408,8 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= -golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM= +golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -467,8 +467,8 @@ golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE= -golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588= +golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY= +golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/pkg/apiserver/go.mod b/pkg/apiserver/go.mod index d2b5e90046b..39a9e18756d 100644 --- a/pkg/apiserver/go.mod +++ b/pkg/apiserver/go.mod @@ -87,7 +87,7 @@ require ( golang.org/x/term v0.29.0 // indirect golang.org/x/text v0.22.0 // indirect golang.org/x/time v0.9.0 // indirect - golang.org/x/tools v0.29.0 // indirect + golang.org/x/tools v0.30.0 // indirect google.golang.org/genproto v0.0.0-20250122153221-138b5a5a4fd4 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250204164813-702378808489 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250207221924-e9438ea467c6 // indirect diff --git a/pkg/apiserver/go.sum b/pkg/apiserver/go.sum index 97748fca3de..1d54379d869 100644 --- a/pkg/apiserver/go.sum +++ b/pkg/apiserver/go.sum @@ -287,8 +287,8 @@ golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE= -golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588= +golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY= +golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/pkg/build/go.mod b/pkg/build/go.mod index 46478d1b62f..14352adba3e 100644 --- a/pkg/build/go.mod +++ b/pkg/build/go.mod @@ -27,7 +27,7 @@ require ( go.opentelemetry.io/otel/sdk v1.35.0 // indirect; @grafana/grafana-backend-group go.opentelemetry.io/otel/trace v1.35.0 // indirect; @grafana/grafana-backend-group golang.org/x/crypto v0.35.0 // indirect; @grafana/grafana-backend-group - golang.org/x/mod v0.22.0 // @grafana/grafana-backend-group + golang.org/x/mod v0.23.0 // @grafana/grafana-backend-group golang.org/x/net v0.36.0 // indirect; @grafana/oss-big-tent @grafana/partner-datasources golang.org/x/oauth2 v0.27.0 // @grafana/identity-access-team golang.org/x/sync v0.11.0 // indirect; @grafana/alerting-backend diff --git a/pkg/build/go.sum b/pkg/build/go.sum index 2dface03828..1d55ac2a88b 100644 --- a/pkg/build/go.sum +++ b/pkg/build/go.sum @@ -303,8 +303,8 @@ golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvx golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= -golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM= +golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= diff --git a/pkg/build/wire/go.mod b/pkg/build/wire/go.mod index 32bfb68353e..4083a088d6c 100644 --- a/pkg/build/wire/go.mod +++ b/pkg/build/wire/go.mod @@ -6,10 +6,10 @@ require ( github.com/google/go-cmp v0.7.0 github.com/google/subcommands v1.2.0 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 - golang.org/x/tools v0.29.0 + golang.org/x/tools v0.30.0 ) require ( - golang.org/x/mod v0.22.0 // indirect + golang.org/x/mod v0.23.0 // indirect golang.org/x/sync v0.11.0 // indirect ) diff --git a/pkg/build/wire/go.sum b/pkg/build/wire/go.sum index 07103d75876..8cc9fd24b82 100644 --- a/pkg/build/wire/go.sum +++ b/pkg/build/wire/go.sum @@ -4,9 +4,9 @@ github.com/google/subcommands v1.2.0 h1:vWQspBTo2nEqTUFita5/KeEWlUL8kQObDFbub/EN github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= -golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM= +golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE= -golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588= +golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY= +golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY= diff --git a/pkg/codegen/go.mod b/pkg/codegen/go.mod index 192cb41aa83..ed8c2bdc787 100644 --- a/pkg/codegen/go.mod +++ b/pkg/codegen/go.mod @@ -6,7 +6,7 @@ require ( cuelang.org/go v0.11.1 github.com/dave/dst v0.27.3 github.com/grafana/codejen v0.0.4-0.20230321061741-77f656893a3d - github.com/grafana/cog v0.0.18 + github.com/grafana/cog v0.0.27 github.com/grafana/cuetsy v0.1.11 github.com/matryer/is v1.4.1 ) @@ -43,11 +43,11 @@ require ( github.com/ugorji/go/codec v1.2.11 // indirect github.com/xlab/treeprint v1.2.0 // indirect github.com/yalue/merged_fs v1.3.0 // indirect - golang.org/x/mod v0.22.0 // indirect + golang.org/x/mod v0.23.0 // indirect golang.org/x/net v0.36.0 // indirect golang.org/x/sync v0.11.0 // indirect golang.org/x/text v0.22.0 // indirect - golang.org/x/tools v0.29.0 // indirect + golang.org/x/tools v0.30.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/pkg/codegen/go.sum b/pkg/codegen/go.sum index 9ad69c311b0..684b40898c0 100644 --- a/pkg/codegen/go.sum +++ b/pkg/codegen/go.sum @@ -31,8 +31,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grafana/codejen v0.0.4-0.20230321061741-77f656893a3d h1:hrXbGJ5jgp6yNITzs5o+zXq0V5yT3siNJ+uM8LGwWKk= github.com/grafana/codejen v0.0.4-0.20230321061741-77f656893a3d/go.mod h1:zmwwM/DRyQB7pfuBjTWII3CWtxcXh8LTwAYGfDfpR6s= -github.com/grafana/cog v0.0.18 h1:pEmzo/yhIFZMHM58ua0M9Eb5frJj6CgTrTTUVlY8e2o= -github.com/grafana/cog v0.0.18/go.mod h1:jrS9indvWuDs60RHEZpLaAkmZdgyoLKMOEUT0jiB1t0= +github.com/grafana/cog v0.0.27 h1:ZKipAtp6KuB08R16nZbqEjnje3e2r1O1bzOp1CetDEo= +github.com/grafana/cog v0.0.27/go.mod h1:JB5lhdn4Hqc0ztYCaNOTKZXoojzJvydBxMkMCGWS6+Q= github.com/grafana/cue v0.0.0-20230926092038-971951014e3f h1:TmYAMnqg3d5KYEAaT6PtTguL2GjLfvr6wnAX8Azw6tQ= github.com/grafana/cue v0.0.0-20230926092038-971951014e3f/go.mod h1:okjJBHFQFer+a41sAe2SaGm1glWS8oEb6CmJvn5Zdws= github.com/grafana/cuetsy v0.1.11 h1:I3IwBhF+UaQxRM79HnImtrAn8REGdb5M3+C4QrYHoWk= @@ -98,16 +98,16 @@ github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= github.com/yalue/merged_fs v1.3.0 h1:qCeh9tMPNy/i8cwDsQTJ5bLr6IRxbs6meakNE5O+wyY= github.com/yalue/merged_fs v1.3.0/go.mod h1:WqqchfVYQyclV2tnR7wtRhBddzBvLVR83Cjw9BKQw0M= -golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= -golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM= +golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA= golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I= golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= -golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE= -golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588= +golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY= +golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/pkg/codegen/jenny_go_spec.go b/pkg/codegen/jenny_go_spec.go index d753d9cae55..1226c584829 100644 --- a/pkg/codegen/jenny_go_spec.go +++ b/pkg/codegen/jenny_go_spec.go @@ -24,10 +24,10 @@ func (jenny *GoSpecJenny) Generate(sfg ...SchemaForGen) (codejen.Files, error) { for i, v := range sfg { packageName := strings.ToLower(v.Name) - cueValue := v.CueFile.LookupPath(cue.ParsePath("lineage.schemas[0].schema.spec")) + cueValue := v.CueFile.LookupPath(cue.ParsePath("lineage.schemas[0].schema")) b, err := cog.TypesFromSchema(). - CUEValue(packageName, cueValue, cog.ForceEnvelope("Spec")). + CUEValue(packageName, cueValue). Golang(cog.GoConfig{}). Run(context.Background()) if err != nil { diff --git a/pkg/kinds/dashboard/dashboard_spec_gen.go b/pkg/kinds/dashboard/dashboard_spec_gen.go index 526c5a66056..2fd8eb8b060 100644 --- a/pkg/kinds/dashboard/dashboard_spec_gen.go +++ b/pkg/kinds/dashboard/dashboard_spec_gen.go @@ -18,6 +18,79 @@ import ( time "time" ) +type Spec struct { + // Unique numeric identifier for the dashboard. + // `id` is internal to a specific Grafana instance. `uid` should be used to identify a dashboard across Grafana instances. + // TODO eliminate this null option + Id *int64 `json:"id,omitempty"` + // Unique dashboard identifier that can be generated by anyone. string (8-40) + Uid *string `json:"uid,omitempty"` + // Title of dashboard. + Title *string `json:"title,omitempty"` + // Description of dashboard. + Description *string `json:"description,omitempty"` + // This property should only be used in dashboards defined by plugins. It is a quick check + // to see if the version has changed since the last time. + Revision *int64 `json:"revision,omitempty"` + // ID of a dashboard imported from the https://grafana.com/grafana/dashboards/ portal + GnetId *string `json:"gnetId,omitempty"` + // Tags associated with dashboard. + Tags []string `json:"tags,omitempty"` + // Timezone of dashboard. Accepted values are IANA TZDB zone ID or "browser" or "utc". + Timezone *string `json:"timezone,omitempty"` + // Whether a dashboard is editable or not. + Editable *bool `json:"editable,omitempty"` + // Configuration of dashboard cursor sync behavior. + // Accepted values are 0 (sync turned off), 1 (shared crosshair), 2 (shared crosshair and tooltip). + GraphTooltip *DashboardCursorSync `json:"graphTooltip,omitempty"` + // Time range for dashboard. + // Accepted values are relative time strings like {from: 'now-6h', to: 'now'} or absolute time strings like {from: '2020-07-10T08:00:00.000Z', to: '2020-07-10T14:00:00.000Z'}. + Time *DashboardSpecTime `json:"time,omitempty"` + // Configuration of the time picker shown at the top of a dashboard. + Timepicker *TimePickerConfig `json:"timepicker,omitempty"` + // The month that the fiscal year starts on. 0 = January, 11 = December + FiscalYearStartMonth *uint8 `json:"fiscalYearStartMonth,omitempty"` + // When set to true, the dashboard will redraw panels at an interval matching the pixel width. + // This will keep data "moving left" regardless of the query refresh rate. This setting helps + // avoid dashboards presenting stale live data + LiveNow *bool `json:"liveNow,omitempty"` + // Day when the week starts. Expressed by the name of the day in lowercase, e.g. "monday". + WeekStart *string `json:"weekStart,omitempty"` + // Refresh rate of dashboard. Represented via interval string, e.g. "5s", "1m", "1h", "1d". + Refresh *string `json:"refresh,omitempty"` + // Version of the JSON schema, incremented each time a Grafana update brings + // changes to said schema. + SchemaVersion uint16 `json:"schemaVersion"` + // Version of the dashboard, incremented each time the dashboard is updated. + Version *uint32 `json:"version,omitempty"` + // List of dashboard panels + Panels []any `json:"panels,omitempty"` + // Configured template variables + Templating *DashboardSpecTemplating `json:"templating,omitempty"` + // Contains the list of annotations that are associated with the dashboard. + // Annotations are used to overlay event markers and overlay event tags on graphs. + // Grafana comes with a native annotation store and the ability to add annotation events directly from the graph panel or via the HTTP API. + // See https://grafana.com/docs/grafana/latest/dashboards/build-dashboards/annotate-visualizations/ + Annotations *AnnotationContainer `json:"annotations,omitempty"` + // Links with references to other dashboards or external websites. + Links []DashboardLink `json:"links,omitempty"` + // Snapshot options. They are present only if the dashboard is a snapshot. + Snapshot *Snapshot `json:"snapshot,omitempty"` + // When set to true, the dashboard will load all panels in the dashboard when it's loaded. + Preload *bool `json:"preload,omitempty"` +} + +// NewSpec creates a new Spec object. +func NewSpec() *Spec { + return &Spec{ + Timezone: (func(input string) *string { return &input })("browser"), + Editable: (func(input bool) *bool { return &input })(true), + GraphTooltip: (func(input DashboardCursorSync) *DashboardCursorSync { return &input })(DashboardCursorSyncOff), + FiscalYearStartMonth: (func(input uint8) *uint8 { return &input })(0), + SchemaVersion: 41, + } +} + // 0 for no shared crosshair or tooltip (default). // 1 for shared crosshair. // 2 for shared crosshair AND shared tooltip. @@ -29,18 +102,6 @@ const ( DashboardCursorSyncTooltip DashboardCursorSync = 2 ) -// Counterpart for TypeScript's TimeOption type. -type TimeOption struct { - Display string `json:"display"` - From string `json:"from"` - To string `json:"to"` -} - -// NewTimeOption creates a new TimeOption object. -func NewTimeOption() *TimeOption { - return &TimeOption{} -} - // Time picker configuration // It defines the default config for the time picker and the refresh picker for the specific dashboard. type TimePickerConfig struct { @@ -62,434 +123,16 @@ func NewTimePickerConfig() *TimePickerConfig { } } -// Schema for panel targets is specified by datasource -// plugins. We use a placeholder definition, which the Go -// schema loader either left open/as-is with the Base -// variant of the Dashboard and Panel families, or filled -// with types derived from plugins in the Instance variant. -// When working directly from CUE, importers can extend this -// type directly to achieve the same effect. -type Target map[string]any - -// Ref to a DataSource instance -type DataSourceRef struct { - // The plugin type-id - Type *string `json:"type,omitempty"` - // Specific datasource instance - Uid *string `json:"uid,omitempty"` +// Counterpart for TypeScript's TimeOption type. +type TimeOption struct { + Display string `json:"display"` + From string `json:"from"` + To string `json:"to"` } -// NewDataSourceRef creates a new DataSourceRef object. -func NewDataSourceRef() *DataSourceRef { - return &DataSourceRef{} -} - -// Position and dimensions of a panel in the grid -type GridPos struct { - // Panel height. The height is the number of rows from the top edge of the panel. - H uint32 `json:"h"` - // Panel width. The width is the number of columns from the left edge of the panel. - W uint32 `json:"w"` - // Panel x. The x coordinate is the number of columns from the left edge of the grid - X uint32 `json:"x"` - // Panel y. The y coordinate is the number of rows from the top edge of the grid - Y uint32 `json:"y"` - // Whether the panel is fixed within the grid. If true, the panel will not be affected by other panels' interactions - Static *bool `json:"static,omitempty"` -} - -// NewGridPos creates a new GridPos object. -func NewGridPos() *GridPos { - return &GridPos{ - H: 9, - W: 12, - X: 0, - Y: 0, - } -} - -// Dashboard Link type. Accepted values are dashboards (to refer to another dashboard) and link (to refer to an external resource) -type DashboardLinkType string - -const ( - DashboardLinkTypeLink DashboardLinkType = "link" - DashboardLinkTypeDashboards DashboardLinkType = "dashboards" -) - -// Links with references to other dashboards or external resources -type DashboardLink struct { - // Title to display with the link - Title string `json:"title"` - // Link type. Accepted values are dashboards (to refer to another dashboard) and link (to refer to an external resource) - Type DashboardLinkType `json:"type"` - // Icon name to be displayed with the link - Icon string `json:"icon"` - // Tooltip to display when the user hovers their mouse over it - Tooltip string `json:"tooltip"` - // Link URL. Only required/valid if the type is link - Url *string `json:"url,omitempty"` - // List of tags to limit the linked dashboards. If empty, all dashboards will be displayed. Only valid if the type is dashboards - Tags []string `json:"tags"` - // If true, all dashboards links will be displayed in a dropdown. If false, all dashboards links will be displayed side by side. Only valid if the type is dashboards - AsDropdown bool `json:"asDropdown"` - // If true, the link will be opened in a new tab - TargetBlank bool `json:"targetBlank"` - // If true, includes current template variables values in the link as query params - IncludeVars bool `json:"includeVars"` - // If true, includes current time range in the link as query params - KeepTime bool `json:"keepTime"` -} - -// NewDashboardLink creates a new DashboardLink object. -func NewDashboardLink() *DashboardLink { - return &DashboardLink{ - AsDropdown: false, - TargetBlank: false, - IncludeVars: false, - KeepTime: false, - } -} - -// Matcher is a predicate configuration. Based on the config a set of field(s) or values is filtered in order to apply override / transformation. -// It comes with in id ( to resolve implementation from registry) and a configuration that’s specific to a particular matcher type. -type MatcherConfig struct { - // The matcher id. This is used to find the matcher implementation from registry. - Id string `json:"id"` - // The matcher options. This is specific to the matcher implementation. - Options any `json:"options,omitempty"` -} - -// NewMatcherConfig creates a new MatcherConfig object. -func NewMatcherConfig() *MatcherConfig { - return &MatcherConfig{ - Id: "", - } -} - -// Transformations allow to manipulate data returned by a query before the system applies a visualization. -// Using transformations you can: rename fields, join time series data, perform mathematical operations across queries, -// use the output of one transformation as the input to another transformation, etc. -type DataTransformerConfig struct { - // Unique identifier of transformer - Id string `json:"id"` - // Disabled transformations are skipped - Disabled *bool `json:"disabled,omitempty"` - // Optional frame matcher. When missing it will be applied to all results - Filter *MatcherConfig `json:"filter,omitempty"` - // Where to pull DataFrames from as input to transformation - // replaced with common.DataTopic - Topic *DataTransformerConfigTopic `json:"topic,omitempty"` - // Options to be passed to the transformer - // Valid options depend on the transformer id - Options any `json:"options"` -} - -// NewDataTransformerConfig creates a new DataTransformerConfig object. -func NewDataTransformerConfig() *DataTransformerConfig { - return &DataTransformerConfig{} -} - -// A library panel is a reusable panel that you can use in any dashboard. -// When you make a change to a library panel, that change propagates to all instances of where the panel is used. -// Library panels streamline reuse of panels across multiple dashboards. -type LibraryPanelRef struct { - // Library panel name - Name string `json:"name"` - // Library panel uid - Uid string `json:"uid"` -} - -// NewLibraryPanelRef creates a new LibraryPanelRef object. -func NewLibraryPanelRef() *LibraryPanelRef { - return &LibraryPanelRef{} -} - -// Result used as replacement with text and color when the value matches -type ValueMappingResult struct { - // Text to display when the value matches - Text *string `json:"text,omitempty"` - // Text to use when the value matches - Color *string `json:"color,omitempty"` - // Icon to display when the value matches. Only specific visualizations. - Icon *string `json:"icon,omitempty"` - // Position in the mapping array. Only used internally. - Index *int32 `json:"index,omitempty"` -} - -// NewValueMappingResult creates a new ValueMappingResult object. -func NewValueMappingResult() *ValueMappingResult { - return &ValueMappingResult{} -} - -// Maps text values to a color or different display text and color. -// For example, you can configure a value mapping so that all instances of the value 10 appear as Perfection! rather than the number. -type ValueMap struct { - Type string `json:"type"` - // Map with : ValueMappingResult. For example: { "10": { text: "Perfection!", color: "green" } } - Options map[string]ValueMappingResult `json:"options"` -} - -// NewValueMap creates a new ValueMap object. -func NewValueMap() *ValueMap { - return &ValueMap{ - Type: "value", - } -} - -// Maps numerical ranges to a display text and color. -// For example, if a value is within a certain range, you can configure a range value mapping to display Low or High rather than the number. -type RangeMap struct { - Type string `json:"type"` - // Range to match against and the result to apply when the value is within the range - Options DashboardRangeMapOptions `json:"options"` -} - -// NewRangeMap creates a new RangeMap object. -func NewRangeMap() *RangeMap { - return &RangeMap{ - Type: "range", - Options: *NewDashboardRangeMapOptions(), - } -} - -// Maps regular expressions to replacement text and a color. -// For example, if a value is www.example.com, you can configure a regex value mapping so that Grafana displays www and truncates the domain. -type RegexMap struct { - Type string `json:"type"` - // Regular expression to match against and the result to apply when the value matches the regex - Options DashboardRegexMapOptions `json:"options"` -} - -// NewRegexMap creates a new RegexMap object. -func NewRegexMap() *RegexMap { - return &RegexMap{ - Type: "regex", - Options: *NewDashboardRegexMapOptions(), - } -} - -// Special value types supported by the `SpecialValueMap` -type SpecialValueMatch string - -const ( - SpecialValueMatchTrue SpecialValueMatch = "true" - SpecialValueMatchFalse SpecialValueMatch = "false" - SpecialValueMatchNull SpecialValueMatch = "null" - SpecialValueMatchNaN SpecialValueMatch = "nan" - SpecialValueMatchNullAndNan SpecialValueMatch = "null+nan" - SpecialValueMatchEmpty SpecialValueMatch = "empty" -) - -// Maps special values like Null, NaN (not a number), and boolean values like true and false to a display text and color. -// See SpecialValueMatch to see the list of special values. -// For example, you can configure a special value mapping so that null values appear as N/A. -type SpecialValueMap struct { - Type string `json:"type"` - Options DashboardSpecialValueMapOptions `json:"options"` -} - -// NewSpecialValueMap creates a new SpecialValueMap object. -func NewSpecialValueMap() *SpecialValueMap { - return &SpecialValueMap{ - Type: "special", - Options: *NewDashboardSpecialValueMapOptions(), - } -} - -// Allow to transform the visual representation of specific data values in a visualization, irrespective of their original units -type ValueMapping = ValueMapOrRangeMapOrRegexMapOrSpecialValueMap - -// NewValueMapping creates a new ValueMapping object. -func NewValueMapping() *ValueMapping { - return NewValueMapOrRangeMapOrRegexMapOrSpecialValueMap() -} - -// Thresholds can either be `absolute` (specific number) or `percentage` (relative to min or max, it will be values between 0 and 1). -type ThresholdsMode string - -const ( - ThresholdsModeAbsolute ThresholdsMode = "absolute" - ThresholdsModePercentage ThresholdsMode = "percentage" -) - -// User-defined value for a metric that triggers visual changes in a panel when this value is met or exceeded -// They are used to conditionally style and color visualizations based on query results , and can be applied to most visualizations. -type Threshold struct { - // Value represents a specified metric for the threshold, which triggers a visual change in the dashboard when this value is met or exceeded. - // Nulls currently appear here when serializing -Infinity to JSON. - Value *float64 `json:"value"` - // Color represents the color of the visual change that will occur in the dashboard when the threshold value is met or exceeded. - Color string `json:"color"` -} - -// NewThreshold creates a new Threshold object. -func NewThreshold() *Threshold { - return &Threshold{} -} - -// Thresholds configuration for the panel -type ThresholdsConfig struct { - // Thresholds mode. - Mode ThresholdsMode `json:"mode"` - // Must be sorted by 'value', first value is always -Infinity - Steps []Threshold `json:"steps"` -} - -// NewThresholdsConfig creates a new ThresholdsConfig object. -func NewThresholdsConfig() *ThresholdsConfig { - return &ThresholdsConfig{} -} - -// Color mode for a field. You can specify a single color, or select a continuous (gradient) color schemes, based on a value. -// Continuous color interpolates a color using the percentage of a value relative to min and max. -// Accepted values are: -// `thresholds`: From thresholds. Informs Grafana to take the color from the matching threshold -// `palette-classic`: Classic palette. Grafana will assign color by looking up a color in a palette by series index. Useful for Graphs and pie charts and other categorical data visualizations -// `palette-classic-by-name`: Classic palette (by name). Grafana will assign color by looking up a color in a palette by series name. Useful for Graphs and pie charts and other categorical data visualizations -// `continuous-GrYlRd`: ontinuous Green-Yellow-Red palette mode -// `continuous-RdYlGr`: Continuous Red-Yellow-Green palette mode -// `continuous-BlYlRd`: Continuous Blue-Yellow-Red palette mode -// `continuous-YlRd`: Continuous Yellow-Red palette mode -// `continuous-BlPu`: Continuous Blue-Purple palette mode -// `continuous-YlBl`: Continuous Yellow-Blue palette mode -// `continuous-blues`: Continuous Blue palette mode -// `continuous-reds`: Continuous Red palette mode -// `continuous-greens`: Continuous Green palette mode -// `continuous-purples`: Continuous Purple palette mode -// `shades`: Shades of a single color. Specify a single color, useful in an override rule. -// `fixed`: Fixed color mode. Specify a single color, useful in an override rule. -type FieldColorModeId string - -const ( - FieldColorModeIdThresholds FieldColorModeId = "thresholds" - FieldColorModeIdPaletteClassic FieldColorModeId = "palette-classic" - FieldColorModeIdPaletteClassicByName FieldColorModeId = "palette-classic-by-name" - FieldColorModeIdContinuousGrYlRd FieldColorModeId = "continuous-GrYlRd" - FieldColorModeIdContinuousRdYlGr FieldColorModeId = "continuous-RdYlGr" - FieldColorModeIdContinuousBlYlRd FieldColorModeId = "continuous-BlYlRd" - FieldColorModeIdContinuousYlRd FieldColorModeId = "continuous-YlRd" - FieldColorModeIdContinuousBlPu FieldColorModeId = "continuous-BlPu" - FieldColorModeIdContinuousYlBl FieldColorModeId = "continuous-YlBl" - FieldColorModeIdContinuousBlues FieldColorModeId = "continuous-blues" - FieldColorModeIdContinuousReds FieldColorModeId = "continuous-reds" - FieldColorModeIdContinuousGreens FieldColorModeId = "continuous-greens" - FieldColorModeIdContinuousPurples FieldColorModeId = "continuous-purples" - FieldColorModeIdFixed FieldColorModeId = "fixed" - FieldColorModeIdShades FieldColorModeId = "shades" -) - -// Defines how to assign a series color from "by value" color schemes. For example for an aggregated data points like a timeseries, the color can be assigned by the min, max or last value. -type FieldColorSeriesByMode string - -const ( - FieldColorSeriesByModeMin FieldColorSeriesByMode = "min" - FieldColorSeriesByModeMax FieldColorSeriesByMode = "max" - FieldColorSeriesByModeLast FieldColorSeriesByMode = "last" -) - -// Map a field to a color. -type FieldColor struct { - // The main color scheme mode. - Mode FieldColorModeId `json:"mode"` - // The fixed color value for fixed or shades color modes. - FixedColor *string `json:"fixedColor,omitempty"` - // Some visualizations need to know how to assign a series color from by value color schemes. - SeriesBy *FieldColorSeriesByMode `json:"seriesBy,omitempty"` -} - -// NewFieldColor creates a new FieldColor object. -func NewFieldColor() *FieldColor { - return &FieldColor{} -} - -// The data model used in Grafana, namely the data frame, is a columnar-oriented table structure that unifies both time series and table query results. -// Each column within this structure is called a field. A field can represent a single time series or table column. -// Field options allow you to change how the data is displayed in your visualizations. -type FieldConfig struct { - // The display value for this field. This supports template variables blank is auto - DisplayName *string `json:"displayName,omitempty"` - // This can be used by data sources that return and explicit naming structure for values and labels - // When this property is configured, this value is used rather than the default naming strategy. - DisplayNameFromDS *string `json:"displayNameFromDS,omitempty"` - // Human readable field metadata - Description *string `json:"description,omitempty"` - // An explicit path to the field in the datasource. When the frame meta includes a path, - // This will default to `${frame.meta.path}/${field.name} - // - // When defined, this value can be used as an identifier within the datasource scope, and - // may be used to update the results - Path *string `json:"path,omitempty"` - // True if data source can write a value to the path. Auth/authz are supported separately - Writeable *bool `json:"writeable,omitempty"` - // True if data source field supports ad-hoc filters - Filterable *bool `json:"filterable,omitempty"` - // Unit a field should use. The unit you select is applied to all fields except time. - // You can use the units ID availables in Grafana or a custom unit. - // Available units in Grafana: https://github.com/grafana/grafana/blob/main/packages/grafana-data/src/valueFormats/categories.ts - // As custom unit, you can use the following formats: - // `suffix:` for custom unit that should go after value. - // `prefix:` for custom unit that should go before value. - // `time:` For custom date time formats type for example `time:YYYY-MM-DD`. - // `si:` for custom SI units. For example: `si: mF`. This one is a bit more advanced as you can specify both a unit and the source data scale. So if your source data is represented as milli (thousands of) something prefix the unit with that SI scale character. - // `count:` for a custom count unit. - // `currency:` for custom a currency unit. - Unit *string `json:"unit,omitempty"` - // Specify the number of decimals Grafana includes in the rendered value. - // If you leave this field blank, Grafana automatically truncates the number of decimals based on the value. - // For example 1.1234 will display as 1.12 and 100.456 will display as 100. - // To display all decimals, set the unit to `String`. - Decimals *float64 `json:"decimals,omitempty"` - // The minimum value used in percentage threshold calculations. Leave blank for auto calculation based on all series and fields. - Min *float64 `json:"min,omitempty"` - // The maximum value used in percentage threshold calculations. Leave blank for auto calculation based on all series and fields. - Max *float64 `json:"max,omitempty"` - // Convert input values into a display string - Mappings []ValueMapping `json:"mappings,omitempty"` - // Map numeric values to states - Thresholds *ThresholdsConfig `json:"thresholds,omitempty"` - // Panel color configuration - Color *FieldColor `json:"color,omitempty"` - // The behavior when clicking on a result - Links []any `json:"links,omitempty"` - // Alternative to empty string - NoValue *string `json:"noValue,omitempty"` - // custom is specified by the FieldConfig field - // in panel plugin schemas. - Custom map[string]any `json:"custom,omitempty"` -} - -// NewFieldConfig creates a new FieldConfig object. -func NewFieldConfig() *FieldConfig { - return &FieldConfig{} -} - -type DynamicConfigValue struct { - Id string `json:"id"` - Value any `json:"value,omitempty"` -} - -// NewDynamicConfigValue creates a new DynamicConfigValue object. -func NewDynamicConfigValue() *DynamicConfigValue { - return &DynamicConfigValue{ - Id: "", - } -} - -// The data model used in Grafana, namely the data frame, is a columnar-oriented table structure that unifies both time series and table query results. -// Each column within this structure is called a field. A field can represent a single time series or table column. -// Field options allow you to change how the data is displayed in your visualizations. -type FieldConfigSource struct { - // Defaults are the options applied to all fields. - Defaults FieldConfig `json:"defaults"` - // Overrides are the options applied to specific fields overriding the defaults. - Overrides []DashboardFieldConfigSourceOverrides `json:"overrides"` -} - -// NewFieldConfigSource creates a new FieldConfigSource object. -func NewFieldConfigSource() *FieldConfigSource { - return &FieldConfigSource{ - Defaults: *NewFieldConfig(), - } +// NewTimeOption creates a new TimeOption object. +func NewTimeOption() *TimeOption { + return &TimeOption{} } // Dashboard panels are the basic visualization building blocks. @@ -568,6 +211,436 @@ func NewPanel() *Panel { } } +// Schema for panel targets is specified by datasource +// plugins. We use a placeholder definition, which the Go +// schema loader either left open/as-is with the Base +// variant of the Dashboard and Panel families, or filled +// with types derived from plugins in the Instance variant. +// When working directly from CUE, importers can extend this +// type directly to achieve the same effect. +type Target map[string]any + +// Ref to a DataSource instance +type DataSourceRef struct { + // The plugin type-id + Type *string `json:"type,omitempty"` + // Specific datasource instance + Uid *string `json:"uid,omitempty"` +} + +// NewDataSourceRef creates a new DataSourceRef object. +func NewDataSourceRef() *DataSourceRef { + return &DataSourceRef{} +} + +// Position and dimensions of a panel in the grid +type GridPos struct { + // Panel height. The height is the number of rows from the top edge of the panel. + H uint32 `json:"h"` + // Panel width. The width is the number of columns from the left edge of the panel. + W uint32 `json:"w"` + // Panel x. The x coordinate is the number of columns from the left edge of the grid + X uint32 `json:"x"` + // Panel y. The y coordinate is the number of rows from the top edge of the grid + Y uint32 `json:"y"` + // Whether the panel is fixed within the grid. If true, the panel will not be affected by other panels' interactions + Static *bool `json:"static,omitempty"` +} + +// NewGridPos creates a new GridPos object. +func NewGridPos() *GridPos { + return &GridPos{ + H: 9, + W: 12, + X: 0, + Y: 0, + } +} + +// Links with references to other dashboards or external resources +type DashboardLink struct { + // Title to display with the link + Title string `json:"title"` + // Link type. Accepted values are dashboards (to refer to another dashboard) and link (to refer to an external resource) + Type DashboardLinkType `json:"type"` + // Icon name to be displayed with the link + Icon string `json:"icon"` + // Tooltip to display when the user hovers their mouse over it + Tooltip string `json:"tooltip"` + // Link URL. Only required/valid if the type is link + Url *string `json:"url,omitempty"` + // List of tags to limit the linked dashboards. If empty, all dashboards will be displayed. Only valid if the type is dashboards + Tags []string `json:"tags"` + // If true, all dashboards links will be displayed in a dropdown. If false, all dashboards links will be displayed side by side. Only valid if the type is dashboards + AsDropdown bool `json:"asDropdown"` + // If true, the link will be opened in a new tab + TargetBlank bool `json:"targetBlank"` + // If true, includes current template variables values in the link as query params + IncludeVars bool `json:"includeVars"` + // If true, includes current time range in the link as query params + KeepTime bool `json:"keepTime"` +} + +// NewDashboardLink creates a new DashboardLink object. +func NewDashboardLink() *DashboardLink { + return &DashboardLink{ + AsDropdown: false, + TargetBlank: false, + IncludeVars: false, + KeepTime: false, + } +} + +// Dashboard Link type. Accepted values are dashboards (to refer to another dashboard) and link (to refer to an external resource) +type DashboardLinkType string + +const ( + DashboardLinkTypeLink DashboardLinkType = "link" + DashboardLinkTypeDashboards DashboardLinkType = "dashboards" +) + +// Transformations allow to manipulate data returned by a query before the system applies a visualization. +// Using transformations you can: rename fields, join time series data, perform mathematical operations across queries, +// use the output of one transformation as the input to another transformation, etc. +type DataTransformerConfig struct { + // Unique identifier of transformer + Id string `json:"id"` + // Disabled transformations are skipped + Disabled *bool `json:"disabled,omitempty"` + // Optional frame matcher. When missing it will be applied to all results + Filter *MatcherConfig `json:"filter,omitempty"` + // Where to pull DataFrames from as input to transformation + // replaced with common.DataTopic + Topic *DataTransformerConfigTopic `json:"topic,omitempty"` + // Options to be passed to the transformer + // Valid options depend on the transformer id + Options any `json:"options"` +} + +// NewDataTransformerConfig creates a new DataTransformerConfig object. +func NewDataTransformerConfig() *DataTransformerConfig { + return &DataTransformerConfig{} +} + +// Matcher is a predicate configuration. Based on the config a set of field(s) or values is filtered in order to apply override / transformation. +// It comes with in id ( to resolve implementation from registry) and a configuration that’s specific to a particular matcher type. +type MatcherConfig struct { + // The matcher id. This is used to find the matcher implementation from registry. + Id string `json:"id"` + // The matcher options. This is specific to the matcher implementation. + Options any `json:"options,omitempty"` +} + +// NewMatcherConfig creates a new MatcherConfig object. +func NewMatcherConfig() *MatcherConfig { + return &MatcherConfig{ + Id: "", + } +} + +// A library panel is a reusable panel that you can use in any dashboard. +// When you make a change to a library panel, that change propagates to all instances of where the panel is used. +// Library panels streamline reuse of panels across multiple dashboards. +type LibraryPanelRef struct { + // Library panel name + Name string `json:"name"` + // Library panel uid + Uid string `json:"uid"` +} + +// NewLibraryPanelRef creates a new LibraryPanelRef object. +func NewLibraryPanelRef() *LibraryPanelRef { + return &LibraryPanelRef{} +} + +// The data model used in Grafana, namely the data frame, is a columnar-oriented table structure that unifies both time series and table query results. +// Each column within this structure is called a field. A field can represent a single time series or table column. +// Field options allow you to change how the data is displayed in your visualizations. +type FieldConfigSource struct { + // Defaults are the options applied to all fields. + Defaults FieldConfig `json:"defaults"` + // Overrides are the options applied to specific fields overriding the defaults. + Overrides []DashboardFieldConfigSourceOverrides `json:"overrides"` +} + +// NewFieldConfigSource creates a new FieldConfigSource object. +func NewFieldConfigSource() *FieldConfigSource { + return &FieldConfigSource{ + Defaults: *NewFieldConfig(), + } +} + +// The data model used in Grafana, namely the data frame, is a columnar-oriented table structure that unifies both time series and table query results. +// Each column within this structure is called a field. A field can represent a single time series or table column. +// Field options allow you to change how the data is displayed in your visualizations. +type FieldConfig struct { + // The display value for this field. This supports template variables blank is auto + DisplayName *string `json:"displayName,omitempty"` + // This can be used by data sources that return and explicit naming structure for values and labels + // When this property is configured, this value is used rather than the default naming strategy. + DisplayNameFromDS *string `json:"displayNameFromDS,omitempty"` + // Human readable field metadata + Description *string `json:"description,omitempty"` + // An explicit path to the field in the datasource. When the frame meta includes a path, + // This will default to `${frame.meta.path}/${field.name} + // + // When defined, this value can be used as an identifier within the datasource scope, and + // may be used to update the results + Path *string `json:"path,omitempty"` + // True if data source can write a value to the path. Auth/authz are supported separately + Writeable *bool `json:"writeable,omitempty"` + // True if data source field supports ad-hoc filters + Filterable *bool `json:"filterable,omitempty"` + // Unit a field should use. The unit you select is applied to all fields except time. + // You can use the units ID availables in Grafana or a custom unit. + // Available units in Grafana: https://github.com/grafana/grafana/blob/main/packages/grafana-data/src/valueFormats/categories.ts + // As custom unit, you can use the following formats: + // `suffix:` for custom unit that should go after value. + // `prefix:` for custom unit that should go before value. + // `time:` For custom date time formats type for example `time:YYYY-MM-DD`. + // `si:` for custom SI units. For example: `si: mF`. This one is a bit more advanced as you can specify both a unit and the source data scale. So if your source data is represented as milli (thousands of) something prefix the unit with that SI scale character. + // `count:` for a custom count unit. + // `currency:` for custom a currency unit. + Unit *string `json:"unit,omitempty"` + // Specify the number of decimals Grafana includes in the rendered value. + // If you leave this field blank, Grafana automatically truncates the number of decimals based on the value. + // For example 1.1234 will display as 1.12 and 100.456 will display as 100. + // To display all decimals, set the unit to `String`. + Decimals *float64 `json:"decimals,omitempty"` + // The minimum value used in percentage threshold calculations. Leave blank for auto calculation based on all series and fields. + Min *float64 `json:"min,omitempty"` + // The maximum value used in percentage threshold calculations. Leave blank for auto calculation based on all series and fields. + Max *float64 `json:"max,omitempty"` + // Convert input values into a display string + Mappings []ValueMapping `json:"mappings,omitempty"` + // Map numeric values to states + Thresholds *ThresholdsConfig `json:"thresholds,omitempty"` + // Panel color configuration + Color *FieldColor `json:"color,omitempty"` + // The behavior when clicking on a result + Links []any `json:"links,omitempty"` + // Alternative to empty string + NoValue *string `json:"noValue,omitempty"` + // custom is specified by the FieldConfig field + // in panel plugin schemas. + Custom map[string]any `json:"custom,omitempty"` +} + +// NewFieldConfig creates a new FieldConfig object. +func NewFieldConfig() *FieldConfig { + return &FieldConfig{} +} + +// Allow to transform the visual representation of specific data values in a visualization, irrespective of their original units +type ValueMapping = ValueMapOrRangeMapOrRegexMapOrSpecialValueMap + +// NewValueMapping creates a new ValueMapping object. +func NewValueMapping() *ValueMapping { + return NewValueMapOrRangeMapOrRegexMapOrSpecialValueMap() +} + +// Maps text values to a color or different display text and color. +// For example, you can configure a value mapping so that all instances of the value 10 appear as Perfection! rather than the number. +type ValueMap struct { + Type MappingType `json:"type"` + // Map with : ValueMappingResult. For example: { "10": { text: "Perfection!", color: "green" } } + Options map[string]ValueMappingResult `json:"options"` +} + +// NewValueMap creates a new ValueMap object. +func NewValueMap() *ValueMap { + return &ValueMap{ + Type: MappingTypeValueToText, + } +} + +// Result used as replacement with text and color when the value matches +type ValueMappingResult struct { + // Text to display when the value matches + Text *string `json:"text,omitempty"` + // Text to use when the value matches + Color *string `json:"color,omitempty"` + // Icon to display when the value matches. Only specific visualizations. + Icon *string `json:"icon,omitempty"` + // Position in the mapping array. Only used internally. + Index *int32 `json:"index,omitempty"` +} + +// NewValueMappingResult creates a new ValueMappingResult object. +func NewValueMappingResult() *ValueMappingResult { + return &ValueMappingResult{} +} + +// Maps numerical ranges to a display text and color. +// For example, if a value is within a certain range, you can configure a range value mapping to display Low or High rather than the number. +type RangeMap struct { + Type MappingType `json:"type"` + // Range to match against and the result to apply when the value is within the range + Options DashboardRangeMapOptions `json:"options"` +} + +// NewRangeMap creates a new RangeMap object. +func NewRangeMap() *RangeMap { + return &RangeMap{ + Type: MappingTypeRangeToText, + Options: *NewDashboardRangeMapOptions(), + } +} + +// Maps regular expressions to replacement text and a color. +// For example, if a value is www.example.com, you can configure a regex value mapping so that Grafana displays www and truncates the domain. +type RegexMap struct { + Type MappingType `json:"type"` + // Regular expression to match against and the result to apply when the value matches the regex + Options DashboardRegexMapOptions `json:"options"` +} + +// NewRegexMap creates a new RegexMap object. +func NewRegexMap() *RegexMap { + return &RegexMap{ + Type: MappingTypeRegexToText, + Options: *NewDashboardRegexMapOptions(), + } +} + +// Maps special values like Null, NaN (not a number), and boolean values like true and false to a display text and color. +// See SpecialValueMatch to see the list of special values. +// For example, you can configure a special value mapping so that null values appear as N/A. +type SpecialValueMap struct { + Type MappingType `json:"type"` + Options DashboardSpecialValueMapOptions `json:"options"` +} + +// NewSpecialValueMap creates a new SpecialValueMap object. +func NewSpecialValueMap() *SpecialValueMap { + return &SpecialValueMap{ + Type: MappingTypeSpecialValue, + Options: *NewDashboardSpecialValueMapOptions(), + } +} + +// Special value types supported by the `SpecialValueMap` +type SpecialValueMatch string + +const ( + SpecialValueMatchTrue SpecialValueMatch = "true" + SpecialValueMatchFalse SpecialValueMatch = "false" + SpecialValueMatchNull SpecialValueMatch = "null" + SpecialValueMatchNaN SpecialValueMatch = "nan" + SpecialValueMatchNullAndNan SpecialValueMatch = "null+nan" + SpecialValueMatchEmpty SpecialValueMatch = "empty" +) + +// Thresholds configuration for the panel +type ThresholdsConfig struct { + // Thresholds mode. + Mode ThresholdsMode `json:"mode"` + // Must be sorted by 'value', first value is always -Infinity + Steps []Threshold `json:"steps"` +} + +// NewThresholdsConfig creates a new ThresholdsConfig object. +func NewThresholdsConfig() *ThresholdsConfig { + return &ThresholdsConfig{} +} + +// Thresholds can either be `absolute` (specific number) or `percentage` (relative to min or max, it will be values between 0 and 1). +type ThresholdsMode string + +const ( + ThresholdsModeAbsolute ThresholdsMode = "absolute" + ThresholdsModePercentage ThresholdsMode = "percentage" +) + +// User-defined value for a metric that triggers visual changes in a panel when this value is met or exceeded +// They are used to conditionally style and color visualizations based on query results , and can be applied to most visualizations. +type Threshold struct { + // Value represents a specified metric for the threshold, which triggers a visual change in the dashboard when this value is met or exceeded. + // Nulls currently appear here when serializing -Infinity to JSON. + Value *float64 `json:"value"` + // Color represents the color of the visual change that will occur in the dashboard when the threshold value is met or exceeded. + Color string `json:"color"` +} + +// NewThreshold creates a new Threshold object. +func NewThreshold() *Threshold { + return &Threshold{} +} + +// Map a field to a color. +type FieldColor struct { + // The main color scheme mode. + Mode FieldColorModeId `json:"mode"` + // The fixed color value for fixed or shades color modes. + FixedColor *string `json:"fixedColor,omitempty"` + // Some visualizations need to know how to assign a series color from by value color schemes. + SeriesBy *FieldColorSeriesByMode `json:"seriesBy,omitempty"` +} + +// NewFieldColor creates a new FieldColor object. +func NewFieldColor() *FieldColor { + return &FieldColor{} +} + +// Color mode for a field. You can specify a single color, or select a continuous (gradient) color schemes, based on a value. +// Continuous color interpolates a color using the percentage of a value relative to min and max. +// Accepted values are: +// `thresholds`: From thresholds. Informs Grafana to take the color from the matching threshold +// `palette-classic`: Classic palette. Grafana will assign color by looking up a color in a palette by series index. Useful for Graphs and pie charts and other categorical data visualizations +// `palette-classic-by-name`: Classic palette (by name). Grafana will assign color by looking up a color in a palette by series name. Useful for Graphs and pie charts and other categorical data visualizations +// `continuous-GrYlRd`: ontinuous Green-Yellow-Red palette mode +// `continuous-RdYlGr`: Continuous Red-Yellow-Green palette mode +// `continuous-BlYlRd`: Continuous Blue-Yellow-Red palette mode +// `continuous-YlRd`: Continuous Yellow-Red palette mode +// `continuous-BlPu`: Continuous Blue-Purple palette mode +// `continuous-YlBl`: Continuous Yellow-Blue palette mode +// `continuous-blues`: Continuous Blue palette mode +// `continuous-reds`: Continuous Red palette mode +// `continuous-greens`: Continuous Green palette mode +// `continuous-purples`: Continuous Purple palette mode +// `shades`: Shades of a single color. Specify a single color, useful in an override rule. +// `fixed`: Fixed color mode. Specify a single color, useful in an override rule. +type FieldColorModeId string + +const ( + FieldColorModeIdThresholds FieldColorModeId = "thresholds" + FieldColorModeIdPaletteClassic FieldColorModeId = "palette-classic" + FieldColorModeIdPaletteClassicByName FieldColorModeId = "palette-classic-by-name" + FieldColorModeIdContinuousGrYlRd FieldColorModeId = "continuous-GrYlRd" + FieldColorModeIdContinuousRdYlGr FieldColorModeId = "continuous-RdYlGr" + FieldColorModeIdContinuousBlYlRd FieldColorModeId = "continuous-BlYlRd" + FieldColorModeIdContinuousYlRd FieldColorModeId = "continuous-YlRd" + FieldColorModeIdContinuousBlPu FieldColorModeId = "continuous-BlPu" + FieldColorModeIdContinuousYlBl FieldColorModeId = "continuous-YlBl" + FieldColorModeIdContinuousBlues FieldColorModeId = "continuous-blues" + FieldColorModeIdContinuousReds FieldColorModeId = "continuous-reds" + FieldColorModeIdContinuousGreens FieldColorModeId = "continuous-greens" + FieldColorModeIdContinuousPurples FieldColorModeId = "continuous-purples" + FieldColorModeIdFixed FieldColorModeId = "fixed" + FieldColorModeIdShades FieldColorModeId = "shades" +) + +// Defines how to assign a series color from "by value" color schemes. For example for an aggregated data points like a timeseries, the color can be assigned by the min, max or last value. +type FieldColorSeriesByMode string + +const ( + FieldColorSeriesByModeMin FieldColorSeriesByMode = "min" + FieldColorSeriesByModeMax FieldColorSeriesByMode = "max" + FieldColorSeriesByModeLast FieldColorSeriesByMode = "last" +) + +type DynamicConfigValue struct { + Id string `json:"id"` + Value any `json:"value,omitempty"` +} + +// NewDynamicConfigValue creates a new DynamicConfigValue object. +func NewDynamicConfigValue() *DynamicConfigValue { + return &DynamicConfigValue{ + Id: "", + } +} + // Row panel type RowPanel struct { // The panel type @@ -596,6 +669,55 @@ func NewRowPanel() *RowPanel { } } +// A variable is a placeholder for a value. You can use variables in metric queries and in panel titles. +type VariableModel struct { + // Type of variable + Type VariableType `json:"type"` + // Name of variable + Name string `json:"name"` + // Optional display name + Label *string `json:"label,omitempty"` + // Visibility configuration for the variable + Hide *VariableHide `json:"hide,omitempty"` + // Whether the variable value should be managed by URL query params or not + SkipUrlSync *bool `json:"skipUrlSync,omitempty"` + // Description of variable. It can be defined but `null`. + Description *string `json:"description,omitempty"` + // Query used to fetch values for a variable + Query *StringOrMap `json:"query,omitempty"` + // Data source used to fetch values for a variable. It can be defined but `null`. + Datasource *DataSourceRef `json:"datasource,omitempty"` + // Shows current selected variable text/value on the dashboard + Current *VariableOption `json:"current,omitempty"` + // Whether multiple values can be selected or not from variable value list + Multi *bool `json:"multi,omitempty"` + // Allow custom values to be entered in the variable + AllowCustomValue *bool `json:"allowCustomValue,omitempty"` + // Options that can be selected for a variable. + Options []VariableOption `json:"options,omitempty"` + // Options to config when to refresh a variable + Refresh *VariableRefresh `json:"refresh,omitempty"` + // Options sort order + Sort *VariableSort `json:"sort,omitempty"` + // Whether all value option is available or not + IncludeAll *bool `json:"includeAll,omitempty"` + // Custom all value + AllValue *string `json:"allValue,omitempty"` + // Optional field, if you want to extract part of a series name or metric node segment. + // Named capture groups can be used to separate the display text and value. + Regex *string `json:"regex,omitempty"` +} + +// NewVariableModel creates a new VariableModel object. +func NewVariableModel() *VariableModel { + return &VariableModel{ + SkipUrlSync: (func(input bool) *bool { return &input })(false), + Multi: (func(input bool) *bool { return &input })(false), + AllowCustomValue: (func(input bool) *bool { return &input })(true), + IncludeAll: (func(input bool) *bool { return &input })(false), + } +} + // Dashboard variable type // `query`: Query-generated list of values such as metric names, server names, sensor IDs, data centers, and so on. // `adhoc`: Key/value filters that are automatically added to all metric queries for a data source (Prometheus, Loki, InfluxDB, and Elasticsearch only). @@ -685,52 +807,51 @@ const ( VariableSortNaturalDesc VariableSort = 8 ) -// A variable is a placeholder for a value. You can use variables in metric queries and in panel titles. -type VariableModel struct { - // Type of variable - Type VariableType `json:"type"` - // Name of variable - Name string `json:"name"` - // Optional display name - Label *string `json:"label,omitempty"` - // Visibility configuration for the variable - Hide *VariableHide `json:"hide,omitempty"` - // Whether the variable value should be managed by URL query params or not - SkipUrlSync *bool `json:"skipUrlSync,omitempty"` - // Description of variable. It can be defined but `null`. - Description *string `json:"description,omitempty"` - // Query used to fetch values for a variable - Query *StringOrMap `json:"query,omitempty"` - // Data source used to fetch values for a variable. It can be defined but `null`. - Datasource *DataSourceRef `json:"datasource,omitempty"` - // Shows current selected variable text/value on the dashboard - Current *VariableOption `json:"current,omitempty"` - // Whether multiple values can be selected or not from variable value list - Multi *bool `json:"multi,omitempty"` - // Allow custom values to be entered in the variable - AllowCustomValue *bool `json:"allowCustomValue,omitempty"` - // Options that can be selected for a variable. - Options []VariableOption `json:"options,omitempty"` - // Options to config when to refresh a variable - Refresh *VariableRefresh `json:"refresh,omitempty"` - // Options sort order - Sort *VariableSort `json:"sort,omitempty"` - // Whether all value option is available or not - IncludeAll *bool `json:"includeAll,omitempty"` - // Custom all value - AllValue *string `json:"allValue,omitempty"` - // Optional field, if you want to extract part of a series name or metric node segment. - // Named capture groups can be used to separate the display text and value. - Regex *string `json:"regex,omitempty"` +// Contains the list of annotations that are associated with the dashboard. +// Annotations are used to overlay event markers and overlay event tags on graphs. +// Grafana comes with a native annotation store and the ability to add annotation events directly from the graph panel or via the HTTP API. +// See https://grafana.com/docs/grafana/latest/dashboards/build-dashboards/annotate-visualizations/ +type AnnotationContainer struct { + // List of annotations + List []AnnotationQuery `json:"list,omitempty"` } -// NewVariableModel creates a new VariableModel object. -func NewVariableModel() *VariableModel { - return &VariableModel{ - SkipUrlSync: (func(input bool) *bool { return &input })(false), - Multi: (func(input bool) *bool { return &input })(false), - AllowCustomValue: (func(input bool) *bool { return &input })(true), - IncludeAll: (func(input bool) *bool { return &input })(false), +// NewAnnotationContainer creates a new AnnotationContainer object. +func NewAnnotationContainer() *AnnotationContainer { + return &AnnotationContainer{} +} + +// TODO docs +// FROM: AnnotationQuery in grafana-data/src/types/annotations.ts +type AnnotationQuery struct { + // Name of annotation. + Name string `json:"name"` + // Datasource where the annotations data is + Datasource DataSourceRef `json:"datasource"` + // When enabled the annotation query is issued with every dashboard refresh + Enable bool `json:"enable"` + // Annotation queries can be toggled on or off at the top of the dashboard. + // When hide is true, the toggle is not shown in the dashboard. + Hide *bool `json:"hide,omitempty"` + // Color to use for the annotation event markers + IconColor string `json:"iconColor"` + // Filters to apply when fetching annotations + Filter *AnnotationPanelFilter `json:"filter,omitempty"` + // TODO.. this should just be a normal query target + Target *AnnotationTarget `json:"target,omitempty"` + // TODO -- this should not exist here, it is based on the --grafana-- datasource + Type *string `json:"type,omitempty"` + // Set to 1 for the standard annotation query all dashboards have by default. + BuiltIn *float64 `json:"builtIn,omitempty"` +} + +// NewAnnotationQuery creates a new AnnotationQuery object. +func NewAnnotationQuery() *AnnotationQuery { + return &AnnotationQuery{ + Datasource: *NewDataSourceRef(), + Enable: true, + Hide: (func(input bool) *bool { return &input })(false), + BuiltIn: (func(input float64) *float64 { return &input })(0), } } @@ -770,54 +891,6 @@ func NewAnnotationTarget() *AnnotationTarget { return &AnnotationTarget{} } -// TODO docs -// FROM: AnnotationQuery in grafana-data/src/types/annotations.ts -type AnnotationQuery struct { - // Name of annotation. - Name string `json:"name"` - // Datasource where the annotations data is - Datasource DataSourceRef `json:"datasource"` - // When enabled the annotation query is issued with every dashboard refresh - Enable bool `json:"enable"` - // Annotation queries can be toggled on or off at the top of the dashboard. - // When hide is true, the toggle is not shown in the dashboard. - Hide *bool `json:"hide,omitempty"` - // Color to use for the annotation event markers - IconColor string `json:"iconColor"` - // Filters to apply when fetching annotations - Filter *AnnotationPanelFilter `json:"filter,omitempty"` - // TODO.. this should just be a normal query target - Target *AnnotationTarget `json:"target,omitempty"` - // TODO -- this should not exist here, it is based on the --grafana-- datasource - Type *string `json:"type,omitempty"` - // Set to 1 for the standard annotation query all dashboards have by default. - BuiltIn *float64 `json:"builtIn,omitempty"` -} - -// NewAnnotationQuery creates a new AnnotationQuery object. -func NewAnnotationQuery() *AnnotationQuery { - return &AnnotationQuery{ - Datasource: *NewDataSourceRef(), - Enable: true, - Hide: (func(input bool) *bool { return &input })(false), - BuiltIn: (func(input float64) *float64 { return &input })(0), - } -} - -// Contains the list of annotations that are associated with the dashboard. -// Annotations are used to overlay event markers and overlay event tags on graphs. -// Grafana comes with a native annotation store and the ability to add annotation events directly from the graph panel or via the HTTP API. -// See https://grafana.com/docs/grafana/latest/dashboards/build-dashboards/annotate-visualizations/ -type AnnotationContainer struct { - // List of annotations - List []AnnotationQuery `json:"list,omitempty"` -} - -// NewAnnotationContainer creates a new AnnotationContainer object. -func NewAnnotationContainer() *AnnotationContainer { - return &AnnotationContainer{} -} - // A dashboard snapshot shares an interactive dashboard publicly. // It is a read-only version of a dashboard, and is not editable. // It is possible to create a snapshot of a snapshot. @@ -855,93 +928,54 @@ func NewSnapshot() *Snapshot { return &Snapshot{} } -type Spec struct { - // Unique numeric identifier for the dashboard. - // `id` is internal to a specific Grafana instance. `uid` should be used to identify a dashboard across Grafana instances. - // TODO eliminate this null option - Id *int64 `json:"id,omitempty"` - // Unique dashboard identifier that can be generated by anyone. string (8-40) - Uid *string `json:"uid,omitempty"` - // Title of dashboard. - Title *string `json:"title,omitempty"` - // Description of dashboard. - Description *string `json:"description,omitempty"` - // This property should only be used in dashboards defined by plugins. It is a quick check - // to see if the version has changed since the last time. - Revision *int64 `json:"revision,omitempty"` - // ID of a dashboard imported from the https://grafana.com/grafana/dashboards/ portal - GnetId *string `json:"gnetId,omitempty"` - // Tags associated with dashboard. - Tags []string `json:"tags,omitempty"` - // Timezone of dashboard. Accepted values are IANA TZDB zone ID or "browser" or "utc". - Timezone *string `json:"timezone,omitempty"` - // Whether a dashboard is editable or not. - Editable *bool `json:"editable,omitempty"` - // Configuration of dashboard cursor sync behavior. - // Accepted values are 0 (sync turned off), 1 (shared crosshair), 2 (shared crosshair and tooltip). - GraphTooltip *DashboardCursorSync `json:"graphTooltip,omitempty"` - // Time range for dashboard. - // Accepted values are relative time strings like {from: 'now-6h', to: 'now'} or absolute time strings like {from: '2020-07-10T08:00:00.000Z', to: '2020-07-10T14:00:00.000Z'}. - Time *DashboardSpecTime `json:"time,omitempty"` - // Configuration of the time picker shown at the top of a dashboard. - Timepicker *TimePickerConfig `json:"timepicker,omitempty"` - // The month that the fiscal year starts on. 0 = January, 11 = December - FiscalYearStartMonth *uint8 `json:"fiscalYearStartMonth,omitempty"` - // When set to true, the dashboard will redraw panels at an interval matching the pixel width. - // This will keep data "moving left" regardless of the query refresh rate. This setting helps - // avoid dashboards presenting stale live data - LiveNow *bool `json:"liveNow,omitempty"` - // Day when the week starts. Expressed by the name of the day in lowercase, e.g. "monday". - WeekStart *string `json:"weekStart,omitempty"` - // Refresh rate of dashboard. Represented via interval string, e.g. "5s", "1m", "1h", "1d". - Refresh *string `json:"refresh,omitempty"` - // Version of the JSON schema, incremented each time a Grafana update brings - // changes to said schema. - SchemaVersion uint16 `json:"schemaVersion"` - // Version of the dashboard, incremented each time the dashboard is updated. - Version *uint32 `json:"version,omitempty"` - // List of dashboard panels - Panels []any `json:"panels,omitempty"` - // Configured template variables - Templating *DashboardSpecTemplating `json:"templating,omitempty"` - // Contains the list of annotations that are associated with the dashboard. - // Annotations are used to overlay event markers and overlay event tags on graphs. - // Grafana comes with a native annotation store and the ability to add annotation events directly from the graph panel or via the HTTP API. - // See https://grafana.com/docs/grafana/latest/dashboards/build-dashboards/annotate-visualizations/ - Annotations *AnnotationContainer `json:"annotations,omitempty"` - // Links with references to other dashboards or external websites. - Links []DashboardLink `json:"links,omitempty"` - // Snapshot options. They are present only if the dashboard is a snapshot. - Snapshot *Snapshot `json:"snapshot,omitempty"` - // When set to true, the dashboard will load all panels in the dashboard when it's loaded. - Preload *bool `json:"preload,omitempty"` +// Supported value mapping types +// `value`: Maps text values to a color or different display text and color. For example, you can configure a value mapping so that all instances of the value 10 appear as Perfection! rather than the number. +// `range`: Maps numerical ranges to a display text and color. For example, if a value is within a certain range, you can configure a range value mapping to display Low or High rather than the number. +// `regex`: Maps regular expressions to replacement text and a color. For example, if a value is www.example.com, you can configure a regex value mapping so that Grafana displays www and truncates the domain. +// `special`: Maps special values like Null, NaN (not a number), and boolean values like true and false to a display text and color. See SpecialValueMatch to see the list of special values. For example, you can configure a special value mapping so that null values appear as N/A. +type MappingType string + +const ( + MappingTypeValueToText MappingType = "value" + MappingTypeRangeToText MappingType = "range" + MappingTypeRegexToText MappingType = "regex" + MappingTypeSpecialValue MappingType = "special" +) + +type DashboardSpecTime struct { + From string `json:"from"` + To string `json:"to"` } -// NewSpec creates a new Spec object. -func NewSpec() *Spec { - return &Spec{ - Timezone: (func(input string) *string { return &input })("browser"), - Editable: (func(input bool) *bool { return &input })(true), - GraphTooltip: (func(input DashboardCursorSync) *DashboardCursorSync { return &input })(DashboardCursorSyncOff), - FiscalYearStartMonth: (func(input uint8) *uint8 { return &input })(0), - SchemaVersion: 41, +// NewDashboardSpecTime creates a new DashboardSpecTime object. +func NewDashboardSpecTime() *DashboardSpecTime { + return &DashboardSpecTime{ + From: "now-6h", + To: "now", } } -type DataTransformerConfigTopic string +type DashboardSpecTemplating struct { + // List of configured template variables with their saved values along with some other metadata + List []VariableModel `json:"list,omitempty"` +} -const ( - DataTransformerConfigTopicSeries DataTransformerConfigTopic = "series" - DataTransformerConfigTopicAnnotations DataTransformerConfigTopic = "annotations" - DataTransformerConfigTopicAlertStates DataTransformerConfigTopic = "alertStates" -) +// NewDashboardSpecTemplating creates a new DashboardSpecTemplating object. +func NewDashboardSpecTemplating() *DashboardSpecTemplating { + return &DashboardSpecTemplating{} +} -type PanelRepeatDirection string +type DashboardFieldConfigSourceOverrides struct { + Matcher MatcherConfig `json:"matcher"` + Properties []DynamicConfigValue `json:"properties"` +} -const ( - PanelRepeatDirectionH PanelRepeatDirection = "h" - PanelRepeatDirectionV PanelRepeatDirection = "v" -) +// NewDashboardFieldConfigSourceOverrides creates a new DashboardFieldConfigSourceOverrides object. +func NewDashboardFieldConfigSourceOverrides() *DashboardFieldConfigSourceOverrides { + return &DashboardFieldConfigSourceOverrides{ + Matcher: *NewMatcherConfig(), + } +} type DashboardRangeMapOptions struct { // Min value of the range. It can be null which means -Infinity @@ -987,40 +1021,20 @@ func NewDashboardSpecialValueMapOptions() *DashboardSpecialValueMapOptions { } } -type DashboardFieldConfigSourceOverrides struct { - Matcher MatcherConfig `json:"matcher"` - Properties []DynamicConfigValue `json:"properties"` -} +type PanelRepeatDirection string -// NewDashboardFieldConfigSourceOverrides creates a new DashboardFieldConfigSourceOverrides object. -func NewDashboardFieldConfigSourceOverrides() *DashboardFieldConfigSourceOverrides { - return &DashboardFieldConfigSourceOverrides{ - Matcher: *NewMatcherConfig(), - } -} +const ( + PanelRepeatDirectionH PanelRepeatDirection = "h" + PanelRepeatDirectionV PanelRepeatDirection = "v" +) -type DashboardSpecTime struct { - From string `json:"from"` - To string `json:"to"` -} +type DataTransformerConfigTopic string -// NewDashboardSpecTime creates a new DashboardSpecTime object. -func NewDashboardSpecTime() *DashboardSpecTime { - return &DashboardSpecTime{ - From: "now-6h", - To: "now", - } -} - -type DashboardSpecTemplating struct { - // List of configured template variables with their saved values along with some other metadata - List []VariableModel `json:"list,omitempty"` -} - -// NewDashboardSpecTemplating creates a new DashboardSpecTemplating object. -func NewDashboardSpecTemplating() *DashboardSpecTemplating { - return &DashboardSpecTemplating{} -} +const ( + DataTransformerConfigTopicSeries DataTransformerConfigTopic = "series" + DataTransformerConfigTopicAnnotations DataTransformerConfigTopic = "annotations" + DataTransformerConfigTopicAlertStates DataTransformerConfigTopic = "alertStates" +) type ValueMapOrRangeMapOrRegexMapOrSpecialValueMap struct { ValueMap *ValueMap `json:"ValueMap,omitempty"` @@ -1107,60 +1121,6 @@ func (resource *ValueMapOrRangeMapOrRegexMapOrSpecialValueMap) UnmarshalJSON(raw return fmt.Errorf("could not unmarshal resource with `type = %v`", discriminator) } -type StringOrArrayOfString struct { - String *string `json:"String,omitempty"` - ArrayOfString []string `json:"ArrayOfString,omitempty"` -} - -// NewStringOrArrayOfString creates a new StringOrArrayOfString object. -func NewStringOrArrayOfString() *StringOrArrayOfString { - return &StringOrArrayOfString{} -} - -// MarshalJSON implements a custom JSON marshalling logic to encode `StringOrArrayOfString` as JSON. -func (resource StringOrArrayOfString) MarshalJSON() ([]byte, error) { - if resource.String != nil { - return json.Marshal(resource.String) - } - - if resource.ArrayOfString != nil { - return json.Marshal(resource.ArrayOfString) - } - - return nil, fmt.Errorf("no value for disjunction of scalars") -} - -// UnmarshalJSON implements a custom JSON unmarshalling logic to decode `StringOrArrayOfString` from JSON. -func (resource *StringOrArrayOfString) UnmarshalJSON(raw []byte) error { - if raw == nil { - return nil - } - - var errList []error - - // String - var String string - if err := json.Unmarshal(raw, &String); err != nil { - errList = append(errList, err) - resource.String = nil - } else { - resource.String = &String - return nil - } - - // ArrayOfString - var ArrayOfString []string - if err := json.Unmarshal(raw, &ArrayOfString); err != nil { - errList = append(errList, err) - resource.ArrayOfString = nil - } else { - resource.ArrayOfString = ArrayOfString - return nil - } - - return errors.Join(errList...) -} - type StringOrMap struct { String *string `json:"String,omitempty"` Map map[string]any `json:"Map,omitempty"` @@ -1214,3 +1174,57 @@ func (resource *StringOrMap) UnmarshalJSON(raw []byte) error { return errors.Join(errList...) } + +type StringOrArrayOfString struct { + String *string `json:"String,omitempty"` + ArrayOfString []string `json:"ArrayOfString,omitempty"` +} + +// NewStringOrArrayOfString creates a new StringOrArrayOfString object. +func NewStringOrArrayOfString() *StringOrArrayOfString { + return &StringOrArrayOfString{} +} + +// MarshalJSON implements a custom JSON marshalling logic to encode `StringOrArrayOfString` as JSON. +func (resource StringOrArrayOfString) MarshalJSON() ([]byte, error) { + if resource.String != nil { + return json.Marshal(resource.String) + } + + if resource.ArrayOfString != nil { + return json.Marshal(resource.ArrayOfString) + } + + return nil, fmt.Errorf("no value for disjunction of scalars") +} + +// UnmarshalJSON implements a custom JSON unmarshalling logic to decode `StringOrArrayOfString` from JSON. +func (resource *StringOrArrayOfString) UnmarshalJSON(raw []byte) error { + if raw == nil { + return nil + } + + var errList []error + + // String + var String string + if err := json.Unmarshal(raw, &String); err != nil { + errList = append(errList, err) + resource.String = nil + } else { + resource.String = &String + return nil + } + + // ArrayOfString + var ArrayOfString []string + if err := json.Unmarshal(raw, &ArrayOfString); err != nil { + errList = append(errList, err) + resource.ArrayOfString = nil + } else { + resource.ArrayOfString = ArrayOfString + return nil + } + + return errors.Join(errList...) +} diff --git a/pkg/kinds/librarypanel/librarypanel_spec_gen.go b/pkg/kinds/librarypanel/librarypanel_spec_gen.go index 177d6c26c41..efc167da68e 100644 --- a/pkg/kinds/librarypanel/librarypanel_spec_gen.go +++ b/pkg/kinds/librarypanel/librarypanel_spec_gen.go @@ -15,35 +15,6 @@ import ( time "time" ) -type LibraryElementDTOMetaUser struct { - Id int64 `json:"id"` - Name string `json:"name"` - AvatarUrl string `json:"avatarUrl"` -} - -// NewLibraryElementDTOMetaUser creates a new LibraryElementDTOMetaUser object. -func NewLibraryElementDTOMetaUser() *LibraryElementDTOMetaUser { - return &LibraryElementDTOMetaUser{} -} - -type LibraryElementDTOMeta struct { - FolderName string `json:"folderName"` - FolderUid string `json:"folderUid"` - ConnectedDashboards int64 `json:"connectedDashboards"` - Created time.Time `json:"created"` - Updated time.Time `json:"updated"` - CreatedBy LibraryElementDTOMetaUser `json:"createdBy"` - UpdatedBy LibraryElementDTOMetaUser `json:"updatedBy"` -} - -// NewLibraryElementDTOMeta creates a new LibraryElementDTOMeta object. -func NewLibraryElementDTOMeta() *LibraryElementDTOMeta { - return &LibraryElementDTOMeta{ - CreatedBy: *NewLibraryElementDTOMetaUser(), - UpdatedBy: *NewLibraryElementDTOMetaUser(), - } -} - type Spec struct { // Folder UID FolderUid *string `json:"folderUid,omitempty"` @@ -70,3 +41,32 @@ type Spec struct { func NewSpec() *Spec { return &Spec{} } + +type LibraryElementDTOMeta struct { + FolderName string `json:"folderName"` + FolderUid string `json:"folderUid"` + ConnectedDashboards int64 `json:"connectedDashboards"` + Created time.Time `json:"created"` + Updated time.Time `json:"updated"` + CreatedBy LibraryElementDTOMetaUser `json:"createdBy"` + UpdatedBy LibraryElementDTOMetaUser `json:"updatedBy"` +} + +// NewLibraryElementDTOMeta creates a new LibraryElementDTOMeta object. +func NewLibraryElementDTOMeta() *LibraryElementDTOMeta { + return &LibraryElementDTOMeta{ + CreatedBy: *NewLibraryElementDTOMetaUser(), + UpdatedBy: *NewLibraryElementDTOMetaUser(), + } +} + +type LibraryElementDTOMetaUser struct { + Id int64 `json:"id"` + Name string `json:"name"` + AvatarUrl string `json:"avatarUrl"` +} + +// NewLibraryElementDTOMetaUser creates a new LibraryElementDTOMetaUser object. +func NewLibraryElementDTOMetaUser() *LibraryElementDTOMetaUser { + return &LibraryElementDTOMetaUser{} +} diff --git a/pkg/kinds/preferences/preferences_spec_gen.go b/pkg/kinds/preferences/preferences_spec_gen.go index 431d2f9e9a0..6e02a1a9eb6 100644 --- a/pkg/kinds/preferences/preferences_spec_gen.go +++ b/pkg/kinds/preferences/preferences_spec_gen.go @@ -11,6 +11,33 @@ package preferences +// Spec defines user, team or org Grafana preferences +// swagger:model Preferences +type Spec struct { + // UID for the home dashboard + HomeDashboardUID *string `json:"homeDashboardUID,omitempty"` + // The timezone selection + // TODO: this should use the timezone defined in common + Timezone *string `json:"timezone,omitempty"` + // day of the week (sunday, monday, etc) + WeekStart *string `json:"weekStart,omitempty"` + // light, dark, empty is default + Theme *string `json:"theme,omitempty"` + // Selected language (beta) + Language *string `json:"language,omitempty"` + // Explore query history preferences + QueryHistory *QueryHistoryPreference `json:"queryHistory,omitempty"` + // Cookie preferences + CookiePreferences *CookiePreferences `json:"cookiePreferences,omitempty"` + // Navigation preferences + Navbar *NavbarPreference `json:"navbar,omitempty"` +} + +// NewSpec creates a new Spec object. +func NewSpec() *Spec { + return &Spec{} +} + type QueryHistoryPreference struct { // one of: '' | 'query' | 'starred'; HomeTab *string `json:"homeTab,omitempty"` @@ -40,30 +67,3 @@ type NavbarPreference struct { func NewNavbarPreference() *NavbarPreference { return &NavbarPreference{} } - -// Spec defines user, team or org Grafana preferences -// swagger:model Preferences -type Spec struct { - // UID for the home dashboard - HomeDashboardUID *string `json:"homeDashboardUID,omitempty"` - // The timezone selection - // TODO: this should use the timezone defined in common - Timezone *string `json:"timezone,omitempty"` - // day of the week (sunday, monday, etc) - WeekStart *string `json:"weekStart,omitempty"` - // light, dark, empty is default - Theme *string `json:"theme,omitempty"` - // Selected language (beta) - Language *string `json:"language,omitempty"` - // Explore query history preferences - QueryHistory *QueryHistoryPreference `json:"queryHistory,omitempty"` - // Cookie preferences - CookiePreferences *CookiePreferences `json:"cookiePreferences,omitempty"` - // Navigation preferences - Navbar *NavbarPreference `json:"navbar,omitempty"` -} - -// NewSpec creates a new Spec object. -func NewSpec() *Spec { - return &Spec{} -} diff --git a/pkg/plugins/codegen/go.mod b/pkg/plugins/codegen/go.mod index b376cf0a489..619bbc470dc 100644 --- a/pkg/plugins/codegen/go.mod +++ b/pkg/plugins/codegen/go.mod @@ -7,7 +7,7 @@ replace github.com/grafana/grafana/pkg/codegen => ../../codegen require ( cuelang.org/go v0.11.1 github.com/grafana/codejen v0.0.4-0.20230321061741-77f656893a3d - github.com/grafana/cog v0.0.18 + github.com/grafana/cog v0.0.27 github.com/grafana/cuetsy v0.1.11 github.com/grafana/grafana/pkg/codegen v0.0.0-00010101000000-000000000000 ) @@ -42,11 +42,11 @@ require ( github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 // indirect github.com/xlab/treeprint v1.2.0 // indirect github.com/yalue/merged_fs v1.3.0 // indirect - golang.org/x/mod v0.22.0 // indirect + golang.org/x/mod v0.23.0 // indirect golang.org/x/net v0.36.0 // indirect golang.org/x/oauth2 v0.24.0 // indirect golang.org/x/sync v0.11.0 // indirect golang.org/x/text v0.22.0 // indirect - golang.org/x/tools v0.29.0 // indirect + golang.org/x/tools v0.30.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/pkg/plugins/codegen/go.sum b/pkg/plugins/codegen/go.sum index d143048952d..159e028e5ae 100644 --- a/pkg/plugins/codegen/go.sum +++ b/pkg/plugins/codegen/go.sum @@ -30,8 +30,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grafana/codejen v0.0.4-0.20230321061741-77f656893a3d h1:hrXbGJ5jgp6yNITzs5o+zXq0V5yT3siNJ+uM8LGwWKk= github.com/grafana/codejen v0.0.4-0.20230321061741-77f656893a3d/go.mod h1:zmwwM/DRyQB7pfuBjTWII3CWtxcXh8LTwAYGfDfpR6s= -github.com/grafana/cog v0.0.18 h1:pEmzo/yhIFZMHM58ua0M9Eb5frJj6CgTrTTUVlY8e2o= -github.com/grafana/cog v0.0.18/go.mod h1:jrS9indvWuDs60RHEZpLaAkmZdgyoLKMOEUT0jiB1t0= +github.com/grafana/cog v0.0.27 h1:ZKipAtp6KuB08R16nZbqEjnje3e2r1O1bzOp1CetDEo= +github.com/grafana/cog v0.0.27/go.mod h1:JB5lhdn4Hqc0ztYCaNOTKZXoojzJvydBxMkMCGWS6+Q= github.com/grafana/cuetsy v0.1.11 h1:I3IwBhF+UaQxRM79HnImtrAn8REGdb5M3+C4QrYHoWk= github.com/grafana/cuetsy v0.1.11/go.mod h1:Ix97+CPD8ws9oSSxR3/Lf4ahU1I4Np83kjJmDVnLZvc= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -92,8 +92,8 @@ github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= github.com/yalue/merged_fs v1.3.0 h1:qCeh9tMPNy/i8cwDsQTJ5bLr6IRxbs6meakNE5O+wyY= github.com/yalue/merged_fs v1.3.0/go.mod h1:WqqchfVYQyclV2tnR7wtRhBddzBvLVR83Cjw9BKQw0M= -golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= -golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM= +golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA= golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I= golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE= @@ -104,8 +104,8 @@ golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= -golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE= -golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588= +golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY= +golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/pkg/promlib/go.mod b/pkg/promlib/go.mod index 57428aed803..12ccc4c4040 100644 --- a/pkg/promlib/go.mod +++ b/pkg/promlib/go.mod @@ -108,12 +108,12 @@ require ( go.opentelemetry.io/proto/otlp v1.5.0 // indirect go.uber.org/atomic v1.11.0 // indirect golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect - golang.org/x/mod v0.22.0 // indirect + golang.org/x/mod v0.23.0 // indirect golang.org/x/net v0.36.0 // indirect golang.org/x/sync v0.11.0 // indirect golang.org/x/sys v0.30.0 // indirect golang.org/x/text v0.22.0 // indirect - golang.org/x/tools v0.29.0 // indirect + golang.org/x/tools v0.30.0 // indirect golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect google.golang.org/api v0.220.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250204164813-702378808489 // indirect diff --git a/pkg/promlib/go.sum b/pkg/promlib/go.sum index 36f1091f0ba..1135d5e1404 100644 --- a/pkg/promlib/go.sum +++ b/pkg/promlib/go.sum @@ -336,8 +336,8 @@ golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWB golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= -golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM= +golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -377,8 +377,8 @@ golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE= -golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588= +golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY= +golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/pkg/storage/unified/apistore/go.mod b/pkg/storage/unified/apistore/go.mod index 9765e457c45..81eebd46fcc 100644 --- a/pkg/storage/unified/apistore/go.mod +++ b/pkg/storage/unified/apistore/go.mod @@ -385,7 +385,7 @@ require ( go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect golang.org/x/crypto v0.35.0 // indirect - golang.org/x/mod v0.22.0 // indirect + golang.org/x/mod v0.23.0 // indirect golang.org/x/net v0.36.0 // indirect golang.org/x/oauth2 v0.27.0 // indirect golang.org/x/sync v0.11.0 // indirect @@ -393,7 +393,7 @@ require ( golang.org/x/term v0.29.0 // indirect golang.org/x/text v0.22.0 // indirect golang.org/x/time v0.9.0 // indirect - golang.org/x/tools v0.29.0 // indirect + golang.org/x/tools v0.30.0 // indirect golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect gonum.org/v1/gonum v0.15.1 // indirect google.golang.org/api v0.220.0 // indirect diff --git a/pkg/storage/unified/apistore/go.sum b/pkg/storage/unified/apistore/go.sum index 1854eebc3fa..636fc8cd18e 100644 --- a/pkg/storage/unified/apistore/go.sum +++ b/pkg/storage/unified/apistore/go.sum @@ -1981,8 +1981,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= -golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM= +golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -2327,8 +2327,8 @@ golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= -golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE= -golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588= +golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY= +golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/pkg/storage/unified/resource/go.mod b/pkg/storage/unified/resource/go.mod index 5d3a0c23caf..0bb4e887241 100644 --- a/pkg/storage/unified/resource/go.mod +++ b/pkg/storage/unified/resource/go.mod @@ -239,14 +239,14 @@ require ( go.uber.org/atomic v1.11.0 // indirect golang.org/x/crypto v0.35.0 // indirect golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect - golang.org/x/mod v0.22.0 // indirect + golang.org/x/mod v0.23.0 // indirect golang.org/x/net v0.36.0 // indirect golang.org/x/oauth2 v0.27.0 // indirect golang.org/x/sys v0.30.0 // indirect golang.org/x/term v0.29.0 // indirect golang.org/x/text v0.22.0 // indirect golang.org/x/time v0.9.0 // indirect - golang.org/x/tools v0.29.0 // indirect + golang.org/x/tools v0.30.0 // indirect golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect google.golang.org/api v0.220.0 // indirect google.golang.org/genproto v0.0.0-20250122153221-138b5a5a4fd4 // indirect diff --git a/pkg/storage/unified/resource/go.sum b/pkg/storage/unified/resource/go.sum index 82c6ae452bd..0ba9d1ae931 100644 --- a/pkg/storage/unified/resource/go.sum +++ b/pkg/storage/unified/resource/go.sum @@ -1712,8 +1712,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= -golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM= +golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -2045,8 +2045,8 @@ golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= -golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE= -golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588= +golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY= +golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/pkg/tsdb/azuremonitor/kinds/dataquery/types_dataquery_gen.go b/pkg/tsdb/azuremonitor/kinds/dataquery/types_dataquery_gen.go index a51aef464a5..8be5bf520bf 100644 --- a/pkg/tsdb/azuremonitor/kinds/dataquery/types_dataquery_gen.go +++ b/pkg/tsdb/azuremonitor/kinds/dataquery/types_dataquery_gen.go @@ -66,27 +66,6 @@ func NewAzureMonitorQuery() *AzureMonitorQuery { return &AzureMonitorQuery{} } -// Defines the supported queryTypes. GrafanaTemplateVariableFn is deprecated -type AzureQueryType string - -const ( - AzureQueryTypeAzureMonitor AzureQueryType = "Azure Monitor" - AzureQueryTypeLogAnalytics AzureQueryType = "Azure Log Analytics" - AzureQueryTypeAzureResourceGraph AzureQueryType = "Azure Resource Graph" - AzureQueryTypeAzureTraces AzureQueryType = "Azure Traces" - AzureQueryTypeSubscriptionsQuery AzureQueryType = "Azure Subscriptions" - AzureQueryTypeResourceGroupsQuery AzureQueryType = "Azure Resource Groups" - AzureQueryTypeNamespacesQuery AzureQueryType = "Azure Namespaces" - AzureQueryTypeResourceNamesQuery AzureQueryType = "Azure Resource Names" - AzureQueryTypeMetricNamesQuery AzureQueryType = "Azure Metric Names" - AzureQueryTypeWorkspacesQuery AzureQueryType = "Azure Workspaces" - AzureQueryTypeLocationsQuery AzureQueryType = "Azure Regions" - AzureQueryTypeGrafanaTemplateVariableFn AzureQueryType = "Grafana Template Variable Function" - AzureQueryTypeTraceExemplar AzureQueryType = "traceql" - AzureQueryTypeCustomNamespacesQuery AzureQueryType = "Azure Custom Namespaces" - AzureQueryTypeCustomMetricNamesQuery AzureQueryType = "Azure Custom Metric Names" -) - type AzureMetricQuery struct { // Array of resource URIs to be queried. Resources []AzureMonitorResource `json:"resources,omitempty"` @@ -133,6 +112,35 @@ func NewAzureMetricQuery() *AzureMetricQuery { return &AzureMetricQuery{} } +type AzureMonitorResource struct { + Subscription *string `json:"subscription,omitempty"` + ResourceGroup *string `json:"resourceGroup,omitempty"` + ResourceName *string `json:"resourceName,omitempty"` + MetricNamespace *string `json:"metricNamespace,omitempty"` + Region *string `json:"region,omitempty"` +} + +// NewAzureMonitorResource creates a new AzureMonitorResource object. +func NewAzureMonitorResource() *AzureMonitorResource { + return &AzureMonitorResource{} +} + +type AzureMetricDimension struct { + // Name of Dimension to be filtered on. + Dimension *string `json:"dimension,omitempty"` + // String denoting the filter operation. Supports 'eq' - equals,'ne' - not equals, 'sw' - starts with. Note that some dimensions may not support all operators. + Operator *string `json:"operator,omitempty"` + // Values to match with the filter. + Filters []string `json:"filters,omitempty"` + // @deprecated filter is deprecated in favour of filters to support multiselect. + Filter *string `json:"filter,omitempty"` +} + +// NewAzureMetricDimension creates a new AzureMetricDimension object. +func NewAzureMetricDimension() *AzureMetricDimension { + return &AzureMetricDimension{} +} + // Azure Monitor Logs sub-query properties type AzureLogsQuery struct { // KQL query to be executed. @@ -160,6 +168,27 @@ func NewAzureLogsQuery() *AzureLogsQuery { return &AzureLogsQuery{} } +type ResultFormat string + +const ( + ResultFormatTable ResultFormat = "table" + ResultFormatTimeSeries ResultFormat = "time_series" + ResultFormatTrace ResultFormat = "trace" + ResultFormatLogs ResultFormat = "logs" +) + +type AzureResourceGraphQuery struct { + // Azure Resource Graph KQL query to be executed. + Query *string `json:"query,omitempty"` + // Specifies the format results should be returned as. Defaults to table. + ResultFormat *string `json:"resultFormat,omitempty"` +} + +// NewAzureResourceGraphQuery creates a new AzureResourceGraphQuery object. +func NewAzureResourceGraphQuery() *AzureResourceGraphQuery { + return &AzureResourceGraphQuery{} +} + // Application Insights Traces sub-query properties type AzureTracesQuery struct { // Specifies the format results should be returned as. @@ -195,89 +224,11 @@ func NewAzureTracesFilter() *AzureTracesFilter { return &AzureTracesFilter{} } -type ResultFormat string +type GrafanaTemplateVariableQuery = AppInsightsMetricNameQueryOrAppInsightsGroupByQueryOrSubscriptionsQueryOrResourceGroupsQueryOrResourceNamesQueryOrMetricNamespaceQueryOrMetricDefinitionsQueryOrMetricNamesQueryOrWorkspacesQueryOrUnknownQuery -const ( - ResultFormatTable ResultFormat = "table" - ResultFormatTimeSeries ResultFormat = "time_series" - ResultFormatTrace ResultFormat = "trace" - ResultFormatLogs ResultFormat = "logs" -) - -type AzureResourceGraphQuery struct { - // Azure Resource Graph KQL query to be executed. - Query *string `json:"query,omitempty"` - // Specifies the format results should be returned as. Defaults to table. - ResultFormat *string `json:"resultFormat,omitempty"` -} - -// NewAzureResourceGraphQuery creates a new AzureResourceGraphQuery object. -func NewAzureResourceGraphQuery() *AzureResourceGraphQuery { - return &AzureResourceGraphQuery{} -} - -type AzureMonitorResource struct { - Subscription *string `json:"subscription,omitempty"` - ResourceGroup *string `json:"resourceGroup,omitempty"` - ResourceName *string `json:"resourceName,omitempty"` - MetricNamespace *string `json:"metricNamespace,omitempty"` - Region *string `json:"region,omitempty"` -} - -// NewAzureMonitorResource creates a new AzureMonitorResource object. -func NewAzureMonitorResource() *AzureMonitorResource { - return &AzureMonitorResource{} -} - -type AzureMetricDimension struct { - // Name of Dimension to be filtered on. - Dimension *string `json:"dimension,omitempty"` - // String denoting the filter operation. Supports 'eq' - equals,'ne' - not equals, 'sw' - starts with. Note that some dimensions may not support all operators. - Operator *string `json:"operator,omitempty"` - // Values to match with the filter. - Filters []string `json:"filters,omitempty"` - // @deprecated filter is deprecated in favour of filters to support multiselect. - Filter *string `json:"filter,omitempty"` -} - -// NewAzureMetricDimension creates a new AzureMetricDimension object. -func NewAzureMetricDimension() *AzureMetricDimension { - return &AzureMetricDimension{} -} - -type GrafanaTemplateVariableQueryType string - -const ( - GrafanaTemplateVariableQueryTypeAppInsightsMetricNameQuery GrafanaTemplateVariableQueryType = "AppInsightsMetricNameQuery" - GrafanaTemplateVariableQueryTypeAppInsightsGroupByQuery GrafanaTemplateVariableQueryType = "AppInsightsGroupByQuery" - GrafanaTemplateVariableQueryTypeSubscriptionsQuery GrafanaTemplateVariableQueryType = "SubscriptionsQuery" - GrafanaTemplateVariableQueryTypeResourceGroupsQuery GrafanaTemplateVariableQueryType = "ResourceGroupsQuery" - GrafanaTemplateVariableQueryTypeResourceNamesQuery GrafanaTemplateVariableQueryType = "ResourceNamesQuery" - GrafanaTemplateVariableQueryTypeMetricNamespaceQuery GrafanaTemplateVariableQueryType = "MetricNamespaceQuery" - GrafanaTemplateVariableQueryTypeMetricNamesQuery GrafanaTemplateVariableQueryType = "MetricNamesQuery" - GrafanaTemplateVariableQueryTypeWorkspacesQuery GrafanaTemplateVariableQueryType = "WorkspacesQuery" - GrafanaTemplateVariableQueryTypeUnknownQuery GrafanaTemplateVariableQueryType = "UnknownQuery" -) - -type BaseGrafanaTemplateVariableQuery struct { - RawQuery *string `json:"rawQuery,omitempty"` -} - -// NewBaseGrafanaTemplateVariableQuery creates a new BaseGrafanaTemplateVariableQuery object. -func NewBaseGrafanaTemplateVariableQuery() *BaseGrafanaTemplateVariableQuery { - return &BaseGrafanaTemplateVariableQuery{} -} - -type UnknownQuery struct { - RawQuery *string `json:"rawQuery,omitempty"` - Kind string `json:"kind"` -} - -// NewUnknownQuery creates a new UnknownQuery object. -func NewUnknownQuery() *UnknownQuery { - return &UnknownQuery{ - Kind: "UnknownQuery", - } +// NewGrafanaTemplateVariableQuery creates a new GrafanaTemplateVariableQuery object. +func NewGrafanaTemplateVariableQuery() *GrafanaTemplateVariableQuery { + return NewAppInsightsMetricNameQueryOrAppInsightsGroupByQueryOrSubscriptionsQueryOrResourceGroupsQueryOrResourceNamesQueryOrMetricNamespaceQueryOrMetricDefinitionsQueryOrMetricNamesQueryOrWorkspacesQueryOrUnknownQuery() } type AppInsightsMetricNameQuery struct { @@ -407,11 +358,60 @@ func NewWorkspacesQuery() *WorkspacesQuery { } } -type GrafanaTemplateVariableQuery = AppInsightsMetricNameQueryOrAppInsightsGroupByQueryOrSubscriptionsQueryOrResourceGroupsQueryOrResourceNamesQueryOrMetricNamespaceQueryOrMetricDefinitionsQueryOrMetricNamesQueryOrWorkspacesQueryOrUnknownQuery +type UnknownQuery struct { + RawQuery *string `json:"rawQuery,omitempty"` + Kind string `json:"kind"` +} -// NewGrafanaTemplateVariableQuery creates a new GrafanaTemplateVariableQuery object. -func NewGrafanaTemplateVariableQuery() *GrafanaTemplateVariableQuery { - return NewAppInsightsMetricNameQueryOrAppInsightsGroupByQueryOrSubscriptionsQueryOrResourceGroupsQueryOrResourceNamesQueryOrMetricNamespaceQueryOrMetricDefinitionsQueryOrMetricNamesQueryOrWorkspacesQueryOrUnknownQuery() +// NewUnknownQuery creates a new UnknownQuery object. +func NewUnknownQuery() *UnknownQuery { + return &UnknownQuery{ + Kind: "UnknownQuery", + } +} + +// Defines the supported queryTypes. GrafanaTemplateVariableFn is deprecated +type AzureQueryType string + +const ( + AzureQueryTypeAzureMonitor AzureQueryType = "Azure Monitor" + AzureQueryTypeLogAnalytics AzureQueryType = "Azure Log Analytics" + AzureQueryTypeAzureResourceGraph AzureQueryType = "Azure Resource Graph" + AzureQueryTypeAzureTraces AzureQueryType = "Azure Traces" + AzureQueryTypeSubscriptionsQuery AzureQueryType = "Azure Subscriptions" + AzureQueryTypeResourceGroupsQuery AzureQueryType = "Azure Resource Groups" + AzureQueryTypeNamespacesQuery AzureQueryType = "Azure Namespaces" + AzureQueryTypeResourceNamesQuery AzureQueryType = "Azure Resource Names" + AzureQueryTypeMetricNamesQuery AzureQueryType = "Azure Metric Names" + AzureQueryTypeWorkspacesQuery AzureQueryType = "Azure Workspaces" + AzureQueryTypeLocationsQuery AzureQueryType = "Azure Regions" + AzureQueryTypeGrafanaTemplateVariableFn AzureQueryType = "Grafana Template Variable Function" + AzureQueryTypeTraceExemplar AzureQueryType = "traceql" + AzureQueryTypeCustomNamespacesQuery AzureQueryType = "Azure Custom Namespaces" + AzureQueryTypeCustomMetricNamesQuery AzureQueryType = "Azure Custom Metric Names" +) + +type GrafanaTemplateVariableQueryType string + +const ( + GrafanaTemplateVariableQueryTypeAppInsightsMetricNameQuery GrafanaTemplateVariableQueryType = "AppInsightsMetricNameQuery" + GrafanaTemplateVariableQueryTypeAppInsightsGroupByQuery GrafanaTemplateVariableQueryType = "AppInsightsGroupByQuery" + GrafanaTemplateVariableQueryTypeSubscriptionsQuery GrafanaTemplateVariableQueryType = "SubscriptionsQuery" + GrafanaTemplateVariableQueryTypeResourceGroupsQuery GrafanaTemplateVariableQueryType = "ResourceGroupsQuery" + GrafanaTemplateVariableQueryTypeResourceNamesQuery GrafanaTemplateVariableQueryType = "ResourceNamesQuery" + GrafanaTemplateVariableQueryTypeMetricNamespaceQuery GrafanaTemplateVariableQueryType = "MetricNamespaceQuery" + GrafanaTemplateVariableQueryTypeMetricNamesQuery GrafanaTemplateVariableQueryType = "MetricNamesQuery" + GrafanaTemplateVariableQueryTypeWorkspacesQuery GrafanaTemplateVariableQueryType = "WorkspacesQuery" + GrafanaTemplateVariableQueryTypeUnknownQuery GrafanaTemplateVariableQueryType = "UnknownQuery" +) + +type BaseGrafanaTemplateVariableQuery struct { + RawQuery *string `json:"rawQuery,omitempty"` +} + +// NewBaseGrafanaTemplateVariableQuery creates a new BaseGrafanaTemplateVariableQuery object. +func NewBaseGrafanaTemplateVariableQuery() *BaseGrafanaTemplateVariableQuery { + return &BaseGrafanaTemplateVariableQuery{} } type AppInsightsMetricNameQueryOrAppInsightsGroupByQueryOrSubscriptionsQueryOrResourceGroupsQueryOrResourceNamesQueryOrMetricNamespaceQueryOrMetricDefinitionsQueryOrMetricNamesQueryOrWorkspacesQueryOrUnknownQuery struct { diff --git a/pkg/tsdb/cloud-monitoring/kinds/dataquery/types_dataquery_gen.go b/pkg/tsdb/cloud-monitoring/kinds/dataquery/types_dataquery_gen.go index 272b4e3a1d9..35ccd8d15f2 100644 --- a/pkg/tsdb/cloud-monitoring/kinds/dataquery/types_dataquery_gen.go +++ b/pkg/tsdb/cloud-monitoring/kinds/dataquery/types_dataquery_gen.go @@ -47,17 +47,6 @@ func NewCloudMonitoringQuery() *CloudMonitoringQuery { return &CloudMonitoringQuery{} } -// Defines the supported queryTypes. -type QueryType string - -const ( - QueryTypeTIMESERIESLIST QueryType = "timeSeriesList" - QueryTypeTIMESERIESQUERY QueryType = "timeSeriesQuery" - QueryTypeSLO QueryType = "slo" - QueryTypeANNOTATION QueryType = "annotation" - QueryTypePROMQL QueryType = "promQL" -) - // Time Series List sub-query properties. type TimeSeriesList struct { // GCP project to execute the query against. @@ -163,6 +152,17 @@ func NewPromQLQuery() *PromQLQuery { return &PromQLQuery{} } +// Defines the supported queryTypes. +type QueryType string + +const ( + QueryTypeTIMESERIESLIST QueryType = "timeSeriesList" + QueryTypeTIMESERIESQUERY QueryType = "timeSeriesQuery" + QueryTypeSLO QueryType = "slo" + QueryTypeANNOTATION QueryType = "annotation" + QueryTypePROMQL QueryType = "promQL" +) + // @deprecated This type is for migration purposes only. Replaced by TimeSeriesList Metric sub-query properties. type MetricQuery struct { // GCP project to execute the query against. diff --git a/pkg/tsdb/cloudwatch/kinds/dataquery/types_dataquery_gen.go b/pkg/tsdb/cloudwatch/kinds/dataquery/types_dataquery_gen.go index a2f35aa9eb4..db6d092792b 100644 --- a/pkg/tsdb/cloudwatch/kinds/dataquery/types_dataquery_gen.go +++ b/pkg/tsdb/cloudwatch/kinds/dataquery/types_dataquery_gen.go @@ -151,7 +151,7 @@ func NewSQLExpression() *SQLExpression { } type QueryEditorFunctionExpression struct { - Type string `json:"type"` + Type QueryEditorExpressionType `json:"type"` Name *string `json:"name,omitempty"` Parameters []QueryEditorFunctionParameterExpression `json:"parameters,omitempty"` } @@ -159,101 +159,35 @@ type QueryEditorFunctionExpression struct { // NewQueryEditorFunctionExpression creates a new QueryEditorFunctionExpression object. func NewQueryEditorFunctionExpression() *QueryEditorFunctionExpression { return &QueryEditorFunctionExpression{ - Type: "function", + Type: QueryEditorExpressionTypeFunction, } } -type QueryEditorExpressionType string - -const ( - QueryEditorExpressionTypeProperty QueryEditorExpressionType = "property" - QueryEditorExpressionTypeOperator QueryEditorExpressionType = "operator" - QueryEditorExpressionTypeOr QueryEditorExpressionType = "or" - QueryEditorExpressionTypeAnd QueryEditorExpressionType = "and" - QueryEditorExpressionTypeGroupBy QueryEditorExpressionType = "groupBy" - QueryEditorExpressionTypeFunction QueryEditorExpressionType = "function" - QueryEditorExpressionTypeFunctionParameter QueryEditorExpressionType = "functionParameter" -) - type QueryEditorFunctionParameterExpression struct { - Type string `json:"type"` - Name *string `json:"name,omitempty"` + Type QueryEditorExpressionType `json:"type"` + Name *string `json:"name,omitempty"` } // NewQueryEditorFunctionParameterExpression creates a new QueryEditorFunctionParameterExpression object. func NewQueryEditorFunctionParameterExpression() *QueryEditorFunctionParameterExpression { return &QueryEditorFunctionParameterExpression{ - Type: "functionParameter", + Type: QueryEditorExpressionTypeFunctionParameter, } } type QueryEditorPropertyExpression struct { - Type string `json:"type"` - Property QueryEditorProperty `json:"property"` + Type QueryEditorExpressionType `json:"type"` + Property QueryEditorProperty `json:"property"` } // NewQueryEditorPropertyExpression creates a new QueryEditorPropertyExpression object. func NewQueryEditorPropertyExpression() *QueryEditorPropertyExpression { return &QueryEditorPropertyExpression{ - Type: "property", + Type: QueryEditorExpressionTypeProperty, Property: *NewQueryEditorProperty(), } } -type QueryEditorGroupByExpression struct { - Type string `json:"type"` - Property QueryEditorProperty `json:"property"` -} - -// NewQueryEditorGroupByExpression creates a new QueryEditorGroupByExpression object. -func NewQueryEditorGroupByExpression() *QueryEditorGroupByExpression { - return &QueryEditorGroupByExpression{ - Type: "groupBy", - Property: *NewQueryEditorProperty(), - } -} - -type QueryEditorOperatorExpression struct { - Type string `json:"type"` - Property QueryEditorProperty `json:"property"` - // TS type is operator: QueryEditorOperator, extended in veneer - Operator QueryEditorOperator `json:"operator"` -} - -// NewQueryEditorOperatorExpression creates a new QueryEditorOperatorExpression object. -func NewQueryEditorOperatorExpression() *QueryEditorOperatorExpression { - return &QueryEditorOperatorExpression{ - Type: "operator", - Property: *NewQueryEditorProperty(), - Operator: *NewQueryEditorOperator(), - } -} - -// TS type is QueryEditorOperator, extended in veneer -type QueryEditorOperator struct { - Name *string `json:"name,omitempty"` - Value *StringOrBoolOrInt64OrArrayOfQueryEditorOperatorType `json:"value,omitempty"` -} - -// NewQueryEditorOperator creates a new QueryEditorOperator object. -func NewQueryEditorOperator() *QueryEditorOperator { - return &QueryEditorOperator{} -} - -type QueryEditorOperatorValueType = StringOrBoolOrInt64OrArrayOfQueryEditorOperatorType - -// NewQueryEditorOperatorValueType creates a new QueryEditorOperatorValueType object. -func NewQueryEditorOperatorValueType() *QueryEditorOperatorValueType { - return NewStringOrBoolOrInt64OrArrayOfQueryEditorOperatorType() -} - -type QueryEditorOperatorType = StringOrBoolOrInt64 - -// NewQueryEditorOperatorType creates a new QueryEditorOperatorType object. -func NewQueryEditorOperatorType() *QueryEditorOperatorType { - return NewStringOrBoolOrInt64() -} - type QueryEditorProperty struct { Type QueryEditorPropertyType `json:"type"` Name *string `json:"name,omitempty"` @@ -284,6 +218,72 @@ func NewQueryEditorArrayExpression() *QueryEditorArrayExpression { type QueryEditorExpression any +type QueryEditorGroupByExpression struct { + Type QueryEditorExpressionType `json:"type"` + Property QueryEditorProperty `json:"property"` +} + +// NewQueryEditorGroupByExpression creates a new QueryEditorGroupByExpression object. +func NewQueryEditorGroupByExpression() *QueryEditorGroupByExpression { + return &QueryEditorGroupByExpression{ + Type: QueryEditorExpressionTypeGroupBy, + Property: *NewQueryEditorProperty(), + } +} + +type QueryEditorOperatorExpression struct { + Type QueryEditorExpressionType `json:"type"` + Property QueryEditorProperty `json:"property"` + // TS type is operator: QueryEditorOperator, extended in veneer + Operator QueryEditorOperator `json:"operator"` +} + +// NewQueryEditorOperatorExpression creates a new QueryEditorOperatorExpression object. +func NewQueryEditorOperatorExpression() *QueryEditorOperatorExpression { + return &QueryEditorOperatorExpression{ + Type: QueryEditorExpressionTypeOperator, + Property: *NewQueryEditorProperty(), + Operator: *NewQueryEditorOperator(), + } +} + +// TS type is QueryEditorOperator, extended in veneer +type QueryEditorOperator struct { + Name *string `json:"name,omitempty"` + Value *StringOrBoolOrInt64OrArrayOfQueryEditorOperatorType `json:"value,omitempty"` +} + +// NewQueryEditorOperator creates a new QueryEditorOperator object. +func NewQueryEditorOperator() *QueryEditorOperator { + return &QueryEditorOperator{} +} + +type QueryEditorOperatorType = StringOrBoolOrInt64 + +// NewQueryEditorOperatorType creates a new QueryEditorOperatorType object. +func NewQueryEditorOperatorType() *QueryEditorOperatorType { + return NewStringOrBoolOrInt64() +} + +type QueryEditorExpressionType string + +const ( + QueryEditorExpressionTypeProperty QueryEditorExpressionType = "property" + QueryEditorExpressionTypeOperator QueryEditorExpressionType = "operator" + QueryEditorExpressionTypeOr QueryEditorExpressionType = "or" + QueryEditorExpressionTypeAnd QueryEditorExpressionType = "and" + QueryEditorExpressionTypeGroupBy QueryEditorExpressionType = "groupBy" + QueryEditorExpressionTypeFunction QueryEditorExpressionType = "function" + QueryEditorExpressionTypeFunctionParameter QueryEditorExpressionType = "functionParameter" +) + +type QueryEditorOperatorValueType = StringOrBoolOrInt64OrArrayOfQueryEditorOperatorType + +// NewQueryEditorOperatorValueType creates a new QueryEditorOperatorValueType object. +func NewQueryEditorOperatorValueType() *QueryEditorOperatorValueType { + return NewStringOrBoolOrInt64OrArrayOfQueryEditorOperatorType() +} + type LogsQueryLanguage string const ( @@ -525,6 +525,60 @@ func (resource *QueryEditorPropertyExpressionOrQueryEditorFunctionExpression) Un return fmt.Errorf("could not unmarshal resource with `type = %v`", discriminator) } +type ArrayOfQueryEditorExpressionOrArrayOfQueryEditorArrayExpression struct { + ArrayOfQueryEditorExpression []QueryEditorExpression `json:"ArrayOfQueryEditorExpression,omitempty"` + ArrayOfQueryEditorArrayExpression []QueryEditorArrayExpression `json:"ArrayOfQueryEditorArrayExpression,omitempty"` +} + +// NewArrayOfQueryEditorExpressionOrArrayOfQueryEditorArrayExpression creates a new ArrayOfQueryEditorExpressionOrArrayOfQueryEditorArrayExpression object. +func NewArrayOfQueryEditorExpressionOrArrayOfQueryEditorArrayExpression() *ArrayOfQueryEditorExpressionOrArrayOfQueryEditorArrayExpression { + return &ArrayOfQueryEditorExpressionOrArrayOfQueryEditorArrayExpression{} +} + +// MarshalJSON implements a custom JSON marshalling logic to encode `ArrayOfQueryEditorExpressionOrArrayOfQueryEditorArrayExpression` as JSON. +func (resource ArrayOfQueryEditorExpressionOrArrayOfQueryEditorArrayExpression) MarshalJSON() ([]byte, error) { + if resource.ArrayOfQueryEditorExpression != nil { + return json.Marshal(resource.ArrayOfQueryEditorExpression) + } + + if resource.ArrayOfQueryEditorArrayExpression != nil { + return json.Marshal(resource.ArrayOfQueryEditorArrayExpression) + } + + return nil, fmt.Errorf("no value for disjunction of scalars") +} + +// UnmarshalJSON implements a custom JSON unmarshalling logic to decode `ArrayOfQueryEditorExpressionOrArrayOfQueryEditorArrayExpression` from JSON. +func (resource *ArrayOfQueryEditorExpressionOrArrayOfQueryEditorArrayExpression) UnmarshalJSON(raw []byte) error { + if raw == nil { + return nil + } + + var errList []error + + // ArrayOfQueryEditorExpression + var ArrayOfQueryEditorExpression []QueryEditorExpression + if err := json.Unmarshal(raw, &ArrayOfQueryEditorExpression); err != nil { + errList = append(errList, err) + resource.ArrayOfQueryEditorExpression = nil + } else { + resource.ArrayOfQueryEditorExpression = ArrayOfQueryEditorExpression + return nil + } + + // ArrayOfQueryEditorArrayExpression + var ArrayOfQueryEditorArrayExpression []QueryEditorArrayExpression + if err := json.Unmarshal(raw, &ArrayOfQueryEditorArrayExpression); err != nil { + errList = append(errList, err) + resource.ArrayOfQueryEditorArrayExpression = nil + } else { + resource.ArrayOfQueryEditorArrayExpression = ArrayOfQueryEditorArrayExpression + return nil + } + + return errors.Join(errList...) +} + type StringOrBoolOrInt64OrArrayOfQueryEditorOperatorType struct { String *string `json:"String,omitempty"` Bool *bool `json:"Bool,omitempty"` @@ -677,57 +731,3 @@ func (resource *StringOrBoolOrInt64) UnmarshalJSON(raw []byte) error { return errors.Join(errList...) } - -type ArrayOfQueryEditorExpressionOrArrayOfQueryEditorArrayExpression struct { - ArrayOfQueryEditorExpression []QueryEditorExpression `json:"ArrayOfQueryEditorExpression,omitempty"` - ArrayOfQueryEditorArrayExpression []QueryEditorArrayExpression `json:"ArrayOfQueryEditorArrayExpression,omitempty"` -} - -// NewArrayOfQueryEditorExpressionOrArrayOfQueryEditorArrayExpression creates a new ArrayOfQueryEditorExpressionOrArrayOfQueryEditorArrayExpression object. -func NewArrayOfQueryEditorExpressionOrArrayOfQueryEditorArrayExpression() *ArrayOfQueryEditorExpressionOrArrayOfQueryEditorArrayExpression { - return &ArrayOfQueryEditorExpressionOrArrayOfQueryEditorArrayExpression{} -} - -// MarshalJSON implements a custom JSON marshalling logic to encode `ArrayOfQueryEditorExpressionOrArrayOfQueryEditorArrayExpression` as JSON. -func (resource ArrayOfQueryEditorExpressionOrArrayOfQueryEditorArrayExpression) MarshalJSON() ([]byte, error) { - if resource.ArrayOfQueryEditorExpression != nil { - return json.Marshal(resource.ArrayOfQueryEditorExpression) - } - - if resource.ArrayOfQueryEditorArrayExpression != nil { - return json.Marshal(resource.ArrayOfQueryEditorArrayExpression) - } - - return nil, fmt.Errorf("no value for disjunction of scalars") -} - -// UnmarshalJSON implements a custom JSON unmarshalling logic to decode `ArrayOfQueryEditorExpressionOrArrayOfQueryEditorArrayExpression` from JSON. -func (resource *ArrayOfQueryEditorExpressionOrArrayOfQueryEditorArrayExpression) UnmarshalJSON(raw []byte) error { - if raw == nil { - return nil - } - - var errList []error - - // ArrayOfQueryEditorExpression - var ArrayOfQueryEditorExpression []QueryEditorExpression - if err := json.Unmarshal(raw, &ArrayOfQueryEditorExpression); err != nil { - errList = append(errList, err) - resource.ArrayOfQueryEditorExpression = nil - } else { - resource.ArrayOfQueryEditorExpression = ArrayOfQueryEditorExpression - return nil - } - - // ArrayOfQueryEditorArrayExpression - var ArrayOfQueryEditorArrayExpression []QueryEditorArrayExpression - if err := json.Unmarshal(raw, &ArrayOfQueryEditorArrayExpression); err != nil { - errList = append(errList, err) - resource.ArrayOfQueryEditorArrayExpression = nil - } else { - resource.ArrayOfQueryEditorArrayExpression = ArrayOfQueryEditorArrayExpression - return nil - } - - return errors.Join(errList...) -} diff --git a/pkg/tsdb/elasticsearch/kinds/dataquery/types_dataquery_gen.go b/pkg/tsdb/elasticsearch/kinds/dataquery/types_dataquery_gen.go index 430a37af4e5..03f9ef7672a 100644 --- a/pkg/tsdb/elasticsearch/kinds/dataquery/types_dataquery_gen.go +++ b/pkg/tsdb/elasticsearch/kinds/dataquery/types_dataquery_gen.go @@ -24,6 +24,106 @@ func NewBucketAggregation() *BucketAggregation { return NewDateHistogramOrHistogramOrTermsOrFiltersOrGeoHashGridOrNested() } +type DateHistogram struct { + Field *string `json:"field,omitempty"` + Id string `json:"id"` + Type BucketAggregationType `json:"type"` + Settings *DataqueryDateHistogramSettings `json:"settings,omitempty"` +} + +// NewDateHistogram creates a new DateHistogram object. +func NewDateHistogram() *DateHistogram { + return &DateHistogram{ + Type: BucketAggregationTypeDateHistogram, + } +} + +type Histogram struct { + Field *string `json:"field,omitempty"` + Id string `json:"id"` + Type BucketAggregationType `json:"type"` + Settings *DataqueryHistogramSettings `json:"settings,omitempty"` +} + +// NewHistogram creates a new Histogram object. +func NewHistogram() *Histogram { + return &Histogram{ + Type: BucketAggregationTypeHistogram, + } +} + +type Terms struct { + Field *string `json:"field,omitempty"` + Id string `json:"id"` + Type BucketAggregationType `json:"type"` + Settings *DataqueryTermsSettings `json:"settings,omitempty"` +} + +// NewTerms creates a new Terms object. +func NewTerms() *Terms { + return &Terms{ + Type: BucketAggregationTypeTerms, + } +} + +type TermsOrder string + +const ( + TermsOrderDesc TermsOrder = "desc" + TermsOrderAsc TermsOrder = "asc" +) + +type Filters struct { + Id string `json:"id"` + Type BucketAggregationType `json:"type"` + Settings *DataqueryFiltersSettings `json:"settings,omitempty"` +} + +// NewFilters creates a new Filters object. +func NewFilters() *Filters { + return &Filters{ + Type: BucketAggregationTypeFilters, + } +} + +type Filter struct { + Query string `json:"query"` + Label string `json:"label"` +} + +// NewFilter creates a new Filter object. +func NewFilter() *Filter { + return &Filter{} +} + +type GeoHashGrid struct { + Field *string `json:"field,omitempty"` + Id string `json:"id"` + Type BucketAggregationType `json:"type"` + Settings *DataqueryGeoHashGridSettings `json:"settings,omitempty"` +} + +// NewGeoHashGrid creates a new GeoHashGrid object. +func NewGeoHashGrid() *GeoHashGrid { + return &GeoHashGrid{ + Type: BucketAggregationTypeGeohashGrid, + } +} + +type Nested struct { + Field *string `json:"field,omitempty"` + Id string `json:"id"` + Type BucketAggregationType `json:"type"` + Settings any `json:"settings,omitempty"` +} + +// NewNested creates a new Nested object. +func NewNested() *Nested { + return &Nested{ + Type: BucketAggregationTypeNested, + } +} + type MetricAggregation = CountOrMovingAverageOrDerivativeOrCumulativeSumOrBucketScriptOrSerialDiffOrRawDataOrRawDocumentOrUniqueCountOrPercentilesOrExtendedStatsOrMinOrMaxOrSumOrAverageOrMovingFunctionOrLogsOrRateOrTopMetrics // NewMetricAggregation creates a new MetricAggregation object. @@ -31,6 +131,323 @@ func NewMetricAggregation() *MetricAggregation { return NewCountOrMovingAverageOrDerivativeOrCumulativeSumOrBucketScriptOrSerialDiffOrRawDataOrRawDocumentOrUniqueCountOrPercentilesOrExtendedStatsOrMinOrMaxOrSumOrAverageOrMovingFunctionOrLogsOrRateOrTopMetrics() } +type Count struct { + Type MetricAggregationType `json:"type"` + Id string `json:"id"` + Hide *bool `json:"hide,omitempty"` +} + +// NewCount creates a new Count object. +func NewCount() *Count { + return &Count{ + Type: MetricAggregationTypeCount, + } +} + +type PipelineMetricAggregation = MovingAverageOrDerivativeOrCumulativeSumOrBucketScript + +// NewPipelineMetricAggregation creates a new PipelineMetricAggregation object. +func NewPipelineMetricAggregation() *PipelineMetricAggregation { + return NewMovingAverageOrDerivativeOrCumulativeSumOrBucketScript() +} + +// #MovingAverage's settings are overridden in types.ts +type MovingAverage struct { + PipelineAgg *string `json:"pipelineAgg,omitempty"` + Field *string `json:"field,omitempty"` + Type MetricAggregationType `json:"type"` + Id string `json:"id"` + Settings map[string]any `json:"settings,omitempty"` + Hide *bool `json:"hide,omitempty"` +} + +// NewMovingAverage creates a new MovingAverage object. +func NewMovingAverage() *MovingAverage { + return &MovingAverage{ + Type: MetricAggregationTypeMovingAvg, + } +} + +type Derivative struct { + PipelineAgg *string `json:"pipelineAgg,omitempty"` + Field *string `json:"field,omitempty"` + Type MetricAggregationType `json:"type"` + Id string `json:"id"` + Settings *DataqueryDerivativeSettings `json:"settings,omitempty"` + Hide *bool `json:"hide,omitempty"` +} + +// NewDerivative creates a new Derivative object. +func NewDerivative() *Derivative { + return &Derivative{ + Type: MetricAggregationTypeDerivative, + } +} + +type CumulativeSum struct { + PipelineAgg *string `json:"pipelineAgg,omitempty"` + Field *string `json:"field,omitempty"` + Type MetricAggregationType `json:"type"` + Id string `json:"id"` + Settings *DataqueryCumulativeSumSettings `json:"settings,omitempty"` + Hide *bool `json:"hide,omitempty"` +} + +// NewCumulativeSum creates a new CumulativeSum object. +func NewCumulativeSum() *CumulativeSum { + return &CumulativeSum{ + Type: MetricAggregationTypeCumulativeSum, + } +} + +type BucketScript struct { + Type MetricAggregationType `json:"type"` + PipelineVariables []PipelineVariable `json:"pipelineVariables,omitempty"` + Id string `json:"id"` + Settings *DataqueryBucketScriptSettings `json:"settings,omitempty"` + Hide *bool `json:"hide,omitempty"` +} + +// NewBucketScript creates a new BucketScript object. +func NewBucketScript() *BucketScript { + return &BucketScript{ + Type: MetricAggregationTypeBucketScript, + } +} + +type PipelineVariable struct { + Name string `json:"name"` + PipelineAgg string `json:"pipelineAgg"` +} + +// NewPipelineVariable creates a new PipelineVariable object. +func NewPipelineVariable() *PipelineVariable { + return &PipelineVariable{} +} + +type InlineScript = StringOrDataqueryInlineScript + +// NewInlineScript creates a new InlineScript object. +func NewInlineScript() *InlineScript { + return NewStringOrDataqueryInlineScript() +} + +type MetricAggregationWithSettings = BucketScriptOrCumulativeSumOrDerivativeOrSerialDiffOrRawDataOrRawDocumentOrUniqueCountOrPercentilesOrExtendedStatsOrMinOrMaxOrSumOrAverageOrMovingAverageOrMovingFunctionOrLogsOrRateOrTopMetrics + +// NewMetricAggregationWithSettings creates a new MetricAggregationWithSettings object. +func NewMetricAggregationWithSettings() *MetricAggregationWithSettings { + return NewBucketScriptOrCumulativeSumOrDerivativeOrSerialDiffOrRawDataOrRawDocumentOrUniqueCountOrPercentilesOrExtendedStatsOrMinOrMaxOrSumOrAverageOrMovingAverageOrMovingFunctionOrLogsOrRateOrTopMetrics() +} + +type SerialDiff struct { + PipelineAgg *string `json:"pipelineAgg,omitempty"` + Field *string `json:"field,omitempty"` + Type MetricAggregationType `json:"type"` + Id string `json:"id"` + Settings *DataquerySerialDiffSettings `json:"settings,omitempty"` + Hide *bool `json:"hide,omitempty"` +} + +// NewSerialDiff creates a new SerialDiff object. +func NewSerialDiff() *SerialDiff { + return &SerialDiff{ + Type: MetricAggregationTypeSerialDiff, + } +} + +type RawData struct { + Type MetricAggregationType `json:"type"` + Id string `json:"id"` + Settings *DataqueryRawDataSettings `json:"settings,omitempty"` + Hide *bool `json:"hide,omitempty"` +} + +// NewRawData creates a new RawData object. +func NewRawData() *RawData { + return &RawData{ + Type: MetricAggregationTypeRawData, + } +} + +type RawDocument struct { + Type MetricAggregationType `json:"type"` + Id string `json:"id"` + Settings *DataqueryRawDocumentSettings `json:"settings,omitempty"` + Hide *bool `json:"hide,omitempty"` +} + +// NewRawDocument creates a new RawDocument object. +func NewRawDocument() *RawDocument { + return &RawDocument{ + Type: MetricAggregationTypeRawDocument, + } +} + +type UniqueCount struct { + Type MetricAggregationType `json:"type"` + Field *string `json:"field,omitempty"` + Id string `json:"id"` + Settings *DataqueryUniqueCountSettings `json:"settings,omitempty"` + Hide *bool `json:"hide,omitempty"` +} + +// NewUniqueCount creates a new UniqueCount object. +func NewUniqueCount() *UniqueCount { + return &UniqueCount{ + Type: MetricAggregationTypeCardinality, + } +} + +type Percentiles struct { + Type MetricAggregationType `json:"type"` + Field *string `json:"field,omitempty"` + Id string `json:"id"` + Settings *DataqueryPercentilesSettings `json:"settings,omitempty"` + Hide *bool `json:"hide,omitempty"` +} + +// NewPercentiles creates a new Percentiles object. +func NewPercentiles() *Percentiles { + return &Percentiles{ + Type: MetricAggregationTypePercentiles, + } +} + +type ExtendedStats struct { + Type MetricAggregationType `json:"type"` + Settings *DataqueryExtendedStatsSettings `json:"settings,omitempty"` + Field *string `json:"field,omitempty"` + Id string `json:"id"` + Meta any `json:"meta,omitempty"` + Hide *bool `json:"hide,omitempty"` +} + +// NewExtendedStats creates a new ExtendedStats object. +func NewExtendedStats() *ExtendedStats { + return &ExtendedStats{ + Type: MetricAggregationTypeExtendedStats, + } +} + +type Min struct { + Type MetricAggregationType `json:"type"` + Field *string `json:"field,omitempty"` + Id string `json:"id"` + Settings *DataqueryMinSettings `json:"settings,omitempty"` + Hide *bool `json:"hide,omitempty"` +} + +// NewMin creates a new Min object. +func NewMin() *Min { + return &Min{ + Type: MetricAggregationTypeMin, + } +} + +type Max struct { + Type MetricAggregationType `json:"type"` + Field *string `json:"field,omitempty"` + Id string `json:"id"` + Settings *DataqueryMaxSettings `json:"settings,omitempty"` + Hide *bool `json:"hide,omitempty"` +} + +// NewMax creates a new Max object. +func NewMax() *Max { + return &Max{ + Type: MetricAggregationTypeMax, + } +} + +type Sum struct { + Type MetricAggregationType `json:"type"` + Field *string `json:"field,omitempty"` + Id string `json:"id"` + Settings *DataquerySumSettings `json:"settings,omitempty"` + Hide *bool `json:"hide,omitempty"` +} + +// NewSum creates a new Sum object. +func NewSum() *Sum { + return &Sum{ + Type: MetricAggregationTypeSum, + } +} + +type Average struct { + Type MetricAggregationType `json:"type"` + Field *string `json:"field,omitempty"` + Id string `json:"id"` + Settings *DataqueryAverageSettings `json:"settings,omitempty"` + Hide *bool `json:"hide,omitempty"` +} + +// NewAverage creates a new Average object. +func NewAverage() *Average { + return &Average{ + Type: MetricAggregationTypeAvg, + } +} + +type MovingFunction struct { + PipelineAgg *string `json:"pipelineAgg,omitempty"` + Field *string `json:"field,omitempty"` + Type MetricAggregationType `json:"type"` + Id string `json:"id"` + Settings *DataqueryMovingFunctionSettings `json:"settings,omitempty"` + Hide *bool `json:"hide,omitempty"` +} + +// NewMovingFunction creates a new MovingFunction object. +func NewMovingFunction() *MovingFunction { + return &MovingFunction{ + Type: MetricAggregationTypeMovingFn, + } +} + +type Logs struct { + Type MetricAggregationType `json:"type"` + Id string `json:"id"` + Settings *DataqueryLogsSettings `json:"settings,omitempty"` + Hide *bool `json:"hide,omitempty"` +} + +// NewLogs creates a new Logs object. +func NewLogs() *Logs { + return &Logs{ + Type: MetricAggregationTypeLogs, + } +} + +type Rate struct { + Type MetricAggregationType `json:"type"` + Field *string `json:"field,omitempty"` + Id string `json:"id"` + Settings *DataqueryRateSettings `json:"settings,omitempty"` + Hide *bool `json:"hide,omitempty"` +} + +// NewRate creates a new Rate object. +func NewRate() *Rate { + return &Rate{ + Type: MetricAggregationTypeRate, + } +} + +type TopMetrics struct { + Type MetricAggregationType `json:"type"` + Id string `json:"id"` + Settings *DataqueryTopMetricsSettings `json:"settings,omitempty"` + Hide *bool `json:"hide,omitempty"` +} + +// NewTopMetrics creates a new TopMetrics object. +func NewTopMetrics() *TopMetrics { + return &TopMetrics{ + Type: MetricAggregationTypeTopMetrics, + } +} + type BucketAggregationType string const ( @@ -65,20 +482,6 @@ func NewBucketAggregationWithField() *BucketAggregationWithField { return &BucketAggregationWithField{} } -type DateHistogram struct { - Field *string `json:"field,omitempty"` - Id string `json:"id"` - Type string `json:"type"` - Settings *DataqueryDateHistogramSettings `json:"settings,omitempty"` -} - -// NewDateHistogram creates a new DateHistogram object. -func NewDateHistogram() *DateHistogram { - return &DateHistogram{ - Type: "date_histogram", - } -} - type DateHistogramSettings struct { Interval *string `json:"interval,omitempty"` MinDocCount *string `json:"min_doc_count,omitempty"` @@ -92,20 +495,6 @@ func NewDateHistogramSettings() *DateHistogramSettings { return &DateHistogramSettings{} } -type Histogram struct { - Field *string `json:"field,omitempty"` - Id string `json:"id"` - Type string `json:"type"` - Settings *DataqueryHistogramSettings `json:"settings,omitempty"` -} - -// NewHistogram creates a new Histogram object. -func NewHistogram() *Histogram { - return &Histogram{ - Type: "histogram", - } -} - type HistogramSettings struct { Interval *string `json:"interval,omitempty"` MinDocCount *string `json:"min_doc_count,omitempty"` @@ -116,41 +505,6 @@ func NewHistogramSettings() *HistogramSettings { return &HistogramSettings{} } -type TermsOrder string - -const ( - TermsOrderDesc TermsOrder = "desc" - TermsOrderAsc TermsOrder = "asc" -) - -type Nested struct { - Field *string `json:"field,omitempty"` - Id string `json:"id"` - Type string `json:"type"` - Settings any `json:"settings,omitempty"` -} - -// NewNested creates a new Nested object. -func NewNested() *Nested { - return &Nested{ - Type: "nested", - } -} - -type Terms struct { - Field *string `json:"field,omitempty"` - Id string `json:"id"` - Type string `json:"type"` - Settings *DataqueryTermsSettings `json:"settings,omitempty"` -} - -// NewTerms creates a new Terms object. -func NewTerms() *Terms { - return &Terms{ - Type: "terms", - } -} - type TermsSettings struct { Order *TermsOrder `json:"order,omitempty"` Size *string `json:"size,omitempty"` @@ -164,29 +518,6 @@ func NewTermsSettings() *TermsSettings { return &TermsSettings{} } -type Filters struct { - Id string `json:"id"` - Type string `json:"type"` - Settings *DataqueryFiltersSettings `json:"settings,omitempty"` -} - -// NewFilters creates a new Filters object. -func NewFilters() *Filters { - return &Filters{ - Type: "filters", - } -} - -type Filter struct { - Query string `json:"query"` - Label string `json:"label"` -} - -// NewFilter creates a new Filter object. -func NewFilter() *Filter { - return &Filter{} -} - type FiltersSettings struct { Filters []Filter `json:"filters,omitempty"` } @@ -196,20 +527,6 @@ func NewFiltersSettings() *FiltersSettings { return &FiltersSettings{} } -type GeoHashGrid struct { - Field *string `json:"field,omitempty"` - Id string `json:"id"` - Type string `json:"type"` - Settings *DataqueryGeoHashGridSettings `json:"settings,omitempty"` -} - -// NewGeoHashGrid creates a new GeoHashGrid object. -func NewGeoHashGrid() *GeoHashGrid { - return &GeoHashGrid{ - Type: "geohash_grid", - } -} - type GeoHashGridSettings struct { Precision *string `json:"precision,omitempty"` } @@ -230,12 +547,29 @@ const ( PipelineMetricAggregationTypeBucketScript PipelineMetricAggregationType = "bucket_script" ) -type MetricAggregationType = StringOrPipelineMetricAggregationType +type MetricAggregationType string -// NewMetricAggregationType creates a new MetricAggregationType object. -func NewMetricAggregationType() *MetricAggregationType { - return NewStringOrPipelineMetricAggregationType() -} +const ( + MetricAggregationTypeCount MetricAggregationType = "count" + MetricAggregationTypeAvg MetricAggregationType = "avg" + MetricAggregationTypeSum MetricAggregationType = "sum" + MetricAggregationTypeMin MetricAggregationType = "min" + MetricAggregationTypeMax MetricAggregationType = "max" + MetricAggregationTypeExtendedStats MetricAggregationType = "extended_stats" + MetricAggregationTypePercentiles MetricAggregationType = "percentiles" + MetricAggregationTypeCardinality MetricAggregationType = "cardinality" + MetricAggregationTypeRawDocument MetricAggregationType = "raw_document" + MetricAggregationTypeRawData MetricAggregationType = "raw_data" + MetricAggregationTypeLogs MetricAggregationType = "logs" + MetricAggregationTypeRate MetricAggregationType = "rate" + MetricAggregationTypeTopMetrics MetricAggregationType = "top_metrics" + MetricAggregationTypeMovingAvg MetricAggregationType = "moving_avg" + MetricAggregationTypeMovingFn MetricAggregationType = "moving_fn" + MetricAggregationTypeDerivative MetricAggregationType = "derivative" + MetricAggregationTypeSerialDiff MetricAggregationType = "serial_diff" + MetricAggregationTypeCumulativeSum MetricAggregationType = "cumulative_sum" + MetricAggregationTypeBucketScript MetricAggregationType = "bucket_script" +) type BaseMetricAggregation struct { Type MetricAggregationType `json:"type"` @@ -245,19 +579,7 @@ type BaseMetricAggregation struct { // NewBaseMetricAggregation creates a new BaseMetricAggregation object. func NewBaseMetricAggregation() *BaseMetricAggregation { - return &BaseMetricAggregation{ - Type: *NewMetricAggregationType(), - } -} - -type PipelineVariable struct { - Name string `json:"name"` - PipelineAgg string `json:"pipelineAgg"` -} - -// NewPipelineVariable creates a new PipelineVariable object. -func NewPipelineVariable() *PipelineVariable { - return &PipelineVariable{} + return &BaseMetricAggregation{} } type MetricAggregationWithField struct { @@ -269,9 +591,7 @@ type MetricAggregationWithField struct { // NewMetricAggregationWithField creates a new MetricAggregationWithField object. func NewMetricAggregationWithField() *MetricAggregationWithField { - return &MetricAggregationWithField{ - Type: *NewMetricAggregationType(), - } + return &MetricAggregationWithField{} } type MetricAggregationWithMissingSupport struct { @@ -283,16 +603,7 @@ type MetricAggregationWithMissingSupport struct { // NewMetricAggregationWithMissingSupport creates a new MetricAggregationWithMissingSupport object. func NewMetricAggregationWithMissingSupport() *MetricAggregationWithMissingSupport { - return &MetricAggregationWithMissingSupport{ - Type: *NewMetricAggregationType(), - } -} - -type InlineScript = StringOrDataqueryInlineScript - -// NewInlineScript creates a new InlineScript object. -func NewInlineScript() *InlineScript { - return NewStringOrDataqueryInlineScript() + return &MetricAggregationWithMissingSupport{} } type MetricAggregationWithInlineScript struct { @@ -304,82 +615,7 @@ type MetricAggregationWithInlineScript struct { // NewMetricAggregationWithInlineScript creates a new MetricAggregationWithInlineScript object. func NewMetricAggregationWithInlineScript() *MetricAggregationWithInlineScript { - return &MetricAggregationWithInlineScript{ - Type: *NewMetricAggregationType(), - } -} - -type Count struct { - Type string `json:"type"` - Id string `json:"id"` - Hide *bool `json:"hide,omitempty"` -} - -// NewCount creates a new Count object. -func NewCount() *Count { - return &Count{ - Type: "count", - } -} - -type Average struct { - Type string `json:"type"` - Field *string `json:"field,omitempty"` - Id string `json:"id"` - Settings *DataqueryAverageSettings `json:"settings,omitempty"` - Hide *bool `json:"hide,omitempty"` -} - -// NewAverage creates a new Average object. -func NewAverage() *Average { - return &Average{ - Type: "avg", - } -} - -type Sum struct { - Type string `json:"type"` - Field *string `json:"field,omitempty"` - Id string `json:"id"` - Settings *DataquerySumSettings `json:"settings,omitempty"` - Hide *bool `json:"hide,omitempty"` -} - -// NewSum creates a new Sum object. -func NewSum() *Sum { - return &Sum{ - Type: "sum", - } -} - -type Max struct { - Type string `json:"type"` - Field *string `json:"field,omitempty"` - Id string `json:"id"` - Settings *DataqueryMaxSettings `json:"settings,omitempty"` - Hide *bool `json:"hide,omitempty"` -} - -// NewMax creates a new Max object. -func NewMax() *Max { - return &Max{ - Type: "max", - } -} - -type Min struct { - Type string `json:"type"` - Field *string `json:"field,omitempty"` - Id string `json:"id"` - Settings *DataqueryMinSettings `json:"settings,omitempty"` - Hide *bool `json:"hide,omitempty"` -} - -// NewMin creates a new Min object. -func NewMin() *Min { - return &Min{ - Type: "min", - } + return &MetricAggregationWithInlineScript{} } type ExtendedStatMetaType string @@ -405,109 +641,6 @@ func NewExtendedStat() *ExtendedStat { return &ExtendedStat{} } -type ExtendedStats struct { - Type string `json:"type"` - Settings *DataqueryExtendedStatsSettings `json:"settings,omitempty"` - Field *string `json:"field,omitempty"` - Id string `json:"id"` - Meta any `json:"meta,omitempty"` - Hide *bool `json:"hide,omitempty"` -} - -// NewExtendedStats creates a new ExtendedStats object. -func NewExtendedStats() *ExtendedStats { - return &ExtendedStats{ - Type: "extended_stats", - } -} - -type Percentiles struct { - Type string `json:"type"` - Field *string `json:"field,omitempty"` - Id string `json:"id"` - Settings *DataqueryPercentilesSettings `json:"settings,omitempty"` - Hide *bool `json:"hide,omitempty"` -} - -// NewPercentiles creates a new Percentiles object. -func NewPercentiles() *Percentiles { - return &Percentiles{ - Type: "percentiles", - } -} - -type UniqueCount struct { - Type string `json:"type"` - Field *string `json:"field,omitempty"` - Id string `json:"id"` - Settings *DataqueryUniqueCountSettings `json:"settings,omitempty"` - Hide *bool `json:"hide,omitempty"` -} - -// NewUniqueCount creates a new UniqueCount object. -func NewUniqueCount() *UniqueCount { - return &UniqueCount{ - Type: "cardinality", - } -} - -type RawDocument struct { - Type string `json:"type"` - Id string `json:"id"` - Settings *DataqueryRawDocumentSettings `json:"settings,omitempty"` - Hide *bool `json:"hide,omitempty"` -} - -// NewRawDocument creates a new RawDocument object. -func NewRawDocument() *RawDocument { - return &RawDocument{ - Type: "raw_document", - } -} - -type RawData struct { - Type string `json:"type"` - Id string `json:"id"` - Settings *DataqueryRawDataSettings `json:"settings,omitempty"` - Hide *bool `json:"hide,omitempty"` -} - -// NewRawData creates a new RawData object. -func NewRawData() *RawData { - return &RawData{ - Type: "raw_data", - } -} - -type Logs struct { - Type string `json:"type"` - Id string `json:"id"` - Settings *DataqueryLogsSettings `json:"settings,omitempty"` - Hide *bool `json:"hide,omitempty"` -} - -// NewLogs creates a new Logs object. -func NewLogs() *Logs { - return &Logs{ - Type: "logs", - } -} - -type Rate struct { - Type string `json:"type"` - Field *string `json:"field,omitempty"` - Id string `json:"id"` - Settings *DataqueryRateSettings `json:"settings,omitempty"` - Hide *bool `json:"hide,omitempty"` -} - -// NewRate creates a new Rate object. -func NewRate() *Rate { - return &Rate{ - Type: "rate", - } -} - type BasePipelineMetricAggregation struct { PipelineAgg *string `json:"pipelineAgg,omitempty"` Field *string `json:"field,omitempty"` @@ -530,9 +663,7 @@ type PipelineMetricAggregationWithMultipleBucketPaths struct { // NewPipelineMetricAggregationWithMultipleBucketPaths creates a new PipelineMetricAggregationWithMultipleBucketPaths object. func NewPipelineMetricAggregationWithMultipleBucketPaths() *PipelineMetricAggregationWithMultipleBucketPaths { - return &PipelineMetricAggregationWithMultipleBucketPaths{ - Type: *NewMetricAggregationType(), - } + return &PipelineMetricAggregationWithMultipleBucketPaths{} } type MovingAverageModel string @@ -567,33 +698,33 @@ func NewBaseMovingAverageModelSettings() *BaseMovingAverageModelSettings { } type MovingAverageSimpleModelSettings struct { - Model string `json:"model"` - Window string `json:"window"` - Predict string `json:"predict"` + Model MovingAverageModel `json:"model"` + Window string `json:"window"` + Predict string `json:"predict"` } // NewMovingAverageSimpleModelSettings creates a new MovingAverageSimpleModelSettings object. func NewMovingAverageSimpleModelSettings() *MovingAverageSimpleModelSettings { return &MovingAverageSimpleModelSettings{ - Model: "simple", + Model: MovingAverageModelSimple, } } type MovingAverageLinearModelSettings struct { - Model string `json:"model"` - Window string `json:"window"` - Predict string `json:"predict"` + Model MovingAverageModel `json:"model"` + Window string `json:"window"` + Predict string `json:"predict"` } // NewMovingAverageLinearModelSettings creates a new MovingAverageLinearModelSettings object. func NewMovingAverageLinearModelSettings() *MovingAverageLinearModelSettings { return &MovingAverageLinearModelSettings{ - Model: "linear", + Model: MovingAverageModelLinear, } } type MovingAverageEWMAModelSettings struct { - Model string `json:"model"` + Model MovingAverageModel `json:"model"` Settings *DataqueryMovingAverageEWMAModelSettingsSettings `json:"settings,omitempty"` Window string `json:"window"` Minimize bool `json:"minimize"` @@ -603,12 +734,12 @@ type MovingAverageEWMAModelSettings struct { // NewMovingAverageEWMAModelSettings creates a new MovingAverageEWMAModelSettings object. func NewMovingAverageEWMAModelSettings() *MovingAverageEWMAModelSettings { return &MovingAverageEWMAModelSettings{ - Model: "ewma", + Model: MovingAverageModelEwma, } } type MovingAverageHoltModelSettings struct { - Model string `json:"model"` + Model MovingAverageModel `json:"model"` Settings DataqueryMovingAverageHoltModelSettingsSettings `json:"settings"` Window string `json:"window"` Minimize bool `json:"minimize"` @@ -618,13 +749,13 @@ type MovingAverageHoltModelSettings struct { // NewMovingAverageHoltModelSettings creates a new MovingAverageHoltModelSettings object. func NewMovingAverageHoltModelSettings() *MovingAverageHoltModelSettings { return &MovingAverageHoltModelSettings{ - Model: "holt", + Model: MovingAverageModelHolt, Settings: *NewDataqueryMovingAverageHoltModelSettingsSettings(), } } type MovingAverageHoltWintersModelSettings struct { - Model string `json:"model"` + Model MovingAverageModel `json:"model"` Settings DataqueryMovingAverageHoltWintersModelSettingsSettings `json:"settings"` Window string `json:"window"` Minimize bool `json:"minimize"` @@ -634,135 +765,11 @@ type MovingAverageHoltWintersModelSettings struct { // NewMovingAverageHoltWintersModelSettings creates a new MovingAverageHoltWintersModelSettings object. func NewMovingAverageHoltWintersModelSettings() *MovingAverageHoltWintersModelSettings { return &MovingAverageHoltWintersModelSettings{ - Model: "holt_winters", + Model: MovingAverageModelHoltWinters, Settings: *NewDataqueryMovingAverageHoltWintersModelSettingsSettings(), } } -// #MovingAverage's settings are overridden in types.ts -type MovingAverage struct { - PipelineAgg *string `json:"pipelineAgg,omitempty"` - Field *string `json:"field,omitempty"` - Type string `json:"type"` - Id string `json:"id"` - Settings map[string]any `json:"settings,omitempty"` - Hide *bool `json:"hide,omitempty"` -} - -// NewMovingAverage creates a new MovingAverage object. -func NewMovingAverage() *MovingAverage { - return &MovingAverage{ - Type: "moving_avg", - } -} - -type MovingFunction struct { - PipelineAgg *string `json:"pipelineAgg,omitempty"` - Field *string `json:"field,omitempty"` - Type string `json:"type"` - Id string `json:"id"` - Settings *DataqueryMovingFunctionSettings `json:"settings,omitempty"` - Hide *bool `json:"hide,omitempty"` -} - -// NewMovingFunction creates a new MovingFunction object. -func NewMovingFunction() *MovingFunction { - return &MovingFunction{ - Type: "moving_fn", - } -} - -type Derivative struct { - PipelineAgg *string `json:"pipelineAgg,omitempty"` - Field *string `json:"field,omitempty"` - Type string `json:"type"` - Id string `json:"id"` - Settings *DataqueryDerivativeSettings `json:"settings,omitempty"` - Hide *bool `json:"hide,omitempty"` -} - -// NewDerivative creates a new Derivative object. -func NewDerivative() *Derivative { - return &Derivative{ - Type: "derivative", - } -} - -type SerialDiff struct { - PipelineAgg *string `json:"pipelineAgg,omitempty"` - Field *string `json:"field,omitempty"` - Type string `json:"type"` - Id string `json:"id"` - Settings *DataquerySerialDiffSettings `json:"settings,omitempty"` - Hide *bool `json:"hide,omitempty"` -} - -// NewSerialDiff creates a new SerialDiff object. -func NewSerialDiff() *SerialDiff { - return &SerialDiff{ - Type: "serial_diff", - } -} - -type CumulativeSum struct { - PipelineAgg *string `json:"pipelineAgg,omitempty"` - Field *string `json:"field,omitempty"` - Type string `json:"type"` - Id string `json:"id"` - Settings *DataqueryCumulativeSumSettings `json:"settings,omitempty"` - Hide *bool `json:"hide,omitempty"` -} - -// NewCumulativeSum creates a new CumulativeSum object. -func NewCumulativeSum() *CumulativeSum { - return &CumulativeSum{ - Type: "cumulative_sum", - } -} - -type BucketScript struct { - Type string `json:"type"` - PipelineVariables []PipelineVariable `json:"pipelineVariables,omitempty"` - Id string `json:"id"` - Settings *DataqueryBucketScriptSettings `json:"settings,omitempty"` - Hide *bool `json:"hide,omitempty"` -} - -// NewBucketScript creates a new BucketScript object. -func NewBucketScript() *BucketScript { - return &BucketScript{ - Type: "bucket_script", - } -} - -type TopMetrics struct { - Type string `json:"type"` - Id string `json:"id"` - Settings *DataqueryTopMetricsSettings `json:"settings,omitempty"` - Hide *bool `json:"hide,omitempty"` -} - -// NewTopMetrics creates a new TopMetrics object. -func NewTopMetrics() *TopMetrics { - return &TopMetrics{ - Type: "top_metrics", - } -} - -type PipelineMetricAggregation = MovingAverageOrDerivativeOrCumulativeSumOrBucketScript - -// NewPipelineMetricAggregation creates a new PipelineMetricAggregation object. -func NewPipelineMetricAggregation() *PipelineMetricAggregation { - return NewMovingAverageOrDerivativeOrCumulativeSumOrBucketScript() -} - -type MetricAggregationWithSettings = BucketScriptOrCumulativeSumOrDerivativeOrSerialDiffOrRawDataOrRawDocumentOrUniqueCountOrPercentilesOrExtendedStatsOrMinOrMaxOrSumOrAverageOrMovingAverageOrMovingFunctionOrLogsOrRateOrTopMetrics - -// NewMetricAggregationWithSettings creates a new MetricAggregationWithSettings object. -func NewMetricAggregationWithSettings() *MetricAggregationWithSettings { - return NewBucketScriptOrCumulativeSumOrDerivativeOrSerialDiffOrRawDataOrRawDocumentOrUniqueCountOrPercentilesOrExtendedStatsOrMinOrMaxOrSumOrAverageOrMovingAverageOrMovingFunctionOrLogsOrRateOrTopMetrics() -} - type ElasticsearchDataQuery struct { // Alias pattern Alias *string `json:"alias,omitempty"` @@ -849,13 +856,31 @@ func NewDataqueryGeoHashGridSettings() *DataqueryGeoHashGridSettings { return &DataqueryGeoHashGridSettings{} } -type DataqueryMetricAggregationWithMissingSupportSettings struct { - Missing *string `json:"missing,omitempty"` +type DataqueryDerivativeSettings struct { + Unit *string `json:"unit,omitempty"` } -// NewDataqueryMetricAggregationWithMissingSupportSettings creates a new DataqueryMetricAggregationWithMissingSupportSettings object. -func NewDataqueryMetricAggregationWithMissingSupportSettings() *DataqueryMetricAggregationWithMissingSupportSettings { - return &DataqueryMetricAggregationWithMissingSupportSettings{} +// NewDataqueryDerivativeSettings creates a new DataqueryDerivativeSettings object. +func NewDataqueryDerivativeSettings() *DataqueryDerivativeSettings { + return &DataqueryDerivativeSettings{} +} + +type DataqueryCumulativeSumSettings struct { + Format *string `json:"format,omitempty"` +} + +// NewDataqueryCumulativeSumSettings creates a new DataqueryCumulativeSumSettings object. +func NewDataqueryCumulativeSumSettings() *DataqueryCumulativeSumSettings { + return &DataqueryCumulativeSumSettings{} +} + +type DataqueryBucketScriptSettings struct { + Script *InlineScript `json:"script,omitempty"` +} + +// NewDataqueryBucketScriptSettings creates a new DataqueryBucketScriptSettings object. +func NewDataqueryBucketScriptSettings() *DataqueryBucketScriptSettings { + return &DataqueryBucketScriptSettings{} } type DataqueryInlineScript struct { @@ -867,64 +892,41 @@ func NewDataqueryInlineScript() *DataqueryInlineScript { return &DataqueryInlineScript{} } -type DataqueryMetricAggregationWithInlineScriptSettings struct { - Script *InlineScript `json:"script,omitempty"` +type DataquerySerialDiffSettings struct { + Lag *string `json:"lag,omitempty"` } -// NewDataqueryMetricAggregationWithInlineScriptSettings creates a new DataqueryMetricAggregationWithInlineScriptSettings object. -func NewDataqueryMetricAggregationWithInlineScriptSettings() *DataqueryMetricAggregationWithInlineScriptSettings { - return &DataqueryMetricAggregationWithInlineScriptSettings{} +// NewDataquerySerialDiffSettings creates a new DataquerySerialDiffSettings object. +func NewDataquerySerialDiffSettings() *DataquerySerialDiffSettings { + return &DataquerySerialDiffSettings{} } -type DataqueryAverageSettings struct { - Script *InlineScript `json:"script,omitempty"` - Missing *string `json:"missing,omitempty"` +type DataqueryRawDataSettings struct { + Size *string `json:"size,omitempty"` } -// NewDataqueryAverageSettings creates a new DataqueryAverageSettings object. -func NewDataqueryAverageSettings() *DataqueryAverageSettings { - return &DataqueryAverageSettings{} +// NewDataqueryRawDataSettings creates a new DataqueryRawDataSettings object. +func NewDataqueryRawDataSettings() *DataqueryRawDataSettings { + return &DataqueryRawDataSettings{} } -type DataquerySumSettings struct { - Script *InlineScript `json:"script,omitempty"` - Missing *string `json:"missing,omitempty"` +type DataqueryRawDocumentSettings struct { + Size *string `json:"size,omitempty"` } -// NewDataquerySumSettings creates a new DataquerySumSettings object. -func NewDataquerySumSettings() *DataquerySumSettings { - return &DataquerySumSettings{} +// NewDataqueryRawDocumentSettings creates a new DataqueryRawDocumentSettings object. +func NewDataqueryRawDocumentSettings() *DataqueryRawDocumentSettings { + return &DataqueryRawDocumentSettings{} } -type DataqueryMaxSettings struct { - Script *InlineScript `json:"script,omitempty"` - Missing *string `json:"missing,omitempty"` +type DataqueryUniqueCountSettings struct { + PrecisionThreshold *string `json:"precision_threshold,omitempty"` + Missing *string `json:"missing,omitempty"` } -// NewDataqueryMaxSettings creates a new DataqueryMaxSettings object. -func NewDataqueryMaxSettings() *DataqueryMaxSettings { - return &DataqueryMaxSettings{} -} - -type DataqueryMinSettings struct { - Script *InlineScript `json:"script,omitempty"` - Missing *string `json:"missing,omitempty"` -} - -// NewDataqueryMinSettings creates a new DataqueryMinSettings object. -func NewDataqueryMinSettings() *DataqueryMinSettings { - return &DataqueryMinSettings{} -} - -type DataqueryExtendedStatsSettings struct { - Script *InlineScript `json:"script,omitempty"` - Missing *string `json:"missing,omitempty"` - Sigma *string `json:"sigma,omitempty"` -} - -// NewDataqueryExtendedStatsSettings creates a new DataqueryExtendedStatsSettings object. -func NewDataqueryExtendedStatsSettings() *DataqueryExtendedStatsSettings { - return &DataqueryExtendedStatsSettings{} +// NewDataqueryUniqueCountSettings creates a new DataqueryUniqueCountSettings object. +func NewDataqueryUniqueCountSettings() *DataqueryUniqueCountSettings { + return &DataqueryUniqueCountSettings{} } type DataqueryPercentilesSettings struct { @@ -938,32 +940,66 @@ func NewDataqueryPercentilesSettings() *DataqueryPercentilesSettings { return &DataqueryPercentilesSettings{} } -type DataqueryUniqueCountSettings struct { - PrecisionThreshold *string `json:"precision_threshold,omitempty"` - Missing *string `json:"missing,omitempty"` +type DataqueryExtendedStatsSettings struct { + Script *InlineScript `json:"script,omitempty"` + Missing *string `json:"missing,omitempty"` + Sigma *string `json:"sigma,omitempty"` } -// NewDataqueryUniqueCountSettings creates a new DataqueryUniqueCountSettings object. -func NewDataqueryUniqueCountSettings() *DataqueryUniqueCountSettings { - return &DataqueryUniqueCountSettings{} +// NewDataqueryExtendedStatsSettings creates a new DataqueryExtendedStatsSettings object. +func NewDataqueryExtendedStatsSettings() *DataqueryExtendedStatsSettings { + return &DataqueryExtendedStatsSettings{} } -type DataqueryRawDocumentSettings struct { - Size *string `json:"size,omitempty"` +type DataqueryMinSettings struct { + Script *InlineScript `json:"script,omitempty"` + Missing *string `json:"missing,omitempty"` } -// NewDataqueryRawDocumentSettings creates a new DataqueryRawDocumentSettings object. -func NewDataqueryRawDocumentSettings() *DataqueryRawDocumentSettings { - return &DataqueryRawDocumentSettings{} +// NewDataqueryMinSettings creates a new DataqueryMinSettings object. +func NewDataqueryMinSettings() *DataqueryMinSettings { + return &DataqueryMinSettings{} } -type DataqueryRawDataSettings struct { - Size *string `json:"size,omitempty"` +type DataqueryMaxSettings struct { + Script *InlineScript `json:"script,omitempty"` + Missing *string `json:"missing,omitempty"` } -// NewDataqueryRawDataSettings creates a new DataqueryRawDataSettings object. -func NewDataqueryRawDataSettings() *DataqueryRawDataSettings { - return &DataqueryRawDataSettings{} +// NewDataqueryMaxSettings creates a new DataqueryMaxSettings object. +func NewDataqueryMaxSettings() *DataqueryMaxSettings { + return &DataqueryMaxSettings{} +} + +type DataquerySumSettings struct { + Script *InlineScript `json:"script,omitempty"` + Missing *string `json:"missing,omitempty"` +} + +// NewDataquerySumSettings creates a new DataquerySumSettings object. +func NewDataquerySumSettings() *DataquerySumSettings { + return &DataquerySumSettings{} +} + +type DataqueryAverageSettings struct { + Script *InlineScript `json:"script,omitempty"` + Missing *string `json:"missing,omitempty"` +} + +// NewDataqueryAverageSettings creates a new DataqueryAverageSettings object. +func NewDataqueryAverageSettings() *DataqueryAverageSettings { + return &DataqueryAverageSettings{} +} + +type DataqueryMovingFunctionSettings struct { + Window *string `json:"window,omitempty"` + Script *InlineScript `json:"script,omitempty"` + Shift *string `json:"shift,omitempty"` +} + +// NewDataqueryMovingFunctionSettings creates a new DataqueryMovingFunctionSettings object. +func NewDataqueryMovingFunctionSettings() *DataqueryMovingFunctionSettings { + return &DataqueryMovingFunctionSettings{} } type DataqueryLogsSettings struct { @@ -985,6 +1021,35 @@ func NewDataqueryRateSettings() *DataqueryRateSettings { return &DataqueryRateSettings{} } +type DataqueryTopMetricsSettings struct { + Order *string `json:"order,omitempty"` + OrderBy *string `json:"orderBy,omitempty"` + Metrics []string `json:"metrics,omitempty"` +} + +// NewDataqueryTopMetricsSettings creates a new DataqueryTopMetricsSettings object. +func NewDataqueryTopMetricsSettings() *DataqueryTopMetricsSettings { + return &DataqueryTopMetricsSettings{} +} + +type DataqueryMetricAggregationWithMissingSupportSettings struct { + Missing *string `json:"missing,omitempty"` +} + +// NewDataqueryMetricAggregationWithMissingSupportSettings creates a new DataqueryMetricAggregationWithMissingSupportSettings object. +func NewDataqueryMetricAggregationWithMissingSupportSettings() *DataqueryMetricAggregationWithMissingSupportSettings { + return &DataqueryMetricAggregationWithMissingSupportSettings{} +} + +type DataqueryMetricAggregationWithInlineScriptSettings struct { + Script *InlineScript `json:"script,omitempty"` +} + +// NewDataqueryMetricAggregationWithInlineScriptSettings creates a new DataqueryMetricAggregationWithInlineScriptSettings object. +func NewDataqueryMetricAggregationWithInlineScriptSettings() *DataqueryMetricAggregationWithInlineScriptSettings { + return &DataqueryMetricAggregationWithInlineScriptSettings{} +} + type DataqueryMovingAverageEWMAModelSettingsSettings struct { Alpha *string `json:"alpha,omitempty"` } @@ -1017,64 +1082,6 @@ func NewDataqueryMovingAverageHoltWintersModelSettingsSettings() *DataqueryMovin return &DataqueryMovingAverageHoltWintersModelSettingsSettings{} } -type DataqueryMovingFunctionSettings struct { - Window *string `json:"window,omitempty"` - Script *InlineScript `json:"script,omitempty"` - Shift *string `json:"shift,omitempty"` -} - -// NewDataqueryMovingFunctionSettings creates a new DataqueryMovingFunctionSettings object. -func NewDataqueryMovingFunctionSettings() *DataqueryMovingFunctionSettings { - return &DataqueryMovingFunctionSettings{} -} - -type DataqueryDerivativeSettings struct { - Unit *string `json:"unit,omitempty"` -} - -// NewDataqueryDerivativeSettings creates a new DataqueryDerivativeSettings object. -func NewDataqueryDerivativeSettings() *DataqueryDerivativeSettings { - return &DataqueryDerivativeSettings{} -} - -type DataquerySerialDiffSettings struct { - Lag *string `json:"lag,omitempty"` -} - -// NewDataquerySerialDiffSettings creates a new DataquerySerialDiffSettings object. -func NewDataquerySerialDiffSettings() *DataquerySerialDiffSettings { - return &DataquerySerialDiffSettings{} -} - -type DataqueryCumulativeSumSettings struct { - Format *string `json:"format,omitempty"` -} - -// NewDataqueryCumulativeSumSettings creates a new DataqueryCumulativeSumSettings object. -func NewDataqueryCumulativeSumSettings() *DataqueryCumulativeSumSettings { - return &DataqueryCumulativeSumSettings{} -} - -type DataqueryBucketScriptSettings struct { - Script *InlineScript `json:"script,omitempty"` -} - -// NewDataqueryBucketScriptSettings creates a new DataqueryBucketScriptSettings object. -func NewDataqueryBucketScriptSettings() *DataqueryBucketScriptSettings { - return &DataqueryBucketScriptSettings{} -} - -type DataqueryTopMetricsSettings struct { - Order *string `json:"order,omitempty"` - OrderBy *string `json:"orderBy,omitempty"` - Metrics []string `json:"metrics,omitempty"` -} - -// NewDataqueryTopMetricsSettings creates a new DataqueryTopMetricsSettings object. -func NewDataqueryTopMetricsSettings() *DataqueryTopMetricsSettings { - return &DataqueryTopMetricsSettings{} -} - type DateHistogramOrHistogramOrTermsOrFiltersOrGeoHashGridOrNested struct { DateHistogram *DateHistogram `json:"DateHistogram,omitempty"` Histogram *Histogram `json:"Histogram,omitempty"` @@ -1449,28 +1456,6 @@ func (resource *CountOrMovingAverageOrDerivativeOrCumulativeSumOrBucketScriptOrS return fmt.Errorf("could not unmarshal resource with `type = %v`", discriminator) } -type StringOrPipelineMetricAggregationType struct { - String *string `json:"String,omitempty"` - PipelineMetricAggregationType *PipelineMetricAggregationType `json:"PipelineMetricAggregationType,omitempty"` -} - -// NewStringOrPipelineMetricAggregationType creates a new StringOrPipelineMetricAggregationType object. -func NewStringOrPipelineMetricAggregationType() *StringOrPipelineMetricAggregationType { - return &StringOrPipelineMetricAggregationType{ - String: (func(input string) *string { return &input })("count"), - } -} - -type StringOrDataqueryInlineScript struct { - String *string `json:"String,omitempty"` - DataqueryInlineScript *DataqueryInlineScript `json:"DataqueryInlineScript,omitempty"` -} - -// NewStringOrDataqueryInlineScript creates a new StringOrDataqueryInlineScript object. -func NewStringOrDataqueryInlineScript() *StringOrDataqueryInlineScript { - return &StringOrDataqueryInlineScript{} -} - type MovingAverageOrDerivativeOrCumulativeSumOrBucketScript struct { MovingAverage *MovingAverage `json:"MovingAverage,omitempty"` Derivative *Derivative `json:"Derivative,omitempty"` @@ -1556,6 +1541,16 @@ func (resource *MovingAverageOrDerivativeOrCumulativeSumOrBucketScript) Unmarsha return fmt.Errorf("could not unmarshal resource with `type = %v`", discriminator) } +type StringOrDataqueryInlineScript struct { + String *string `json:"String,omitempty"` + DataqueryInlineScript *DataqueryInlineScript `json:"DataqueryInlineScript,omitempty"` +} + +// NewStringOrDataqueryInlineScript creates a new StringOrDataqueryInlineScript object. +func NewStringOrDataqueryInlineScript() *StringOrDataqueryInlineScript { + return &StringOrDataqueryInlineScript{} +} + type BucketScriptOrCumulativeSumOrDerivativeOrSerialDiffOrRawDataOrRawDocumentOrUniqueCountOrPercentilesOrExtendedStatsOrMinOrMaxOrSumOrAverageOrMovingAverageOrMovingFunctionOrLogsOrRateOrTopMetrics struct { BucketScript *BucketScript `json:"BucketScript,omitempty"` CumulativeSum *CumulativeSum `json:"CumulativeSum,omitempty"` diff --git a/pkg/tsdb/tempo/kinds/dataquery/types_dataquery_gen.go b/pkg/tsdb/tempo/kinds/dataquery/types_dataquery_gen.go index 079185206f1..874c4161741 100644 --- a/pkg/tsdb/tempo/kinds/dataquery/types_dataquery_gen.go +++ b/pkg/tsdb/tempo/kinds/dataquery/types_dataquery_gen.go @@ -70,57 +70,6 @@ func NewTempoQuery() *TempoQuery { return &TempoQuery{} } -type TempoQueryType string - -const ( - TempoQueryTypeTraceql TempoQueryType = "traceql" - TempoQueryTypeTraceqlSearch TempoQueryType = "traceqlSearch" - TempoQueryTypeServiceMap TempoQueryType = "serviceMap" - TempoQueryTypeUpload TempoQueryType = "upload" - TempoQueryTypeNativeSearch TempoQueryType = "nativeSearch" - TempoQueryTypeTraceId TempoQueryType = "traceId" - TempoQueryTypeClear TempoQueryType = "clear" -) - -type MetricsQueryType string - -const ( - MetricsQueryTypeRange MetricsQueryType = "range" - MetricsQueryTypeInstant MetricsQueryType = "instant" -) - -// The state of the TraceQL streaming search query -type SearchStreamingState string - -const ( - SearchStreamingStatePending SearchStreamingState = "pending" - SearchStreamingStateStreaming SearchStreamingState = "streaming" - SearchStreamingStateDone SearchStreamingState = "done" - SearchStreamingStateError SearchStreamingState = "error" -) - -// The type of the table that is used to display the search results -type SearchTableType string - -const ( - SearchTableTypeTraces SearchTableType = "traces" - SearchTableTypeSpans SearchTableType = "spans" - SearchTableTypeRaw SearchTableType = "raw" -) - -// static fields are pre-set in the UI, dynamic fields are added by the user -type TraceqlSearchScope string - -const ( - TraceqlSearchScopeIntrinsic TraceqlSearchScope = "intrinsic" - TraceqlSearchScopeUnscoped TraceqlSearchScope = "unscoped" - TraceqlSearchScopeEvent TraceqlSearchScope = "event" - TraceqlSearchScopeInstrumentation TraceqlSearchScope = "instrumentation" - TraceqlSearchScopeLink TraceqlSearchScope = "link" - TraceqlSearchScopeResource TraceqlSearchScope = "resource" - TraceqlSearchScopeSpan TraceqlSearchScope = "span" -) - type TraceqlFilter struct { // Uniquely identify the filter, will not be used in the query generation Id string `json:"id"` @@ -141,6 +90,57 @@ func NewTraceqlFilter() *TraceqlFilter { return &TraceqlFilter{} } +// static fields are pre-set in the UI, dynamic fields are added by the user +type TraceqlSearchScope string + +const ( + TraceqlSearchScopeIntrinsic TraceqlSearchScope = "intrinsic" + TraceqlSearchScopeUnscoped TraceqlSearchScope = "unscoped" + TraceqlSearchScopeEvent TraceqlSearchScope = "event" + TraceqlSearchScopeInstrumentation TraceqlSearchScope = "instrumentation" + TraceqlSearchScopeLink TraceqlSearchScope = "link" + TraceqlSearchScopeResource TraceqlSearchScope = "resource" + TraceqlSearchScopeSpan TraceqlSearchScope = "span" +) + +// The type of the table that is used to display the search results +type SearchTableType string + +const ( + SearchTableTypeTraces SearchTableType = "traces" + SearchTableTypeSpans SearchTableType = "spans" + SearchTableTypeRaw SearchTableType = "raw" +) + +type MetricsQueryType string + +const ( + MetricsQueryTypeRange MetricsQueryType = "range" + MetricsQueryTypeInstant MetricsQueryType = "instant" +) + +type TempoQueryType string + +const ( + TempoQueryTypeTraceql TempoQueryType = "traceql" + TempoQueryTypeTraceqlSearch TempoQueryType = "traceqlSearch" + TempoQueryTypeServiceMap TempoQueryType = "serviceMap" + TempoQueryTypeUpload TempoQueryType = "upload" + TempoQueryTypeNativeSearch TempoQueryType = "nativeSearch" + TempoQueryTypeTraceId TempoQueryType = "traceId" + TempoQueryTypeClear TempoQueryType = "clear" +) + +// The state of the TraceQL streaming search query +type SearchStreamingState string + +const ( + SearchStreamingStatePending SearchStreamingState = "pending" + SearchStreamingStateStreaming SearchStreamingState = "streaming" + SearchStreamingStateDone SearchStreamingState = "done" + SearchStreamingStateError SearchStreamingState = "error" +) + type StringOrArrayOfString struct { String *string `json:"String,omitempty"` ArrayOfString []string `json:"ArrayOfString,omitempty"` From 3bf6e3dc37036d0633284a060af6c496b90159ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 12 Mar 2025 10:26:59 +0100 Subject: [PATCH 050/141] Dashboards: Fix issues with panel selection and dragging (#102000) --- .../components/PanelChrome/PanelChrome.tsx | 56 ++++++++++--------- 1 file changed, 31 insertions(+), 25 deletions(-) diff --git a/packages/grafana-ui/src/components/PanelChrome/PanelChrome.tsx b/packages/grafana-ui/src/components/PanelChrome/PanelChrome.tsx index b629dbeee83..310ae7fe7b4 100644 --- a/packages/grafana-ui/src/components/PanelChrome/PanelChrome.tsx +++ b/packages/grafana-ui/src/components/PanelChrome/PanelChrome.tsx @@ -1,5 +1,5 @@ import { css, cx } from '@emotion/css'; -import { CSSProperties, PointerEvent, ReactElement, ReactNode, useId, useRef, useState } from 'react'; +import { CSSProperties, ReactElement, ReactNode, useId, useRef, useState } from 'react'; import * as React from 'react'; import { useMeasure, useToggle } from 'react-use'; @@ -143,7 +143,6 @@ export function PanelChrome({ onFocus, onMouseMove, onMouseEnter, - onDragStart, showMenuAlways = false, }: PanelChromeProps) { const theme = useTheme2(); @@ -151,7 +150,7 @@ export function PanelChrome({ const panelContentId = useId(); const panelTitleId = useId().replace(/:/g, '_'); const { isSelected, onSelect, isSelectable } = useElementSelection(selectionId); - const pointerDownEvt = useRef(null); + const pointerDownPos = useRef<{ screenX: number; screenY: number }>({ screenX: 0, screenY: 0 }); const hasHeader = !hoverHeader; @@ -196,6 +195,33 @@ export function PanelChrome({ const testid = typeof title === 'string' ? selectors.components.Panels.Panel.title(title) : 'Panel'; + // Handle drag & selection events + // Mainly the tricky bit of differentiating between dragging and selecting + + const onPointerUp = (evt: React.PointerEvent) => { + evt.stopPropagation(); + + const distance = Math.sqrt( + Math.pow(pointerDownPos.current.screenX - evt.screenX, 2) + + Math.pow(pointerDownPos.current.screenY - evt.screenY, 2) + ); + + // If we are dragging some distance or clicking on elements that should cancel dragging (panel menu, etc) + if ( + distance > 10 || + (dragClassCancel && evt.target instanceof HTMLElement && evt.target.closest(`.${dragClassCancel}`)) + ) { + return; + } + + onSelect?.(evt); + }; + + const onPointerDown = (evt: React.PointerEvent) => { + evt.stopPropagation(); + pointerDownPos.current = { screenX: evt.screenX, screenY: evt.screenY }; + }; + const headerContent = ( <> {/* Non collapsible title */} @@ -321,30 +347,10 @@ export function PanelChrome({ className={cx(styles.headerContainer, dragClass)} style={headerStyles} data-testid="header-container" - onPointerDown={(evt) => { - evt.stopPropagation(); - pointerDownEvt.current = evt; - }} - onPointerMove={() => { - if (pointerDownEvt.current) { - onDragStart?.(pointerDownEvt.current); - pointerDownEvt.current = null; - } - }} + onPointerDown={onPointerDown} onMouseEnter={isSelectable ? onHeaderEnter : undefined} onMouseLeave={isSelectable ? onHeaderLeave : undefined} - onPointerUp={(evt) => { - evt.stopPropagation(); - if ( - pointerDownEvt.current && - dragClassCancel && - evt.target instanceof HTMLElement && - !evt.target.closest(`.${dragClassCancel}`) - ) { - onSelect?.(pointerDownEvt.current); - pointerDownEvt.current = null; - } - }} + onPointerUp={onPointerUp} > {statusMessage && (
From 2d0b1c6154a640e65860700b87ab2b6f4d902b96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 12 Mar 2025 10:29:07 +0100 Subject: [PATCH 051/141] Dashboards: Move settings button into edit pane (#101942) --- .../edit-pane/DashboardEditableElement.tsx | 25 ++++++++++-- .../edit-pane/EditPaneHeader.tsx | 1 + .../scene/NavToolbarActions.tsx | 40 ++++++++++--------- .../scene/types/EditableDashboardElement.ts | 7 ++++ public/locales/en-US/grafana.json | 3 ++ 5 files changed, 53 insertions(+), 23 deletions(-) diff --git a/public/app/features/dashboard-scene/edit-pane/DashboardEditableElement.tsx b/public/app/features/dashboard-scene/edit-pane/DashboardEditableElement.tsx index 7ccf14b4ff6..7d2c598c7ac 100644 --- a/public/app/features/dashboard-scene/edit-pane/DashboardEditableElement.tsx +++ b/public/app/features/dashboard-scene/edit-pane/DashboardEditableElement.tsx @@ -1,7 +1,7 @@ -import { useMemo } from 'react'; +import { ReactNode, useMemo } from 'react'; -import { Input, TextArea } from '@grafana/ui'; -import { t } from 'app/core/internationalization'; +import { Button, Icon, Input, Stack, TextArea } from '@grafana/ui'; +import { t, Trans } from 'app/core/internationalization'; import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor'; import { OptionsPaneItemDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor'; @@ -60,6 +60,23 @@ export class DashboardEditableElement implements EditableDashboardElement { return [dashboardOptions]; } + + public renderActions(): ReactNode { + return ( + + ); + } } export function DashboardTitleInput({ dashboard }: { dashboard: DashboardScene }) { @@ -71,5 +88,5 @@ export function DashboardTitleInput({ dashboard }: { dashboard: DashboardScene } export function DashboardDescriptionInput({ dashboard }: { dashboard: DashboardScene }) { const { description } = dashboard.useState(); - return