diff --git a/.drone.yml b/.drone.yml index 61469de878f..ba8d9612d6a 100644 --- a/.drone.yml +++ b/.drone.yml @@ -499,7 +499,7 @@ steps: name: identify-runner - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.47/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.50/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -851,7 +851,7 @@ steps: name: clone-enterprise - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.47/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.50/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -1774,7 +1774,7 @@ steps: name: identify-runner - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.47/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.50/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -2223,7 +2223,7 @@ services: steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.47/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.50/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -2425,7 +2425,7 @@ steps: name: identify-runner - commands: - $$ProgressPreference = "SilentlyContinue" - - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.47/windows/grabpl.exe + - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.50/windows/grabpl.exe -OutFile grabpl.exe image: grafana/ci-wix:0.1.1 name: windows-init @@ -2555,7 +2555,7 @@ steps: name: identify-runner - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.47/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.50/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -3215,7 +3215,7 @@ steps: name: identify-runner - commands: - $$ProgressPreference = "SilentlyContinue" - - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.47/windows/grabpl.exe + - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.50/windows/grabpl.exe -OutFile grabpl.exe image: grafana/ci-wix:0.1.1 name: windows-init @@ -4017,7 +4017,7 @@ services: steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.47/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v3.0.50/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -4800,6 +4800,6 @@ kind: secret name: gcr_credentials --- kind: signature -hmac: f82455098bcac4c4b46f62ec7bc768660ccce0bb4c869da8bb85026e5845aa49 +hmac: c5243aaa05fbb21c64d03465f079ad710c9dcddb81db19b89ebb61cc9b60bc72 ... diff --git a/CHANGELOG.md b/CHANGELOG.md index 6dcc0430c51..ee66a614138 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ + + +# 10.3.3 (2024-02-02) + +### Bug fixes + +- **Elasticsearch:** Fix creating of legend so it is backward compatible with frontend produced frames. [#81786](https://github.com/grafana/grafana/issues/81786), [@ivanahuckova](https://github.com/ivanahuckova) +- **ShareModal:** Fixes url sync issue that caused issue with save drawer. [#81721](https://github.com/grafana/grafana/issues/81721), [@ivanortegaalba](https://github.com/ivanortegaalba) + + # 10.3.1 (2024-01-22) @@ -103,6 +113,29 @@ Users who have InfluxDB datasource configured with SQL querying language must up Removes `NamespaceID` from responses of all GET routes underneath the path `/api/ruler/grafana/api/v1/rules` - 3 affected endpoints. All affected routes are not in the publicly documented or `stable` marked portion of the ngalert API. This only breaks clients who are directly using the unstable portion of the API. Such clients should use `NamespaceUID` rather than `NamespaceID` to identify namespaces. Issue [#79359](https://github.com/grafana/grafana/issues/79359) + + +# 10.2.4 (2024-01-29) + +### Features and enhancements + +- **Chore:** Upgrade Go to 1.21.5. [#79560](https://github.com/grafana/grafana/issues/79560), [@tolzhabayev](https://github.com/tolzhabayev) + +### Bug fixes + +- **Field:** Fix perf regression in getUniqueFieldName(). [#81417](https://github.com/grafana/grafana/issues/81417), [@leeoniya](https://github.com/leeoniya) +- **Alerting:** Fix Graphite subqueries. [#80816](https://github.com/grafana/grafana/issues/80816), [@gillesdemey](https://github.com/gillesdemey) +- **Alerting:** Fix Graphite subqueries. [#80744](https://github.com/grafana/grafana/issues/80744), [@gillesdemey](https://github.com/gillesdemey) +- **Annotations:** Split cleanup into separate queries and deletes to avoid deadlocks on MySQL. [#80485](https://github.com/grafana/grafana/issues/80485), [@alexweav](https://github.com/alexweav) +- **Loki:** Fix bug duplicating parsed labels across multiple log lines. [#80368](https://github.com/grafana/grafana/issues/80368), [@svennergr](https://github.com/svennergr) +- **Alerting:** Fix NoData & Error alerts not resolving when rule is reset. [#80241](https://github.com/grafana/grafana/issues/80241), [@JacobsonMT](https://github.com/JacobsonMT) +- **Auth:** Fix a panic during logout when OAuth provider is not set. [#80221](https://github.com/grafana/grafana/issues/80221), [@dmihai](https://github.com/dmihai) +- **Gauges:** Fixing broken auto sizing. [#79940](https://github.com/grafana/grafana/issues/79940), [@torkelo](https://github.com/torkelo) +- **Templating:** Json interpolation of single-value default selection does not create valid json. [#79503](https://github.com/grafana/grafana/issues/79503), [@kaydelaney](https://github.com/kaydelaney) +- **Tempo:** Fix cache in TraceQL editor. [#79471](https://github.com/grafana/grafana/issues/79471), [@adrapereira](https://github.com/adrapereira) +- **Alerting:** Fix for data source filter on cloud rules. (#79327). [#79350](https://github.com/grafana/grafana/issues/79350), [@soniaAguilarPeiron](https://github.com/soniaAguilarPeiron) + + # 10.2.3 (2023-12-18) @@ -756,6 +789,15 @@ Starting with 10.2, `parentRowIndex` is deprecated. It will be removed in a futu - **Drawer:** Make content scroll by default. [#75287](https://github.com/grafana/grafana/issues/75287), [@ashharrison90](https://github.com/ashharrison90) + + +# 10.1.7 (2024-01-29) + +### Bug fixes + +- **Annotations:** Split cleanup into separate queries and deletes to avoid deadlocks on MySQL. [#80678](https://github.com/grafana/grafana/issues/80678), [@alexweav](https://github.com/alexweav) + + # 10.1.6 (2023-12-18) @@ -1257,6 +1299,15 @@ Starting with 10.0, changing the folder UID is deprecated. It will be removed in - **Grafana/ui:** Fix margin in RadioButtonGroup option when only icon is present. [#68899](https://github.com/grafana/grafana/issues/68899), [@aocenas](https://github.com/aocenas) + + +# 10.0.11 (2024-01-29) + +### Bug fixes + +- **Annotations:** Split cleanup into separate queries and deletes to avoid deadlocks on MySQL. [#80681](https://github.com/grafana/grafana/issues/80681), [@alexweav](https://github.com/alexweav) + + # 10.0.10 (2023-12-18) @@ -1789,6 +1840,15 @@ The `database` field has been deprecated in the Elasticsearch datasource provisi - **InteractiveTable:** Updated design and minor tweak to Correlactions page. [#66443](https://github.com/grafana/grafana/issues/66443), [@torkelo](https://github.com/torkelo) + + +# 9.5.16 (2024-01-29) + +### Bug fixes + +- **Annotations:** Split cleanup into separate queries and deletes to avoid deadlocks on MySQL. [#80682](https://github.com/grafana/grafana/issues/80682), [@alexweav](https://github.com/alexweav) + + # 9.5.15 (2023-12-18) diff --git a/docs/sources/developers/http_api/dashboard_public.md b/docs/sources/developers/http_api/dashboard_public.md index 6093ac42341..d0f1980c6b0 100644 --- a/docs/sources/developers/http_api/dashboard_public.md +++ b/docs/sources/developers/http_api/dashboard_public.md @@ -207,7 +207,7 @@ See note in the [introduction](#public-dashboard-api) for an explanation. **Example Request**: ```http -GET /api/dashboards/uid/xCpsVuc4z HTTP/1.1 +GET /api/dashboards/uid/xCpsVuc4z/public-dashboards/ HTTP/1.1 Accept: application/json Content-Type: application/json Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk diff --git a/docs/sources/developers/kinds/core/dashboard/schema-reference.md b/docs/sources/developers/kinds/core/dashboard/schema-reference.md index eb287875f5c..561580fd2fd 100644 --- a/docs/sources/developers/kinds/core/dashboard/schema-reference.md +++ b/docs/sources/developers/kinds/core/dashboard/schema-reference.md @@ -186,6 +186,7 @@ Sensitive information stripped: queries (metric, template,annotation) and panel | `key` | string | **Yes** | | Optional, defined the unique key of the snapshot, required if external is true | | `name` | string | **Yes** | | Optional, name of the snapshot | | `orgId` | uint32 | **Yes** | | org id of the snapshot | +| `originalUrl` | string | **Yes** | | original url, url of the dashboard that was snapshotted | | `updated` | string | **Yes** | | last time when the snapshot was updated | | `userId` | uint32 | **Yes** | | user id of the snapshot creator | | `url` | string | No | | url of the snapshot, if snapshot was shared internally | diff --git a/kinds/dashboard/dashboard_kind.cue b/kinds/dashboard/dashboard_kind.cue index 6cace8d26ff..fe963ef5bab 100644 --- a/kinds/dashboard/dashboard_kind.cue +++ b/kinds/dashboard/dashboard_kind.cue @@ -494,6 +494,8 @@ lineage: schemas: [{ external: bool @grafanamaturity(NeedsExpertReview) // external url, if snapshot was shared in external grafana instance externalUrl: string @grafanamaturity(NeedsExpertReview) + // original url, url of the dashboard that was snapshotted + originalUrl: string @grafanamaturity(NeedsExpertReview) // Unique identifier of the snapshot id: uint32 @grafanamaturity(NeedsExpertReview) // Optional, defined the unique key of the snapshot, required if external is true diff --git a/package.json b/package.json index 23a93b6ff68..982def9c4c5 100644 --- a/package.json +++ b/package.json @@ -238,12 +238,12 @@ "@grafana/faro-web-sdk": "^1.3.6", "@grafana/flamegraph": "workspace:*", "@grafana/google-sdk": "0.1.2", - "@grafana/lezer-logql": "0.2.2", + "@grafana/lezer-logql": "0.2.3", "@grafana/monaco-logql": "^0.0.7", "@grafana/o11y-ds-frontend": "workspace:*", "@grafana/prometheus": "workspace:*", "@grafana/runtime": "workspace:*", - "@grafana/scenes": "^2.6.5", + "@grafana/scenes": "^3.2.1", "@grafana/schema": "workspace:*", "@grafana/sql": "workspace:*", "@grafana/ui": "workspace:*", diff --git a/packages/grafana-e2e-selectors/src/selectors/pages.ts b/packages/grafana-e2e-selectors/src/selectors/pages.ts index 4acc843136e..f456dc70576 100644 --- a/packages/grafana-e2e-selectors/src/selectors/pages.ts +++ b/packages/grafana-e2e-selectors/src/selectors/pages.ts @@ -182,6 +182,10 @@ export const Pages = { stepCountIntervalSelect: 'data-testid interval variable step count input', minIntervalInput: 'data-testid interval variable mininum interval input', }, + AdHocFiltersVariable: { + datasourceSelect: Components.DataSourcePicker.inputV2, + infoText: 'data-testid ad-hoc filters variable info text', + }, }, }, }, diff --git a/packages/grafana-schema/src/raw/dashboard/x/dashboard_types.gen.ts b/packages/grafana-schema/src/raw/dashboard/x/dashboard_types.gen.ts index c34707a521c..3e7d114de76 100644 --- a/packages/grafana-schema/src/raw/dashboard/x/dashboard_types.gen.ts +++ b/packages/grafana-schema/src/raw/dashboard/x/dashboard_types.gen.ts @@ -1088,6 +1088,10 @@ export interface Dashboard { * external url, if snapshot was shared in external grafana instance */ externalUrl: string; + /** + * original url, url of the dashboard that was snapshotted + */ + originalUrl: string; /** * Unique identifier of the snapshot */ diff --git a/packages/grafana-ui/src/components/TabbedContainer/TabbedContainer.tsx b/packages/grafana-ui/src/components/TabbedContainer/TabbedContainer.tsx index 1794b1a96da..7a527bad325 100644 --- a/packages/grafana-ui/src/components/TabbedContainer/TabbedContainer.tsx +++ b/packages/grafana-ui/src/components/TabbedContainer/TabbedContainer.tsx @@ -5,7 +5,7 @@ import { SelectableValue, GrafanaTheme2 } from '@grafana/data'; import { IconButton } from '../../components/IconButton/IconButton'; import { TabsBar, Tab, TabContent } from '../../components/Tabs'; -import { useStyles2 } from '../../themes'; +import { useStyles2, useTheme2 } from '../../themes'; import { IconName } from '../../types/icon'; import { CustomScrollbar } from '../CustomScrollbar/CustomScrollbar'; @@ -25,12 +25,14 @@ export interface TabbedContainerProps { export function TabbedContainer({ tabs, defaultTab, closeIconTooltip, onClose }: TabbedContainerProps) { const [activeTab, setActiveTab] = useState(tabs.some((tab) => tab.value === defaultTab) ? defaultTab : tabs[0].value); + const styles = useStyles2(getStyles); + const theme = useTheme2(); const onSelectTab = (item: SelectableValue) => { setActiveTab(item.value!); }; - const styles = useStyles2(getStyles); + const autoHeight = `calc(100% - (${theme.components.menuTabs.height}px + ${theme.spacing(1)}))`; return (
@@ -46,7 +48,7 @@ export function TabbedContainer({ tabs, defaultTab, closeIconTooltip, onClose }: ))} - + {tabs.find((t) => t.value === activeTab)?.content}
@@ -60,7 +62,7 @@ const getStyles = (theme: GrafanaTheme2) => ({ tabContent: css({ padding: theme.spacing(2), backgroundColor: theme.colors.background.primary, - height: `calc(100% - ${theme.components.menuTabs.height}px)`, + height: `100%`, }), close: css({ position: 'absolute', diff --git a/pkg/kinds/dashboard/dashboard_spec_gen.go b/pkg/kinds/dashboard/dashboard_spec_gen.go index dfd9bf19629..3014bba97ef 100644 --- a/pkg/kinds/dashboard/dashboard_spec_gen.go +++ b/pkg/kinds/dashboard/dashboard_spec_gen.go @@ -694,6 +694,9 @@ type Snapshot struct { // OrgId org id of the snapshot OrgId int `json:"orgId"` + // OriginalUrl original url, url of the dashboard that was snapshotted + OriginalUrl string `json:"originalUrl"` + // Updated last time when the snapshot was updated Updated time.Time `json:"updated"` diff --git a/pkg/kindsysreport/codegen/report.json b/pkg/kindsysreport/codegen/report.json index 015db1369bd..a44e34a9472 100644 --- a/pkg/kindsysreport/codegen/report.json +++ b/pkg/kindsysreport/codegen/report.json @@ -374,7 +374,7 @@ 0 ], "description": "A Grafana dashboard.", - "grafanaMaturityCount": 103, + "grafanaMaturityCount": 105, "lineageIsGroup": false, "links": { "docs": "https://grafana.com/docs/grafana/next/developers/kinds/core/dashboard/schema-reference", diff --git a/pkg/services/folder/folderimpl/folder.go b/pkg/services/folder/folderimpl/folder.go index 92d599091ac..79ad5718aa2 100644 --- a/pkg/services/folder/folderimpl/folder.go +++ b/pkg/services/folder/folderimpl/folder.go @@ -227,8 +227,10 @@ func (s *Service) Get(ctx context.Context, q *folder.GetFolderQuery) (*folder.Fo } if !s.features.IsEnabled(ctx, featuremgmt.FlagNestedFolders) { + dashFolder.Fullpath = dashFolder.Title return dashFolder, nil } + metrics.MFolderIDsServiceCount.WithLabelValues(metrics.Folder).Inc() // nolint:staticcheck if q.ID != nil { @@ -247,6 +249,10 @@ func (s *Service) Get(ctx context.Context, q *folder.GetFolderQuery) (*folder.Fo f.ID = dashFolder.ID f.Version = dashFolder.Version + if !s.features.IsEnabled(ctx, featuremgmt.FlagNestedFolders) { + f.Fullpath = f.Title // set full path to the folder title (unescaped) + } + return f, err } diff --git a/pkg/services/folder/folderimpl/folder_test.go b/pkg/services/folder/folderimpl/folder_test.go index bb6c8f2c758..dfa61a2444e 100644 --- a/pkg/services/folder/folderimpl/folder_test.go +++ b/pkg/services/folder/folderimpl/folder_test.go @@ -1611,6 +1611,106 @@ func TestIntegrationNestedFolderSharedWithMe(t *testing.T) { }) } +func TestFolderServiceGetFolder(t *testing.T) { + db := sqlstore.InitTestDB(t) + + signedInAdminUser := user.SignedInUser{UserID: 1, OrgID: orgID, Permissions: map[int64]map[string][]string{ + orgID: { + dashboards.ActionFoldersCreate: {}, + dashboards.ActionFoldersWrite: {dashboards.ScopeFoldersAll}, + dashboards.ActionFoldersRead: {dashboards.ScopeFoldersAll}, + }, + }} + + guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{ + CanSaveValue: true, + CanViewValue: true, + }) + + getSvc := func(features featuremgmt.FeatureToggles) Service { + quotaService := quotatest.New(false, nil) + folderStore := ProvideDashboardFolderStore(db) + + cfg := setting.NewCfg() + + featuresFlagOff := featuremgmt.WithFeatures() + dashStore, err := database.ProvideDashboardStore(db, db.Cfg, featuresFlagOff, tagimpl.ProvideService(db), quotaService) + require.NoError(t, err) + nestedFolderStore := ProvideStore(db, db.Cfg) + + b := bus.ProvideBus(tracing.InitializeTracerForTest()) + ac := acimpl.ProvideAccessControl(cfg) + + return Service{ + cfg: cfg, + log: log.New("test-folder-service"), + dashboardStore: dashStore, + dashboardFolderStore: folderStore, + store: nestedFolderStore, + features: features, + bus: b, + db: db, + accessControl: ac, + registry: make(map[string]folder.RegistryService), + metrics: newFoldersMetrics(nil), + } + } + + folderSvcOn := getSvc(featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders)) + folderSvcOff := getSvc(featuremgmt.WithFeatures()) + + createCmd := folder.CreateFolderCommand{ + OrgID: orgID, + ParentUID: "", + SignedInUser: &signedInAdminUser, + } + + depth := 3 + folders := CreateSubtreeInStore(t, folderSvcOn.store, &folderSvcOn, depth, "get/folder-", createCmd) + f := folders[1] + + testCases := []struct { + name string + svc *Service + WithFullpath bool + expectedFullpath string + }{ + { + name: "when flag is off", + svc: &folderSvcOff, + expectedFullpath: f.Title, + }, + { + name: "when flag is on and WithFullpath is false", + svc: &folderSvcOn, + WithFullpath: false, + expectedFullpath: "", + }, + { + name: "when flag is on and WithFullpath is true", + svc: &folderSvcOn, + WithFullpath: true, + expectedFullpath: "get\\/folder-folder-0/get\\/folder-folder-1", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + q := folder.GetFolderQuery{ + OrgID: orgID, + UID: &f.UID, + WithFullpath: tc.WithFullpath, + SignedInUser: &signedInAdminUser, + } + fldr, err := tc.svc.Get(context.Background(), &q) + require.NoError(t, err) + require.Equal(t, f.UID, fldr.UID) + + require.Equal(t, tc.expectedFullpath, fldr.Fullpath) + }) + } +} + func TestFolderServiceGetFolders(t *testing.T) { db := sqlstore.InitTestDB(t) quotaService := quotatest.New(false, nil) @@ -1687,7 +1787,7 @@ func TestFolderServiceGetFolders(t *testing.T) { }) } -func CreateSubtreeInStore(t *testing.T, store *sqlStore, service *Service, depth int, prefix string, cmd folder.CreateFolderCommand) []*folder.Folder { +func CreateSubtreeInStore(t *testing.T, store store, service *Service, depth int, prefix string, cmd folder.CreateFolderCommand) []*folder.Folder { t.Helper() folders := make([]*folder.Folder, 0, depth) diff --git a/pkg/services/folder/folderimpl/sqlstore.go b/pkg/services/folder/folderimpl/sqlstore.go index fe758499557..7b09069eccc 100644 --- a/pkg/services/folder/folderimpl/sqlstore.go +++ b/pkg/services/folder/folderimpl/sqlstore.go @@ -171,27 +171,55 @@ func (ss *sqlStore) Update(ctx context.Context, cmd folder.UpdateFolderCommand) return foldr.WithURL(), err } +// If WithFullpath is true it computes also the full path of a folder. +// The full path is a string that contains the titles of all parent folders separated by a slash. +// For example, if the folder structure is: +// +// A +// └── B +// └── C +// +// The full path of C is "A/B/C". +// The full path of B is "A/B". +// The full path of A is "A". +// If a folder contains a slash in its title, it is escaped with a backslash. +// For example, if the folder structure is: +// +// A +// └── B/C +// +// The full path of C is "A/B\/C". func (ss *sqlStore) Get(ctx context.Context, q folder.GetFolderQuery) (*folder.Folder, error) { foldr := &folder.Folder{} err := ss.db.WithDbSession(ctx, func(sess *db.Session) error { exists := false var err error + s := strings.Builder{} + s.WriteString("SELECT *") + if q.WithFullpath { + s.WriteString(fmt.Sprintf(`, %s AS fullpath`, getFullpathSQL(ss.db.GetDialect()))) + } + s.WriteString(" FROM folder f0") + if q.WithFullpath { + s.WriteString(getFullpathJoinsSQL()) + } switch { case q.UID != nil: - exists, err = sess.SQL("SELECT * FROM folder WHERE uid = ? AND org_id = ?", q.UID, q.OrgID).Get(foldr) + s.WriteString(" WHERE f0.uid = ? AND f0.org_id = ?") + exists, err = sess.SQL(s.String(), q.UID, q.OrgID).Get(foldr) // nolint:staticcheck case q.ID != nil: + s.WriteString(" WHERE f0.id = ?") metrics.MFolderIDsServiceCount.WithLabelValues(metrics.Folder).Inc() - exists, err = sess.SQL("SELECT * FROM folder WHERE id = ?", q.ID).Get(foldr) + exists, err = sess.SQL(s.String(), q.ID).Get(foldr) case q.Title != nil: - s := strings.Builder{} - s.WriteString("SELECT * FROM folder WHERE title = ? AND org_id = ?") + s.WriteString(" WHERE f0.title = ? AND f0.org_id = ?") args := []any{*q.Title, q.OrgID} if q.ParentUID != nil { - s.WriteString(" AND parent_uid = ?") + s.WriteString(" AND f0.parent_uid = ?") args = append(args, *q.ParentUID) } else { - s.WriteString(" AND parent_uid IS NULL") + s.WriteString(" AND f0.parent_uid IS NULL") } exists, err = sess.SQL(s.String(), args...).Get(foldr) default: @@ -207,6 +235,7 @@ func (ss *sqlStore) Get(ctx context.Context, q folder.GetFolderQuery) (*folder.F return nil }) + foldr.Fullpath = strings.TrimLeft(foldr.Fullpath, "/") return foldr.WithURL(), err } @@ -274,15 +303,16 @@ func (ss *sqlStore) GetChildren(ctx context.Context, q folder.GetChildrenQuery) args = append(args, q.UID, q.OrgID) } - if q.FolderUIDs != nil { - sql.WriteString(" AND uid IN (?") - for range q.FolderUIDs[1:] { - sql.WriteString(", ?") - } - sql.WriteString(")") - for _, uid := range q.FolderUIDs { + if len(q.FolderUIDs) > 0 { + sql.WriteString(" AND uid IN (") + for i, uid := range q.FolderUIDs { + if i > 0 { + sql.WriteString(", ") + } + sql.WriteString("?") args = append(args, uid) } + sql.WriteString(")") } sql.WriteString(" ORDER BY title ASC") diff --git a/pkg/services/folder/folderimpl/sqlstore_test.go b/pkg/services/folder/folderimpl/sqlstore_test.go index 59e354b3e8a..08df34d0568 100644 --- a/pkg/services/folder/folderimpl/sqlstore_test.go +++ b/pkg/services/folder/folderimpl/sqlstore_test.go @@ -3,6 +3,7 @@ package folderimpl import ( "context" "fmt" + "path" "slices" "sort" "testing" @@ -391,11 +392,7 @@ func TestIntegrationGet(t *testing.T) { UID: util.GenerateShortUID(), ParentUID: f.UID, }) - - t.Cleanup(func() { - err := folderStore.Delete(context.Background(), []string{f.UID}, orgID) - require.NoError(t, err) - }) + require.NoError(t, err) t.Run("should gently fail in case of bad request", func(t *testing.T) { _, err = folderStore.Get(context.Background(), folder.GetFolderQuery{}) @@ -466,6 +463,24 @@ func TestIntegrationGet(t *testing.T) { assert.NotEmpty(t, ff.Updated) assert.NotEmpty(t, ff.URL) }) + + t.Run("get folder with fullpath should set fullpath as expected", func(t *testing.T) { + ff, err := folderStore.Get(context.Background(), folder.GetFolderQuery{ + UID: &subfolderWithSameName.UID, + OrgID: orgID, + WithFullpath: true, + }) + require.NoError(t, err) + assert.Equal(t, subfolderWithSameName.UID, ff.UID) + assert.Equal(t, subfolderWithSameName.OrgID, ff.OrgID) + assert.Equal(t, subfolderWithSameName.Title, ff.Title) + assert.Equal(t, subfolderWithSameName.Description, ff.Description) + assert.Equal(t, path.Join(f.Title, subfolderWithSameName.Title), ff.Fullpath) + assert.Equal(t, f.UID, ff.ParentUID) + assert.NotEmpty(t, ff.Created) + assert.NotEmpty(t, ff.Updated) + assert.NotEmpty(t, ff.URL) + }) } func TestIntegrationGetParents(t *testing.T) { diff --git a/pkg/services/folder/model.go b/pkg/services/folder/model.go index 09f8e0cafaa..e111e90fb3b 100644 --- a/pkg/services/folder/model.go +++ b/pkg/services/folder/model.go @@ -147,10 +147,11 @@ type DeleteFolderCommand struct { type GetFolderQuery struct { UID *string // Deprecated: use FolderUID instead - ID *int64 - Title *string - ParentUID *string - OrgID int64 + ID *int64 + Title *string + ParentUID *string + OrgID int64 + WithFullpath bool SignedInUser identity.Requester `json:"-"` } diff --git a/pkg/services/folder/service.go b/pkg/services/folder/service.go index a7d09a8393d..50a1bec3391 100644 --- a/pkg/services/folder/service.go +++ b/pkg/services/folder/service.go @@ -18,6 +18,7 @@ type Service interface { // specificity (UID, ID, Title). // When fetching a folder by Title, callers can optionally define a ParentUID. // If ParentUID is not set then the folder will be fetched from the root level. + // If WithFullpath is true it computes also the full path of a folder. Get(ctx context.Context, q *GetFolderQuery) (*Folder, error) // Update is used to update a folder's UID, Title and Description. To change diff --git a/pkg/services/ngalert/api/persist.go b/pkg/services/ngalert/api/persist.go index c8f759920d9..59d4930d7d0 100644 --- a/pkg/services/ngalert/api/persist.go +++ b/pkg/services/ngalert/api/persist.go @@ -24,5 +24,5 @@ type RuleStore interface { DeleteAlertRulesByUID(ctx context.Context, orgID int64, ruleUID ...string) error // IncreaseVersionForAllRulesInNamespace Increases version for all rules that have specified namespace. Returns all rules that belong to the namespace - IncreaseVersionForAllRulesInNamespace(ctx context.Context, orgID int64, namespaceUID string) ([]ngmodels.AlertRuleKeyWithVersionAndPauseStatus, error) + IncreaseVersionForAllRulesInNamespace(ctx context.Context, orgID int64, namespaceUID string) ([]ngmodels.AlertRuleKeyWithVersion, error) } diff --git a/pkg/services/ngalert/models/alert_rule.go b/pkg/services/ngalert/models/alert_rule.go index 881fc311d87..f93eb3a5484 100644 --- a/pkg/services/ngalert/models/alert_rule.go +++ b/pkg/services/ngalert/models/alert_rule.go @@ -368,11 +368,6 @@ type AlertRuleKeyWithVersion struct { AlertRuleKey `xorm:"extends"` } -type AlertRuleKeyWithVersionAndPauseStatus struct { - IsPaused bool - AlertRuleKeyWithVersion `xorm:"extends"` -} - type AlertRuleKeyWithId struct { AlertRuleKey ID int64 diff --git a/pkg/services/ngalert/store/alert_rule.go b/pkg/services/ngalert/store/alert_rule.go index 585ddf14261..71c97b6db78 100644 --- a/pkg/services/ngalert/store/alert_rule.go +++ b/pkg/services/ngalert/store/alert_rule.go @@ -72,8 +72,8 @@ func (st DBstore) DeleteAlertRulesByUID(ctx context.Context, orgID int64, ruleUI } // IncreaseVersionForAllRulesInNamespace Increases version for all rules that have specified namespace. Returns all rules that belong to the namespace -func (st DBstore) IncreaseVersionForAllRulesInNamespace(ctx context.Context, orgID int64, namespaceUID string) ([]ngmodels.AlertRuleKeyWithVersionAndPauseStatus, error) { - var keys []ngmodels.AlertRuleKeyWithVersionAndPauseStatus +func (st DBstore) IncreaseVersionForAllRulesInNamespace(ctx context.Context, orgID int64, namespaceUID string) ([]ngmodels.AlertRuleKeyWithVersion, error) { + var keys []ngmodels.AlertRuleKeyWithVersion err := st.SQLStore.WithTransactionalDbSession(ctx, func(sess *db.Session) error { now := TimeNow() _, err := sess.Exec("UPDATE alert_rule SET version = version + 1, updated = ? WHERE namespace_uid = ? AND org_id = ?", now, namespaceUID, orgID) diff --git a/pkg/services/ngalert/tests/fakes/rules.go b/pkg/services/ngalert/tests/fakes/rules.go index 6c8c28161d0..9185047c989 100644 --- a/pkg/services/ngalert/tests/fakes/rules.go +++ b/pkg/services/ngalert/tests/fakes/rules.go @@ -315,7 +315,7 @@ func (f *RuleStore) UpdateRuleGroup(ctx context.Context, orgID int64, namespaceU return nil } -func (f *RuleStore) IncreaseVersionForAllRulesInNamespace(_ context.Context, orgID int64, namespaceUID string) ([]models.AlertRuleKeyWithVersionAndPauseStatus, error) { +func (f *RuleStore) IncreaseVersionForAllRulesInNamespace(_ context.Context, orgID int64, namespaceUID string) ([]models.AlertRuleKeyWithVersion, error) { f.mtx.Lock() defer f.mtx.Unlock() @@ -324,18 +324,15 @@ func (f *RuleStore) IncreaseVersionForAllRulesInNamespace(_ context.Context, org Params: []any{orgID, namespaceUID}, }) - var result []models.AlertRuleKeyWithVersionAndPauseStatus + var result []models.AlertRuleKeyWithVersion for _, rule := range f.Rules[orgID] { if rule.NamespaceUID == namespaceUID && rule.OrgID == orgID { rule.Version++ rule.Updated = time.Now() - result = append(result, models.AlertRuleKeyWithVersionAndPauseStatus{ - IsPaused: rule.IsPaused, - AlertRuleKeyWithVersion: models.AlertRuleKeyWithVersion{ - Version: rule.Version, - AlertRuleKey: rule.GetKey(), - }, + result = append(result, models.AlertRuleKeyWithVersion{ + Version: rule.Version, + AlertRuleKey: rule.GetKey(), }) } } diff --git a/pkg/tsdb/influxdb/influxql/converter/converter.go b/pkg/tsdb/influxdb/influxql/converter/converter.go index d81d2ffc9b6..feaeb4b0406 100644 --- a/pkg/tsdb/influxdb/influxql/converter/converter.go +++ b/pkg/tsdb/influxdb/influxql/converter/converter.go @@ -7,11 +7,11 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/data" + sdkjsoniter "github.com/grafana/grafana-plugin-sdk-go/data/utils/jsoniter" jsoniter "github.com/json-iterator/go" "github.com/grafana/grafana/pkg/tsdb/influxdb/influxql/util" "github.com/grafana/grafana/pkg/tsdb/influxdb/models" - "github.com/grafana/grafana/pkg/util/converter/jsonitere" ) func rspErr(e error) *backend.DataResponse { @@ -19,7 +19,7 @@ func rspErr(e error) *backend.DataResponse { } func ReadInfluxQLStyleResult(jIter *jsoniter.Iterator, query *models.Query) *backend.DataResponse { - iter := jsonitere.NewIterator(jIter) + iter := sdkjsoniter.NewIterator(jIter) var rsp *backend.DataResponse l1Fields: @@ -51,7 +51,7 @@ l1Fields: return rsp } -func readResults(iter *jsonitere.Iterator, query *models.Query) *backend.DataResponse { +func readResults(iter *sdkjsoniter.Iterator, query *models.Query) *backend.DataResponse { rsp := &backend.DataResponse{Frames: make(data.Frames, 0)} l1Fields: for more, err := iter.ReadArray(); more; more, err = iter.ReadArray() { @@ -79,7 +79,7 @@ l1Fields: return rsp } -func readSeries(iter *jsonitere.Iterator, query *models.Query) *backend.DataResponse { +func readSeries(iter *sdkjsoniter.Iterator, query *models.Query) *backend.DataResponse { var ( measurement string tags map[string]string @@ -179,7 +179,7 @@ func readSeries(iter *jsonitere.Iterator, query *models.Query) *backend.DataResp return rsp } -func readTags(iter *jsonitere.Iterator) (map[string]string, error) { +func readTags(iter *sdkjsoniter.Iterator) (map[string]string, error) { tags := make(map[string]string) for l1Field, err := iter.ReadObject(); l1Field != ""; l1Field, err = iter.ReadObject() { if err != nil { @@ -194,7 +194,7 @@ func readTags(iter *jsonitere.Iterator) (map[string]string, error) { return tags, nil } -func readColumns(iter *jsonitere.Iterator) (columns []string, err error) { +func readColumns(iter *sdkjsoniter.Iterator) (columns []string, err error) { for more, err := iter.ReadArray(); more; more, err = iter.ReadArray() { if err != nil { return nil, err @@ -209,7 +209,7 @@ func readColumns(iter *jsonitere.Iterator) (columns []string, err error) { return columns, nil } -func readValues(iter *jsonitere.Iterator, hasTimeColumn bool) (valueFields data.Fields, err error) { +func readValues(iter *sdkjsoniter.Iterator, hasTimeColumn bool) (valueFields data.Fields, err error) { if hasTimeColumn { valueFields = append(valueFields, data.NewField("Time", nil, make([]time.Time, 0))) } diff --git a/pkg/util/converter/jsonitere/jsonitere.go b/pkg/util/converter/jsonitere/jsonitere.go deleted file mode 100644 index 4bc0c2ed55f..00000000000 --- a/pkg/util/converter/jsonitere/jsonitere.go +++ /dev/null @@ -1,66 +0,0 @@ -// Package jsonitere wraps json-iterator/go's Iterator methods with error returns -// so linting can catch unchecked errors. -// The underlying iterator's Error property is returned and not reset. -// See json-iterator/go for method documentation and additional methods that -// can be added to this library. -package jsonitere - -import ( - j "github.com/json-iterator/go" -) - -type Iterator struct { - // named property instead of embedded so there is no - // confusion about which method or property is called - i *j.Iterator -} - -func NewIterator(i *j.Iterator) *Iterator { - return &Iterator{i} -} - -func (iter *Iterator) Read() (any, error) { - return iter.i.Read(), iter.i.Error -} - -func (iter *Iterator) ReadAny() (j.Any, error) { - return iter.i.ReadAny(), iter.i.Error -} - -func (iter *Iterator) ReadArray() (bool, error) { - return iter.i.ReadArray(), iter.i.Error -} - -func (iter *Iterator) ReadObject() (string, error) { - return iter.i.ReadObject(), iter.i.Error -} - -func (iter *Iterator) ReadString() (string, error) { - return iter.i.ReadString(), iter.i.Error -} - -func (iter *Iterator) WhatIsNext() (j.ValueType, error) { - return iter.i.WhatIsNext(), iter.i.Error -} - -func (iter *Iterator) Skip() error { - iter.i.Skip() - return iter.i.Error -} - -func (iter *Iterator) SkipAndReturnBytes() []byte { - return iter.i.SkipAndReturnBytes() -} - -func (iter *Iterator) ReadVal(obj any) error { - iter.i.ReadVal(obj) - return iter.i.Error -} - -func (iter *Iterator) ReadFloat64() (float64, error) { - return iter.i.ReadFloat64(), iter.i.Error -} - -func (iter *Iterator) ReadInt8() (int8, error) { - return iter.i.ReadInt8(), iter.i.Error -} diff --git a/pkg/util/converter/prom.go b/pkg/util/converter/prom.go index 60215e5cff9..7b5cd6b95f6 100644 --- a/pkg/util/converter/prom.go +++ b/pkg/util/converter/prom.go @@ -8,15 +8,15 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/data" + sdkjsoniter "github.com/grafana/grafana-plugin-sdk-go/data/utils/jsoniter" jsoniter "github.com/json-iterator/go" - "golang.org/x/exp/slices" - "github.com/grafana/grafana/pkg/util/converter/jsonitere" + "golang.org/x/exp/slices" ) // helpful while debugging all the options that may appear func logf(format string, a ...any) { - //fmt.Printf(format, a...) + // fmt.Printf(format, a...) } type Options struct { @@ -29,7 +29,7 @@ func rspErr(e error) backend.DataResponse { // ReadPrometheusStyleResult will read results from a prometheus or loki server and return data frames func ReadPrometheusStyleResult(jIter *jsoniter.Iterator, opt Options) backend.DataResponse { - iter := jsonitere.NewIterator(jIter) + iter := sdkjsoniter.NewIterator(jIter) var rsp backend.DataResponse status := "unknown" errorType := "" @@ -102,14 +102,14 @@ l1Fields: return rsp } -func readWarnings(iter *jsonitere.Iterator) ([]data.Notice, error) { +func readWarnings(iter *sdkjsoniter.Iterator) ([]data.Notice, error) { warnings := []data.Notice{} next, err := iter.WhatIsNext() if err != nil { return nil, err } - if next != jsoniter.ArrayValue { + if next != sdkjsoniter.ArrayValue { return warnings, nil } @@ -121,7 +121,7 @@ func readWarnings(iter *jsonitere.Iterator) ([]data.Notice, error) { if err != nil { return nil, err } - if next == jsoniter.StringValue { + if next == sdkjsoniter.StringValue { s, err := iter.ReadString() if err != nil { return nil, err @@ -137,18 +137,18 @@ func readWarnings(iter *jsonitere.Iterator) ([]data.Notice, error) { return warnings, nil } -func readPrometheusData(iter *jsonitere.Iterator, opt Options) backend.DataResponse { +func readPrometheusData(iter *sdkjsoniter.Iterator, opt Options) backend.DataResponse { var rsp backend.DataResponse t, err := iter.WhatIsNext() if err != nil { return rspErr(err) } - if t == jsoniter.ArrayValue { + if t == sdkjsoniter.ArrayValue { return readArrayData(iter) } - if t != jsoniter.ObjectValue { + if t != sdkjsoniter.ObjectValue { return backend.DataResponse{ Error: fmt.Errorf("expected object type"), } @@ -190,7 +190,7 @@ l1Fields: // if we have saved resultBytes we will parse them here // we saved them because when we had them we don't know the resultType if len(resultBytes) > 0 { - ji := jsonitere.NewIterator(jsoniter.ParseBytes(jsoniter.ConfigDefault, resultBytes)) + ji := sdkjsoniter.NewIterator(jsoniter.ParseBytes(sdkjsoniter.ConfigDefault, resultBytes)) rsp = readResult(resultType, rsp, ji, opt, encodingFlags) } case "result": @@ -200,7 +200,7 @@ l1Fields: if resultTypeFound { rsp = readResult(resultType, rsp, iter, opt, encodingFlags) } else { - resultBytes = iter.SkipAndReturnBytes() + resultBytes, _ = iter.SkipAndReturnBytes() } case "stats": @@ -241,7 +241,7 @@ l1Fields: } // will read the result object based on the resultType and return a DataResponse -func readResult(resultType string, rsp backend.DataResponse, iter *jsonitere.Iterator, opt Options, encodingFlags []string) backend.DataResponse { +func readResult(resultType string, rsp backend.DataResponse, iter *sdkjsoniter.Iterator, opt Options, encodingFlags []string) backend.DataResponse { switch resultType { case "matrix", "vector": rsp = readMatrixOrVectorMulti(iter, resultType, opt) @@ -279,7 +279,7 @@ func readResult(resultType string, rsp backend.DataResponse, iter *jsonitere.Ite } // will return strings or exemplars -func readArrayData(iter *jsonitere.Iterator) backend.DataResponse { +func readArrayData(iter *sdkjsoniter.Iterator) backend.DataResponse { lookup := make(map[string]*data.Field) var labelFrame *data.Frame @@ -298,7 +298,7 @@ func readArrayData(iter *jsonitere.Iterator) backend.DataResponse { } switch next { - case jsoniter.StringValue: + case sdkjsoniter.StringValue: s, err := iter.ReadString() if err != nil { return rspErr(err) @@ -306,7 +306,7 @@ func readArrayData(iter *jsonitere.Iterator) backend.DataResponse { stringField.Append(s) // Either label or exemplars - case jsoniter.ObjectValue: + case sdkjsoniter.ObjectValue: exemplar, labelPairs, err := readLabelsOrExemplars(iter) if err != nil { rspErr(err) @@ -365,7 +365,7 @@ func readArrayData(iter *jsonitere.Iterator) backend.DataResponse { } // For consistent ordering read values to an array not a map -func readLabelsAsPairs(iter *jsonitere.Iterator) ([][2]string, error) { +func readLabelsAsPairs(iter *sdkjsoniter.Iterator) ([][2]string, error) { pairs := make([][2]string, 0, 10) for k, err := iter.ReadObject(); k != ""; k, err = iter.ReadObject() { if err != nil { @@ -380,7 +380,7 @@ func readLabelsAsPairs(iter *jsonitere.Iterator) ([][2]string, error) { return pairs, nil } -func readLabelsOrExemplars(iter *jsonitere.Iterator) (*data.Frame, [][2]string, error) { +func readLabelsOrExemplars(iter *sdkjsoniter.Iterator) (*data.Frame, [][2]string, error) { pairs := make([][2]string, 0, 10) labels := data.Labels{} var frame *data.Frame @@ -496,7 +496,7 @@ l1Fields: return frame, pairs, nil } -func readString(iter *jsonitere.Iterator) backend.DataResponse { +func readString(iter *sdkjsoniter.Iterator) backend.DataResponse { timeField := data.NewFieldFromFieldType(data.FieldTypeTime, 0) timeField.Name = data.TimeSeriesTimeFieldName valueField := data.NewFieldFromFieldType(data.FieldTypeString, 0) @@ -541,7 +541,7 @@ func readString(iter *jsonitere.Iterator) backend.DataResponse { } } -func readScalar(iter *jsonitere.Iterator, dataPlane bool) backend.DataResponse { +func readScalar(iter *sdkjsoniter.Iterator, dataPlane bool) backend.DataResponse { rsp := backend.DataResponse{} timeField := data.NewFieldFromFieldType(data.FieldTypeTime, 0) @@ -573,7 +573,7 @@ func readScalar(iter *jsonitere.Iterator, dataPlane bool) backend.DataResponse { } } -func readMatrixOrVectorMulti(iter *jsonitere.Iterator, resultType string, opt Options) backend.DataResponse { +func readMatrixOrVectorMulti(iter *sdkjsoniter.Iterator, resultType string, opt Options) backend.DataResponse { rsp := backend.DataResponse{} for more, err := iter.ReadArray(); more; more, err = iter.ReadArray() { @@ -679,7 +679,7 @@ func readMatrixOrVectorMulti(iter *jsonitere.Iterator, resultType string, opt Op return rsp } -func readTimeValuePair(iter *jsonitere.Iterator) (time.Time, float64, error) { +func readTimeValuePair(iter *sdkjsoniter.Iterator) (time.Time, float64, error) { if _, err := iter.ReadArray(); err != nil { return time.Time{}, 0, err } @@ -708,7 +708,7 @@ func readTimeValuePair(iter *jsonitere.Iterator) (time.Time, float64, error) { } type histogramInfo struct { - //XMax (time) YMin Ymax Count YLayout + // XMax (time) YMin Ymax Count YLayout time *data.Field yMin *data.Field // will have labels? yMax *data.Field @@ -734,7 +734,7 @@ func newHistogramInfo() *histogramInfo { // This will read a single sparse histogram // [ time, { count, sum, buckets: [...] }] -func readHistogram(iter *jsonitere.Iterator, hist *histogramInfo) error { +func readHistogram(iter *sdkjsoniter.Iterator, hist *histogramInfo) error { // first element if _, err := iter.ReadArray(); err != nil { return err @@ -834,7 +834,7 @@ func readHistogram(iter *jsonitere.Iterator, hist *histogramInfo) error { return nil } -func appendValueFromString(iter *jsonitere.Iterator, field *data.Field) error { +func appendValueFromString(iter *sdkjsoniter.Iterator, field *data.Field) error { var err error var s string if s, err = iter.ReadString(); err != nil { @@ -850,7 +850,7 @@ func appendValueFromString(iter *jsonitere.Iterator, field *data.Field) error { return nil } -func readStream(iter *jsonitere.Iterator) backend.DataResponse { +func readStream(iter *sdkjsoniter.Iterator) backend.DataResponse { rsp := backend.DataResponse{} labelsField := data.NewFieldFromFieldType(data.FieldTypeJSON, 0) @@ -950,7 +950,7 @@ func readStream(iter *jsonitere.Iterator) backend.DataResponse { return rsp } -func readCategorizedStream(iter *jsonitere.Iterator) backend.DataResponse { +func readCategorizedStream(iter *sdkjsoniter.Iterator) backend.DataResponse { rsp := backend.DataResponse{} labelsField := data.NewFieldFromFieldType(data.FieldTypeJSON, 0) @@ -1084,7 +1084,7 @@ func readCategorizedStream(iter *jsonitere.Iterator) backend.DataResponse { return rsp } -func readCategorizedStreamField(iter *jsonitere.Iterator) (map[string]interface{}, map[string]interface{}, error) { +func readCategorizedStreamField(iter *sdkjsoniter.Iterator) (map[string]interface{}, map[string]interface{}, error) { parsedLabels := data.Labels{} structuredMetadata := data.Labels{} var parsedLabelsMap map[string]interface{} diff --git a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/contactPoint/ContactPointSelector.tsx b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/contactPoint/ContactPointSelector.tsx index 5788b9dd44a..1e7d178496b 100644 --- a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/contactPoint/ContactPointSelector.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/contactPoint/ContactPointSelector.tsx @@ -30,6 +30,8 @@ export interface ContactPointSelectorProps { refetchReceivers: () => Promise; } +const MAX_CONTACT_POINTS_RENDERED = 500; + export function ContactPointSelector({ alertManager, options, @@ -42,9 +44,10 @@ export function ContactPointSelector({ const contactPointInForm = watch(`contactPoints.${alertManager}.selectedContactPoint`); const selectedContactPointWithMetadata = options.find((option) => option.value.name === contactPointInForm)?.value; - const selectedContactPointSelectableValue = selectedContactPointWithMetadata - ? { value: selectedContactPointWithMetadata, label: selectedContactPointWithMetadata.name } - : undefined; + const selectedContactPointSelectableValue: SelectableValue = + selectedContactPointWithMetadata + ? { value: selectedContactPointWithMetadata, label: selectedContactPointWithMetadata.name } + : { value: undefined, label: '' }; const LOADING_SPINNER_DURATION = 1000; @@ -80,8 +83,8 @@ export function ContactPointSelector({ render={({ field: { onChange, ref, ...field }, fieldState: { error } }) => ( <>
-